mirror of
https://github.com/boostorg/redis.git
synced 2026-01-20 05:02:12 +00:00
Compare commits
643 Commits
v0.0.1
...
parser_eve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a76a621b0b | ||
|
|
16bf57cf33 | ||
|
|
88d8f3c0ca | ||
|
|
20ab2c7e70 | ||
|
|
8ee2213efe | ||
|
|
97d71d1d6b | ||
|
|
76129bbcb8 | ||
|
|
adf17f2b3b | ||
|
|
6499818911 | ||
|
|
963ae8d145 | ||
|
|
fe4ba64a97 | ||
|
|
b82224700e | ||
|
|
620b1e9510 | ||
|
|
f04d97ffa5 | ||
|
|
7304d99bf6 | ||
|
|
89a42dbf74 | ||
|
|
a39d130240 | ||
|
|
6d5b550bb3 | ||
|
|
4a2085c800 | ||
|
|
2fc54bc73b | ||
|
|
0c8c6fcc09 | ||
|
|
479068e778 | ||
|
|
35d50700b9 | ||
|
|
b58e4f94de | ||
|
|
e8b13bd7a0 | ||
|
|
c1c50e3e24 | ||
|
|
328ad97a79 | ||
|
|
1060733b84 | ||
|
|
f9d0679be5 | ||
|
|
11e54de8f8 | ||
|
|
109248d53b | ||
|
|
f7b4ec291e | ||
|
|
c4e31e5f2f | ||
|
|
f32da1b0e3 | ||
|
|
8b98081ccf | ||
|
|
77ade122ea | ||
|
|
746fb62619 | ||
|
|
ac4e6e29b3 | ||
|
|
412f5535ea | ||
|
|
31ceed9f8f | ||
|
|
e7c1b9ed5f | ||
|
|
35cfdae3f3 | ||
|
|
3a74575ada | ||
|
|
6cdbd64eb8 | ||
|
|
c21ac7569a | ||
|
|
7dd5a77d4f | ||
|
|
a53278aa2d | ||
|
|
6abef73cb1 | ||
|
|
9a48633bdf | ||
|
|
c615902559 | ||
|
|
d910557db4 | ||
|
|
5089eef376 | ||
|
|
8f4e980fd5 | ||
|
|
8df535f50e | ||
|
|
26197e8300 | ||
|
|
f592073330 | ||
|
|
32bc39a63a | ||
|
|
ca2c4efa35 | ||
|
|
7f9ac6f0fc | ||
|
|
d68c4583ed | ||
|
|
943cf0d740 | ||
|
|
53cabf0745 | ||
|
|
c309c5776b | ||
|
|
a535862d51 | ||
|
|
dfecf0bd0a | ||
|
|
ce4e5cbffa | ||
|
|
c370e930c5 | ||
|
|
e30bb373aa | ||
|
|
b8a52e5e61 | ||
|
|
302d50e262 | ||
|
|
86015cbdde | ||
|
|
84ead6a3e4 | ||
|
|
dbfb72d853 | ||
|
|
9039c3cd90 | ||
|
|
abde1afcb0 | ||
|
|
3adac158ab | ||
|
|
63aad808b6 | ||
|
|
90a07a1e07 | ||
|
|
585988bb61 | ||
|
|
b5e90ea5f4 | ||
|
|
42d1250b53 | ||
|
|
299e7ca3a8 | ||
|
|
0f56460056 | ||
|
|
6e7c42be7f | ||
|
|
6c6bcd03c0 | ||
|
|
3098fca125 | ||
|
|
25929578b9 | ||
|
|
1957cc7106 | ||
|
|
a8c4f7520f | ||
|
|
e8dd4d69eb | ||
|
|
d1822761a5 | ||
|
|
a4d2bb983d | ||
|
|
30ba3c3eb6 | ||
|
|
90f3d6cd92 | ||
|
|
ef6dea1666 | ||
|
|
90b001a54e | ||
|
|
ec1ca876eb | ||
|
|
1b90346b7c | ||
|
|
8f46d1eaa9 | ||
|
|
a4e9a60f34 | ||
|
|
e9b16a3140 | ||
|
|
ced4f9bd02 | ||
|
|
43878d68a3 | ||
|
|
29892f2837 | ||
|
|
b71dff6dd0 | ||
|
|
a9204fcc91 | ||
|
|
1788ebc80b | ||
|
|
5656571aa4 | ||
|
|
aa6622ffa2 | ||
|
|
f459d5d89b | ||
|
|
35276b6acb | ||
|
|
122ffb20b2 | ||
|
|
1f4b709b21 | ||
|
|
7d52065a87 | ||
|
|
f0d92c16a5 | ||
|
|
1df18258d8 | ||
|
|
71c60a5a89 | ||
|
|
098fbd68d2 | ||
|
|
4f2b12adbc | ||
|
|
e7cec45cb2 | ||
|
|
b19067cfed | ||
|
|
0af1c2e73d | ||
|
|
da48368d53 | ||
|
|
b6e1280075 | ||
|
|
5d553f5d71 | ||
|
|
5c46b62958 | ||
|
|
6cde6eab44 | ||
|
|
78792199ef | ||
|
|
f5793ac9bc | ||
|
|
dfc2bd1ac2 | ||
|
|
0445e74fa3 | ||
|
|
234f961e87 | ||
|
|
8bb0004188 | ||
|
|
4257b2eaec | ||
|
|
96da11a2cc | ||
|
|
3861c5de74 | ||
|
|
168ee6148a | ||
|
|
723e72797f | ||
|
|
7caea928af | ||
|
|
71b9a4f428 | ||
|
|
d89a976729 | ||
|
|
154d0b106d | ||
|
|
2b12525206 | ||
|
|
0bcbf6d4e4 | ||
|
|
6389daa783 | ||
|
|
ab2d6cdea8 | ||
|
|
63ce40e365 | ||
|
|
f2a005a8c4 | ||
|
|
0c06be66de | ||
|
|
0380e643ed | ||
|
|
ff734694ab | ||
|
|
548e3d4cb6 | ||
|
|
66b632b13d | ||
|
|
11c9c1b787 | ||
|
|
d6f9e435c7 | ||
|
|
9a7816dbf4 | ||
|
|
199fb6c261 | ||
|
|
4d30d1e0c0 | ||
|
|
92be6d958f | ||
|
|
14d3c0232e | ||
|
|
7412b37e08 | ||
|
|
60ba5b62af | ||
|
|
0303ae0dbc | ||
|
|
ea6c5536c1 | ||
|
|
d386b30c3a | ||
|
|
2951acc80f | ||
|
|
faf15fe7e8 | ||
|
|
7f3f8b0c13 | ||
|
|
f37e514961 | ||
|
|
b7b4f8f449 | ||
|
|
686cb306ea | ||
|
|
fcbe2c431c | ||
|
|
a7b3fbdd9a | ||
|
|
5ea0d3c467 | ||
|
|
2cd487784b | ||
|
|
b41e2704a1 | ||
|
|
765f0d45e8 | ||
|
|
84c8649d66 | ||
|
|
0bf4e76981 | ||
|
|
1d329df81b | ||
|
|
56f7d5af69 | ||
|
|
d0c3b3f7ee | ||
|
|
87ebc6cf4a | ||
|
|
ffc35e8e3e | ||
|
|
a02837ab33 | ||
|
|
4a39a0d20a | ||
|
|
56d9a2778f | ||
|
|
c732f33b48 | ||
|
|
221016f1c9 | ||
|
|
cb9fdba0a4 | ||
|
|
1c96a60709 | ||
|
|
b66d067af8 | ||
|
|
bc08a8d411 | ||
|
|
53ef947cf3 | ||
|
|
ecfe51c7ae | ||
|
|
be20c0d48c | ||
|
|
d5031c3f69 | ||
|
|
6748f7682a | ||
|
|
2a4936a9e1 | ||
|
|
4547e1ac07 | ||
|
|
44a608c0ba | ||
|
|
1ed8e0182c | ||
|
|
d8cf431dc2 | ||
|
|
401dd24419 | ||
|
|
509635f222 | ||
|
|
4fbd0c6853 | ||
|
|
b8899ecdc7 | ||
|
|
7d09040646 | ||
|
|
0de26fb0ce | ||
|
|
84ee2f37f1 | ||
|
|
81927deda4 | ||
|
|
34ff1cea63 | ||
|
|
10603b7d3a | ||
|
|
ad3c2914db | ||
|
|
91014b13bf | ||
|
|
4f6f8b454d | ||
|
|
9ebcc544ae | ||
|
|
7d16259749 | ||
|
|
9dec63515e | ||
|
|
46525371b9 | ||
|
|
b5f8348598 | ||
|
|
69d12421e2 | ||
|
|
a715c251bf | ||
|
|
d29a057fa6 | ||
|
|
82430afc8b | ||
|
|
607946f00e | ||
|
|
c99790ab5c | ||
|
|
635b3608ad | ||
|
|
a8a78c38c6 | ||
|
|
e09a53ff08 | ||
|
|
ec8a1c7286 | ||
|
|
3c02a7662b | ||
|
|
538ab8f35f | ||
|
|
f5f57e370b | ||
|
|
7abfc5fd8d | ||
|
|
11eebcf771 | ||
|
|
c21f70bc07 | ||
|
|
22bacbd52c | ||
|
|
2982f831f6 | ||
|
|
663e9ac671 | ||
|
|
c0aa4356ea | ||
|
|
6f9fd5b2fb | ||
|
|
30a6e34e4e | ||
|
|
1f9b3e8008 | ||
|
|
3808fec0e3 | ||
|
|
607a9e9dd6 | ||
|
|
2d53bb748e | ||
|
|
a6cb4ca323 | ||
|
|
5ac4f7e8ad | ||
|
|
7a08588808 | ||
|
|
e7ff1cedf3 | ||
|
|
0bcb7dcf16 | ||
|
|
c28969674b | ||
|
|
c7f49c6677 | ||
|
|
90bcd621fb | ||
|
|
fd967204df | ||
|
|
cd00047a49 | ||
|
|
728b35cfe0 | ||
|
|
52e62ba78c | ||
|
|
bb18ff4891 | ||
|
|
6ce793e413 | ||
|
|
a83c0e7803 | ||
|
|
64820bd25b | ||
|
|
16b5c8d1ba | ||
|
|
8ef4d3cf0b | ||
|
|
d01a9acf3b | ||
|
|
ac7e425d47 | ||
|
|
d620cdee59 | ||
|
|
5f07b730f7 | ||
|
|
6d3a112f94 | ||
|
|
1f3ef6b486 | ||
|
|
a850a6ed63 | ||
|
|
c8b73c2fe8 | ||
|
|
8b02268182 | ||
|
|
1b60eeb352 | ||
|
|
b93f36163d | ||
|
|
071f9a93aa | ||
|
|
5a6ca14a67 | ||
|
|
a5c86107f8 | ||
|
|
3a4445022e | ||
|
|
bfb26f2602 | ||
|
|
7e70cb4ad7 | ||
|
|
886561409a | ||
|
|
0c5ff09685 | ||
|
|
4b07b6d516 | ||
|
|
c1ce8358c7 | ||
|
|
13e16b7a60 | ||
|
|
e11502e0df | ||
|
|
b2344384cf | ||
|
|
56c0b28003 | ||
|
|
c88fcfb9ed | ||
|
|
a56bf982ab | ||
|
|
5d0ed0e986 | ||
|
|
15deaa637d | ||
|
|
bb8ff90351 | ||
|
|
7d4902369a | ||
|
|
607ca17a89 | ||
|
|
3849ba42fd | ||
|
|
56bcdb7914 | ||
|
|
73ad66eb93 | ||
|
|
9cf00d6a23 | ||
|
|
a00c9e7439 | ||
|
|
0520791100 | ||
|
|
14b376e36e | ||
|
|
4f9dcc7dc5 | ||
|
|
ad5dd8c30b | ||
|
|
842f864689 | ||
|
|
63f9b74502 | ||
|
|
801f60a026 | ||
|
|
c37fcb641c | ||
|
|
48c3f37168 | ||
|
|
3c63911802 | ||
|
|
1645881a44 | ||
|
|
730e06c38d | ||
|
|
cf3a79737d | ||
|
|
edb384c843 | ||
|
|
f745faddf8 | ||
|
|
927117568e | ||
|
|
1e7c176f92 | ||
|
|
449b5f7e7c | ||
|
|
75f91f3b11 | ||
|
|
b9a23568e3 | ||
|
|
4ac2509afa | ||
|
|
e9dab97992 | ||
|
|
2e8cad858d | ||
|
|
5a6e426028 | ||
|
|
c55978a379 | ||
|
|
6f51397e49 | ||
|
|
6b9ba6b2d9 | ||
|
|
d29c03cb38 | ||
|
|
34cfbaa22f | ||
|
|
c9354fe320 | ||
|
|
bb555cb509 | ||
|
|
5b209afa1d | ||
|
|
3f5491654d | ||
|
|
2bdc25752f | ||
|
|
faafce1c64 | ||
|
|
562075230f | ||
|
|
5dc677c6d8 | ||
|
|
395a167d48 | ||
|
|
f93f3cab58 | ||
|
|
df68fb0235 | ||
|
|
15e6883bc1 | ||
|
|
3816d1d358 | ||
|
|
bb15c70723 | ||
|
|
297b7f15eb | ||
|
|
ec6e99d99a | ||
|
|
8dc6db069b | ||
|
|
bac27c1770 | ||
|
|
feaaedc6c0 | ||
|
|
000ebddf44 | ||
|
|
268ea2c10f | ||
|
|
d8b67f6e23 | ||
|
|
ce1fa6a683 | ||
|
|
b8ede6ccb7 | ||
|
|
6dce1a9226 | ||
|
|
8566745d83 | ||
|
|
0b4906fcba | ||
|
|
2c8bb92071 | ||
|
|
770e224917 | ||
|
|
4fb2b20954 | ||
|
|
c01a57b6cb | ||
|
|
ea0b333c4d | ||
|
|
ba82c6cd84 | ||
|
|
4c298ddc6b | ||
|
|
690002b2f1 | ||
|
|
12899da4db | ||
|
|
b2c19df113 | ||
|
|
61f9a29ebc | ||
|
|
dc9b333f1e | ||
|
|
dac7bea54f | ||
|
|
da84321378 | ||
|
|
3295459700 | ||
|
|
dff8833fe3 | ||
|
|
3a03a43c06 | ||
|
|
b614826bb4 | ||
|
|
4e30a9d53d | ||
|
|
85ba41ae5a | ||
|
|
fd82204ba9 | ||
|
|
084e95cbc7 | ||
|
|
798f193f14 | ||
|
|
a23a3db9ac | ||
|
|
bca6511333 | ||
|
|
e2d642f34c | ||
|
|
d8607af669 | ||
|
|
223a9aa74b | ||
|
|
75433cd028 | ||
|
|
552c6cf6e4 | ||
|
|
16b9347c51 | ||
|
|
03558b1466 | ||
|
|
f8165bcb6f | ||
|
|
8aad27269c | ||
|
|
f11622e746 | ||
|
|
8249360a52 | ||
|
|
d00f26d3da | ||
|
|
480ec13119 | ||
|
|
4cc3fc59a1 | ||
|
|
11807c82b7 | ||
|
|
24a215d78b | ||
|
|
b7abe20703 | ||
|
|
225095944c | ||
|
|
a31d797e43 | ||
|
|
cca8d5d6dc | ||
|
|
6c5bee6920 | ||
|
|
c4714d0037 | ||
|
|
38bf2395af | ||
|
|
7511d6b4d8 | ||
|
|
ddc2815fe5 | ||
|
|
de6f5de655 | ||
|
|
8d454ada0e | ||
|
|
ebac88f2ca | ||
|
|
d26ecb65ca | ||
|
|
c57f97b8c1 | ||
|
|
37ab1e7387 | ||
|
|
54d448cad4 | ||
|
|
97428dedb3 | ||
|
|
83802f217a | ||
|
|
08140f9186 | ||
|
|
3ddb017edb | ||
|
|
20328cd423 | ||
|
|
6577ddbaab | ||
|
|
217d2bd87b | ||
|
|
f96dd22153 | ||
|
|
f1fd0cfa8c | ||
|
|
8728914109 | ||
|
|
e0041ac7ae | ||
|
|
317a185eb0 | ||
|
|
aa81200a8f | ||
|
|
55fc0e861c | ||
|
|
04271855b0 | ||
|
|
700e0c823e | ||
|
|
63c6465a4a | ||
|
|
c86422cf50 | ||
|
|
0168ed5faf | ||
|
|
7bffa252f4 | ||
|
|
0bb65599c4 | ||
|
|
edd538944f | ||
|
|
42880e788b | ||
|
|
bcc3917174 | ||
|
|
b08dd63192 | ||
|
|
76b6106caa | ||
|
|
ab68e8a31d | ||
|
|
2673557ce5 | ||
|
|
2a302dcb65 | ||
|
|
ffc4230368 | ||
|
|
59b5d35672 | ||
|
|
835a1decf4 | ||
|
|
3fb018ccc6 | ||
|
|
1fe4a87287 | ||
|
|
70cdff41e0 | ||
|
|
2edd9f3d87 | ||
|
|
fa4181b197 | ||
|
|
9e2cd8855e | ||
|
|
bef70870cd | ||
|
|
9885439845 | ||
|
|
b5a9162efb | ||
|
|
6ca0bcc945 | ||
|
|
efd0a0379a | ||
|
|
97153abc3c | ||
|
|
f4710941d3 | ||
|
|
f8ff3034f4 | ||
|
|
561eb5dccb | ||
|
|
95d609b75c | ||
|
|
d5f9e702d7 | ||
|
|
5add83b73c | ||
|
|
200974d9be | ||
|
|
649c84d7d0 | ||
|
|
240cce4b09 | ||
|
|
b140216f0d | ||
|
|
4f0d9de393 | ||
|
|
888bb476d7 | ||
|
|
eae37ace0b | ||
|
|
0c3ed1afee | ||
|
|
0f5e8e3d1f | ||
|
|
963b228e02 | ||
|
|
bddf47d626 | ||
|
|
b3b8dfc243 | ||
|
|
e013d846b2 | ||
|
|
d2ba54a7a6 | ||
|
|
250e24d5fb | ||
|
|
9dcccca11e | ||
|
|
8af1c9f19c | ||
|
|
b058cc0c02 | ||
|
|
df3f2b8ca5 | ||
|
|
8e4928347c | ||
|
|
33461d54c8 | ||
|
|
5328cdff9a | ||
|
|
452589d4e7 | ||
|
|
4036df9255 | ||
|
|
9f2df4d052 | ||
|
|
1571afbd88 | ||
|
|
b43e6dfb68 | ||
|
|
ce9cb04168 | ||
|
|
77fe3a0f5f | ||
|
|
40dfacb0b7 | ||
|
|
6d859c57f8 | ||
|
|
9e43541a5e | ||
|
|
97cb5b5b25 | ||
|
|
a40c9fe35f | ||
|
|
a411cc50fc | ||
|
|
5893f0913e | ||
|
|
dea7712a29 | ||
|
|
56479b88eb | ||
|
|
dfeb3bbfcf | ||
|
|
7464851e9e | ||
|
|
226c2b228c | ||
|
|
fee892b6ad | ||
|
|
74e0a6ca23 | ||
|
|
ebef2f9e23 | ||
|
|
485bdc316b | ||
|
|
36fb83e1d6 | ||
|
|
3753c27dcf | ||
|
|
091cad6ee7 | ||
|
|
1e98c04603 | ||
|
|
4858c078f9 | ||
|
|
3dff0b78de | ||
|
|
7300f1498b | ||
|
|
f6fc45d8ba | ||
|
|
5eb88b5042 | ||
|
|
f7d2f3ab28 | ||
|
|
f62ad6a8bf | ||
|
|
1efcf7b7d8 | ||
|
|
29166a2cf0 | ||
|
|
215fd7ea73 | ||
|
|
9b8ca4dbc8 | ||
|
|
4075dc380d | ||
|
|
161cd848f8 | ||
|
|
7c7eed4a53 | ||
|
|
e70b00e976 | ||
|
|
52d7b95cf8 | ||
|
|
641032fa9a | ||
|
|
2a2a13c4dc | ||
|
|
76741d8466 | ||
|
|
0f79214d37 | ||
|
|
de476169ae | ||
|
|
d1bf3a91be | ||
|
|
4be6e6cc1e | ||
|
|
394bdf5b5e | ||
|
|
a4745a1a5d | ||
|
|
b99da962d1 | ||
|
|
172de6235c | ||
|
|
5e99a58685 | ||
|
|
b952a2d2d8 | ||
|
|
16d1f8df24 | ||
|
|
82b804e397 | ||
|
|
22530724c6 | ||
|
|
610aef5c5e | ||
|
|
87a03a0b6b | ||
|
|
0e3de3688e | ||
|
|
980c39084f | ||
|
|
abd339d0f2 | ||
|
|
a86a18c969 | ||
|
|
f0deda42a4 | ||
|
|
d1e7ec6f03 | ||
|
|
0e8e31f310 | ||
|
|
ba86c9fb05 | ||
|
|
ccef30c8ac | ||
|
|
ce77524d6f | ||
|
|
54a9fddc40 | ||
|
|
ad3127d1ca | ||
|
|
0363353154 | ||
|
|
b453fc63c9 | ||
|
|
1a47dece35 | ||
|
|
048a711e51 | ||
|
|
a940f7f4bf | ||
|
|
fd0cae92ee | ||
|
|
a5376bc05f | ||
|
|
98580eb0ea | ||
|
|
6ed2d96b07 | ||
|
|
245fdb55b6 | ||
|
|
e6443cbe26 | ||
|
|
2101def89f | ||
|
|
ddfe9defc5 | ||
|
|
cd28ff285f | ||
|
|
c4bd338e79 | ||
|
|
bb28f34ecc | ||
|
|
aa6251b96c | ||
|
|
b12140ae8a | ||
|
|
d845434869 | ||
|
|
5e86fb9d03 | ||
|
|
dc80c04347 | ||
|
|
20d6c67f3d | ||
|
|
8c4816fc8d | ||
|
|
18aa9ff726 | ||
|
|
b68e413351 | ||
|
|
27b3bb89fb | ||
|
|
70cd9b0ffd | ||
|
|
43a08e834c | ||
|
|
19f03e2f41 | ||
|
|
0fff6496fb | ||
|
|
57ba376544 | ||
|
|
46b86c20e5 | ||
|
|
577c6d35fb | ||
|
|
42ef8a3b06 | ||
|
|
f9af8e585b | ||
|
|
350ae19936 | ||
|
|
57f2644903 | ||
|
|
0ac0c3cf23 | ||
|
|
5c23299a8a | ||
|
|
379da7a340 | ||
|
|
bcf13c03c0 | ||
|
|
be79f58808 | ||
|
|
2c96b07623 | ||
|
|
577d32f0e2 | ||
|
|
5f93257502 | ||
|
|
409ee61522 | ||
|
|
5aa034d6a5 | ||
|
|
0ff0e537ce | ||
|
|
320ee6b3cc | ||
|
|
5061e5a7a6 | ||
|
|
0bda78dd9c | ||
|
|
8704e7756f | ||
|
|
8207ef7e8a | ||
|
|
4712872daf | ||
|
|
06752ac664 | ||
|
|
b3be596f77 | ||
|
|
f92290be16 | ||
|
|
331010c240 | ||
|
|
11b697c572 | ||
|
|
1218b2ef01 | ||
|
|
8cc142d55b | ||
|
|
0723922ac1 | ||
|
|
54ba34c70c | ||
|
|
efe44fbd45 | ||
|
|
268b59afbc | ||
|
|
6b6272694c | ||
|
|
f36f3b7b5e | ||
|
|
b4fef73b87 | ||
|
|
c27b3b6b85 | ||
|
|
feb383d7e2 | ||
|
|
ee71ff885d | ||
|
|
80a80f44ff | ||
|
|
053ce3aea9 | ||
|
|
db27e3cc60 | ||
|
|
449ba0dd73 | ||
|
|
8728f58981 | ||
|
|
2573f39aa1 | ||
|
|
f460bf43a5 | ||
|
|
85d0a30dad | ||
|
|
b7f22d8c61 | ||
|
|
b04cc12d64 | ||
|
|
317690ee91 | ||
|
|
fe3cc2efb0 |
176
.clang-format
Normal file
176
.clang-format
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
Language: Cpp
|
||||
ColumnLimit: 100
|
||||
IndentWidth: 3
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: true
|
||||
AccessModifierOffset: -3
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
PointerAlignment: Left
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<boost/redis.*>|<boost/redis/.*>'
|
||||
Priority: -10
|
||||
SortPriority: 1
|
||||
- Regex: '^<boost/.*>'
|
||||
Priority: -8
|
||||
SortPriority: 3
|
||||
- Regex: "^<.*"
|
||||
Priority: -6
|
||||
SortPriority: 5
|
||||
- Regex: ".*"
|
||||
Priority: -5
|
||||
SortPriority: 4
|
||||
IndentCaseLabels: true
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlignArrayOfStructures: Left
|
||||
DerivePointerAlignment: false
|
||||
PenaltyBreakAssignment: 2000000
|
||||
PenaltyBreakBeforeFirstCallParameter: 0
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyBreakScopeResolution: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200000000
|
||||
|
||||
# Defaults (based on Google)
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: ExceptShortType
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakTemplateDeclarations: Leave
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
CommentPragmas: '(^ IWYU pragma:)|(^\\copydoc )|(^ ?\\n)'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 0
|
||||
ContinuationIndentWidth: 3
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeIsMainRegex: null
|
||||
IncludeIsMainSourceRegex: ""
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ""
|
||||
MacroBlockEnd: ""
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- "c++"
|
||||
- "C++"
|
||||
CanonicalDelimiter: ""
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
CanonicalDelimiter: ""
|
||||
BasedOnStyle: google
|
||||
ReflowComments: false
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: true
|
||||
AlignCaseColons: false
|
||||
---
|
||||
|
||||
172
.clang-tidy
Normal file
172
.clang-tidy
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
# Enable ALL the things! Except not really
|
||||
# misc-non-private-member-variables-in-classes: the options don't do anything
|
||||
Checks: "*,\
|
||||
-readability-*,\
|
||||
-readability-identifier-length,\
|
||||
-google-readability-todo,\
|
||||
-google-readability-namespace-comments,\
|
||||
-google-readability-braces-around-statements,\
|
||||
-hicpp-braces-around-statements,\
|
||||
-hicpp-named-parameter,\
|
||||
-hicpp-avoid-goto,\
|
||||
-google-build-using-namespace,\
|
||||
-altera-*,\
|
||||
-fuchsia-*,\
|
||||
fuchsia-multiple-inheritance,\
|
||||
-llvm-namespace-comment,\
|
||||
-llvm-header-guard,\
|
||||
-llvm-include-order,\
|
||||
-llvmlibc-*,\
|
||||
-misc-non-private-member-variables-in-classes,\
|
||||
-bugprone-use-after-move,\
|
||||
-hicpp-invalid-access-moved,\
|
||||
-misc-no-recursion,\
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,\
|
||||
-cppcoreguidelines-avoid-magic-numbers,\
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,\
|
||||
-cppcoreguidelines-interfaces-global-init,\
|
||||
-cppcoreguidelines-macro-usage,\
|
||||
-cppcoreguidelines-avoid-goto,\
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes"
|
||||
WarningsAsErrors: ''
|
||||
CheckOptions:
|
||||
- key: 'bugprone-argument-comment.StrictMode'
|
||||
value: 'true'
|
||||
# Prefer using enum classes with 2 values for parameters instead of bools
|
||||
- key: 'bugprone-argument-comment.CommentBoolLiterals'
|
||||
value: 'true'
|
||||
- key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts'
|
||||
value: 'true'
|
||||
- key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression'
|
||||
value: 'true'
|
||||
- key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison'
|
||||
value: 'true'
|
||||
- key: 'readability-simplify-boolean-expr.ChainedConditionalReturn'
|
||||
value: 'true'
|
||||
- key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment'
|
||||
value: 'true'
|
||||
- key: 'readability-uniqueptr-delete-release.PreferResetCall'
|
||||
value: 'true'
|
||||
- key: 'cppcoreguidelines-init-variables.MathHeader'
|
||||
value: '<cmath>'
|
||||
- key: 'cppcoreguidelines-narrowing-conversions.PedanticMode'
|
||||
value: 'true'
|
||||
- key: 'readability-else-after-return.WarnOnUnfixable'
|
||||
value: 'true'
|
||||
- key: 'readability-else-after-return.WarnOnConditionVariables'
|
||||
value: 'true'
|
||||
- key: 'readability-inconsistent-declaration-parameter-name.Strict'
|
||||
value: 'true'
|
||||
- key: 'readability-qualified-auto.AddConstToQualified'
|
||||
value: 'true'
|
||||
- key: 'readability-redundant-access-specifiers.CheckFirstDeclaration'
|
||||
value: 'true'
|
||||
# These seem to be the most common identifier styles
|
||||
- key: 'readability-identifier-naming.AbstractClassCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantPointerParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprFunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.EnumCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.EnumConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.FunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalConstantPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalFunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.InlineNamespaceCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalConstantPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.MacroDefinitionCase'
|
||||
value: 'UPPER_CASE'
|
||||
- key: 'readability-identifier-naming.MemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.MethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.NamespaceCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ParameterPackCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PointerParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PrivateMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PrivateMemberPrefix'
|
||||
value: 'm_'
|
||||
- key: 'readability-identifier-naming.PrivateMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ProtectedMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ProtectedMemberPrefix'
|
||||
value: 'm_'
|
||||
- key: 'readability-identifier-naming.ProtectedMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PublicMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PublicMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ScopedEnumConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StaticConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StaticVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StructCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.TemplateTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.TypeAliasCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TypedefCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TypeTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.UnionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ValueTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.VariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.VirtualMethodCase'
|
||||
value: 'lower_case'
|
||||
...
|
||||
22
.codecov.yml
Normal file
22
.codecov.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
codecov:
|
||||
max_report_age: off
|
||||
require_ci_to_pass: yes
|
||||
notify:
|
||||
after_n_builds: 1
|
||||
wait_for_ci: yes
|
||||
|
||||
ignore:
|
||||
- "benchmarks/cpp/asio/*"
|
||||
- "example/*"
|
||||
- "tests/*"
|
||||
- "/usr/*"
|
||||
- "**/boost/*"
|
||||
|
||||
parsers:
|
||||
gcov:
|
||||
branch_detection:
|
||||
conditional: no
|
||||
loop: no
|
||||
method: no
|
||||
macro: no
|
||||
|
||||
357
.github/workflows/ci.yml
vendored
Normal file
357
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,357 @@
|
||||
# CI script to verify that CMake and B2 builds work.
|
||||
# B2 builds include only tests that don't require a DB server, to avoid race conditions.
|
||||
# CMake tests include the actual project tests and all the CMake integration workflows
|
||||
# recommended by Boost.CI.
|
||||
# Windows CMake jobs build the code but don't run the tests,
|
||||
# since we don't have a way to set up a Redis server on Windows (yet).
|
||||
# Subcommands are implemented by the tools/ci.py script in a platform-independent manner.
|
||||
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
windows-cmake:
|
||||
name: "CMake ${{matrix.toolset}} ${{matrix.build-type}} C++${{matrix.cxxstd}}"
|
||||
runs-on: ${{matrix.os}}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
#- { toolset: msvc-14.2, os: windows-2019, generator: "Visual Studio 16 2019", cxxstd: '17', build-type: 'Debug', build-shared-libs: 1 }
|
||||
#- { toolset: msvc-14.2, os: windows-2019, generator: "Visual Studio 16 2019", cxxstd: '17', build-type: 'Release', build-shared-libs: 0 }
|
||||
- { toolset: msvc-14.3, os: windows-2022, generator: "Visual Studio 17 2022", cxxstd: '20', build-type: 'Debug', build-shared-libs: 0 }
|
||||
- { toolset: msvc-14.3, os: windows-2022, generator: "Visual Studio 17 2022", cxxstd: '20', build-type: 'Release', build-shared-libs: 1 }
|
||||
env:
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: 4
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Boost
|
||||
run: python3 tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build a Boost distribution using B2
|
||||
run: |
|
||||
python3 tools/ci.py build-b2-distro \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Build a Boost distribution using CMake
|
||||
run: |
|
||||
python3 tools/ci.py build-cmake-distro \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
- name: Build the project tests
|
||||
run: |
|
||||
python3 tools/ci.py build-cmake-standalone-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
# # TODO: re-enable this when a Redis server is available for this job
|
||||
# - name: Run the project tests
|
||||
# run: |
|
||||
# python3 tools/ci.py run-cmake-standalone-tests \
|
||||
# --build-type ${{ matrix.build-type }}
|
||||
|
||||
- name: Run add_subdirectory tests
|
||||
run: |
|
||||
python3 tools/ci.py run-cmake-add-subdirectory-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
- name: Run find_package tests with the built cmake distribution
|
||||
run: |
|
||||
python3 tools/ci.py run-cmake-find-package-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
- name: Run find_package tests with the built b2 distribution
|
||||
run: |
|
||||
python3 tools/ci.py run-cmake-b2-find-package-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
windows-b2:
|
||||
name: "B2 ${{matrix.toolset}}"
|
||||
runs-on: ${{matrix.os}}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
#- { toolset: msvc-14.2, os: windows-2019 }
|
||||
- { toolset: msvc-14.3, os: windows-2022 }
|
||||
env:
|
||||
OPENSSL_ROOT: "C:\\Program Files\\OpenSSL"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup user-config.jam
|
||||
run: cp tools/user-config.jam "${HOMEDRIVE}${HOMEPATH}/"
|
||||
|
||||
- name: Setup Boost
|
||||
run: python3 tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build and run project tests using B2
|
||||
run: |
|
||||
python3 tools/ci.py run-b2-tests \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--cxxstd 17,20 \
|
||||
--variant debug,release
|
||||
|
||||
posix-cmake:
|
||||
name: "CMake ${{ matrix.toolset }} ${{ matrix.cxxstd }} ${{ matrix.build-type }} ${{ matrix.cxxflags }}"
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- toolset: gcc-11
|
||||
install: g++-11
|
||||
container: ubuntu:22.04
|
||||
cxxstd: '17'
|
||||
build-type: 'Debug'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: gcc-11
|
||||
install: g++-11
|
||||
container: ubuntu:22.04
|
||||
cxxstd: '20'
|
||||
build-type: 'Release'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-11
|
||||
install: clang-11
|
||||
container: ubuntu:22.04
|
||||
cxxstd: '17'
|
||||
build-type: 'Debug'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-11
|
||||
install: clang-11
|
||||
container: ubuntu:22.04
|
||||
cxxstd: '20'
|
||||
build-type: 'Debug'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-13
|
||||
install: clang-13
|
||||
container: ubuntu:22.04
|
||||
cxxstd: '17'
|
||||
build-type: 'Release'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-13
|
||||
install: clang-13
|
||||
container: ubuntu:22.04
|
||||
cxxstd: '20'
|
||||
build-type: 'Release'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-14
|
||||
install: 'clang-14 libc++-14-dev libc++abi-14-dev'
|
||||
container: ubuntu:22.04
|
||||
cxxstd: '17'
|
||||
build-type: 'Debug'
|
||||
cxxflags: '-stdlib=libc++'
|
||||
ldflags: '-lc++'
|
||||
|
||||
- toolset: clang-14
|
||||
install: 'clang-14 libc++-14-dev libc++abi-14-dev'
|
||||
container: ubuntu:22.04
|
||||
cxxstd: '20'
|
||||
build-type: 'Release'
|
||||
cxxflags: '-stdlib=libc++'
|
||||
ldflags: '-lc++'
|
||||
|
||||
- toolset: clang-19
|
||||
install: 'clang-19'
|
||||
container: ubuntu:24.04
|
||||
cxxstd: '23'
|
||||
build-type: 'Debug'
|
||||
cxxflags: '-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all'
|
||||
ldflags: '-fsanitize=address -fsanitize=undefined'
|
||||
|
||||
- toolset: gcc-14
|
||||
install: 'g++-14'
|
||||
container: ubuntu:24.04
|
||||
cxxstd: '23'
|
||||
build-type: 'Debug'
|
||||
cxxflags: '-DBOOST_ASIO_DISABLE_LOCAL_SOCKETS=1' # If a system had no UNIX socket support, we build correctly
|
||||
|
||||
- toolset: gcc-14
|
||||
install: 'g++-14'
|
||||
container: ubuntu:24.04
|
||||
cxxstd: '23'
|
||||
build-type: 'Debug'
|
||||
cxxflags: '-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all'
|
||||
ldflags: '-fsanitize=address -fsanitize=undefined'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CXXFLAGS: ${{matrix.cxxflags}} -Wall -Wextra
|
||||
LDFLAGS: ${{matrix.ldflags}}
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: 4
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up the required containers
|
||||
run: |
|
||||
IMAGE=${{ matrix.container }} docker compose -f tools/docker-compose.yml up -d --wait || (docker compose logs; exit 1)
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
docker exec builder apt-get update
|
||||
docker exec builder apt-get -y --no-install-recommends install \
|
||||
git \
|
||||
g++ \
|
||||
libssl-dev \
|
||||
make \
|
||||
ca-certificates \
|
||||
cmake \
|
||||
protobuf-compiler \
|
||||
python3 \
|
||||
${{ matrix.install }}
|
||||
|
||||
- name: Setup Boost
|
||||
run: docker exec builder /boost-redis/tools/ci.py setup-boost --source-dir=/boost-redis
|
||||
|
||||
- name: Build a Boost distribution using B2
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py build-b2-distro \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Build a Boost distribution using CMake
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py build-cmake-distro \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Build the project tests
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py build-cmake-standalone-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Run the project tests
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py run-cmake-standalone-tests \
|
||||
--build-type ${{ matrix.build-type }}
|
||||
|
||||
- name: Run add_subdirectory tests
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py run-cmake-add-subdirectory-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Run find_package tests with the built cmake distribution
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py run-cmake-find-package-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Run find_package tests with the built b2 distribution
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py run-cmake-b2-find-package-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
posix-b2:
|
||||
name: "B2 ${{ matrix.toolset }}"
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- toolset: gcc-11
|
||||
install: g++-11
|
||||
cxxstd: "11,17,20" # Having C++11 shouldn't break the build
|
||||
os: ubuntu-latest
|
||||
container: ubuntu:22.04
|
||||
- toolset: clang-14
|
||||
install: clang-14
|
||||
os: ubuntu-latest
|
||||
container: ubuntu:22.04
|
||||
cxxstd: "17,20"
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{matrix.container}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup container environment
|
||||
if: matrix.container
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -y install sudo python3 git g++ libssl-dev
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install python3 ${{ matrix.install }}
|
||||
|
||||
- name: Setup Boost
|
||||
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build and run project tests using B2
|
||||
run: |
|
||||
python3 tools/ci.py run-b2-tests \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--variant debug,release
|
||||
|
||||
# Checks that we don't have any errors in docs
|
||||
check-docs:
|
||||
name: Check docs
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Boost
|
||||
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
cd ~/boost-root
|
||||
./b2 libs/redis/doc
|
||||
[ -f ~/boost-root/libs/redis/doc/html/index.html ]
|
||||
4
BUILD_STATUS.md
Normal file
4
BUILD_STATUS.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Branch | GH Actions | codecov.io |
|
||||
:-------------: | ---------- | ---------- |
|
||||
[`master`](https://github.com/mzimbres/aedis/tree/master) | [](https://github.com/mzimbres/aedis/actions/workflows/ci.yml) | [](https://codecov.io/gh/mzimbres/aedis/branch/master)
|
||||
|
||||
141
CMakeLists.txt
Normal file
141
CMakeLists.txt
Normal file
@@ -0,0 +1,141 @@
|
||||
cmake_minimum_required(VERSION 3.8...3.20)
|
||||
|
||||
# determine whether it's main/root project
|
||||
# or being built under another project.
|
||||
if (NOT DEFINED BOOST_REDIS_MAIN_PROJECT)
|
||||
set(BOOST_REDIS_MAIN_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(BOOST_REDIS_MAIN_PROJECT ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
project(boost_redis VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
|
||||
|
||||
# Library
|
||||
add_library(boost_redis INTERFACE)
|
||||
add_library(Boost::redis ALIAS boost_redis)
|
||||
target_include_directories(boost_redis INTERFACE include)
|
||||
target_compile_features(boost_redis INTERFACE cxx_std_17)
|
||||
|
||||
# Dependencies
|
||||
if (BOOST_REDIS_MAIN_PROJECT)
|
||||
# TODO: Understand why we have to list all dependencies below
|
||||
# instead of
|
||||
#set(BOOST_INCLUDE_LIBRARIES redis)
|
||||
#set(BOOST_EXCLUDE_LIBRARIES redis)
|
||||
#add_subdirectory(../.. boostorg/boost EXCLUDE_FROM_ALL)
|
||||
|
||||
set(deps
|
||||
system
|
||||
assert
|
||||
config
|
||||
throw_exception
|
||||
asio
|
||||
variant2
|
||||
mp11
|
||||
winapi
|
||||
predef
|
||||
align
|
||||
context
|
||||
core
|
||||
static_assert
|
||||
pool
|
||||
date_time
|
||||
smart_ptr
|
||||
exception
|
||||
integer
|
||||
move
|
||||
type_traits
|
||||
algorithm
|
||||
utility
|
||||
io
|
||||
lexical_cast
|
||||
numeric/conversion
|
||||
mpl
|
||||
range
|
||||
tokenizer
|
||||
tuple
|
||||
array
|
||||
bind
|
||||
concept_check
|
||||
function
|
||||
iterator
|
||||
regex
|
||||
unordered
|
||||
preprocessor
|
||||
container
|
||||
conversion
|
||||
container_hash
|
||||
detail
|
||||
optional
|
||||
function_types
|
||||
fusion
|
||||
intrusive
|
||||
describe
|
||||
typeof
|
||||
functional
|
||||
test
|
||||
json
|
||||
endian
|
||||
)
|
||||
|
||||
foreach(dep IN LISTS deps)
|
||||
add_subdirectory(../${dep} boostorg/${dep})
|
||||
endforeach()
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(boost_redis
|
||||
INTERFACE
|
||||
Boost::system
|
||||
Boost::asio
|
||||
Threads::Threads
|
||||
OpenSSL::Crypto
|
||||
OpenSSL::SSL
|
||||
)
|
||||
else()
|
||||
# If we're in the superproject or called from add_subdirectory,
|
||||
# Boost dependencies should be already available.
|
||||
# If other dependencies are not found, we bail out
|
||||
find_package(Threads)
|
||||
if(NOT Threads_FOUND)
|
||||
message(STATUS "Boost.Redis has been disabled, because the required package Threads hasn't been found")
|
||||
return()
|
||||
endif()
|
||||
find_package(OpenSSL)
|
||||
if(NOT OpenSSL_FOUND)
|
||||
message(STATUS "Boost.Redis has been disabled, because the required package OpenSSL hasn't been found")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# This is generated by boostdep
|
||||
target_link_libraries(boost_redis
|
||||
INTERFACE
|
||||
Boost::asio
|
||||
Boost::assert
|
||||
Boost::core
|
||||
Boost::mp11
|
||||
Boost::system
|
||||
Boost::throw_exception
|
||||
Threads::Threads
|
||||
OpenSSL::Crypto
|
||||
OpenSSL::SSL
|
||||
)
|
||||
endif()
|
||||
|
||||
# Enable testing. If we're being called from the superproject, this has already been done
|
||||
if (BOOST_REDIS_MAIN_PROJECT)
|
||||
include(CTest)
|
||||
endif()
|
||||
|
||||
# Most tests require a running Redis server, so we only run them if we're the main project
|
||||
if(BOOST_REDIS_MAIN_PROJECT AND BUILD_TESTING)
|
||||
# Tests and common utilities
|
||||
add_subdirectory(test)
|
||||
|
||||
# Benchmarks. Build them with tests to prevent code rotting
|
||||
add_subdirectory(benchmarks)
|
||||
|
||||
# Examples
|
||||
add_subdirectory(example)
|
||||
endif()
|
||||
178
CMakePresets.json
Normal file
178
CMakePresets.json
Normal file
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"version": 2,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 14,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "cmake-pedantic",
|
||||
"hidden": true,
|
||||
"warnings": {
|
||||
"dev": true,
|
||||
"deprecated": true,
|
||||
"uninitialized": false,
|
||||
"unusedCli": true,
|
||||
"systemVars": false
|
||||
},
|
||||
"errors": {
|
||||
"dev": true,
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "coverage",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/coverage",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Coverage",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra",
|
||||
"CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/coverage",
|
||||
"COVERAGE_HTML_COMMAND": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "g++-11",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/g++-11",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_CXX_COMPILER": "g++-11",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "g++-11-release",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/g++-11-release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra",
|
||||
"CMAKE_CXX_COMPILER": "g++-11",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang++-13",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/clang++-13",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_CXX_COMPILER": "clang++-13",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang++-14",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/clang++-14",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_CXX_COMPILER": "clang++-14",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-14"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libc++-14-cpp17",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/libc++-14-cpp17",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -stdlib=libc++ -std=c++17",
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-lc++",
|
||||
"CMAKE_CXX_COMPILER": "clang++-14",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp17"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libc++-14-cpp20",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/libc++-14-cpp20",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -stdlib=libc++ -std=c++17",
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-lc++",
|
||||
"CMAKE_CXX_COMPILER": "clang++-14",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang-tidy",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["g++-11"],
|
||||
"binaryDir": "${sourceDir}/build/clang-tidy",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=${sourceDir}/include/*",
|
||||
"CMAKE_CXX_STANDARD": "20"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{ "name": "coverage", "configurePreset": "coverage" },
|
||||
{ "name": "g++-11", "configurePreset": "g++-11" },
|
||||
{ "name": "g++-11-release", "configurePreset": "g++-11-release" },
|
||||
{ "name": "clang++-13", "configurePreset": "clang++-13" },
|
||||
{ "name": "clang++-14", "configurePreset": "clang++-14" },
|
||||
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17" },
|
||||
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20" },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy" }
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "test",
|
||||
"hidden": true,
|
||||
"output": {"outputOnFailure": true},
|
||||
"execution": {"noTestsAction": "error", "stopOnFailure": true}
|
||||
},
|
||||
{ "name": "coverage", "configurePreset": "coverage", "inherits": ["test"] },
|
||||
{ "name": "g++-11", "configurePreset": "g++-11", "inherits": ["test"] },
|
||||
{ "name": "g++-11-release", "configurePreset": "g++-11-release", "inherits": ["test"] },
|
||||
{ "name": "clang++-13", "configurePreset": "clang++-13", "inherits": ["test"] },
|
||||
{ "name": "clang++-14", "configurePreset": "clang++-14", "inherits": ["test"] },
|
||||
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17", "inherits": ["test"] },
|
||||
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20", "inherits": ["test"] },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy", "inherits": ["test"] }
|
||||
]
|
||||
}
|
||||
373
LICENSE
373
LICENSE
@@ -1,373 +0,0 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
23
LICENSE.txt
Normal file
23
LICENSE.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
103
Makefile.am
103
Makefile.am
@@ -1,103 +0,0 @@
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
AM_COLOR_TESTS = always
|
||||
DISTCHECK_CONFIGURE_FLAGS = CPPFLAGS="$(BOOST_CPPFLAGS) $(CPPFLAGS)" LDFLAGS="$(BOOST_LDFLAGS)"
|
||||
|
||||
AM_CPPFLAGS =
|
||||
AM_CPPFLAGS += $(BOOST_CPPFLAGS)
|
||||
|
||||
AM_LDFLAGS =
|
||||
AM_LDFLAGS += -pthread
|
||||
|
||||
check_PROGRAMS =
|
||||
check_PROGRAMS += intro
|
||||
check_PROGRAMS += sets
|
||||
check_PROGRAMS += hashes
|
||||
check_PROGRAMS += serialization
|
||||
check_PROGRAMS += multipurpose_response
|
||||
check_PROGRAMS += lists
|
||||
check_PROGRAMS += key_expiration
|
||||
check_PROGRAMS += response_adapter
|
||||
check_PROGRAMS += sync
|
||||
check_PROGRAMS += multipurpose_client
|
||||
check_PROGRAMS += test_offline
|
||||
check_PROGRAMS += test_online
|
||||
check_PROGRAMS += transaction
|
||||
|
||||
EXTRA_PROGRAMS =
|
||||
EXTRA_PROGRAMS += subscriber
|
||||
EXTRA_PROGRAMS += echo_server
|
||||
EXTRA_PROGRAMS += chat_room
|
||||
EXTRA_PROGRAMS += commands
|
||||
|
||||
CLEANFILES =
|
||||
CLEANFILES += $(EXTRA_PROGRAMS)
|
||||
|
||||
.PHONY: all
|
||||
all: $(check_PROGRAMS) $(EXTRA_PROGRAMS)
|
||||
|
||||
intro_SOURCES = $(top_srcdir)/examples/intro.cpp
|
||||
sets_SOURCES = $(top_srcdir)/examples/sets.cpp
|
||||
hashes_SOURCES = $(top_srcdir)/examples/hashes.cpp
|
||||
serialization_SOURCES = $(top_srcdir)/examples/serialization.cpp
|
||||
multipurpose_response_SOURCES = $(top_srcdir)/examples/multipurpose_response.cpp
|
||||
lists_SOURCES = $(top_srcdir)/examples/lists.cpp
|
||||
key_expiration_SOURCES = $(top_srcdir)/examples/key_expiration.cpp
|
||||
response_adapter_SOURCES = $(top_srcdir)/examples/response_adapter.cpp
|
||||
sync_SOURCES = $(top_srcdir)/examples/sync.cpp
|
||||
multipurpose_client_SOURCES = $(top_srcdir)/examples/multipurpose_client.cpp
|
||||
commands_SOURCES = $(top_srcdir)/tools/commands.cpp
|
||||
test_offline_SOURCES = $(top_srcdir)/tests/offline.cpp
|
||||
test_online_SOURCES = $(top_srcdir)/tests/online.cpp
|
||||
subscriber_SOURCES = $(top_srcdir)/examples/subscriber.cpp
|
||||
echo_server_SOURCES = $(top_srcdir)/examples/echo_server.cpp
|
||||
chat_room_SOURCES = $(top_srcdir)/examples/chat_room.cpp
|
||||
transaction_SOURCES = $(top_srcdir)/examples/transaction.cpp
|
||||
|
||||
nobase_include_HEADERS =\
|
||||
$(top_srcdir)/aedis/config.hpp\
|
||||
$(top_srcdir)/aedis/src.hpp\
|
||||
$(top_srcdir)/aedis/redis/command.hpp\
|
||||
$(top_srcdir)/aedis/sentinel/command.hpp\
|
||||
$(top_srcdir)/aedis/aedis.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapter/detail/adapters.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapter/error.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapt.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/composer.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/read_ops.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/parser.hpp\
|
||||
$(top_srcdir)/aedis/resp3/serializer.hpp\
|
||||
$(top_srcdir)/aedis/resp3/response_traits.hpp\
|
||||
$(top_srcdir)/aedis/resp3/node.hpp\
|
||||
$(top_srcdir)/aedis/resp3/error.hpp\
|
||||
$(top_srcdir)/aedis/resp3/type.hpp\
|
||||
$(top_srcdir)/aedis/resp3/read.hpp\
|
||||
$(top_srcdir)/aedis/redis/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/sentinel/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/resp3/detail/impl/parser.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/type.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/node.ipp\
|
||||
$(top_srcdir)/aedis/redis/experimental/client.hpp\
|
||||
$(top_srcdir)/aedis/redis/experimental/impl/client.ipp
|
||||
|
||||
nobase_noinst_HEADERS =\
|
||||
$(top_srcdir)/examples/lib/net_utils.hpp\
|
||||
$(top_srcdir)/examples/lib/user_session.hpp\
|
||||
$(top_srcdir)/tests/check.hpp\
|
||||
$(top_srcdir)/tests/test_stream.hpp
|
||||
|
||||
TESTS = $(check_PROGRAMS)
|
||||
|
||||
EXTRA_DIST =
|
||||
EXTRA_DIST += $(top_srcdir)/README.md
|
||||
EXTRA_DIST += $(top_srcdir)/doc/DoxygenLayout.xml
|
||||
EXTRA_DIST += $(top_srcdir)/doc/aedis.css
|
||||
EXTRA_DIST += $(top_srcdir)/doc/htmlfooter.html
|
||||
EXTRA_DIST += $(top_srcdir)/doc/htmlheader.html
|
||||
|
||||
.PHONY: doc
|
||||
doc:
|
||||
rm -rf ../aedis-gh-pages/*
|
||||
doxygen doc/Doxyfile
|
||||
|
||||
130
README.md
130
README.md
@@ -1 +1,129 @@
|
||||
See https://mzimbres.github.io/aedis/
|
||||
# Boost.Redis
|
||||
|
||||
Boost.Redis is a high-level [Redis](https://redis.io/) client library built on top of
|
||||
[Boost.Asio](https://www.boost.org/doc/libs/latest/doc/html/boost_asio.html)
|
||||
that implements the Redis protocol
|
||||
[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
|
||||
|
||||
Full documentation is [here](https://www.boost.org/doc/libs/master/libs/redis/index.html).
|
||||
|
||||
## Requirements
|
||||
|
||||
The requirements for using Boost.Redis are:
|
||||
|
||||
* Boost 1.84 or higher. Boost.Redis is included in Boost installations since Boost 1.84.
|
||||
* C++17 or higher. Supported compilers include gcc 11 and later, clang 11 and later, and Visual Studio 16 (2019) and later.
|
||||
* Redis 6 or higher (must support RESP3).
|
||||
* OpenSSL.
|
||||
|
||||
The documentation assumes basic-level knowledge about [Redis](https://redis.io/docs/) and [Boost.Asio](https://www.boost.org/doc/libs/latest/doc/html/boost_asio.html).
|
||||
|
||||
## Building the library
|
||||
|
||||
To use the library it is necessary to include the following:
|
||||
|
||||
```cpp
|
||||
#include <boost/redis/src.hpp>
|
||||
```
|
||||
|
||||
in exactly one source file in your applications. Otherwise, the library is header-only.
|
||||
|
||||
Boost.Redis unconditionally requires OpenSSL. Targets using Boost.Redis need to link
|
||||
to the OpenSSL libraries.
|
||||
|
||||
## Tutorial
|
||||
|
||||
The code below uses a short-lived connection to
|
||||
[ping](https://redis.io/commands/ping/) a Redis server:
|
||||
|
||||
|
||||
```cpp
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto co_main(config const& cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
// A request containing only a ping command.
|
||||
request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
// Response object.
|
||||
response<std::string> resp;
|
||||
|
||||
// Executes the request.
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
The roles played by the `async_run` and `async_exec` functions are:
|
||||
|
||||
* `connection::async_exec`: executes the commands contained in the
|
||||
request and stores the individual responses in the response object. Can
|
||||
be called from multiple places in your code concurrently.
|
||||
* `connection::async_run`: keeps the connection healthy. It takes care of hostname resolution, session establishment, health-checks, reconnection and coordination of low-level read and write operations. It should be called only once per connection, regardless of the number of requests to execute.
|
||||
|
||||
## Server pushes
|
||||
|
||||
Redis servers can also send a variety of pushes to the client. Some of
|
||||
them are:
|
||||
|
||||
* [Pubsub messages](https://redis.io/docs/manual/pubsub/).
|
||||
* [Keyspace notifications](https://redis.io/docs/manual/keyspace-notifications/).
|
||||
* [Client-side caching](https://redis.io/docs/manual/client-side-caching/).
|
||||
|
||||
The connection class supports server pushes by means of the
|
||||
`connection::async_receive` function, which can be
|
||||
called in the same connection that is being used to execute commands.
|
||||
The coroutine below shows how to use it:
|
||||
|
||||
|
||||
```cpp
|
||||
auto
|
||||
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
generic_response resp;
|
||||
conn->set_receive_response(resp);
|
||||
|
||||
// Loop while reconnection is enabled
|
||||
while (conn->will_reconnect()) {
|
||||
|
||||
// Reconnect to channels.
|
||||
co_await conn->async_exec(req, ignore);
|
||||
|
||||
// Loop reading Redis pushes.
|
||||
for (;;) {
|
||||
error_code ec;
|
||||
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
|
||||
// Use the response resp in some way and then clear it.
|
||||
...
|
||||
|
||||
consume_one(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Further reading
|
||||
|
||||
Full documentation is [here](https://www.boost.org/doc/libs/master/libs/redis/index.html).
|
||||
183
aedis/aedis.hpp
183
aedis/aedis.hpp
@@ -1,183 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
#include <aedis/sentinel/command.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/adapt.hpp>
|
||||
#include <aedis/resp3/error.hpp>
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
#include <aedis/redis/experimental/client.hpp>
|
||||
|
||||
/** \mainpage Documentation
|
||||
\tableofcontents
|
||||
|
||||
\section Overview
|
||||
|
||||
Aedis is low-level redis client library built on top of Boost.Asio
|
||||
that implements communication with a Redis server over its native
|
||||
protocol RESP3. It has first-class support for STL containers and
|
||||
C++ built in types among other things. You will be able to
|
||||
implement your own redis client or use a general purpose provided
|
||||
by the library. For more information about Redis see
|
||||
https://redis.io/
|
||||
|
||||
\section examples Examples
|
||||
|
||||
\b Basics: Focuses on small examples that show basic usage of
|
||||
the library.
|
||||
|
||||
- intro.cpp: A good starting point. Some commands are sent to the
|
||||
Redis server and the responses are printed to screen.
|
||||
|
||||
- transaction.cpp: Shows how to read the responses to a trasaction
|
||||
efficiently. See also https://redis.io/topics/transactions.
|
||||
|
||||
- multipurpose_response.cpp: Shows how to read any responses to
|
||||
Redis commands, including nested aggegagtes.
|
||||
|
||||
- subscriber.cpp: Shows how channel subscription works at a low
|
||||
level. See also https://redis.io/topics/pubsub.
|
||||
|
||||
- sync.cpp: Shows hot to use the Aedis synchronous api.
|
||||
|
||||
- key_expiration.cpp: Shows how to use \c std::optional to deal
|
||||
with keys that may have expired or do not exist.
|
||||
|
||||
\b STL \b Containers: Many of the Redis data structures can be
|
||||
directly translated in to STL containers, below you will find some
|
||||
example code. For a list of Redis data types see
|
||||
https://redis.io/topics/data-types.
|
||||
|
||||
- hashes.cpp: Shows how to read Redis hashes in a \c std::map, \c
|
||||
std::unordered_map and \c std::vector.
|
||||
|
||||
- lists.cpp: Shows how to read Redis lists in \c std::list,
|
||||
\c std::deque, \c std::vector. It also illustrates basic serialization.
|
||||
|
||||
- sets.cpp: Shows how to read Redis sets in a \c std::set, \c
|
||||
std::unordered_set and \c std::vector.
|
||||
|
||||
\b Customization \b points: Shows how de/serialize user types
|
||||
avoiding copies. This is particularly useful for low latency
|
||||
applications that want to avoid unneeded copies, for examples when
|
||||
storing json strings in Redis keys.
|
||||
|
||||
- serialization.cpp: Shows how to de/serialize your own
|
||||
non-aggregate data-structures.
|
||||
|
||||
- response_adapter.cpp: Customization point for users that want to
|
||||
de/serialize their own data-structures like containers for example.
|
||||
|
||||
\b Asynchronous \b servers: Contains some non-trivial examples
|
||||
servers that interact with users and Redis asynchronously over
|
||||
long lasting connections using a higher level API.
|
||||
|
||||
- multipurpose_client.cpp: Shows how to use and experimental high
|
||||
level redis client that keeps a long lasting connections to a
|
||||
redis server. This is the starting point for the next examples.
|
||||
|
||||
- echo_server.cpp: Shows the basic principles behind asynchronous
|
||||
communication with a database in an asynchronous server. In this
|
||||
case, the server is a proxy between the user and Redis.
|
||||
|
||||
- chat_room.cpp: Shows how to build a scalable chat room that
|
||||
scales to millions of users.
|
||||
|
||||
\section using-aedis Using Aedis
|
||||
|
||||
To install and use Aedis you will need
|
||||
|
||||
- Boost 1.78 or greater.
|
||||
- Unix Shell and Make.
|
||||
- Compiler with C++20 coroutine support e.g. GCC 10 or greater.
|
||||
- Redis server.
|
||||
|
||||
Some examples will also require interaction with
|
||||
|
||||
- redis-cli: used in one example.
|
||||
- Redis Sentinel Server: used in some examples.
|
||||
|
||||
\subsection Installation
|
||||
|
||||
Start by downloading and configuring the library
|
||||
|
||||
```
|
||||
# Download the libray on github.
|
||||
$ wget https://github.com/mzimbres/aedis/release-path # TODO
|
||||
|
||||
# Uncompress the tarball and cd into the dir
|
||||
$ tar -xzvf aedis-1.0.0.tar.gz && cd aedis-1.0.0
|
||||
|
||||
# Run configure with appropriate C++ flags and your boost
|
||||
# installation, for example # You may also have to use
|
||||
# -Wno-subobject-linkage on gcc.
|
||||
$ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall"\
|
||||
./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0 --with-boost-libdir=/opt/boost_1_78_0/lib
|
||||
|
||||
```
|
||||
|
||||
To install the library run
|
||||
|
||||
```
|
||||
# Install Aedis in the path specified in --prefix
|
||||
$ sudo make install
|
||||
|
||||
```
|
||||
|
||||
At this point you can start using Aedis. To build the examples and
|
||||
test you can also run
|
||||
|
||||
```
|
||||
# Build aedis examples.
|
||||
$ make examples
|
||||
|
||||
# Test aedis in your machine.
|
||||
$ make check
|
||||
```
|
||||
|
||||
Finally you will have to include the following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
```
|
||||
in exactly one source file in your applications.
|
||||
|
||||
Windows users can use aedis by either adding the project root
|
||||
directory to their include path or manually copying to another
|
||||
location.
|
||||
|
||||
\subsection Developers
|
||||
|
||||
To generate the build system run
|
||||
|
||||
```
|
||||
$ autoreconf -i
|
||||
```
|
||||
|
||||
After that you will have a config in the project dir that you can
|
||||
run as explained above, for example, to use a compiler other that
|
||||
the system compiler use
|
||||
|
||||
```
|
||||
CC=/opt/gcc-10.2.0/bin/gcc-10.2.0\
|
||||
CXX=/opt/gcc-10.2.0/bin/g++-10.2.0\
|
||||
CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage -Werror" ./configure ...
|
||||
```
|
||||
\section Referece
|
||||
|
||||
See \subpage any.
|
||||
*/
|
||||
|
||||
/** \defgroup any Reference
|
||||
*/
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// TODO: Remove this.
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace net = boost::asio;
|
||||
}
|
||||
|
||||
@@ -1,471 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace redis {
|
||||
|
||||
/** \brief Redis commands.
|
||||
* \ingroup any
|
||||
*
|
||||
* For a full list of commands see https://redis.io/commands.
|
||||
*
|
||||
* \remark The list of commands below are read from Redis with the
|
||||
* help of the command \c command.
|
||||
*/
|
||||
enum class command {
|
||||
/// https://redis.io/commands/acl
|
||||
acl,
|
||||
/// https://redis.io/commands/append
|
||||
append,
|
||||
/// https://redis.io/commands/asking
|
||||
asking,
|
||||
/// https://redis.io/commands/auth
|
||||
auth,
|
||||
/// https://redis.io/commands/bgrewriteaof
|
||||
bgrewriteaof,
|
||||
/// https://redis.io/commands/bgsave
|
||||
bgsave,
|
||||
/// https://redis.io/commands/bitcount
|
||||
bitcount,
|
||||
/// https://redis.io/commands/bitfield
|
||||
bitfield,
|
||||
/// https://redis.io/commands/bitfield_ro
|
||||
bitfield_ro,
|
||||
/// https://redis.io/commands/bitop
|
||||
bitop,
|
||||
/// https://redis.io/commands/bitpos
|
||||
bitpos,
|
||||
/// https://redis.io/commands/blpop
|
||||
blpop,
|
||||
/// https://redis.io/commands/brpop
|
||||
brpop,
|
||||
/// https://redis.io/commands/brpoplpush
|
||||
brpoplpush,
|
||||
/// https://redis.io/commands/bzpopmax
|
||||
bzpopmax,
|
||||
/// https://redis.io/commands/bzpopmin
|
||||
bzpopmin,
|
||||
/// https://redis.io/commands/client
|
||||
client,
|
||||
/// https://redis.io/commands/cluster
|
||||
cluster,
|
||||
/// https://redis.io/commands/command
|
||||
command,
|
||||
/// https://redis.io/commands/config
|
||||
config,
|
||||
/// https://redis.io/commands/dbsize
|
||||
dbsize,
|
||||
/// https://redis.io/commands/debug
|
||||
debug,
|
||||
/// https://redis.io/commands/decr
|
||||
decr,
|
||||
/// https://redis.io/commands/decrby
|
||||
decrby,
|
||||
/// https://redis.io/commands/del
|
||||
del,
|
||||
/// https://redis.io/commands/discard
|
||||
discard,
|
||||
/// https://redis.io/commands/dump
|
||||
dump,
|
||||
/// https://redis.io/commands/echo
|
||||
echo,
|
||||
/// https://redis.io/commands/eval
|
||||
eval,
|
||||
/// https://redis.io/commands/evalsha
|
||||
evalsha,
|
||||
/// https://redis.io/commands/exec
|
||||
exec,
|
||||
/// https://redis.io/commands/exists
|
||||
exists,
|
||||
/// https://redis.io/commands/expire
|
||||
expire,
|
||||
/// https://redis.io/commands/expireat
|
||||
expireat,
|
||||
/// https://redis.io/commands/flushall
|
||||
flushall,
|
||||
/// https://redis.io/commands/flushdb
|
||||
flushdb,
|
||||
/// https://redis.io/commands/geoadd
|
||||
geoadd,
|
||||
/// https://redis.io/commands/geodist
|
||||
geodist,
|
||||
/// https://redis.io/commands/geohash
|
||||
geohash,
|
||||
/// https://redis.io/commands/geopos
|
||||
geopos,
|
||||
/// https://redis.io/commands/georadius
|
||||
georadius,
|
||||
/// https://redis.io/commands/georadius_ro
|
||||
georadius_ro,
|
||||
/// https://redis.io/commands/georadiusbymember
|
||||
georadiusbymember,
|
||||
/// https://redis.io/commands/georadiusbymember_ro
|
||||
georadiusbymember_ro,
|
||||
/// https://redis.io/commands/get
|
||||
get,
|
||||
/// https://redis.io/commands/getbit
|
||||
getbit,
|
||||
/// https://redis.io/commands/getrange
|
||||
getrange,
|
||||
/// https://redis.io/commands/getset
|
||||
getset,
|
||||
/// https://redis.io/commands/hdel
|
||||
hdel,
|
||||
/// https://redis.io/commands/hello
|
||||
hello,
|
||||
/// https://redis.io/commands/hexists
|
||||
hexists,
|
||||
/// https://redis.io/commands/hget
|
||||
hget,
|
||||
/// https://redis.io/commands/hgetall
|
||||
hgetall,
|
||||
/// https://redis.io/commands/hincrby
|
||||
hincrby,
|
||||
/// https://redis.io/commands/hincrbyfloat
|
||||
hincrbyfloat,
|
||||
/// https://redis.io/commands/hkeys
|
||||
hkeys,
|
||||
/// https://redis.io/commands/hlen
|
||||
hlen,
|
||||
/// https://redis.io/commands/hmget
|
||||
hmget,
|
||||
/// https://redis.io/commands/hmset
|
||||
hmset,
|
||||
/// https://redis.io/commands/hscan
|
||||
hscan,
|
||||
/// https://redis.io/commands/hset
|
||||
hset,
|
||||
/// https://redis.io/commands/hsetnx
|
||||
hsetnx,
|
||||
/// https://redis.io/commands/hstrlen
|
||||
hstrlen,
|
||||
/// https://redis.io/commands/hvals
|
||||
hvals,
|
||||
/// https://redis.io/commands/incr
|
||||
incr,
|
||||
/// https://redis.io/commands/incrby
|
||||
incrby,
|
||||
/// https://redis.io/commands/incrbyfloat
|
||||
incrbyfloat,
|
||||
/// https://redis.io/commands/info
|
||||
info,
|
||||
/// https://redis.io/commands/keys
|
||||
keys,
|
||||
/// https://redis.io/commands/lastsave
|
||||
lastsave,
|
||||
/// https://redis.io/commands/latency
|
||||
latency,
|
||||
/// https://redis.io/commands/lindex
|
||||
lindex,
|
||||
/// https://redis.io/commands/linsert
|
||||
linsert,
|
||||
/// https://redis.io/commands/llen
|
||||
llen,
|
||||
/// https://redis.io/commands/lolwut
|
||||
lolwut,
|
||||
/// https://redis.io/commands/lpop
|
||||
lpop,
|
||||
/// https://redis.io/commands/lpos
|
||||
lpos,
|
||||
/// https://redis.io/commands/lpush
|
||||
lpush,
|
||||
/// https://redis.io/commands/lpushx
|
||||
lpushx,
|
||||
/// https://redis.io/commands/lrange
|
||||
lrange,
|
||||
/// https://redis.io/commands/lrem
|
||||
lrem,
|
||||
/// https://redis.io/commands/lset
|
||||
lset,
|
||||
/// https://redis.io/commands/ltrim
|
||||
ltrim,
|
||||
/// https://redis.io/commands/memory
|
||||
memory,
|
||||
/// https://redis.io/commands/mget
|
||||
mget,
|
||||
/// https://redis.io/commands/migrate
|
||||
migrate,
|
||||
/// https://redis.io/commands/module
|
||||
module,
|
||||
/// https://redis.io/commands/monitor
|
||||
monitor,
|
||||
/// https://redis.io/commands/move
|
||||
move,
|
||||
/// https://redis.io/commands/mset
|
||||
mset,
|
||||
/// https://redis.io/commands/msetnx
|
||||
msetnx,
|
||||
/// https://redis.io/commands/multi
|
||||
multi,
|
||||
/// https://redis.io/commands/object
|
||||
object,
|
||||
/// https://redis.io/commands/persist
|
||||
persist,
|
||||
/// https://redis.io/commands/pexpire
|
||||
pexpire,
|
||||
/// https://redis.io/commands/pexpireat
|
||||
pexpireat,
|
||||
/// https://redis.io/commands/pfadd
|
||||
pfadd,
|
||||
/// https://redis.io/commands/pfcount
|
||||
pfcount,
|
||||
/// https://redis.io/commands/pfdebug
|
||||
pfdebug,
|
||||
/// https://redis.io/commands/pfmerge
|
||||
pfmerge,
|
||||
/// https://redis.io/commands/pfselftest
|
||||
pfselftest,
|
||||
/// https://redis.io/commands/ping
|
||||
ping,
|
||||
/// https://redis.io/commands/post
|
||||
post,
|
||||
/// https://redis.io/commands/psetex
|
||||
psetex,
|
||||
/// https://redis.io/commands/psubscribe
|
||||
psubscribe,
|
||||
/// https://redis.io/commands/psync
|
||||
psync,
|
||||
/// https://redis.io/commands/pttl
|
||||
pttl,
|
||||
/// https://redis.io/commands/publish
|
||||
publish,
|
||||
/// https://redis.io/commands/pubsub
|
||||
pubsub,
|
||||
/// https://redis.io/commands/punsubscribe
|
||||
punsubscribe,
|
||||
/// https://redis.io/commands/randomkey
|
||||
randomkey,
|
||||
/// https://redis.io/commands/readonly
|
||||
readonly,
|
||||
/// https://redis.io/commands/readwrite
|
||||
readwrite,
|
||||
/// https://redis.io/commands/rename
|
||||
rename,
|
||||
/// https://redis.io/commands/renamenx
|
||||
renamenx,
|
||||
/// https://redis.io/commands/replconf
|
||||
replconf,
|
||||
/// https://redis.io/commands/replicaof
|
||||
replicaof,
|
||||
/// https://redis.io/commands/restore
|
||||
restore,
|
||||
/// https://redis.io/commands/role
|
||||
role,
|
||||
/// https://redis.io/commands/rpop
|
||||
rpop,
|
||||
/// https://redis.io/commands/rpoplpush
|
||||
rpoplpush,
|
||||
/// https://redis.io/commands/rpush
|
||||
rpush,
|
||||
/// https://redis.io/commands/rpushx
|
||||
rpushx,
|
||||
/// https://redis.io/commands/sadd
|
||||
sadd,
|
||||
/// https://redis.io/commands/save
|
||||
save,
|
||||
/// https://redis.io/commands/scan
|
||||
scan,
|
||||
/// https://redis.io/commands/scard
|
||||
scard,
|
||||
/// https://redis.io/commands/script
|
||||
script,
|
||||
/// https://redis.io/commands/sdiff
|
||||
sdiff,
|
||||
/// https://redis.io/commands/sdiffstore
|
||||
sdiffstore,
|
||||
/// https://redis.io/commands/select
|
||||
select,
|
||||
/// https://redis.io/commands/set
|
||||
set,
|
||||
/// https://redis.io/commands/setbit
|
||||
setbit,
|
||||
/// https://redis.io/commands/setex
|
||||
setex,
|
||||
/// https://redis.io/commands/setnx
|
||||
setnx,
|
||||
/// https://redis.io/commands/setrange
|
||||
setrange,
|
||||
/// https://redis.io/commands/shutdown
|
||||
shutdown,
|
||||
/// https://redis.io/commands/sinter
|
||||
sinter,
|
||||
/// https://redis.io/commands/sinterstore
|
||||
sinterstore,
|
||||
/// https://redis.io/commands/sismember
|
||||
sismember,
|
||||
/// https://redis.io/commands/slaveof
|
||||
slaveof,
|
||||
/// https://redis.io/commands/slowlog
|
||||
slowlog,
|
||||
/// https://redis.io/commands/smembers
|
||||
smembers,
|
||||
/// https://redis.io/commands/smove
|
||||
smove,
|
||||
/// https://redis.io/commands/sort
|
||||
sort,
|
||||
/// https://redis.io/commands/spop
|
||||
spop,
|
||||
/// https://redis.io/commands/srandmember
|
||||
srandmember,
|
||||
/// https://redis.io/commands/srem
|
||||
srem,
|
||||
/// https://redis.io/commands/sscan
|
||||
sscan,
|
||||
/// https://redis.io/commands/stralgo
|
||||
stralgo,
|
||||
/// https://redis.io/commands/strlen
|
||||
strlen,
|
||||
/// https://redis.io/commands/subscribe
|
||||
subscribe,
|
||||
/// https://redis.io/commands/substr
|
||||
substr,
|
||||
/// https://redis.io/commands/sunion
|
||||
sunion,
|
||||
/// https://redis.io/commands/sunionstore
|
||||
sunionstore,
|
||||
/// https://redis.io/commands/swapdb
|
||||
swapdb,
|
||||
/// https://redis.io/commands/sync
|
||||
sync,
|
||||
/// https://redis.io/commands/time
|
||||
time,
|
||||
/// https://redis.io/commands/touch
|
||||
touch,
|
||||
/// https://redis.io/commands/ttl
|
||||
ttl,
|
||||
/// https://redis.io/commands/type
|
||||
type,
|
||||
/// https://redis.io/commands/unlink
|
||||
unlink,
|
||||
/// https://redis.io/commands/quit
|
||||
quit,
|
||||
/// https://redis.io/commands/unsubscribe
|
||||
unsubscribe,
|
||||
/// https://redis.io/commands/unwatch
|
||||
unwatch,
|
||||
/// https://redis.io/commands/wait
|
||||
wait,
|
||||
/// https://redis.io/commands/watch
|
||||
watch,
|
||||
/// https://redis.io/commands/xack
|
||||
xack,
|
||||
/// https://redis.io/commands/xadd
|
||||
xadd,
|
||||
/// https://redis.io/commands/xclaim
|
||||
xclaim,
|
||||
/// https://redis.io/commands/xdel
|
||||
xdel,
|
||||
/// https://redis.io/commands/xgroup
|
||||
xgroup,
|
||||
/// https://redis.io/commands/xinfo
|
||||
xinfo,
|
||||
/// https://redis.io/commands/xlen
|
||||
xlen,
|
||||
/// https://redis.io/commands/xpending
|
||||
xpending,
|
||||
/// https://redis.io/commands/xrange
|
||||
xrange,
|
||||
/// https://redis.io/commands/xread
|
||||
xread,
|
||||
/// https://redis.io/commands/xreadgroup
|
||||
xreadgroup,
|
||||
/// https://redis.io/commands/xrevrange
|
||||
xrevrange,
|
||||
/// https://redis.io/commands/xsetid
|
||||
xsetid,
|
||||
/// https://redis.io/commands/xtrim
|
||||
xtrim,
|
||||
/// https://redis.io/commands/zadd
|
||||
zadd,
|
||||
/// https://redis.io/commands/zcard
|
||||
zcard,
|
||||
/// https://redis.io/commands/zcount
|
||||
zcount,
|
||||
/// https://redis.io/commands/zincrby
|
||||
zincrby,
|
||||
/// https://redis.io/commands/zinterstore
|
||||
zinterstore,
|
||||
/// https://redis.io/commands/zlexcount
|
||||
zlexcount,
|
||||
/// https://redis.io/commands/zpopmax
|
||||
zpopmax,
|
||||
/// https://redis.io/commands/zpopmin
|
||||
zpopmin,
|
||||
/// https://redis.io/commands/zrange
|
||||
zrange,
|
||||
/// https://redis.io/commands/zrangebylex
|
||||
zrangebylex,
|
||||
/// https://redis.io/commands/zrangebyscore
|
||||
zrangebyscore,
|
||||
/// https://redis.io/commands/zrank
|
||||
zrank,
|
||||
/// https://redis.io/commands/zrem
|
||||
zrem,
|
||||
/// https://redis.io/commands/zremrangebylex
|
||||
zremrangebylex,
|
||||
/// https://redis.io/commands/zremrangebyrank
|
||||
zremrangebyrank,
|
||||
/// https://redis.io/commands/zremrangebyscore
|
||||
zremrangebyscore,
|
||||
/// https://redis.io/commands/zrevrange
|
||||
zrevrange,
|
||||
/// https://redis.io/commands/zrevrangebylex
|
||||
zrevrangebylex,
|
||||
/// https://redis.io/commands/zrevrangebyscore
|
||||
zrevrangebyscore,
|
||||
/// https://redis.io/commands/zrevrank
|
||||
zrevrank,
|
||||
/// https://redis.io/commands/zscan
|
||||
zscan,
|
||||
/// https://redis.io/commands/zscore
|
||||
zscore,
|
||||
/// https://redis.io/commands/zunionstore
|
||||
zunionstore,
|
||||
/// Unknown/invalid command.
|
||||
unknown
|
||||
};
|
||||
|
||||
/** \brief Converts a command to a string
|
||||
* \ingroup any
|
||||
*
|
||||
* \param c The command to convert.
|
||||
*/
|
||||
char const* to_string(command c);
|
||||
|
||||
/** \brief Write the text for a command name to an output stream.
|
||||
* \ingroup operators
|
||||
*
|
||||
* \param os Output stream.
|
||||
* \param c Redis command
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, command c);
|
||||
|
||||
/** \brief Returns true for commands with push response.
|
||||
* \ingroup any
|
||||
*/
|
||||
bool has_push_response(command cmd);
|
||||
|
||||
/** \brief Creates a serializer for a \c std::string.
|
||||
* \ingroup any
|
||||
* \param storage The string.
|
||||
*/
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
resp3::serializer<std::string, command>
|
||||
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
|
||||
{
|
||||
return resp3::serializer<std::basic_string<CharT, Traits, Allocator>, command>(storage);
|
||||
}
|
||||
|
||||
} // redis
|
||||
} // aedis
|
||||
@@ -1,158 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace experimental {
|
||||
|
||||
/** \brief A high level redis client.
|
||||
* \ingroup any
|
||||
*
|
||||
* This Redis client keeps a connection to the database open and
|
||||
* uses it for all communication with Redis. For examples on how to
|
||||
* use see the examples chat_room.cpp, echo_server.cpp and redis_client.cpp.
|
||||
*
|
||||
* \remarks This class reuses its internal buffers for requests and
|
||||
* for reading Redis responses. With time it will allocate less and
|
||||
* less.
|
||||
*/
|
||||
class client : public std::enable_shared_from_this<client> {
|
||||
public:
|
||||
/** \brief The extended response adapter type.
|
||||
*
|
||||
* The difference between the adapter and extended_adapter
|
||||
* concepts is that the extended get a command redis::parameter.
|
||||
*/
|
||||
using extented_adapter_type = std::function<void(redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&)>;
|
||||
|
||||
/// The type of the message callback.
|
||||
using on_message_type = std::function<void(std::error_code ec, redis::command)>;
|
||||
|
||||
/// The type of the socket used by the client.
|
||||
//using socket_type = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using socket_type = net::ip::tcp::socket;
|
||||
|
||||
private:
|
||||
struct request_info {
|
||||
// Request size in bytes.
|
||||
std::size_t size = 0;
|
||||
|
||||
// The number of commands it contains excluding commands that
|
||||
// have push types as responses, see has_push_response.
|
||||
std::size_t cmds = 0;
|
||||
};
|
||||
|
||||
// Requests payload.
|
||||
std::string requests_;
|
||||
|
||||
// The commands contained in the requests.
|
||||
std::queue<redis::command> commands_;
|
||||
|
||||
// Info about the requests.
|
||||
std::queue<request_info> req_info_;
|
||||
|
||||
// The stream.
|
||||
socket_type socket_;
|
||||
|
||||
// Timer used to inform the write coroutine that it can write the
|
||||
// next message in the output queue.
|
||||
net::steady_timer timer_;
|
||||
|
||||
// Response adapter.
|
||||
extented_adapter_type extended_adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {};
|
||||
|
||||
// Message callback.
|
||||
on_message_type on_msg_ = [](std::error_code ec, redis::command) {};
|
||||
|
||||
// Set when the writer coroutine should stop.
|
||||
bool stop_writer_ = false;
|
||||
|
||||
// A coroutine that keeps reading the socket. When a message
|
||||
// arrives it calls on_message.
|
||||
net::awaitable<void> reader();
|
||||
|
||||
// Write coroutine. It is kept suspended until there are messages
|
||||
// to be sent.
|
||||
net::awaitable<void> writer();
|
||||
|
||||
/* Prepares the back of the queue to receive further commands.
|
||||
*
|
||||
* If true is returned the request in the front of the queue can be
|
||||
* sent to the server. See async_write_some.
|
||||
*/
|
||||
bool prepare_next();
|
||||
|
||||
public:
|
||||
/** \brief Client constructor.
|
||||
*
|
||||
* Constructos the client from an executor.
|
||||
*
|
||||
* \param ex The executor.
|
||||
*/
|
||||
client(net::any_io_executor ex);
|
||||
|
||||
/// Returns the executor used for I/O with Redis.
|
||||
auto get_executor() {return socket_.get_executor();}
|
||||
|
||||
/** \brief Starts communication with Redis.
|
||||
*
|
||||
* This functions will send the hello command to Redis and spawn
|
||||
* the read and write coroutines.
|
||||
*
|
||||
* \param socket A socket that is connected to redis.
|
||||
*
|
||||
* \returns This function returns an awaitable on which users should \c
|
||||
* co_await. When the communication with Redis is lost the
|
||||
* coroutine will finally co_return.
|
||||
*/
|
||||
net::awaitable<void> engage(socket_type socket);
|
||||
|
||||
/** \brief Adds a command to the command queue.
|
||||
*
|
||||
* \sa serializer.hpp
|
||||
*/
|
||||
template <class... Ts>
|
||||
void send(redis::command cmd, Ts const&... args);
|
||||
|
||||
/// Sets an extended response adapter.
|
||||
void set_extended_adapter(extented_adapter_type adapter);
|
||||
|
||||
/// Sets the message callback;
|
||||
void set_msg_callback(on_message_type on_msg);
|
||||
};
|
||||
|
||||
template <class... Ts>
|
||||
void client::send(redis::command cmd, Ts const&... args)
|
||||
{
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
auto sr = redis::make_serializer(requests_);
|
||||
auto const before = std::size(requests_);
|
||||
sr.push(cmd, args...);
|
||||
auto const after = std::size(requests_);
|
||||
req_info_.front().size += after - before;;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.emplace(cmd);
|
||||
++req_info_.front().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
} // experimental
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,200 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/redis/experimental/client.hpp>
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace experimental {
|
||||
|
||||
client::client(net::any_io_executor ex)
|
||||
: socket_{ex}
|
||||
, timer_{ex}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
net::awaitable<void> client::reader()
|
||||
{
|
||||
// Writes and reads continuosly from the socket.
|
||||
for (std::string buffer;;) {
|
||||
// Notice this coro can get scheduled while the write operation
|
||||
// in the writer is ongoing. so we have to check.
|
||||
while (!std::empty(req_info_) && req_info_.front().size != 0) {
|
||||
assert(!std::empty(requests_));
|
||||
boost::system::error_code ec;
|
||||
co_await
|
||||
net::async_write(
|
||||
socket_,
|
||||
net::buffer(requests_.data(), req_info_.front().size),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
requests_.erase(0, req_info_.front().size);
|
||||
req_info_.front().size = 0;
|
||||
|
||||
if (req_info_.front().cmds != 0)
|
||||
break; // We must await the responses.
|
||||
|
||||
req_info_.pop();
|
||||
}
|
||||
|
||||
do { // Keeps reading while there are no messages queued waiting to be sent.
|
||||
do { // Consumes the responses to all commands in the request.
|
||||
boost::system::error_code ec;
|
||||
auto const t =
|
||||
co_await async_read_type(socket_, net::dynamic_buffer(buffer),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec) {
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
if (t == type::push) {
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
on_msg_(ec, redis::command::unknown);
|
||||
if (ec) { // TODO: Return only on non aedis errors.
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
} else {
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(commands_.front(), t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
on_msg_(ec, commands_.front());
|
||||
if (ec) { // TODO: Return only on non aedis errors.
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
commands_.pop();
|
||||
--req_info_.front().cmds;
|
||||
}
|
||||
|
||||
} while (!std::empty(req_info_) && req_info_.front().cmds != 0);
|
||||
|
||||
// We may exit the loop above either because we are done
|
||||
// with the response or because we received a server push
|
||||
// while the queue was empty so we have to check before
|
||||
// poping..
|
||||
if (!std::empty(req_info_))
|
||||
req_info_.pop();
|
||||
|
||||
} while (std::empty(req_info_));
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> client::writer()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
while (socket_.is_open()) {
|
||||
ec = {};
|
||||
co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
if (stop_writer_)
|
||||
co_return;
|
||||
|
||||
// Notice this coro can get scheduled while the write operation
|
||||
// in the reader is ongoing. so we have to check.
|
||||
while (!std::empty(req_info_) && req_info_.front().size != 0) {
|
||||
assert(!std::empty(requests_));
|
||||
ec = {};
|
||||
co_await net::async_write(
|
||||
socket_, net::buffer(requests_.data(), req_info_.front().size),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec) {
|
||||
// What should we do here exactly? Closing the socket will
|
||||
// cause the reader coroutine to return so that the engage
|
||||
// coroutine returns to the user.
|
||||
socket_.close();
|
||||
co_return;
|
||||
}
|
||||
|
||||
requests_.erase(0, req_info_.front().size);
|
||||
req_info_.front().size = 0;
|
||||
|
||||
if (req_info_.front().cmds != 0)
|
||||
break;
|
||||
|
||||
req_info_.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool client::prepare_next()
|
||||
{
|
||||
if (std::empty(req_info_)) {
|
||||
req_info_.push({});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req_info_.front().size == 0) {
|
||||
// It has already been written and we are waiting for the
|
||||
// responses.
|
||||
req_info_.push({});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void client::set_extended_adapter(extented_adapter_type adapter)
|
||||
{
|
||||
extended_adapter_ = adapter;
|
||||
}
|
||||
|
||||
void client::set_msg_callback(on_message_type on_msg)
|
||||
{
|
||||
on_msg_ = on_msg;
|
||||
}
|
||||
|
||||
net::awaitable<void> client::engage(socket_type socket)
|
||||
{
|
||||
using namespace aedis::net::experimental::awaitable_operators;
|
||||
|
||||
socket_ = std::move(socket);
|
||||
|
||||
std::string request;
|
||||
auto sr = redis::make_serializer(request);
|
||||
sr.push(redis::command::hello, 3);
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await net::async_write(socket_, net::buffer(request), net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
co_return;
|
||||
|
||||
std::string buffer;
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
co_await
|
||||
resp3::async_read(
|
||||
socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
on_msg_(ec, redis::command::hello);
|
||||
|
||||
if (ec)
|
||||
co_return;
|
||||
|
||||
co_await (reader() && writer());
|
||||
}
|
||||
|
||||
} // experimental
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace redis {
|
||||
|
||||
char const* to_string(command c)
|
||||
{
|
||||
static char const* table[] = {
|
||||
"ACL",
|
||||
"APPEND",
|
||||
"ASKING",
|
||||
"AUTH",
|
||||
"BGREWRITEAOF",
|
||||
"BGSAVE",
|
||||
"BITCOUNT",
|
||||
"BITFIELD",
|
||||
"BITFIELD_RO",
|
||||
"BITOP",
|
||||
"BITPOS",
|
||||
"BLPOP",
|
||||
"BRPOP",
|
||||
"BRPOPLPUSH",
|
||||
"BZPOPMAX",
|
||||
"BZPOPMIN",
|
||||
"CLIENT",
|
||||
"CLUSTER",
|
||||
"COMMAND",
|
||||
"CONFIG",
|
||||
"DBSIZE",
|
||||
"DEBUG",
|
||||
"DECR",
|
||||
"DECRBY",
|
||||
"DEL",
|
||||
"DISCARD",
|
||||
"DUMP",
|
||||
"ECHO",
|
||||
"EVAL",
|
||||
"EVALSHA",
|
||||
"EXEC",
|
||||
"EXISTS",
|
||||
"EXPIRE",
|
||||
"EXPIREAT",
|
||||
"FLUSHALL",
|
||||
"FLUSHDB",
|
||||
"GEOADD",
|
||||
"GEODIST",
|
||||
"GEOHASH",
|
||||
"GEOPOS",
|
||||
"GEORADIUS",
|
||||
"GEORADIUS_RO",
|
||||
"GEORADIUSBYMEMBER",
|
||||
"GEORADIUSBYMEMBER_RO",
|
||||
"GET",
|
||||
"GETBIT",
|
||||
"GETRANGE",
|
||||
"GETSET",
|
||||
"HDEL",
|
||||
"HELLO",
|
||||
"HEXISTS",
|
||||
"HGET",
|
||||
"HGETALL",
|
||||
"HINCRBY",
|
||||
"HINCRBYFLOAT",
|
||||
"HKEYS",
|
||||
"HLEN",
|
||||
"HMGET",
|
||||
"HMSET",
|
||||
"HSCAN",
|
||||
"HSET",
|
||||
"HSETNX",
|
||||
"HSTRLEN",
|
||||
"HVALS",
|
||||
"INCR",
|
||||
"INCRBY",
|
||||
"INCRBYFLOAT",
|
||||
"INFO",
|
||||
"KEYS",
|
||||
"LASTSAVE",
|
||||
"LATENCY",
|
||||
"LINDEX",
|
||||
"LINSERT",
|
||||
"LLEN",
|
||||
"LOLWUT",
|
||||
"LPOP",
|
||||
"LPOS",
|
||||
"LPUSH",
|
||||
"LPUSHX",
|
||||
"LRANGE",
|
||||
"LREM",
|
||||
"LSET",
|
||||
"LTRIM",
|
||||
"MEMORY",
|
||||
"MGET",
|
||||
"MIGRATE",
|
||||
"MODULE",
|
||||
"MONITOR",
|
||||
"MOVE",
|
||||
"MSET",
|
||||
"MSETNX",
|
||||
"MULTI",
|
||||
"OBJECT",
|
||||
"PERSIST",
|
||||
"PEXPIRE",
|
||||
"PEXPIREAT",
|
||||
"PFADD",
|
||||
"PFCOUNT",
|
||||
"PFDEBUG",
|
||||
"PFMERGE",
|
||||
"PFSELFTEST",
|
||||
"PING",
|
||||
"POST",
|
||||
"PSETEX",
|
||||
"PSUBSCRIBE",
|
||||
"PSYNC",
|
||||
"PTTL",
|
||||
"PUBLISH",
|
||||
"PUBSUB",
|
||||
"PUNSUBSCRIBE",
|
||||
"RANDOMKEY",
|
||||
"READONLY",
|
||||
"READWRITE",
|
||||
"RENAME",
|
||||
"RENAMENX",
|
||||
"REPLCONF",
|
||||
"REPLICAOF",
|
||||
"RESTORE",
|
||||
"ROLE",
|
||||
"RPOP",
|
||||
"RPOPLPUSH",
|
||||
"RPUSH",
|
||||
"RPUSHX",
|
||||
"SADD",
|
||||
"SAVE",
|
||||
"SCAN",
|
||||
"SCARD",
|
||||
"SCRIPT",
|
||||
"SDIFF",
|
||||
"SDIFFSTORE",
|
||||
"SELECT",
|
||||
"SET",
|
||||
"SETBIT",
|
||||
"SETEX",
|
||||
"SETNX",
|
||||
"SETRANGE",
|
||||
"SHUTDOWN",
|
||||
"SINTER",
|
||||
"SINTERSTORE",
|
||||
"SISMEMBER",
|
||||
"SLAVEOF",
|
||||
"SLOWLOG",
|
||||
"SMEMBERS",
|
||||
"SMOVE",
|
||||
"SORT",
|
||||
"SPOP",
|
||||
"SRANDMEMBER",
|
||||
"SREM",
|
||||
"SSCAN",
|
||||
"STRALGO",
|
||||
"STRLEN",
|
||||
"SUBSCRIBE",
|
||||
"SUBSTR",
|
||||
"SUNION",
|
||||
"SUNIONSTORE",
|
||||
"SWAPDB",
|
||||
"SYNC",
|
||||
"TIME",
|
||||
"TOUCH",
|
||||
"TTL",
|
||||
"TYPE",
|
||||
"UNLINK",
|
||||
"QUIT",
|
||||
"UNSUBSCRIBE",
|
||||
"UNWATCH",
|
||||
"WAIT",
|
||||
"WATCH",
|
||||
"XACK",
|
||||
"XADD",
|
||||
"XCLAIM",
|
||||
"XDEL",
|
||||
"XGROUP",
|
||||
"XINFO",
|
||||
"XLEN",
|
||||
"XPENDING",
|
||||
"XRANGE",
|
||||
"XREAD",
|
||||
"XREADGROUP",
|
||||
"XREVRANGE",
|
||||
"XSETID",
|
||||
"XTRIM",
|
||||
"ZADD",
|
||||
"ZCARD",
|
||||
"ZCOUNT",
|
||||
"ZINCRBY",
|
||||
"ZINTERSTORE",
|
||||
"ZLEXCOUNT",
|
||||
"ZPOPMAX",
|
||||
"ZPOPMIN",
|
||||
"ZRANGE",
|
||||
"ZRANGEBYLEX",
|
||||
"ZRANGEBYSCORE",
|
||||
"ZRANK",
|
||||
"ZREM",
|
||||
"ZREMRANGEBYLEX",
|
||||
"ZREMRANGEBYRANK",
|
||||
"ZREMRANGEBYSCORE",
|
||||
"ZREVRANGE",
|
||||
"ZREVRANGEBYLEX",
|
||||
"ZREVRANGEBYSCORE",
|
||||
"ZREVRANK",
|
||||
"ZSCAN",
|
||||
"ZSCORE",
|
||||
"ZUNIONSTORE",
|
||||
};
|
||||
|
||||
return table[static_cast<int>(c)];
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, command c)
|
||||
{
|
||||
os << to_string(c);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool has_push_response(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::subscribe:
|
||||
case command::unsubscribe:
|
||||
case command::psubscribe:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // redis
|
||||
} // aedis
|
||||
@@ -1,94 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Creates a void response adapter.
|
||||
\ingroup any
|
||||
|
||||
The adapter returned by this function ignores responses and is
|
||||
useful to avoid wasting time with responses which the user is
|
||||
insterested in.
|
||||
|
||||
Example usage:
|
||||
|
||||
@code
|
||||
co_await async_read(socket, buffer, adapt());
|
||||
@endcode
|
||||
*/
|
||||
inline
|
||||
auto adapt() noexcept
|
||||
{ return response_traits<void>::adapt(); }
|
||||
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup any
|
||||
*
|
||||
* For example
|
||||
* The following types are supported.
|
||||
*
|
||||
* - Integer data types e.g. `int`, `unsigned`, etc.
|
||||
*
|
||||
* - `std::string`
|
||||
*
|
||||
* We also support the following C++ containers
|
||||
*
|
||||
* - `std::vector<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::deque<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::list<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::set<T>`. Can be used with RESP3 set type.
|
||||
*
|
||||
* - `std::unordered_set<T>`. Can be used with RESP3 set type.
|
||||
*
|
||||
* - `std::map<T>`. Can be used with RESP3 hash type.
|
||||
*
|
||||
* - `std::unordered_map<T>`. Can be used with RESP3 hash type.
|
||||
*
|
||||
* All these types can be wrapped in an `std::optional<T>`. This
|
||||
* function also support \c std::tuple to read the response to
|
||||
* tuples. At the moment this feature supports only transactions that
|
||||
* contain simple types or aggregates that don't contain aggregates
|
||||
* themselves (as in most cases).
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code
|
||||
* std::unordered_map<std::string, std::string> cont;
|
||||
* co_await async_read(socket, buffer, adapt(cont));
|
||||
* @endcode
|
||||
*
|
||||
* For a transaction
|
||||
*
|
||||
* @code
|
||||
sr.push(command::multi);
|
||||
sr.push(command::ping, ...);
|
||||
sr.push(command::incr, ...);
|
||||
sr.push_range(command::rpush, ...);
|
||||
sr.push(command::lrange, ...);
|
||||
sr.push(command::incr, ...);
|
||||
sr.push(command::exec);
|
||||
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the response to a transaction
|
||||
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
* @endcode
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,382 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
#include <aedis/resp3/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
from_string(
|
||||
T& i,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
auto const res = std::from_chars(data, data + data_size, i);
|
||||
if (res.ec != std::errc())
|
||||
ec = std::make_error_code(res.ec);
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
void
|
||||
from_string(
|
||||
std::basic_string<CharT, Traits, Allocator>& s,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code&)
|
||||
{
|
||||
s.assign(data, data_size);
|
||||
}
|
||||
|
||||
void set_on_resp3_error(type t, std::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case type::blob_error: ec = adapter::error::blob_error; return;
|
||||
case type::null: ec = adapter::error::null; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// For optional responses.
|
||||
void set_on_resp3_error2(type t, std::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case type::blob_error: ec = adapter::error::blob_error; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapter that ignores responses.
|
||||
struct ignore {
|
||||
void
|
||||
operator()(
|
||||
type, std::size_t, std::size_t, char const*, std::size_t,
|
||||
std::error_code&) { }
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class general {
|
||||
private:
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
general(Container* c = nullptr): result_(c) {}
|
||||
|
||||
/** @brief Function called by the parser when new data has been processed.
|
||||
*
|
||||
* Users who what to customize their response types are required to derive
|
||||
* from this class and override this function, see examples.
|
||||
*
|
||||
* \param t The RESP3 type of the data.
|
||||
*
|
||||
* \param n When t is an aggregate data type this will contain its size
|
||||
* (see also element_multiplicity) for simple data types this is always 1.
|
||||
*
|
||||
* \param depth The element depth in the tree.
|
||||
*
|
||||
* \param data A pointer to the data.
|
||||
*
|
||||
* \param size The size of data.
|
||||
*/
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code&)
|
||||
{
|
||||
result_->emplace_back(t, aggregate_size, depth, std::string{data, size});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node>
|
||||
class adapter_node {
|
||||
private:
|
||||
Node* result_;
|
||||
|
||||
public:
|
||||
adapter_node(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code&)
|
||||
{
|
||||
result_->data_type = t;
|
||||
result_->aggregate_size = aggregate_size;
|
||||
result_->depth = depth;
|
||||
result_->data.assign(data, data_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Adapter for RESP3 simple data types.
|
||||
template <class T>
|
||||
class simple {
|
||||
private:
|
||||
T* result_;
|
||||
|
||||
public:
|
||||
simple(T* t = nullptr) : result_(t) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
from_string(*result_, data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class simple_optional {
|
||||
private:
|
||||
std::optional<T>* result_;
|
||||
|
||||
public:
|
||||
simple_optional(std::optional<T>* o = nullptr) : result_(o) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error2(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (depth != 0) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
if (t == type::null)
|
||||
return;
|
||||
|
||||
if (!result_->has_value())
|
||||
*result_ = T{};
|
||||
|
||||
from_string(result_->value(), data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
/* A std::vector adapter.
|
||||
*/
|
||||
template <class Container>
|
||||
class vector {
|
||||
private:
|
||||
int i_ = -1;
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
vector(Container* v = nullptr) : result_{v} {}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
if (i_ != -1) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const m = element_multiplicity(t);
|
||||
result_->resize(m * aggregate_size);
|
||||
++i_;
|
||||
} else {
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
from_string(result_->at(i_), data, data_size, ec);
|
||||
++i_;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class list {
|
||||
private:
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
list(Container* ref = nullptr): result_(ref) {}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
if (depth != 0) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (depth != 1) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
result_->push_back({});
|
||||
from_string(result_->back(), data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class set {
|
||||
private:
|
||||
Container* result_;
|
||||
typename Container::iterator hint_;
|
||||
|
||||
public:
|
||||
set(Container* c = nullptr)
|
||||
: result_(c)
|
||||
, hint_(std::end(*c))
|
||||
{}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (t == type::set) {
|
||||
assert(depth == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!is_aggregate(t));
|
||||
|
||||
assert(depth == 1);
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
typename Container::key_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
if (hint_ == std::end(*result_)) {
|
||||
auto const ret = result_->insert(std::move(obj));
|
||||
hint_ = ret.first;
|
||||
} else {
|
||||
hint_ = result_->insert(hint_, std::move(obj));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class map {
|
||||
private:
|
||||
Container* result_;
|
||||
typename Container::iterator current_;
|
||||
bool on_key_ = true;
|
||||
|
||||
public:
|
||||
map(Container* c = nullptr)
|
||||
: result_(c)
|
||||
, current_(std::end(*c))
|
||||
{}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (t == type::map) {
|
||||
assert(depth == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!is_aggregate(t));
|
||||
assert(depth == 1);
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (on_key_) {
|
||||
typename Container::key_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
current_ = result_->insert(current_, {std::move(obj), {}});
|
||||
} else {
|
||||
typename Container::mapped_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
current_->second = std::move(obj);
|
||||
}
|
||||
|
||||
on_key_ = !on_key_;
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
# include <system_error>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Errors that may occurr when reading a response.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Expects a simple RESP3 type but got an aggregate.
|
||||
expects_simple_type = 1,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_unsupported,
|
||||
|
||||
/// Got RESP3 simple error.
|
||||
simple_error,
|
||||
|
||||
/// Got RESP3 blob_error.
|
||||
blob_error,
|
||||
|
||||
/// The tuple used as response has incompatible size.
|
||||
incompatible_tuple_size,
|
||||
|
||||
/// Got RESP3 null type.
|
||||
null
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : std::error_category {
|
||||
|
||||
/// \todo Fix string lifetime.
|
||||
char const* name() const noexcept override
|
||||
{ return "aedis.response_adapter"; }
|
||||
|
||||
// TODO: Move to .ipp
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::expects_simple_type: return "Expects a simple RESP3 type";
|
||||
case error::nested_unsupported: return "Nested responses unsupported.";
|
||||
case error::simple_error: return "Got RESP3 simple-error.";
|
||||
case error::blob_error: return "Got RESP3 blob-error.";
|
||||
case error::incompatible_tuple_size: return "The tuple used as response has incompatible size.";
|
||||
case error::null: return "Got RESP3 null.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
std::error_category const& adapter_category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
inline
|
||||
std::error_code make_error_code(error e)
|
||||
{
|
||||
static detail::error_category_impl const eci{};
|
||||
return std::error_code{static_cast<int>(e), detail::adapter_category()};
|
||||
}
|
||||
|
||||
inline
|
||||
std::error_condition make_error_condition(error e)
|
||||
{
|
||||
return std::error_condition(static_cast<int>(e), detail::adapter_category());
|
||||
}
|
||||
|
||||
} // adapter
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::resp3::adapter::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
@@ -1,87 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
template <class>
|
||||
struct needs_to_string : std::true_type {};
|
||||
|
||||
template <> struct needs_to_string<std::string> : std::false_type {};
|
||||
template <> struct needs_to_string<std::string_view> : std::false_type {};
|
||||
template <> struct needs_to_string<char const*> : std::false_type {};
|
||||
template <> struct needs_to_string<char*> : std::false_type {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct needs_to_string<char[N]> : std::false_type {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct needs_to_string<char const[N]> : std::false_type {};
|
||||
|
||||
template <class Storage>
|
||||
void add_header(Storage& to, int size)
|
||||
{
|
||||
// std::string does not support allocators.
|
||||
using std::to_string;
|
||||
auto const str = to_string(size);
|
||||
|
||||
to += "*";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Storage>
|
||||
void add_bulk(Storage& to, std::string_view data)
|
||||
{
|
||||
// std::string does not support allocators.
|
||||
using std::to_string;
|
||||
auto const str = to_string(std::size(data));
|
||||
|
||||
to += "$";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
to += data;
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Storage, class T>
|
||||
void add_bulk(Storage& to, T const& data, typename std::enable_if<needs_to_string<T>::value, bool>::type = false)
|
||||
{
|
||||
using std::to_string;
|
||||
auto const s = to_string(data);
|
||||
add_bulk(to, s);
|
||||
}
|
||||
|
||||
// Overload for pairs.
|
||||
// TODO: Overload for tuples.
|
||||
template <class Storage, class T1, class T2>
|
||||
void add_bulk(Storage& to, std::pair<T1, T2> const& pair)
|
||||
{
|
||||
add_bulk(to, pair.first);
|
||||
add_bulk(to, pair.second);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct value_type_size {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct value_type_size<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,40 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
type to_type(char c)
|
||||
{
|
||||
switch (c) {
|
||||
case '!': return type::blob_error;
|
||||
case '=': return type::verbatim_string;
|
||||
case '$': return type::blob_string;
|
||||
case ';': return type::streamed_string_part;
|
||||
case '-': return type::simple_error;
|
||||
case ':': return type::number;
|
||||
case ',': return type::doublean;
|
||||
case '#': return type::boolean;
|
||||
case '(': return type::big_number;
|
||||
case '+': return type::simple_string;
|
||||
case '_': return type::null;
|
||||
case '>': return type::push;
|
||||
case '~': return type::set;
|
||||
case '*': return type::array;
|
||||
case '|': return type::attribute;
|
||||
case '%': return type::map;
|
||||
default: return type::invalid;
|
||||
}
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,199 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <charconv>
|
||||
#include <system_error>
|
||||
#include <limits>
|
||||
|
||||
#include <aedis/resp3/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
type to_type(char c);
|
||||
|
||||
template <class ResponseAdapter>
|
||||
class parser {
|
||||
private:
|
||||
static constexpr std::size_t max_embedded_depth = 5;
|
||||
|
||||
ResponseAdapter adapter_;
|
||||
|
||||
// The current depth. Simple data types will have depth 0, whereas
|
||||
// the elements of aggregates will have depth 1. Embedded types
|
||||
// will have increasing depth.
|
||||
std::size_t depth_ = 0;
|
||||
|
||||
// The parser supports up to 5 levels of nested structures. The
|
||||
// first element in the sizes stack is a sentinel and must be
|
||||
// different from 1.
|
||||
std::size_t sizes_[max_embedded_depth + 1] = {1};
|
||||
|
||||
// Contains the length expected in the next bulk read.
|
||||
std::size_t bulk_length_ = (std::numeric_limits<std::size_t>::max)();
|
||||
|
||||
// The type of the next bulk. Contains type::invalid if no bulk is
|
||||
// expected.
|
||||
type bulk_ = type::invalid;
|
||||
|
||||
public:
|
||||
parser(ResponseAdapter adapter)
|
||||
: adapter_{adapter}
|
||||
{
|
||||
sizes_[0] = 2; // The sentinel must be more than 1.
|
||||
}
|
||||
|
||||
// Returns the number of bytes that have been consumed.
|
||||
std::size_t
|
||||
advance(char const* data, std::size_t n, std::error_code& ec)
|
||||
{
|
||||
if (bulk_ != type::invalid) {
|
||||
n = bulk_length_ + 2;
|
||||
switch (bulk_) {
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
if (bulk_length_ == 0) {
|
||||
sizes_[depth_] = 1;
|
||||
} else {
|
||||
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bulk_ = type::invalid;
|
||||
--sizes_[depth_];
|
||||
|
||||
} else if (sizes_[depth_] != 0) {
|
||||
auto const t = to_type(*data);
|
||||
switch (t) {
|
||||
case type::blob_error:
|
||||
case type::verbatim_string:
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
auto const r =
|
||||
std::from_chars(data + 1, data + n - 2, bulk_length_);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bulk_ = t;
|
||||
} break;
|
||||
case type::blob_string:
|
||||
{
|
||||
if (*(data + 1) == '?') {
|
||||
// Trick: A streamed string is read as an aggregate
|
||||
// of infinite lenght. When the streaming is done
|
||||
// the server is supposed to send a part with lenght
|
||||
// 0.
|
||||
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
|
||||
} else {
|
||||
auto const r =
|
||||
std::from_chars(data + 1, data + n - 2, bulk_length_);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bulk_ = type::blob_string;
|
||||
}
|
||||
} break;
|
||||
case type::simple_error:
|
||||
case type::number:
|
||||
case type::doublean:
|
||||
case type::boolean:
|
||||
case type::big_number:
|
||||
case type::simple_string:
|
||||
{
|
||||
adapter_(t, 1, depth_, data + 1, n - 3, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::null:
|
||||
{
|
||||
adapter_(type::null, 1, depth_, nullptr, 0, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::push:
|
||||
case type::set:
|
||||
case type::array:
|
||||
case type::attribute:
|
||||
case type::map:
|
||||
{
|
||||
std::size_t l;
|
||||
auto const r = std::from_chars(data + 1, data + n - 2, l);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
|
||||
adapter_(t, l, depth_, nullptr, 0, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (l == 0) {
|
||||
--sizes_[depth_];
|
||||
} else {
|
||||
if (depth_ == max_embedded_depth) {
|
||||
ec = error::exceeeds_max_nested_depth;
|
||||
return 0;
|
||||
}
|
||||
|
||||
++depth_;
|
||||
|
||||
sizes_[depth_] = l * element_multiplicity(t);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
ec = error::invalid_type;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (sizes_[depth_] == 0) {
|
||||
--depth_;
|
||||
--sizes_[depth_];
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// Returns true when the parser is done with the current message.
|
||||
auto done() const noexcept
|
||||
{ return depth_ == 0 && bulk_ == type::invalid; }
|
||||
|
||||
// The bulk type expected in the next read. If none is expected returns
|
||||
// type::invalid.
|
||||
auto bulk() const noexcept { return bulk_; }
|
||||
|
||||
// The length expected in the the next bulk.
|
||||
auto bulk_length() const noexcept { return bulk_length_; }
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,137 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
// TODO: Use asio::coroutine.
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter>
|
||||
class parse_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
parser<ResponseAdapter> parser_;
|
||||
std::size_t consumed_;
|
||||
int start_;
|
||||
|
||||
public:
|
||||
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
|
||||
: stream_ {stream}
|
||||
, buf_ {buf}
|
||||
, parser_ {adapter}
|
||||
, consumed_{0}
|
||||
, start_{1}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
switch (start_) {
|
||||
for (;;) {
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
case 1:
|
||||
start_ = 0;
|
||||
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
return;
|
||||
}
|
||||
|
||||
// On a bulk read we can't read until delimiter since the
|
||||
// payload may contain the delimiter itself so we have to
|
||||
// read the whole chunk. However if the bulk blob is small
|
||||
// enough it may be already on the buffer (from the last
|
||||
// read), in which case there is no need of initiating
|
||||
// another async op, otherwise we have to read the missing
|
||||
// bytes.
|
||||
if (std::size(buf_) < (parser_.bulk_length() + 2)) {
|
||||
start_ = 0;
|
||||
auto const s = std::size(buf_);
|
||||
auto const l = parser_.bulk_length();
|
||||
auto const to_read = l + 2 - s;
|
||||
buf_.grow(to_read);
|
||||
net::async_read(stream_, buf_.data(s, to_read), net::transfer_all(), std::move(self));
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
n = parser_.advance((char const*)buf_.data(0, n).data(), n, ec);
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
buf_.consume(n);
|
||||
consumed_ += n;
|
||||
if (parser_.done()) {
|
||||
self.complete({}, consumed_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Use asio::coroutine.
|
||||
template <class AsyncReadStream, class DynamicBuffer>
|
||||
class type_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
|
||||
public:
|
||||
type_op(AsyncReadStream& stream, DynamicBuffer buf)
|
||||
: stream_ {stream}
|
||||
, buf_ {buf}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, type::invalid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::size(buf_) == 0) {
|
||||
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
return;
|
||||
}
|
||||
|
||||
auto const* data = (char const*)buf_.data(0, n).data();
|
||||
auto const type = to_type(*data);
|
||||
self.complete(ec, type);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
# include <system_error>
|
||||
|
||||
/// \file error.hpp
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief RESP3 parsing errors.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Invalid RESP3 type.
|
||||
invalid_type = 1,
|
||||
|
||||
/// Can't parse the string as an integer.
|
||||
not_a_number,
|
||||
|
||||
/// Received less bytes than expected.
|
||||
unexpected_read_size,
|
||||
|
||||
/// The maximum depth of a nested response was exceeded.
|
||||
exceeeds_max_nested_depth
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : std::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{ return "aedis.resp3"; }
|
||||
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::invalid_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::unexpected_read_size: return "Unexpected read size.";
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
std::error_category const& category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
/** \brief Converts an error in an std::error_code object.
|
||||
* \ingroup any
|
||||
*/
|
||||
inline
|
||||
std::error_code make_error_code(error e)
|
||||
{
|
||||
static detail::error_category_impl const eci{};
|
||||
return std::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
inline
|
||||
std::error_condition make_error_condition(error e)
|
||||
{
|
||||
return std::error_condition(static_cast<int>(e), detail::category());
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::resp3::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
@@ -1,67 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
std::string to_string(node const& in)
|
||||
{
|
||||
std::string out;
|
||||
out += std::to_string(in.depth);
|
||||
out += '\t';
|
||||
out += to_string(in.data_type);
|
||||
out += '\t';
|
||||
out += std::to_string(in.aggregate_size);
|
||||
out += '\t';
|
||||
if (!is_aggregate(in.data_type))
|
||||
out += in.data;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool operator==(node const& a, node const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
&& a.data_type == b.data_type
|
||||
&& a.data == b.data;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, node const& o)
|
||||
{
|
||||
os << to_string(o);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r)
|
||||
{
|
||||
os << to_string(r);
|
||||
return os;
|
||||
}
|
||||
|
||||
// TODO: Output like in redis-cli.
|
||||
std::string to_string(std::vector<node> const& vec)
|
||||
{
|
||||
if (std::empty(vec))
|
||||
return {};
|
||||
|
||||
auto begin = std::cbegin(vec);
|
||||
std::string res;
|
||||
for (; begin != std::prev(std::cend(vec)); ++begin) {
|
||||
res += to_string(*begin);
|
||||
res += '\n';
|
||||
}
|
||||
|
||||
res += to_string(*begin);
|
||||
return res;
|
||||
}
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
char const* to_string(type t)
|
||||
{
|
||||
static char const* table[] =
|
||||
{ "array"
|
||||
, "push"
|
||||
, "set"
|
||||
, "map"
|
||||
, "attribute"
|
||||
, "simple_string"
|
||||
, "simple_error"
|
||||
, "number"
|
||||
, "doublean"
|
||||
, "boolean"
|
||||
, "big_number"
|
||||
, "null"
|
||||
, "blob_error"
|
||||
, "verbatim_string"
|
||||
, "blob_string"
|
||||
, "streamed_string_part"
|
||||
, "invalid"
|
||||
};
|
||||
|
||||
return table[static_cast<int>(t)];
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, type t)
|
||||
{
|
||||
os << to_string(t);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool is_aggregate(type t)
|
||||
{
|
||||
switch (t) {
|
||||
case type::array:
|
||||
case type::push:
|
||||
case type::set:
|
||||
case type::map:
|
||||
case type::attribute: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t element_multiplicity(type t)
|
||||
{
|
||||
switch (t) {
|
||||
case type::map:
|
||||
case type::attribute: return 2ULL;
|
||||
default: return 1ULL;
|
||||
}
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief A node in the response tree.
|
||||
* \ingroup any
|
||||
*
|
||||
* Redis responses are the pre-order view of the response tree (see
|
||||
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
*/
|
||||
struct node {
|
||||
/// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
/// The number of elements of an aggregate.
|
||||
std::size_t aggregate_size;
|
||||
|
||||
/// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
/// The actual data. For aggregate data types this is always empty.
|
||||
std::string data;
|
||||
};
|
||||
|
||||
/** \brief Converts the node to a string.
|
||||
* \ingroup any
|
||||
*
|
||||
* \param obj The node object.
|
||||
*/
|
||||
std::string to_string(node const& obj);
|
||||
|
||||
/** \brief Compares a node for equality.
|
||||
* \ingroup any
|
||||
*/
|
||||
bool operator==(node const& a, node const& b);
|
||||
|
||||
/** \brief Writes the node to the stream.
|
||||
* \ingroup any
|
||||
*
|
||||
* NOTE: Binary data is not converted to text.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, node const& o);
|
||||
|
||||
/** \brief Writes the response to the output stream
|
||||
* \ingroup any
|
||||
*/
|
||||
std::string to_string(std::vector<node> const& vec);
|
||||
|
||||
/** \brief Writes the response to the output stream
|
||||
* \ingroup any
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r);
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,197 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/adapt.hpp>
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/detail/read_ops.hpp>
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Read the response to a command sychronously.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buf Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \param ec Error if any.
|
||||
* \returns The number of bytes that have been consumed from the
|
||||
* auxiliary buffer.
|
||||
*/
|
||||
template <
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter
|
||||
>
|
||||
std::size_t
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
detail::parser p {adapter};
|
||||
std::size_t n = 0;
|
||||
std::size_t consumed = 0;
|
||||
do {
|
||||
if (p.bulk() == type::invalid) {
|
||||
n = net::read_until(stream, buf, "\r\n", ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (n < 3) {
|
||||
ec = error::unexpected_read_size;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
auto const s = std::size(buf);
|
||||
auto const l = p.bulk_length();
|
||||
if (s < (l + 2)) {
|
||||
auto const to_read = l + 2 - s;
|
||||
buf.grow(to_read);
|
||||
n = net::read(stream, buf.data(s, to_read), ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (n < to_read) {
|
||||
ec = error::unexpected_read_size;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
auto const* data = (char const*) buf.data(0, n).data();
|
||||
n = p.advance(data, n, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
buf.consume(n);
|
||||
consumed += n;
|
||||
} while (!p.done());
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/** \brief Reads the reponse to a command.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buf Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \returns The number of bytes that have been consumed from the
|
||||
* auxiliary buffer.
|
||||
*/
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = response_traits<void>::adapter_type>
|
||||
std::size_t
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter = adapt())
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
auto const n = resp3::read(stream, buf, adapter, ec);
|
||||
|
||||
if (ec)
|
||||
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/** @brief Reads the response to a Redis command asynchronously.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
*
|
||||
* The completion handler must have the following signature.
|
||||
*
|
||||
* @code
|
||||
* void(boost::system::error_code, std::size_t)
|
||||
* @endcode
|
||||
*
|
||||
* The second argumet to the completion handler is the number of
|
||||
* bytes that have been consumed in the read operation.
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buffer Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \param token The completion token.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = response_traits<void>::adapter_type,
|
||||
class CompletionToken = net::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
ResponseAdapter adapter = adapt(),
|
||||
CompletionToken&& token =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return net::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
|
||||
token,
|
||||
stream);
|
||||
}
|
||||
|
||||
/** \brief Reads the RESP3 type of the next incomming.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function won't consume any data from the buffer. The
|
||||
* completion handler must have the following signature.
|
||||
*
|
||||
* @code
|
||||
void(boost::system::error_code, type)
|
||||
* @endcode
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buffer Auxiliary read buffer, usually a `std::string`.
|
||||
* \param token The completion token.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read_type(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
CompletionToken&& token =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return net::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, type)
|
||||
>(detail::type_op<AsyncReadStream, DynamicBuffer> {stream, buffer}, token, stream);
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
@@ -1,229 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/adapter/detail/adapters.hpp>
|
||||
#include <aedis/resp3/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Traits class for response objects.
|
||||
* \ingroup any
|
||||
*/
|
||||
template <class T>
|
||||
struct response_traits
|
||||
{
|
||||
/// The response type.
|
||||
using response_type = T;
|
||||
|
||||
/// The adapter type.
|
||||
using adapter_type = adapter::detail::simple<response_type>;
|
||||
|
||||
/// Returns an adapter for the reponse r
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
/// Template typedef for response_traits.
|
||||
template <class T>
|
||||
using response_traits_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
template <class T>
|
||||
struct response_traits<std::optional<T>>
|
||||
{
|
||||
using response_type = std::optional<T>;
|
||||
using adapter_type = adapter::detail::simple_optional<typename response_type::value_type>;
|
||||
static auto adapt(response_type& i) noexcept { return adapter_type{&i}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::vector<T, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<T, Allocator>;
|
||||
using adapter_type = adapter::detail::vector<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<node>
|
||||
{
|
||||
using response_type = node;
|
||||
using adapter_type = adapter::detail::adapter_node<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
struct response_traits<std::vector<node, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<node, Allocator>;
|
||||
using adapter_type = adapter::detail::general<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::list<T, Allocator>>
|
||||
{
|
||||
using response_type = std::list<T, Allocator>;
|
||||
using adapter_type = adapter::detail::list<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::deque<T, Allocator>>
|
||||
{
|
||||
using response_type = std::deque<T, Allocator>;
|
||||
using adapter_type = adapter::detail::list<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct response_traits<std::set<Key, Compare, Allocator>>
|
||||
{
|
||||
using response_type = std::set<Key, Compare, Allocator>;
|
||||
using adapter_type = adapter::detail::set<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct response_traits<std::unordered_set<Key, Hash, KeyEqual, Allocator>>
|
||||
{
|
||||
using response_type = std::unordered_set<Key, Hash, KeyEqual, Allocator>;
|
||||
using adapter_type = adapter::detail::set<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct response_traits<std::map<Key, T, Compare, Allocator>>
|
||||
{
|
||||
using response_type = std::map<Key, T, Compare, Allocator>;
|
||||
using adapter_type = adapter::detail::map<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct response_traits<std::unordered_map<Key, Hash, KeyEqual, Allocator>>
|
||||
{
|
||||
using response_type = std::unordered_map<Key, Hash, KeyEqual, Allocator>;
|
||||
using adapter_type = adapter::detail::map<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<void>
|
||||
{
|
||||
using response_type = void;
|
||||
using adapter_type = adapter::detail::ignore;
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
|
||||
assigner<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[0] = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
class flat_transaction_adapter {
|
||||
private:
|
||||
using variant_type =
|
||||
boost::mp11::mp_rename<boost::mp11::mp_transform<response_traits_t, Tuple>, std::variant>;
|
||||
|
||||
std::size_t i_ = 0;
|
||||
std::size_t aggregate_size_ = 0;
|
||||
std::array<variant_type, std::tuple_size<Tuple>::value> adapters_;
|
||||
|
||||
public:
|
||||
flat_transaction_adapter(Tuple* r)
|
||||
{ assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r); }
|
||||
|
||||
void count(type t, std::size_t aggregate_size, std::size_t depth)
|
||||
{
|
||||
if (depth == 1) {
|
||||
if (is_aggregate(t))
|
||||
aggregate_size_ = aggregate_size;
|
||||
else
|
||||
++i_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (--aggregate_size_ == 0)
|
||||
++i_;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
if (depth == 0) {
|
||||
if (aggregate_size != std::tuple_size<Tuple>::value) {
|
||||
ec = adapter::error::incompatible_tuple_size;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::visit([&](auto& arg){arg(t, aggregate_size, depth, data, size, ec);}, adapters_[i_]);
|
||||
count(t, aggregate_size, depth);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
|
||||
// The adapter of responses to transactions, move it to its own header?
|
||||
template <class... Ts>
|
||||
struct response_traits<std::tuple<Ts...>>
|
||||
{
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = adapter::detail::flat_transaction_adapter<response_type>;
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,157 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/detail/composer.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** @brief Creates a Redis request from user data.
|
||||
* \ingroup any
|
||||
*
|
||||
* A request is composed of one or more redis commands and is
|
||||
* referred to in the redis documentation as a pipeline, see
|
||||
* https://redis.io/topics/pipelining.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* @code
|
||||
* std::string request;
|
||||
* auto sr = make_serializer(request);
|
||||
* sr.push(command::hello, 3);
|
||||
* sr.push(command::flushall);
|
||||
* sr.push(command::ping);
|
||||
* sr.push(command::incr, "key");
|
||||
* sr.push(command::quit);
|
||||
* co_await async_write(socket, buffer(request));
|
||||
* @endcode
|
||||
*
|
||||
* \tparam Storage The storage type. This is currently a \c std::string.
|
||||
* \tparam Command The command to serialize.
|
||||
*
|
||||
* \remarks Non-string types will be converted to string by using \c
|
||||
* to_string, which must be made available over ADL.
|
||||
*/
|
||||
|
||||
// NOTE: Consider adding an overload for containers.
|
||||
//
|
||||
// TODO: Should we detect any std::pair or tuple in the type in the parameter
|
||||
// pack to calculate the header size correctly?
|
||||
//
|
||||
// NOTE: For some commands like hset it would be a good idea to assert
|
||||
// the value type is a pair.
|
||||
template <class Storage, class Command>
|
||||
class serializer {
|
||||
private:
|
||||
Storage* request_;
|
||||
|
||||
public:
|
||||
/** \brief Constructor
|
||||
*
|
||||
* \param storage Object where the serialized request will be
|
||||
* stored.
|
||||
*/
|
||||
serializer(Storage& storage) : request_(&storage) {}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* \code
|
||||
* std::string request;
|
||||
* auto sr = make_serializer<command>(request);
|
||||
* sr.push(command::set, "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the \c set command with payload "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The redis command.
|
||||
* \param args The arguments of the Redis command.
|
||||
*
|
||||
*/
|
||||
template <class... Ts>
|
||||
void push(Command cmd, Ts const&... args)
|
||||
{
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
detail::add_header(*request_, 1 + pack_size);
|
||||
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
(detail::add_bulk(*request_, args), ...);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that require a key. For example
|
||||
*
|
||||
* \code{.cpp}
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
* , {"key3", "value3"}
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param key The key the Redis command refers to.
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class Key, class ForwardIterator>
|
||||
void push_range(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
auto constexpr size = detail::value_type_size<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(*request_, 2 + size * distance);
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
detail::add_bulk(*request_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(*request_, *begin);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that don't have a key. For
|
||||
* example
|
||||
*
|
||||
* \code
|
||||
* std::set<std::string> channels
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push(command::subscribe, std::cbegin(channels), std::cedn(channels));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range(Command cmd, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
auto constexpr size = detail::value_type_size<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(*request_, 1 + size * distance);
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(*request_, *begin);
|
||||
}
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,88 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief RESP3 types
|
||||
\ingroup any
|
||||
|
||||
The RESP3 full specification can be found at https://github.com/antirez/RESP3/blob/74adea588783e463c7e84793b325b088fe6edd1c/spec.md
|
||||
*/
|
||||
enum class type
|
||||
{ /// Aggregate
|
||||
array,
|
||||
/// Aaggregate
|
||||
push,
|
||||
/// Aggregate
|
||||
set,
|
||||
/// Aggregate
|
||||
map,
|
||||
/// Aggregate
|
||||
attribute,
|
||||
/// Simple
|
||||
simple_string,
|
||||
/// Simple
|
||||
simple_error,
|
||||
/// Simple
|
||||
number,
|
||||
/// Simple
|
||||
doublean,
|
||||
/// Simple
|
||||
boolean,
|
||||
/// Simple
|
||||
big_number,
|
||||
/// Simple
|
||||
null,
|
||||
/// Simple
|
||||
blob_error,
|
||||
/// Simple
|
||||
verbatim_string,
|
||||
/// Simple
|
||||
blob_string,
|
||||
/// Simple
|
||||
streamed_string_part,
|
||||
/// Invalid
|
||||
invalid
|
||||
};
|
||||
|
||||
/** \brief Returns the string representation of the type.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
char const* to_string(type t);
|
||||
|
||||
/** \brief Writes the type to the output stream.
|
||||
* \ingroup operators
|
||||
* \param os Output stream.
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, type t);
|
||||
|
||||
/** \brief Returns true if the data type is an aggregate.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
bool is_aggregate(type t);
|
||||
|
||||
/** @brief Returns the element multilicity.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
*
|
||||
* For type map and attribute this value is 2, all other types have
|
||||
* 1.
|
||||
*/
|
||||
std::size_t element_multiplicity(type t);
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,79 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace aedis {
|
||||
namespace sentinel {
|
||||
|
||||
/** \brief Sentinel commands.
|
||||
* \ingroup any
|
||||
*
|
||||
* For a full list of commands see https://redis.io/topics/sentinel
|
||||
*
|
||||
* \remark The list of commands below are read from Redis with the
|
||||
* help of the command \c command.
|
||||
*/
|
||||
enum class command {
|
||||
/// https://redis.io/commands/acl
|
||||
acl,
|
||||
/// https://redis.io/commands/auth
|
||||
auth,
|
||||
/// https://redis.io/commands/client
|
||||
client,
|
||||
/// https://redis.io/commands/command
|
||||
command,
|
||||
/// https://redis.io/commands/hello
|
||||
hello,
|
||||
/// https://redis.io/commands/info
|
||||
info,
|
||||
/// https://redis.io/commands/ping
|
||||
ping,
|
||||
/// https://redis.io/commands/psubscribe
|
||||
psubscribe,
|
||||
/// https://redis.io/commands/publish
|
||||
publish,
|
||||
/// https://redis.io/commands/punsubscribe
|
||||
punsubscribe,
|
||||
/// https://redis.io/commands/role
|
||||
role,
|
||||
/// https://redis.io/topics/sentinel
|
||||
sentinel,
|
||||
/// https://redis.io/commands/shutdown
|
||||
shutdown,
|
||||
/// https://redis.io/commands/subscribe
|
||||
subscribe,
|
||||
/// https://redis.io/commands/unsubscribe
|
||||
unsubscribe,
|
||||
/// Unknown/invalid command.
|
||||
unknown
|
||||
};
|
||||
|
||||
/** \brief Converts a sentinel command to a string
|
||||
* \ingroup any
|
||||
*
|
||||
* \param c The command to convert.
|
||||
*/
|
||||
char const* to_string(command c);
|
||||
|
||||
/** \brief Write the text for a sentinel command name to an output stream.
|
||||
* \ingroup operators
|
||||
*
|
||||
* \param os Output stream.
|
||||
* \param c Sentinel command
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, command c);
|
||||
|
||||
/** \brief Returns true for sentinel commands with push response.
|
||||
* \ingroup any
|
||||
*/
|
||||
bool has_push_response(command cmd);
|
||||
|
||||
} // sentinel
|
||||
} // aedis
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/sentinel/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace sentinel {
|
||||
|
||||
char const* to_string(command c)
|
||||
{
|
||||
static char const* table[] = {
|
||||
"ACL",
|
||||
"AUTH",
|
||||
"CLIENT",
|
||||
"COMMAND",
|
||||
"HELLO",
|
||||
"INFO",
|
||||
"PING",
|
||||
"PSUBSCRIBE",
|
||||
"PUBLISH",
|
||||
"PUNSUBSCRIBE",
|
||||
"ROLE",
|
||||
"SENTINEL",
|
||||
"SHUTDOWN",
|
||||
"SUBSCRIBE",
|
||||
"UNSUBSCRIBE",
|
||||
};
|
||||
|
||||
return table[static_cast<int>(c)];
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, command c)
|
||||
{
|
||||
os << to_string(c);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool has_push_response(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::subscribe:
|
||||
case command::unsubscribe:
|
||||
case command::psubscribe:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // sentinel
|
||||
} // aedis
|
||||
@@ -1,15 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// Include this file in no more than one source file in your application.
|
||||
|
||||
#include <aedis/redis/impl/command.ipp>
|
||||
#include <aedis/sentinel/impl/command.ipp>
|
||||
#include <aedis/resp3/impl/type.ipp>
|
||||
#include <aedis/resp3/impl/node.ipp>
|
||||
#include <aedis/redis/experimental/impl/client.ipp>
|
||||
#include <aedis/resp3/detail/impl/parser.ipp>
|
||||
20
benchmarks/CMakeLists.txt
Normal file
20
benchmarks/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
add_library(benchmarks_options INTERFACE)
|
||||
target_link_libraries(benchmarks_options INTERFACE boost_redis_src)
|
||||
target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options)
|
||||
target_compile_features(benchmarks_options INTERFACE cxx_std_20)
|
||||
|
||||
add_executable(echo_server_client cpp/asio/echo_server_client.cpp)
|
||||
target_link_libraries(echo_server_client PRIVATE benchmarks_options)
|
||||
|
||||
add_executable(echo_server_direct cpp/asio/echo_server_direct.cpp)
|
||||
target_link_libraries(echo_server_direct PRIVATE benchmarks_options)
|
||||
|
||||
# TODO
|
||||
#=======================================================================
|
||||
|
||||
#.PHONY: bench
|
||||
#bench:
|
||||
# pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex
|
||||
# pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex
|
||||
# pdftoppm {input.pdf} {output.file} -png
|
||||
71
benchmarks/benchmarks.tex
Normal file
71
benchmarks/benchmarks.tex
Normal file
@@ -0,0 +1,71 @@
|
||||
\documentclass{article}
|
||||
\usepackage{pgfplots}
|
||||
\pgfrealjobname{echo}
|
||||
\pgfplotsset{compat=newest}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\beginpgfgraphicnamed{echo-f0}
|
||||
% time ./echo_server_client 1000 5000
|
||||
\begin{tikzpicture}[scale=1.0]
|
||||
\begin{axis}[
|
||||
y dir=reverse,
|
||||
%xbar stacked,
|
||||
xbar, xmin=0,
|
||||
%hide x axis,
|
||||
bar shift=0pt,
|
||||
width=15cm, height=6cm, enlarge y limits=0.5,
|
||||
title={TCP Echo Server Performance},
|
||||
xlabel={Seconds},
|
||||
symbolic y coords={Asio,Tokio,Go,Libuv,Nodejs},
|
||||
ytick=data,
|
||||
%bar width=1cm,
|
||||
nodes near coords,
|
||||
nodes near coords align={horizontal},
|
||||
]
|
||||
\addplot coordinates {
|
||||
(29.5,Asio)
|
||||
(30.7,Tokio)
|
||||
(35.6,Go)
|
||||
(43.6,Libuv)
|
||||
(74.2,Nodejs)
|
||||
};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\endpgfgraphicnamed
|
||||
|
||||
\beginpgfgraphicnamed{echo-f1}
|
||||
%$ time ./echo_server_client 1000 1000
|
||||
\begin{tikzpicture}[scale=1.0]
|
||||
\begin{axis}[
|
||||
y dir=reverse,
|
||||
%xbar stacked,
|
||||
xbar, xmin=0,
|
||||
%hide x axis,
|
||||
bar shift=0pt,
|
||||
width=12cm, height=6cm, enlarge y limits=0.5,
|
||||
title={TCP Echo Server Performance (over Redis)},
|
||||
xlabel={Seconds},
|
||||
symbolic y coords={Aedis,Rust-rs,Libuv,Node-redis,Go-redis},
|
||||
ytick=data,
|
||||
%bar width=1cm,
|
||||
nodes near coords,
|
||||
nodes near coords align={horizontal},
|
||||
]
|
||||
\addplot coordinates {
|
||||
(12.6,Aedis)
|
||||
(28.8,Node-redis)
|
||||
(352.4,Go-redis)
|
||||
};
|
||||
%\addplot coordinates {
|
||||
% (30.0,Asio)
|
||||
% (90.6,Rust-rs)
|
||||
% (0.0,Libuv)
|
||||
% (68.9,Nodejs)
|
||||
% (0.0,Go)
|
||||
%};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\endpgfgraphicnamed
|
||||
|
||||
\end{document}
|
||||
7
benchmarks/c/libuv/README.md
Normal file
7
benchmarks/c/libuv/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
This example was taken from
|
||||
|
||||
https://github.com/libuv/libuv/tree/v1.x/docs/code/tcp-echo-server
|
||||
|
||||
To build it run, for example
|
||||
|
||||
$ gcc echo_server_direct.c -luv -O2 -o echo_server_direct
|
||||
87
benchmarks/c/libuv/echo_server_direct.c
Normal file
87
benchmarks/c/libuv/echo_server_direct.c
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <uv.h>
|
||||
|
||||
#define DEFAULT_PORT 55555
|
||||
#define DEFAULT_BACKLOG 1024
|
||||
|
||||
uv_loop_t *loop;
|
||||
struct sockaddr_in addr;
|
||||
|
||||
typedef struct {
|
||||
uv_write_t req;
|
||||
uv_buf_t buf;
|
||||
} write_req_t;
|
||||
|
||||
void free_write_req(uv_write_t *req) {
|
||||
write_req_t *wr = (write_req_t*) req;
|
||||
free(wr->buf.base);
|
||||
free(wr);
|
||||
}
|
||||
|
||||
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
||||
buf->base = (char*) malloc(suggested_size);
|
||||
buf->len = suggested_size;
|
||||
}
|
||||
|
||||
void on_close(uv_handle_t* handle) {
|
||||
free(handle);
|
||||
}
|
||||
|
||||
void echo_write(uv_write_t *req, int status) {
|
||||
if (status) {
|
||||
fprintf(stderr, "Write error %s\n", uv_strerror(status));
|
||||
}
|
||||
free_write_req(req);
|
||||
}
|
||||
|
||||
void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {
|
||||
if (nread > 0) {
|
||||
write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));
|
||||
req->buf = uv_buf_init(buf->base, nread);
|
||||
uv_write((uv_write_t*) req, client, &req->buf, 1, echo_write);
|
||||
return;
|
||||
}
|
||||
if (nread < 0) {
|
||||
if (nread != UV_EOF)
|
||||
fprintf(stderr, "Read error %s\n", uv_err_name(nread));
|
||||
uv_close((uv_handle_t*) client, on_close);
|
||||
}
|
||||
|
||||
free(buf->base);
|
||||
}
|
||||
|
||||
void on_new_connection(uv_stream_t *server, int status) {
|
||||
if (status < 0) {
|
||||
fprintf(stderr, "New connection error %s\n", uv_strerror(status));
|
||||
// error!
|
||||
return;
|
||||
}
|
||||
|
||||
uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
|
||||
uv_tcp_init(loop, client);
|
||||
if (uv_accept(server, (uv_stream_t*) client) == 0) {
|
||||
uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
|
||||
}
|
||||
else {
|
||||
uv_close((uv_handle_t*) client, on_close);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
loop = uv_default_loop();
|
||||
|
||||
uv_tcp_t server;
|
||||
uv_tcp_init(loop, &server);
|
||||
|
||||
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);
|
||||
|
||||
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
|
||||
int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);
|
||||
if (r) {
|
||||
fprintf(stderr, "Listen error %s\n", uv_strerror(r));
|
||||
return 1;
|
||||
}
|
||||
return uv_run(loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
72
benchmarks/cpp/asio/echo_server_client.cpp
Normal file
72
benchmarks/cpp/asio/echo_server_client.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using net::ip::tcp;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using timer_type = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
|
||||
auto example(boost::asio::ip::tcp::endpoint ep, std::string msg, int n) -> net::awaitable<void>
|
||||
{
|
||||
try {
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
tcp_socket socket{ex};
|
||||
co_await socket.async_connect(ep);
|
||||
|
||||
std::string buffer;
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
co_await net::async_write(socket, net::buffer(msg));
|
||||
auto bytes_read = co_await net::async_read_until(socket, dbuffer, "\n");
|
||||
//std::printf("> %s", buffer.data());
|
||||
dbuffer.consume(bytes_read);
|
||||
}
|
||||
|
||||
//std::printf("Ok: %s", msg.data());
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try {
|
||||
int sessions = 1;
|
||||
int msgs = 1;
|
||||
|
||||
if (argc == 3) {
|
||||
sessions = std::stoi(argv[1]);
|
||||
msgs = std::stoi(argv[2]);
|
||||
}
|
||||
|
||||
net::io_context ioc;
|
||||
|
||||
tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve("127.0.0.1", "55555");
|
||||
auto ep = *std::begin(res);
|
||||
|
||||
for (int i = 0; i < sessions; ++i)
|
||||
net::co_spawn(ioc, example(ep, "Some message\n", msgs), net::detached);
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int
|
||||
{
|
||||
std::cout << "Requires coroutine support." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
68
benchmarks/cpp/asio/echo_server_direct.cpp
Normal file
68
benchmarks/cpp/asio/echo_server_direct.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// echo_server.cpp
|
||||
// ~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace this_coro = net::this_coro;
|
||||
using net::ip::tcp;
|
||||
using net::detached;
|
||||
using executor_type = net::io_context::executor_type;
|
||||
using socket_type = net::basic_stream_socket<net::ip::tcp, executor_type>;
|
||||
using tcp_socket = net::use_awaitable_t<executor_type>::as_default_on_t<socket_type>;
|
||||
using acceptor_type = net::basic_socket_acceptor<net::ip::tcp, executor_type>;
|
||||
using tcp_acceptor = net::use_awaitable_t<executor_type>::as_default_on_t<acceptor_type>;
|
||||
using awaitable_type = net::awaitable<void, executor_type>;
|
||||
constexpr net::use_awaitable_t<executor_type> use_awaitable;
|
||||
|
||||
awaitable_type echo(tcp_socket socket)
|
||||
{
|
||||
try {
|
||||
char data[1024];
|
||||
for (;;) {
|
||||
std::size_t n = co_await socket.async_read_some(net::buffer(data), use_awaitable);
|
||||
co_await async_write(socket, net::buffer(data, n), use_awaitable);
|
||||
}
|
||||
} catch (std::exception const&) {
|
||||
//std::printf("echo Exception: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
awaitable_type listener()
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
tcp_acceptor acceptor(ex, {tcp::v4(), 55555});
|
||||
for (;;) {
|
||||
tcp_socket socket = co_await acceptor.async_accept(use_awaitable);
|
||||
co_spawn(ex, echo(std::move(socket)), detached);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context io_context{BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO};
|
||||
co_spawn(io_context, listener(), detached);
|
||||
io_context.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::printf("Exception: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int
|
||||
{
|
||||
std::cout << "Requires coroutine support." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
43
benchmarks/go/echo_server_direct.go
Normal file
43
benchmarks/go/echo_server_direct.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func echo(conn net.Conn) {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
break;
|
||||
}
|
||||
|
||||
conn.Write(buf[:n])
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
l, err := net.Listen("tcp", "0.0.0.0:55555")
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
continue
|
||||
}
|
||||
go echo(conn)
|
||||
}
|
||||
}
|
||||
54
benchmarks/go/echo_server_over_redis.go
Normal file
54
benchmarks/go/echo_server_over_redis.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
var rdb = redis.NewClient(&redis.Options{Addr: "db.occase.de:6379", Password: "", DB: 0,})
|
||||
|
||||
func echo(conn net.Conn) {
|
||||
r := bufio.NewReader(conn)
|
||||
for {
|
||||
line, err := r.ReadBytes(byte('\n'))
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
case io.EOF:
|
||||
default:
|
||||
fmt.Println("ERROR", err)
|
||||
}
|
||||
|
||||
err2 := rdb.Ping(ctx).Err()
|
||||
if err2 != nil {
|
||||
fmt.Println("ERROR", err2)
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
conn.Write(line)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
l, err := net.Listen("tcp", "0.0.0.0:55555")
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go echo(conn)
|
||||
}
|
||||
}
|
||||
105
benchmarks/java/echo_server_direct/TcpEchoServer.java
Normal file
105
benchmarks/java/echo_server_direct/TcpEchoServer.java
Normal file
@@ -0,0 +1,105 @@
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TcpEchoServer {
|
||||
|
||||
public static int DEFAULT_PORT = 55555;
|
||||
|
||||
public static void main(String[] args) {
|
||||
int port;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
}
|
||||
|
||||
catch (Exception ex) {
|
||||
port = DEFAULT_PORT;
|
||||
}
|
||||
|
||||
//System.out.println("Listening for connections on port " + port);
|
||||
ServerSocketChannel serverChannel;
|
||||
Selector selector;
|
||||
try {
|
||||
serverChannel = ServerSocketChannel.open( );
|
||||
ServerSocket ss = serverChannel.socket( );
|
||||
InetSocketAddress address = new InetSocketAddress(port);
|
||||
ss.bind(address);
|
||||
serverChannel.configureBlocking(false);
|
||||
selector = Selector.open( );
|
||||
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
ex.printStackTrace( );
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
selector.select( );
|
||||
}
|
||||
catch (IOException ex) {
|
||||
ex.printStackTrace( );
|
||||
break;
|
||||
}
|
||||
Set readyKeys = selector.selectedKeys( );
|
||||
Iterator iterator = readyKeys.iterator( );
|
||||
while (iterator.hasNext( )) {
|
||||
SelectionKey key = (SelectionKey) iterator.next( );
|
||||
iterator.remove( );
|
||||
try {
|
||||
if (key.isAcceptable( )) {
|
||||
ServerSocketChannel server = (ServerSocketChannel ) key.channel( );
|
||||
SocketChannel client = server.accept( );
|
||||
//System.out.println("Accepted connection from " + client);
|
||||
client.configureBlocking(false);
|
||||
SelectionKey clientKey = client.register(
|
||||
selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(100);
|
||||
clientKey.attach(buffer);
|
||||
//System.out.println(buffer.toString());
|
||||
|
||||
}
|
||||
|
||||
if (key.isReadable( )) {
|
||||
SocketChannel client = (SocketChannel) key.channel( );
|
||||
ByteBuffer output = (ByteBuffer) key.attachment( );
|
||||
client.read(output);
|
||||
}
|
||||
|
||||
if (key.isWritable( )) {
|
||||
SocketChannel client = (SocketChannel) key.channel( );
|
||||
ByteBuffer output = (ByteBuffer) key.attachment( );
|
||||
output.flip( );
|
||||
client.write(output);
|
||||
output.compact( );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
catch (IOException ex) {
|
||||
key.cancel( );
|
||||
try {
|
||||
key.channel().close();
|
||||
}
|
||||
|
||||
catch (IOException cex) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
4
benchmarks/nodejs/README.txt
Normal file
4
benchmarks/nodejs/README.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
$ npm install
|
||||
|
||||
$ node echo_server_over_redis.js
|
||||
@@ -0,0 +1,7 @@
|
||||
var net = require('net');
|
||||
|
||||
net.createServer(function(socket){
|
||||
socket.on('data', function(data){
|
||||
socket.write(data.toString())
|
||||
});
|
||||
}).listen(55555);
|
||||
2
benchmarks/nodejs/echo_server_direct/package.json
Normal file
2
benchmarks/nodejs/echo_server_direct/package.json
Normal file
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createClient } from 'redis';
|
||||
import * as net from 'net';
|
||||
|
||||
const client = createClient({url: 'redis://aedis.occase.de:63799' });
|
||||
client.on('error', (err) => console.log('Redis Client Error', err));
|
||||
await client.connect();
|
||||
|
||||
net.createServer(function(socket){
|
||||
socket.on('data', async function(data) {
|
||||
const value = await client.ping(data.toString());
|
||||
socket.write(data)
|
||||
});
|
||||
}).listen(55555);
|
||||
169
benchmarks/nodejs/echo_server_over_redis/package-lock.json
generated
Normal file
169
benchmarks/nodejs/echo_server_over_redis/package-lock.json
generated
Normal file
@@ -0,0 +1,169 @@
|
||||
{
|
||||
"name": "echo_server_over_redis",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"redis": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz",
|
||||
"integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz",
|
||||
"integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz",
|
||||
"integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==",
|
||||
"dependencies": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.2.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.0.6",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/client": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz",
|
||||
"integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==",
|
||||
"requires": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
}
|
||||
},
|
||||
"@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/search": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz",
|
||||
"integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"requires": {}
|
||||
},
|
||||
"cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg=="
|
||||
},
|
||||
"redis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz",
|
||||
"integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==",
|
||||
"requires": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.2.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.0.6",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
6
benchmarks/nodejs/echo_server_over_redis/package.json
Normal file
6
benchmarks/nodejs/echo_server_over_redis/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"redis": "^4.2.0"
|
||||
}
|
||||
}
|
||||
309
benchmarks/rust/echo_server_direct/Cargo.lock
generated
Normal file
309
benchmarks/rust/echo_server_direct/Cargo.lock
generated
Normal file
@@ -0,0 +1,309 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "echo_server_direct"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f53dc8cf16a769a6f677e09e7ff2cd4be1ea0f48754aac39520536962011de0d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
9
benchmarks/rust/echo_server_direct/Cargo.toml
Normal file
9
benchmarks/rust/echo_server_direct/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "echo_server_direct"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
35
benchmarks/rust/echo_server_direct/src/main.rs
Normal file
35
benchmarks/rust/echo_server_direct/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:55555").await?;
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Write the data back
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
eprintln!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
11
benchmarks/rust/echo_server_over_redis/Cargo.toml
Normal file
11
benchmarks/rust/echo_server_over_redis/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "echo_server_over_redis"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
redis = { version = "0.21.5", features = ["tokio-comp"] }
|
||||
futures = "0.3"
|
||||
44
benchmarks/rust/echo_server_over_redis/src/main.rs
Normal file
44
benchmarks/rust/echo_server_over_redis/src/main.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::Mutex;
|
||||
use std::sync::{Arc};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:55555").await?;
|
||||
let client = redis::Client::open("redis://db.occase.de/").unwrap();
|
||||
let con = Arc::new(Mutex::new(client.get_async_connection().await?));
|
||||
|
||||
loop {
|
||||
let conn = Arc::clone(&con);
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut local_conn = conn.lock().await;
|
||||
|
||||
let result =
|
||||
redis::cmd("PING")
|
||||
.arg(&buf[0..n])
|
||||
.query_async::<redis::aio::Connection, String>(&mut local_conn).await.unwrap();
|
||||
|
||||
if let Err(e) = socket.write_all(result.as_bytes()).await {
|
||||
eprintln!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
28
build.jam
Normal file
28
build.jam
Normal file
@@ -0,0 +1,28 @@
|
||||
# Copyright René Ferdinand Rivera Morell 2024
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
require-b2 5.2 ;
|
||||
|
||||
constant boost_dependencies :
|
||||
/boost/asio//boost_asio
|
||||
/boost/assert//boost_assert
|
||||
/boost/core//boost_core
|
||||
/boost/mp11//boost_mp11
|
||||
/boost/system//boost_system
|
||||
/boost/throw_exception//boost_throw_exception ;
|
||||
|
||||
project /boost/redis
|
||||
: common-requirements
|
||||
<include>include
|
||||
;
|
||||
|
||||
explicit
|
||||
[ alias boost_redis : : : : <library>$(boost_dependencies) ]
|
||||
[ alias all : boost_redis test ]
|
||||
;
|
||||
|
||||
call-if : boost-library redis
|
||||
;
|
||||
|
||||
22
configure.ac
22
configure.ac
@@ -1,22 +0,0 @@
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([Aedis], [0.0.1], [mzimbres@gmail.com])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
#AC_CONFIG_SRCDIR([src/aedis.cpp])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AM_INIT_AUTOMAKE([-Wall foreign])
|
||||
|
||||
# Checks for programs.
|
||||
AC_PROG_CXX
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_LN_S
|
||||
AC_PROG_RANLIB
|
||||
AM_PROG_AR
|
||||
|
||||
AX_BOOST_BASE([1.78],, AC_MSG_ERROR[Boost not found])
|
||||
|
||||
AC_CHECK_HEADER_STDBOOL
|
||||
AC_TYPE_UINT64_T
|
||||
AC_CHECK_TYPES([ptrdiff_t])
|
||||
|
||||
AC_CONFIG_FILES([Makefile doc/Doxyfile])
|
||||
AC_OUTPUT
|
||||
26
doc/CMakeLists.txt
Normal file
26
doc/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
# Root CMakeLists.txt so MrDocs knows how to build our code
|
||||
cmake_minimum_required(VERSION 3.8...3.22)
|
||||
|
||||
# Project
|
||||
project(boost_redis_mrdocs LANGUAGES CXX)
|
||||
|
||||
# MrDocs forces CMAKE_EXPORT_COMPILE_COMMANDS=ON, incorrectly
|
||||
# causing all targets to be dumped to the compilation database.
|
||||
# Disable this setting so we can set EXPORT_COMPILE_COMMANDS
|
||||
# only to our target of interest
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS OFF)
|
||||
|
||||
# Add Boost
|
||||
add_subdirectory($ENV{BOOST_SRC_DIR} deps/boost)
|
||||
|
||||
# Add the target for mrdocs to analyze
|
||||
add_executable(mrdocs mrdocs.cpp)
|
||||
target_link_libraries(mrdocs PRIVATE Boost::redis)
|
||||
set_target_properties(mrdocs PROPERTIES EXPORT_COMPILE_COMMANDS ON)
|
||||
2565
doc/Doxyfile
2565
doc/Doxyfile
File diff suppressed because it is too large
Load Diff
@@ -1,226 +0,0 @@
|
||||
<doxygenlayout version="1.0">
|
||||
<!-- Generated by doxygen 1.9.1 -->
|
||||
<!-- Navigation index tabs for HTML output -->
|
||||
<navindex>
|
||||
<tab type="mainpage" visible="yes" title="Contents"/>
|
||||
<tab type="pages" visible="no" title="" intro=""/>
|
||||
<tab type="modules" visible="no" title="Reference" intro=""/>
|
||||
<tab type="namespaces" visible="no" title="">
|
||||
<tab type="namespacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="namespacemembers" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="interfaces" visible="no" title="">
|
||||
<tab type="interfacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="classes" visible="no" title="">
|
||||
<tab type="classlist" visible="no" title="" intro=""/>
|
||||
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="hierarchy" visible="no" title="" intro=""/>
|
||||
<tab type="classmembers" visible="no" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="structs" visible="no" title="">
|
||||
<tab type="structlist" visible="no" title="" intro=""/>
|
||||
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
</tab>
|
||||
<tab type="exceptions" visible="no" title="">
|
||||
<tab type="exceptionlist" visible="no" title="" intro=""/>
|
||||
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="files" visible="no" title="">
|
||||
<tab type="filelist" visible="no" title="" intro=""/>
|
||||
<tab type="globals" visible="no" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="examples" visible="yes" title="" intro=""/>
|
||||
</navindex>
|
||||
|
||||
<!-- Layout definition for a class page -->
|
||||
<class>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<inheritancegraph visible="$CLASS_GRAPH"/>
|
||||
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
|
||||
<memberdecl>
|
||||
<nestedclasses visible="yes" title=""/>
|
||||
<publictypes title=""/>
|
||||
<services title=""/>
|
||||
<interfaces title=""/>
|
||||
<publicslots title=""/>
|
||||
<signals title=""/>
|
||||
<publicmethods title=""/>
|
||||
<publicstaticmethods title=""/>
|
||||
<publicattributes title=""/>
|
||||
<publicstaticattributes title=""/>
|
||||
<protectedtypes title=""/>
|
||||
<protectedslots title=""/>
|
||||
<protectedmethods title=""/>
|
||||
<protectedstaticmethods title=""/>
|
||||
<protectedattributes title=""/>
|
||||
<protectedstaticattributes title=""/>
|
||||
<packagetypes title=""/>
|
||||
<packagemethods title=""/>
|
||||
<packagestaticmethods title=""/>
|
||||
<packageattributes title=""/>
|
||||
<packagestaticattributes title=""/>
|
||||
<properties title=""/>
|
||||
<events title=""/>
|
||||
<privatetypes title=""/>
|
||||
<privateslots title=""/>
|
||||
<privatemethods title=""/>
|
||||
<privatestaticmethods title=""/>
|
||||
<privateattributes title=""/>
|
||||
<privatestaticattributes title=""/>
|
||||
<friends title=""/>
|
||||
<related title="" subtitle=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<services title=""/>
|
||||
<interfaces title=""/>
|
||||
<constructors title=""/>
|
||||
<functions title=""/>
|
||||
<related title=""/>
|
||||
<variables title=""/>
|
||||
<properties title=""/>
|
||||
<events title=""/>
|
||||
</memberdef>
|
||||
<allmemberslink visible="yes"/>
|
||||
<usedfiles visible="$SHOW_USED_FILES"/>
|
||||
<authorsection visible="yes"/>
|
||||
</class>
|
||||
|
||||
<!-- Layout definition for a namespace page -->
|
||||
<namespace>
|
||||
<briefdescription visible="yes"/>
|
||||
<memberdecl>
|
||||
<nestednamespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<structs visible="yes" title=""/>
|
||||
<exceptions visible="yes" title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</namespace>
|
||||
|
||||
<!-- Layout definition for a file page -->
|
||||
<file>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<includegraph visible="$INCLUDE_GRAPH"/>
|
||||
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
|
||||
<sourcelink visible="yes"/>
|
||||
<memberdecl>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<structs visible="yes" title=""/>
|
||||
<exceptions visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
</memberdef>
|
||||
<authorsection/>
|
||||
</file>
|
||||
|
||||
<!-- Layout definition for a group page -->
|
||||
<group>
|
||||
<briefdescription visible="yes"/>
|
||||
<groupgraph visible="$GROUP_GRAPHS"/>
|
||||
<memberdecl>
|
||||
<nestedgroups visible="yes" title=""/>
|
||||
<dirs visible="yes" title=""/>
|
||||
<files visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<enumvalues title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<signals title=""/>
|
||||
<publicslots title=""/>
|
||||
<protectedslots title=""/>
|
||||
<privateslots title=""/>
|
||||
<events title=""/>
|
||||
<properties title=""/>
|
||||
<friends title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<pagedocs/>
|
||||
<inlineclasses title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<enumvalues title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<signals title=""/>
|
||||
<publicslots title=""/>
|
||||
<protectedslots title=""/>
|
||||
<privateslots title=""/>
|
||||
<events title=""/>
|
||||
<properties title=""/>
|
||||
<friends title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</group>
|
||||
|
||||
<!-- Layout definition for a directory page -->
|
||||
<directory>
|
||||
<briefdescription visible="yes"/>
|
||||
<directorygraph visible="yes"/>
|
||||
<memberdecl>
|
||||
<dirs visible="yes"/>
|
||||
<files visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
</directory>
|
||||
</doxygenlayout>
|
||||
24
doc/Jamfile
Normal file
24
doc/Jamfile
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
# Adapted from Boost.Unordered
|
||||
make html/index.html : build_antora.sh : @run-script ;
|
||||
|
||||
# Runs the Antora script
|
||||
actions run-script
|
||||
{
|
||||
bash -x $(>)
|
||||
}
|
||||
|
||||
# These are used to inform the build system of the
|
||||
# means to build the integrated and stand-alone docs.
|
||||
|
||||
alias boostdoc ;
|
||||
explicit boostdoc ;
|
||||
|
||||
alias boostrelease : html/index.html ;
|
||||
explicit boostrelease ;
|
||||
@@ -1,32 +0,0 @@
|
||||
/* Doxygen HTML_EXTRA_STYLESHEET */
|
||||
|
||||
div.contents {
|
||||
max-width: 100em;
|
||||
margin-right: 5em;
|
||||
margin-left: 5em;
|
||||
}
|
||||
|
||||
.ui-resizable-e {
|
||||
background-image:url("splitbar.png");
|
||||
background-size:100%;
|
||||
background-repeat:repeat-y;
|
||||
background-attachment: scroll;
|
||||
cursor:ew-resize;
|
||||
height:100%;
|
||||
right:0;
|
||||
top:0;
|
||||
width:1px;
|
||||
}
|
||||
|
||||
.pyrootbox {
|
||||
border: 1px solid #879ecb;
|
||||
background-color: #f9fafc;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/*
|
||||
code
|
||||
{
|
||||
background-color:#EFD25E;
|
||||
}
|
||||
*/
|
||||
15
doc/antora.yml
Normal file
15
doc/antora.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
name: redis
|
||||
title: Boost.Redis
|
||||
version: ~
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
ext:
|
||||
cpp-reference:
|
||||
config: doc/mrdocs.yml
|
||||
19
doc/build_antora.sh
Executable file
19
doc/build_antora.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Required by our CMake.
|
||||
# Prevents Antora from cloning Boost again
|
||||
export BOOST_SRC_DIR=$(realpath $SCRIPT_DIR/../../..)
|
||||
|
||||
npm ci
|
||||
npx antora --log-format=pretty redis-playbook.yml
|
||||
@@ -1,19 +0,0 @@
|
||||
<!-- HTML footer for doxygen 1.8.14-->
|
||||
<!-- start footer part -->
|
||||
<!--BEGIN GENERATE_TREEVIEW-->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
$navpath
|
||||
<li class="footer">
|
||||
Aedis 1.0.0 - Reference Guide generated on $datetime using Doxygen $doxygenversion   
|
||||
<img class="footer" src="rootlogo_s.gif" alt="root"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--END GENERATE_TREEVIEW-->
|
||||
<!--BEGIN !GENERATE_TREEVIEW-->
|
||||
<hr class="footer"/><address class="footer">
|
||||
Author: Marcelo Zimbres Silva.
|
||||
</address>
|
||||
<!--END !GENERATE_TREEVIEW-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,34 +0,0 @@
|
||||
<!-- HTML header for doxygen 1.8.14-->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen $doxygenversion"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
$search
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
$extrastylesheet
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table bgcolor="#346295" cellspacing="0" cellpadding="6">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="middle" style="color: #FFFFFF" nowrap="nowrap"><font size="6">$projectname $projectnumber</font>   <br> $projectbrief </td>
|
||||
<td style="width:100%"> $searchbox </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
10
doc/modules/ROOT/nav.adoc
Normal file
10
doc/modules/ROOT/nav.adoc
Normal file
@@ -0,0 +1,10 @@
|
||||
* xref:index.adoc[Introduction]
|
||||
* xref:requests_responses.adoc[]
|
||||
* xref:serialization.adoc[]
|
||||
* xref:logging.adoc[]
|
||||
* xref:benchmarks.adoc[]
|
||||
* xref:comparison.adoc[]
|
||||
* xref:examples.adoc[]
|
||||
* xref:reference.adoc[Reference]
|
||||
* xref:acknowledgements.adoc[]
|
||||
* xref:changelog.adoc[]
|
||||
37
doc/modules/ROOT/pages/acknowledgements.adoc
Normal file
37
doc/modules/ROOT/pages/acknowledgements.adoc
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
= Acknowledgements
|
||||
|
||||
Acknowledgement to people that helped shape Boost.Redis
|
||||
|
||||
* Richard Hodges (https://github.com/madmongo1[madmongo1]): For very helpful support with Asio, the design of asynchronous programs, etc.
|
||||
* Vinícius dos Santos Oliveira (https://github.com/vinipsmaker[vinipsmaker]): For useful discussion about how Boost.Redis consumes buffers in the read operation.
|
||||
* Petr Dannhofer (https://github.com/Eddie-cz[Eddie-cz]): For helping me understand how the `AUTH` and `HELLO` command can influence each other.
|
||||
* Mohammad Nejati (https://github.com/ashtum[ashtum]): For pointing out scenarios where calls to `async_exec` should fail when the connection is lost.
|
||||
* Klemens Morgenstern (https://github.com/klemens-morgenstern[klemens-morgenstern]): For useful discussion about timeouts, cancellation, synchronous interfaces and general help with Asio.
|
||||
* Vinnie Falco (https://github.com/vinniefalco[vinniefalco]): For general suggestions about how to improve the code and the documentation.
|
||||
* Bram Veldhoen (https://github.com/bveldhoen[bveldhoen]): For contributing a Redis-streams example.
|
||||
|
||||
Also many thanks to all individuals that participated in the Boost
|
||||
review
|
||||
|
||||
* Zach Laine: https://lists.boost.org/Archives/boost/2023/01/253883.php
|
||||
* Vinnie Falco: https://lists.boost.org/Archives/boost/2023/01/253886.php
|
||||
* Christian Mazakas: https://lists.boost.org/Archives/boost/2023/01/253900.php
|
||||
* Ruben Perez: https://lists.boost.org/Archives/boost/2023/01/253915.php
|
||||
* Dmitry Arkhipov: https://lists.boost.org/Archives/boost/2023/01/253925.php
|
||||
* Alan de Freitas: https://lists.boost.org/Archives/boost/2023/01/253927.php
|
||||
* Mohammad Nejati: https://lists.boost.org/Archives/boost/2023/01/253929.php
|
||||
* Sam Hartsfield: https://lists.boost.org/Archives/boost/2023/01/253931.php
|
||||
* Miguel Portilla: https://lists.boost.org/Archives/boost/2023/01/253935.php
|
||||
* Robert A.H. Leahy: https://lists.boost.org/Archives/boost/2023/01/253928.php
|
||||
|
||||
The Reviews can be found at:
|
||||
https://lists.boost.org/Archives/boost/2023/01/date.php. The thread
|
||||
with the ACCEPT from the review manager can be found here:
|
||||
https://lists.boost.org/Archives/boost/2023/01/253944.php.
|
||||
95
doc/modules/ROOT/pages/benchmarks.adoc
Normal file
95
doc/modules/ROOT/pages/benchmarks.adoc
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
= Echo server benchmark
|
||||
|
||||
This document benchmarks the performance of TCP echo servers I
|
||||
implemented in different languages using different Redis clients. The
|
||||
main motivations for choosing an echo server are
|
||||
|
||||
* Simple to implement and does not require expertise level in most languages.
|
||||
* I/O bound: Echo servers have very low CPU consumption in general
|
||||
and therefore are excellent to measure how a program handles concurrent requests.
|
||||
* It simulates very well a typical backend in regard to concurrency.
|
||||
|
||||
I also imposed some constraints on the implementations
|
||||
|
||||
* It should be simple enough and not require writing too much code.
|
||||
* Favor the use standard idioms and avoid optimizations that require expert level.
|
||||
* Avoid the use of complex things like connection and thread pool.
|
||||
|
||||
To reproduce these results run one of the echo-server programs in one
|
||||
terminal and the
|
||||
https://github.com/boostorg/redis/blob/42880e788bec6020dd018194075a211ad9f339e8/benchmarks/cpp/asio/echo_server_client.cpp[echo-server-client] in another.
|
||||
|
||||
== Without Redis
|
||||
|
||||
First I tested a pure TCP echo server, i.e. one that sends the messages
|
||||
directly to the client without interacting with Redis. The result can
|
||||
be seen below
|
||||
|
||||
image::https://boostorg.github.io/redis/tcp-echo-direct.png[]
|
||||
|
||||
The tests were performed with a 1000 concurrent TCP connections on the
|
||||
localhost where latency is 0.07ms on average on my machine. On higher
|
||||
latency networks the difference among libraries is expected to
|
||||
decrease.
|
||||
|
||||
* I expected Libuv to have similar performance to Asio and Tokio.
|
||||
* I did expect nodejs to come a little behind given it is is
|
||||
javascript code. Otherwise I did expect it to have similar
|
||||
performance to libuv since it is the framework behind it.
|
||||
* Go did surprise me: faster than nodejs and libuv!
|
||||
|
||||
The code used in the benchmarks can be found at
|
||||
|
||||
* https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/cpp/asio/echo_server_direct.cpp[Asio]: A variation of https://github.com/chriskohlhoff/asio/blob/4915cfd8a1653c157a1480162ae5601318553eb8/asio/src/examples/cpp20/coroutines/echo_server.cpp[this Asio example].
|
||||
* https://github.com/boostorg/redis/tree/835a1decf477b09317f391eddd0727213cdbe12b/benchmarks/c/libuv[Libuv]: Taken from https://github.com/libuv/libuv/blob/06948c6ee502862524f233af4e2c3e4ca876f5f6/docs/code/tcp-echo-server/main.c[this Libuv example].
|
||||
* https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/rust/echo_server_direct[Tokio]: Taken from https://docs.rs/tokio/latest/tokio/[here].
|
||||
* https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/nodejs/echo_server_direct[Nodejs]
|
||||
* https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/go/echo_server_direct.go[Go]
|
||||
|
||||
== With Redis
|
||||
|
||||
This is similar to the echo server described above but messages are
|
||||
echoed by Redis and not by the echo-server itself, which acts
|
||||
as a proxy between the client and the Redis server. The results
|
||||
can be seen below
|
||||
|
||||
image::https://boostorg.github.io/redis/tcp-echo-over-redis.png[]
|
||||
|
||||
The tests were performed on a network where latency is 35ms on
|
||||
average, otherwise it uses the same number of TCP connections
|
||||
as the previous example.
|
||||
|
||||
As the reader can see, the Libuv and the Rust test are not depicted
|
||||
in the graph, the reasons are
|
||||
|
||||
* https://github.com/redis-rs/redis-rs[redis-rs]: This client
|
||||
comes so far behind that it can't even be represented together
|
||||
with the other benchmarks without making them look insignificant.
|
||||
I don't know for sure why it is so slow, I suppose it has
|
||||
something to do with its lack of automatic
|
||||
https://redis.io/docs/manual/pipelining/[pipelining] support.
|
||||
In fact, the more TCP connections I launch the worse its
|
||||
performance gets.
|
||||
|
||||
* Libuv: I left it out because it would require me writing to much
|
||||
c code. More specifically, I would have to use hiredis and
|
||||
implement support for pipelines manually.
|
||||
|
||||
The code used in the benchmarks can be found at
|
||||
|
||||
* https://github.com/boostorg/redis[Boost.Redis]: https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/examples/echo_server.cpp[code]
|
||||
* https://github.com/redis/node-redis[node-redis]: https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/nodejs/echo_server_over_redis[code]
|
||||
* https://github.com/go-redis/redis[go-redis]: https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/go/echo_server_over_redis.go[code]
|
||||
|
||||
|
||||
== Conclusion
|
||||
|
||||
Redis clients have to support automatic pipelining to have competitive performance. For updates to this document follow https://github.com/boostorg/redis[].
|
||||
|
||||
397
doc/modules/ROOT/pages/changelog.adoc
Normal file
397
doc/modules/ROOT/pages/changelog.adoc
Normal file
@@ -0,0 +1,397 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
= Changelog
|
||||
|
||||
== Boost 1.88
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/233[233])
|
||||
To deal with keys that might not exits in the Redis server, the
|
||||
library supports `std::optional`, for example
|
||||
`response<std::optional<std::vector<std::string>>>`. In some cases
|
||||
however, such as the https://redis.io/docs/latest/commands/mget/[MGET] command,
|
||||
each element in the vector might be non exiting, now it is possible
|
||||
to specify a response as `response<std::optional<std::vector<std::optional<std::string>>>>`.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/225[225])
|
||||
Use `deferred` as the connection default completion token.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/128[128])
|
||||
Adds a new `async_exec` overload that allows passing response
|
||||
adapters. This makes it possible to receive Redis responses directly
|
||||
in custom data structures thereby avoiding unnecessary data copying.
|
||||
Thanks to Ruben Perez (@anarthal) for implementing this feature.
|
||||
|
||||
* There are also other multiple small improvements in this release,
|
||||
users can refer to the git history for more details.
|
||||
|
||||
== Boost 1.87
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/205[205])
|
||||
Improves reaction time to disconnection by using `wait_for_one_error`
|
||||
instead of `wait_for_all`. The function `connection::async_run` was
|
||||
also changed to return EOF to the user when that error is received
|
||||
from the server. That is a breaking change.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/210[210])
|
||||
Fixes the adapter of empty nested responses.
|
||||
|
||||
* (Issues https://github.com/boostorg/redis/issues/211[211] and https://github.com/boostorg/redis/issues/212[212])
|
||||
Fixes the reconnect loop that would hang under certain conditions,
|
||||
see the linked issues for more details.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/219[219])
|
||||
Changes the default log level from `disabled` to `debug`.
|
||||
|
||||
== Boost 1.85
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/170[170])
|
||||
Under load and on low-latency networks it is possible to start
|
||||
receiving responses before the write operation completed and while
|
||||
the request is still marked as staged and not written. This messes
|
||||
up with the heuristics that classifies responses as unsolicited or
|
||||
not.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/168[168]).
|
||||
Provides a way of passing a custom SSL context to the connection.
|
||||
The design here differs from that of Boost.Beast and Boost.MySql
|
||||
since in Boost.Redis the connection owns the context instead of only
|
||||
storing a reference to a user provided one. This is ok so because
|
||||
apps need only one connection for their entire application, which
|
||||
makes the overhead of one ssl-context per connection negligible.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/181[181]).
|
||||
See a detailed description of this bug in
|
||||
https://github.com/boostorg/redis/issues/181#issuecomment-1913346983[this comment].
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/182[182]).
|
||||
Sets `"default"` as the default value of `config::username`. This
|
||||
makes it simpler to use the `requirepass` configuration in Redis.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/189[189]).
|
||||
Fixes narrowing conversion by using `std::size_t` instead of
|
||||
`std::uint64_t` for the sizes of bulks and aggregates. The code
|
||||
relies now on `std::from_chars` returning an error if a value
|
||||
greater than 32 is received on platforms on which the size
|
||||
of `std::size_t` is 32.
|
||||
|
||||
|
||||
== Boost 1.84 (First release in Boost)
|
||||
|
||||
* Deprecates the `async_receive` overload that takes a response. Users
|
||||
should now first call `set_receive_response` to avoid constantly and
|
||||
unnecessarily setting the same response.
|
||||
|
||||
* Uses `std::function` to type erase the response adapter. This change
|
||||
should not influence users in any way but allowed important
|
||||
simplification in the connections internals. This resulted in
|
||||
massive performance improvement.
|
||||
|
||||
* The connection has a new member `get_usage()` that returns the
|
||||
connection usage information, such as number of bytes written,
|
||||
received etc.
|
||||
|
||||
* There are massive performance improvements in the consuming of
|
||||
server pushes which are now communicated with an `asio::channel` and
|
||||
therefore can be buffered which avoids blocking the socket read-loop.
|
||||
Batch reads are also supported by means of `channel.try_send` and
|
||||
buffered messages can be consumed synchronously with
|
||||
`connection::receive`. The function `boost::redis::cancel_one` has
|
||||
been added to simplify processing multiple server pushes contained
|
||||
in the same `generic_response`. *IMPORTANT*: These changes may
|
||||
result in more than one push in the response when
|
||||
`connection::async_receive` resumes. The user must therefore be
|
||||
careful when calling `resp.clear()`: either ensure that all message
|
||||
have been processed or just use `consume_one`.
|
||||
|
||||
== v1.4.2 (incorporates changes to conform the boost review and more)
|
||||
|
||||
* Adds `boost::redis::config::database_index` to make it possible to
|
||||
choose a database before starting running commands e.g. after an
|
||||
automatic reconnection.
|
||||
|
||||
* Massive performance improvement. One of my tests went from
|
||||
140k req/s to 390k/s. This was possible after a parser
|
||||
simplification that reduced the number of reschedules and buffer
|
||||
rotations.
|
||||
|
||||
* Adds Redis stream example.
|
||||
|
||||
* Renames the project to Boost.Redis and moves the code into namespace
|
||||
`boost::redis`.
|
||||
|
||||
* As pointed out in the reviews the `to_bulk` and `from_bulk` names were too
|
||||
generic for ADL customization points. They gained the prefix `boost_redis_`.
|
||||
|
||||
* Moves `boost::redis::resp3::request` to `boost::redis::request`.
|
||||
|
||||
* Adds new typedef `boost::redis::response` that should be used instead of
|
||||
`std::tuple`.
|
||||
|
||||
* Adds new typedef `boost::redis::generic_response` that should be used instead
|
||||
of `std::vector<resp3::node<std::string>>`.
|
||||
|
||||
* Renames `redis::ignore` to `redis::ignore_t`.
|
||||
|
||||
* Changes `async_exec` to receive a `redis::response` instead of an adapter,
|
||||
namely, instead of passing `adapt(resp)` users should pass `resp` directly.
|
||||
|
||||
* Introduces `boost::redis::adapter::result` to store responses to commands
|
||||
including possible resp3 errors without losing the error diagnostic part. To
|
||||
access values now use `std::get<N>(resp).value()` instead of
|
||||
`std::get<N>(resp)`.
|
||||
|
||||
* Implements full-duplex communication. Before these changes the connection
|
||||
would wait for a response to arrive before sending the next one. Now requests
|
||||
are continuously coalesced and written to the socket. `request::coalesce`
|
||||
became unnecessary and was removed. I could measure significative performance
|
||||
gains with these changes.
|
||||
|
||||
* Improves serialization examples using Boost.Describe to serialize to JSON and protobuf. See
|
||||
cpp20_json.cpp and cpp20_protobuf.cpp for more details.
|
||||
|
||||
* Upgrades to Boost 1.81.0.
|
||||
|
||||
* Fixes build with pass:[libc++].
|
||||
|
||||
* Adds high-level functionality to the connection classes. For
|
||||
example, `boost::redis::connection::async_run` will automatically
|
||||
resolve, connect, reconnect and perform health checks.
|
||||
|
||||
== v1.4.0-1
|
||||
|
||||
* Renames `retry_on_connection_lost` to `cancel_if_unresponded`. (v1.4.1)
|
||||
* Removes dependency on Boost.Hana, `boost::string_view`, Boost.Variant2 and Boost.Spirit.
|
||||
* Fixes build and setup CI on windows.
|
||||
|
||||
== v1.3.0-1
|
||||
|
||||
* Upgrades to Boost 1.80.0
|
||||
|
||||
* Removes automatic sending of the `HELLO` command. This can't be
|
||||
implemented properly without bloating the connection class. It is
|
||||
now a user responsibility to send HELLO. Requests that contain it have
|
||||
priority over other requests and will be moved to the front of the
|
||||
queue, see `aedis::request::config`
|
||||
|
||||
* Automatic name resolving and connecting have been removed from
|
||||
`aedis::connection::async_run`. Users have to do this step manually
|
||||
now. The reason for this change is that having them built-in doesn't
|
||||
offer enough flexibility that is need for boost users.
|
||||
|
||||
* Removes healthy checks and idle timeout. This functionality must now
|
||||
be implemented by users, see the examples. This is
|
||||
part of making Aedis useful to a larger audience and suitable for
|
||||
the Boost review process.
|
||||
|
||||
* The `aedis::connection` is now using a typeddef to a
|
||||
`net::ip::tcp::socket` and `aedis::ssl::connection` to
|
||||
`net::ssl::stream<net::ip::tcp::socket>`. Users that need to use
|
||||
other stream type must now specialize `aedis::basic_connection`.
|
||||
|
||||
* Adds a low level example of async code.
|
||||
|
||||
== v1.2.0
|
||||
|
||||
* `aedis::adapt` supports now tuples created with `std::tie`.
|
||||
`aedis::ignore` is now an alias to the type of `std::ignore`.
|
||||
|
||||
* Provides allocator support for the internal queue used in the
|
||||
`aedis::connection` class.
|
||||
|
||||
* Changes the behaviour of `async_run` to complete with success if
|
||||
asio::error::eof is received. This makes it easier to write
|
||||
composed operations with awaitable operators.
|
||||
|
||||
* Adds allocator support in the `aedis::request` (a
|
||||
contribution from Klemens Morgenstern).
|
||||
|
||||
* Renames `aedis::request::push_range2` to `push_range`. The
|
||||
suffix 2 was used for disambiguation. Klemens fixed it with SFINAE.
|
||||
|
||||
* Renames `fail_on_connection_lost` to
|
||||
`aedis::request::config::cancel_on_connection_lost`. Now, it will
|
||||
only cause connections to be canceled when `async_run` completes.
|
||||
|
||||
* Introduces `aedis::request::config::cancel_if_not_connected` which will
|
||||
cause a request to be canceled if `async_exec` is called before a
|
||||
connection has been established.
|
||||
|
||||
* Introduces new request flag `aedis::request::config::retry` that if
|
||||
set to true will cause the request to not be canceled when it was
|
||||
sent to Redis but remained unresponded after `async_run` completed.
|
||||
It provides a way to avoid executing commands twice.
|
||||
|
||||
* Removes the `aedis::connection::async_run` overload that takes
|
||||
request and adapter as parameters.
|
||||
|
||||
* Changes the way `aedis::adapt()` behaves with
|
||||
`std::vector<aedis::resp3::node<T>>`. Receiving RESP3 simple errors,
|
||||
blob errors or null won't causes an error but will be treated as
|
||||
normal response. It is the user responsibility to check the content
|
||||
in the vector.
|
||||
|
||||
* Fixes a bug in `connection::cancel(operation::exec)`. Now this
|
||||
call will only cancel non-written requests.
|
||||
|
||||
* Implements per-operation implicit cancellation support for
|
||||
`aedis::connection::async_exec`. The following call will `co_await (conn.async_exec(...) || timer.async_wait(...))`
|
||||
will cancel the request as long as it has not been written.
|
||||
|
||||
* Changes `aedis::connection::async_run` completion signature to
|
||||
`f(error_code)`. This is how is was in the past, the second
|
||||
parameter was not helpful.
|
||||
|
||||
* Renames `operation::receive_push` to `aedis::operation::receive`.
|
||||
|
||||
== v1.1.0-1
|
||||
|
||||
* Removes `coalesce_requests` from the `aedis::connection::config`, it
|
||||
became a request property now, see `aedis::request::config::coalesce`.
|
||||
|
||||
* Removes `max_read_size` from the `aedis::connection::config`. The maximum
|
||||
read size can be specified now as a parameter of the
|
||||
`aedis::adapt()` function.
|
||||
|
||||
* Removes `aedis::sync` class, see intro_sync.cpp for how to perform
|
||||
synchronous and thread safe calls. This is possible in Boost. 1.80
|
||||
only as it requires `boost::asio::deferred`.
|
||||
|
||||
* Moves from `boost::optional` to `std::optional`. This is part of
|
||||
moving to pass:[C++17].
|
||||
|
||||
* Changes the behaviour of the second `aedis::connection::async_run` overload
|
||||
so that it always returns an error when the connection is lost.
|
||||
|
||||
* Adds TLS support, see intro_tls.cpp.
|
||||
|
||||
* Adds an example that shows how to resolve addresses over sentinels,
|
||||
see subscriber_sentinel.cpp.
|
||||
|
||||
* Adds a `aedis::connection::timeouts::resp3_handshake_timeout`. This is
|
||||
timeout used to send the `HELLO` command.
|
||||
|
||||
* Adds `aedis::endpoint` where in addition to host and port, users can
|
||||
optionally provide username, password and the expected server role
|
||||
(see `aedis::error::unexpected_server_role`).
|
||||
|
||||
* `aedis::connection::async_run` checks whether the server role received in
|
||||
the hello command is equal to the expected server role specified in
|
||||
`aedis::endpoint`. To skip this check let the role variable empty.
|
||||
|
||||
* Removes reconnect functionality from `aedis::connection`. It
|
||||
is possible in simple reconnection strategies but bloats the class
|
||||
in more complex scenarios, for example, with sentinel,
|
||||
authentication and TLS. This is trivial to implement in a separate
|
||||
coroutine. As a result the `enum event` and `async_receive_event`
|
||||
have been removed from the class too.
|
||||
|
||||
* Fixes a bug in `connection::async_receive_push` that prevented
|
||||
passing any response adapter other that `adapt(std::vector<node>)`.
|
||||
|
||||
* Changes the behaviour of `aedis::adapt()` that caused RESP3 errors
|
||||
to be ignored. One consequence of it is that `connection::async_run`
|
||||
would not exit with failure in servers that required authentication.
|
||||
|
||||
* Changes the behaviour of `connection::async_run` that would cause it
|
||||
to complete with success when an error in the
|
||||
`connection::async_exec` occurred.
|
||||
|
||||
* Ports the buildsystem from autotools to CMake.
|
||||
|
||||
== v1.0.0
|
||||
|
||||
* Adds experimental cmake support for windows users.
|
||||
|
||||
* Adds new class `aedis::sync` that wraps an `aedis::connection` in
|
||||
a thread-safe and synchronous API. All free functions from the
|
||||
`sync.hpp` are now member functions of `aedis::sync`.
|
||||
|
||||
* Split `aedis::connection::async_receive_event` in two functions, one
|
||||
to receive events and another for server side pushes, see
|
||||
`aedis::connection::async_receive_push`.
|
||||
|
||||
* Removes collision between `aedis::adapter::adapt` and
|
||||
`aedis::adapt`.
|
||||
|
||||
* Adds `connection::operation` enum to replace `cancel_*` member
|
||||
functions with a single cancel function that gets the operations
|
||||
that should be cancelled as argument.
|
||||
|
||||
* Bugfix: a bug on reconnect from a state where the `connection` object
|
||||
had unsent commands. It could cause `async_exec` to never
|
||||
complete under certain conditions.
|
||||
|
||||
* Bugfix: Documentation of `adapt()` functions were missing from
|
||||
Doxygen.
|
||||
|
||||
== v0.3.0
|
||||
|
||||
* Adds `experimental::exec` and `receive_event` functions to offer a
|
||||
thread safe and synchronous way of executing requests across
|
||||
threads. See `intro_sync.cpp` and `subscriber_sync.cpp` for
|
||||
examples.
|
||||
|
||||
* `connection::async_read_push` was renamed to `async_receive_event`.
|
||||
|
||||
* `connection::async_receive_event` is now being used to communicate
|
||||
internal events to the user, such as resolve, connect, push etc. For
|
||||
examples see cpp20_subscriber.cpp and `connection::event`.
|
||||
|
||||
* The `aedis` directory has been moved to `include` to look more
|
||||
similar to Boost libraries. Users should now replace `-I/aedis-path`
|
||||
with `-I/aedis-path/include` in the compiler flags.
|
||||
|
||||
* The `AUTH` and `HELLO` commands are now sent automatically. This change was
|
||||
necessary to implement reconnection. The username and password
|
||||
used in `AUTH` should be provided by the user on
|
||||
`connection::config`.
|
||||
|
||||
* Adds support for reconnection. See `connection::enable_reconnect`.
|
||||
|
||||
* Fixes a bug in the `connection::async_run(host, port)` overload
|
||||
that was causing crashes on reconnection.
|
||||
|
||||
* Fixes the executor usage in the connection class. Before these
|
||||
changes it was imposing `any_io_executor` on users.
|
||||
|
||||
* `connection::async_receiver_event` is not cancelled anymore when
|
||||
`connection::async_run` exits. This change makes user code simpler.
|
||||
|
||||
* `connection::async_exec` with host and port overload has been
|
||||
removed. Use the other `connection::async_run` overload.
|
||||
|
||||
* The host and port parameters from `connection::async_run` have been
|
||||
move to `connection::config` to better support authentication and
|
||||
failover.
|
||||
|
||||
* Many simplifications in the `chat_room` example.
|
||||
|
||||
* Fixes build in clang the compilers and makes some improvements in
|
||||
the documentation.
|
||||
|
||||
== v0.2.0-1
|
||||
|
||||
* Fixes a bug that happens on very high load. (v0.2.1)
|
||||
* Major rewrite of the high-level API. There is no more need to use the low-level API anymore.
|
||||
* No more callbacks: Sending requests follows the ASIO asynchronous model.
|
||||
* Support for reconnection: Pending requests are not canceled when a connection is lost and are re-sent when a new one is established.
|
||||
* The library is not sending HELLO-3 on user behalf anymore. This is important to support AUTH properly.
|
||||
|
||||
== v0.1.0-2
|
||||
|
||||
* Adds reconnect coroutine in the `echo_server` example. (v0.1.2)
|
||||
* Corrects `client::async_wait_for_data` with `make_parallel_group` to launch operation. (v0.1.2)
|
||||
* Improvements in the documentation. (v0.1.2)
|
||||
* Avoids dynamic memory allocation in the client class after reconnection. (v0.1.2)
|
||||
* Improves the documentation and adds some features to the high-level client. (v.0.1.1)
|
||||
* Improvements in the design and documentation.
|
||||
|
||||
== v0.0.1
|
||||
|
||||
* First release to collect design feedback.
|
||||
|
||||
121
doc/modules/ROOT/pages/comparison.adoc
Normal file
121
doc/modules/ROOT/pages/comparison.adoc
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
= Comparison with other Redis clients
|
||||
|
||||
## Comparison
|
||||
|
||||
The main reason for why I started writing Boost.Redis was to have a client
|
||||
compatible with the Asio asynchronous model. As I made progresses I could
|
||||
also address what I considered weaknesses in other libraries. Due to
|
||||
time constraints I won't be able to give a detailed comparison with
|
||||
each client listed in the
|
||||
https://redis.io/docs/clients/#cpp[official list].
|
||||
Instead, I will focus on the most popular pass:[C++] client on github in number of stars, namely:
|
||||
|
||||
https://github.com/sewenew/redis-plus-plus[]
|
||||
|
||||
### Boost.Redis vs Redis-plus-plus
|
||||
|
||||
Before we start it is important to mention some of the things
|
||||
redis-plus-plus does not support
|
||||
|
||||
* The latest version of the communication protocol RESP3. Without that it is impossible to support some important Redis features like client side caching, among other things.
|
||||
* Coroutines.
|
||||
* Reading responses directly in user data structures to avoid creating temporaries.
|
||||
* Error handling with support for error-code.
|
||||
* Cancellation.
|
||||
|
||||
The remaining points will be addressed individually. Let us first
|
||||
have a look at what sending a command a pipeline and a transaction
|
||||
look like
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
auto redis = Redis("tcp://127.0.0.1:6379");
|
||||
|
||||
// Send commands
|
||||
redis.set("key", "val");
|
||||
auto val = redis.get("key"); // val is of type OptionalString.
|
||||
if (val)
|
||||
std::cout << *val << std::endl;
|
||||
|
||||
// Sending pipelines
|
||||
auto pipe = redis.pipeline();
|
||||
auto pipe_replies = pipe.set("key", "value")
|
||||
.get("key")
|
||||
.rename("key", "new-key")
|
||||
.rpush("list", {"a", "b", "c"})
|
||||
.lrange("list", 0, -1)
|
||||
.exec();
|
||||
|
||||
// Parse reply with reply type and index.
|
||||
auto set_cmd_result = pipe_replies.get<bool>(0);
|
||||
// ...
|
||||
|
||||
// Sending a transaction
|
||||
auto tx = redis.transaction();
|
||||
auto tx_replies = tx.incr("num0")
|
||||
.incr("num1")
|
||||
.mget({"num0", "num1"})
|
||||
.exec();
|
||||
|
||||
auto incr_result0 = tx_replies.get<long long>(0);
|
||||
// ...
|
||||
----
|
||||
|
||||
Some of the problems with this API are
|
||||
|
||||
* Heterogeneous treatment of commands, pipelines and transaction. This makes auto-pipelining impossible.
|
||||
* Any Api that sends individual commands has a very restricted scope of usability and should be avoided for performance reasons.
|
||||
* The API imposes exceptions on users, no error-code overload is provided.
|
||||
* No way to reuse the buffer for new calls to e.g. redis.get in order to avoid further dynamic memory allocations.
|
||||
* Error handling of resolve and connection not clear.
|
||||
|
||||
According to the documentation, pipelines in redis-plus-plus have
|
||||
the following characteristics
|
||||
|
||||
> +NOTE+: By default, creating a Pipeline object is NOT cheap, since
|
||||
> it creates a new connection.
|
||||
|
||||
This is clearly a downside in the API as pipelines should be the
|
||||
default way of communicating and not an exception, paying such a
|
||||
high price for each pipeline imposes a severe cost in performance.
|
||||
Transactions also suffer from the very same problem:
|
||||
|
||||
> +NOTE+: Creating a Transaction object is NOT cheap, since it
|
||||
> creates a new connection.
|
||||
|
||||
In Boost.Redis there is no difference between sending one command, a
|
||||
pipeline or a transaction because requests are decoupled
|
||||
from the I/O objects.
|
||||
|
||||
> redis-plus-plus also supports async interface, however, async
|
||||
> support for Transaction and Subscriber is still on the way.
|
||||
>
|
||||
> The async interface depends on third-party event library, and so
|
||||
> far, only libuv is supported.
|
||||
|
||||
Async code in redis-plus-plus looks like the following
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
auto async_redis = AsyncRedis(opts, pool_opts);
|
||||
|
||||
Future<string> ping_res = async_redis.ping();
|
||||
|
||||
cout << ping_res.get() << endl;
|
||||
----
|
||||
|
||||
As the reader can see, the async interface is based on futures
|
||||
which is also known to have a bad performance. The biggest
|
||||
problem however with this async design is that it makes it
|
||||
impossible to write asynchronous programs correctly since it
|
||||
starts an async operation on every command sent instead of
|
||||
enqueueing a message and triggering a write when it can be sent.
|
||||
It is also not clear how are pipelines realised with this design
|
||||
(if at all).
|
||||
27
doc/modules/ROOT/pages/examples.adoc
Normal file
27
doc/modules/ROOT/pages/examples.adoc
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
= Examples
|
||||
|
||||
The examples below show how to use the features discussed throughout this documentation:
|
||||
|
||||
* {site-url}/example/cpp20_intro.cpp[cpp20_intro.cpp]: Does not use awaitable operators.
|
||||
* {site-url}/example/cpp20_intro_tls.cpp[cpp20_intro_tls.cpp]: Communicates over TLS.
|
||||
* {site-url}/example/cpp20_unix_sockets.cpp[cpp20_unix_sockets.cpp]: Communicates over UNIX domain sockets.
|
||||
* {site-url}/example/cpp20_containers.cpp[cpp20_containers.cpp]: Shows how to send and receive STL containers and how to use transactions.
|
||||
* {site-url}/example/cpp20_json.cpp[cpp20_json.cpp]: Shows how to serialize types using Boost.Json.
|
||||
* {site-url}/example/cpp20_protobuf.cpp[cpp20_protobuf.cpp]: Shows how to serialize types using protobuf.
|
||||
* {site-url}/example/cpp20_resolve_with_sentinel.cpp[cpp20_resolve_with_sentinel.cpp]: Shows how to resolve a master address using sentinels.
|
||||
* {site-url}/example/cpp20_subscriber.cpp[cpp20_subscriber.cpp]: Shows how to implement pubsub with reconnection re-subscription.
|
||||
* {site-url}/example/cpp20_echo_server.cpp[cpp20_echo_server.cpp]: A simple TCP echo server.
|
||||
* {site-url}/example/cpp20_chat_room.cpp[cpp20_chat_room.cpp]: A command line chat built on Redis pubsub.
|
||||
* {site-url}/example/cpp17_intro.cpp[cpp17_intro.cpp]: Uses callbacks and requires pass:[C++17].
|
||||
* {site-url}/example/cpp17_intro_sync.cpp[cpp17_intro_sync.cpp]: Runs `async_run` in a separate thread and performs synchronous calls to `async_exec`.
|
||||
* {site-url}/example/cpp17_spdlog.cpp[cpp17_spdlog.cpp]: Shows how to use third-party logging libraries like `spdlog` with Boost.Redis.
|
||||
|
||||
The main function used in some async examples has been factored out in
|
||||
the {site-url}/example/main.cpp[main.cpp] file.
|
||||
143
doc/modules/ROOT/pages/index.adoc
Normal file
143
doc/modules/ROOT/pages/index.adoc
Normal file
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
[#intro]
|
||||
= Introduction
|
||||
|
||||
Boost.Redis is a high-level https://redis.io/[Redis] client library built on top of
|
||||
https://www.boost.org/doc/libs/latest/doc/html/boost_asio.html[Boost.Asio]
|
||||
that implements the Redis protocol
|
||||
https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md[RESP3].
|
||||
|
||||
== Requirements
|
||||
|
||||
The requirements for using Boost.Redis are:
|
||||
|
||||
* Boost 1.84 or higher. Boost.Redis is included in Boost installations since Boost 1.84.
|
||||
* pass:[C++17] or higher. Supported compilers include gcc 11 and later, clang 11 and later, and Visual Studio 16 (2019) and later.
|
||||
* Redis 6 or higher (must support RESP3).
|
||||
* OpenSSL.
|
||||
|
||||
The documentation assumes basic-level knowledge about https://redis.io/docs/[Redis] and https://www.boost.org/doc/libs/latest/doc/html/boost_asio.html[Boost.Asio].
|
||||
|
||||
== Building the library
|
||||
|
||||
To use the library it is necessary to include the following:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
#include <boost/redis/src.hpp>
|
||||
----
|
||||
|
||||
in exactly one source file in your applications. Otherwise, the library is header-only.
|
||||
|
||||
Boost.Redis unconditionally requires OpenSSL. Targets using Boost.Redis need to link
|
||||
to the OpenSSL libraries.
|
||||
|
||||
== Tutorial
|
||||
|
||||
The code below uses a short-lived connection to
|
||||
https://redis.io/commands/ping/[ping] a Redis server:
|
||||
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto co_main(config const& cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
// A request containing only a ping command.
|
||||
request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
// Response object.
|
||||
response<std::string> resp;
|
||||
|
||||
// Executes the request.
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
|
||||
}
|
||||
----
|
||||
|
||||
The roles played by the `async_run` and `async_exec` functions are:
|
||||
|
||||
* xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`connection::async_exec`]: executes the commands contained in the
|
||||
request and stores the individual responses in the response object. Can
|
||||
be called from multiple places in your code concurrently.
|
||||
* xref:reference:boost/redis/basic_connection/async_run-04.adoc[`connection::async_run`]: keeps the connection healthy. It takes care of hostname resolution, session establishment, health-checks, reconnection and coordination of low-level read and write operations. It should be called only once per connection, regardless of the number of requests to execute.
|
||||
|
||||
== Server pushes
|
||||
|
||||
Redis servers can also send a variety of pushes to the client. Some of
|
||||
them are:
|
||||
|
||||
* https://redis.io/docs/manual/pubsub/[Pubsub messages].
|
||||
* https://redis.io/docs/manual/keyspace-notifications/[Keyspace notifications].
|
||||
* https://redis.io/docs/manual/client-side-caching/[Client-side caching].
|
||||
|
||||
The connection class supports server pushes by means of the
|
||||
xref:reference:boost/redis/basic_connection/async_receive.adoc[`connection::async_receive`] function, which can be
|
||||
called in the same connection that is being used to execute commands.
|
||||
The coroutine below shows how to use it:
|
||||
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
auto
|
||||
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
generic_response resp;
|
||||
conn->set_receive_response(resp);
|
||||
|
||||
// Loop while reconnection is enabled
|
||||
while (conn->will_reconnect()) {
|
||||
|
||||
// Reconnect to channels.
|
||||
co_await conn->async_exec(req, ignore);
|
||||
|
||||
// Loop reading Redis pushes.
|
||||
for (;;) {
|
||||
error_code ec;
|
||||
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
|
||||
// Use the response resp in some way and then clear it.
|
||||
...
|
||||
|
||||
consume_one(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Further reading
|
||||
|
||||
Here is a list of topics that you might be interested in:
|
||||
|
||||
* xref:requests_responses.adoc[More on requests and responses].
|
||||
* xref:serialization.adoc[Serializing and parsing into custom types].
|
||||
* xref:logging.adoc[Configuring logging].
|
||||
* xref:examples.adoc[Examples].
|
||||
45
doc/modules/ROOT/pages/logging.adoc
Normal file
45
doc/modules/ROOT/pages/logging.adoc
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
= Logging
|
||||
|
||||
xref:reference:boost/redis/basic_connection/async_run-04.adoc[`connection::async_run`]
|
||||
is a complex algorithm, with features like built-in reconnection.
|
||||
This can make configuration problems, like a misconfigured hostname, difficult to debug -
|
||||
Boost.Redis will keep retrying to connect to the same hostname over and over.
|
||||
For this reason, Boost.Redis incorporates a lightweight logging solution, and
|
||||
*will log some status messages to stderr by default*.
|
||||
|
||||
Logging can be customized by passing a
|
||||
xref:reference:boost/redis/logger.adoc[`logger`] object to the connection's constructor. For example, logging can be disabled by writing:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
asio::io_context ioc;
|
||||
connection conn {ioc, logger{logger::level::disabled}};
|
||||
----
|
||||
|
||||
Every message logged by the library is attached a
|
||||
https://en.wikipedia.org/wiki/Syslog#Severity_level[syslog-like severity]
|
||||
tag (a xref:reference:boost/redis/logger/level.adoc[`logger::level`]).
|
||||
You can filter messages by severity by creating a `logger` with a specific level:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
asio::io_context ioc;
|
||||
|
||||
// Logs to stderr messages with severity >= level::error.
|
||||
// This will hide all informational output.
|
||||
connection conn {ioc, logger{logger::level::error}};
|
||||
----
|
||||
|
||||
The `logger` constructor accepts a `std::function<void(logger::level, std::string_view)>`
|
||||
as second argument. If supplied, Boost.Redis will call this function when logging
|
||||
instead of printing to stderr. This can be used to integrate third-party logging
|
||||
libraries. See our {site-url}/example/cpp17_spdlog.cpp[spdlog integration example]
|
||||
for sample code.
|
||||
90
doc/modules/ROOT/pages/reference.adoc
Normal file
90
doc/modules/ROOT/pages/reference.adoc
Normal file
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
[#reference]
|
||||
= Reference
|
||||
|
||||
[width=100%,cols="5*"]
|
||||
|===
|
||||
|
||||
| *Connections*
|
||||
| *Requests and responses*
|
||||
| *Adapters*
|
||||
| *RESP3 protocol*
|
||||
| *Unstable low-level APIs*
|
||||
|
||||
|
|
||||
xref:reference:boost/redis/connection.adoc[`connection`]
|
||||
|
||||
xref:reference:boost/redis/basic_connection.adoc[`basic_connection`]
|
||||
|
||||
xref:reference:boost/redis/address.adoc[`address`]
|
||||
|
||||
xref:reference:boost/redis/config.adoc[`config`]
|
||||
|
||||
xref:reference:boost/redis/error.adoc[`error`]
|
||||
|
||||
xref:reference:boost/redis/logger.adoc[`logger`]
|
||||
|
||||
xref:reference:boost/redis/logger/level.adoc[`logger::level`]
|
||||
|
||||
xref:reference:boost/redis/operation.adoc[`operation`]
|
||||
|
||||
xref:reference:boost/redis/usage.adoc[`usage`]
|
||||
|
||||
|
||||
|
|
||||
xref:reference:boost/redis/ignore_t.adoc[`ignore_t`]
|
||||
|
||||
xref:reference:boost/redis/ignore.adoc[`ignore`]
|
||||
|
||||
xref:reference:boost/redis/request.adoc[`request`]
|
||||
|
||||
xref:reference:boost/redis/request/config.adoc[`request::config`]
|
||||
|
||||
xref:reference:boost/redis/response.adoc[`response`]
|
||||
|
||||
xref:reference:boost/redis/generic_response.adoc[`generic_response`]
|
||||
|
||||
xref:reference:boost/redis/consume_one-08.adoc[`consume_one`]
|
||||
|
||||
|
||||
|
|
||||
xref:reference:boost/redis/adapter/boost_redis_adapt.adoc[`boost_redis_adapt`]
|
||||
|
||||
xref:reference:boost/redis/adapter/ignore.adoc[`adapter::ignore`]
|
||||
|
||||
xref:reference:boost/redis/adapter/error.adoc[`adapter::error`]
|
||||
|
||||
xref:reference:boost/redis/adapter/result.adoc[`adapter::result`]
|
||||
|
||||
xref:reference:boost/redis/any_adapter.adoc[`any_adapter`]
|
||||
|
||||
|
|
||||
xref:reference:boost/redis/resp3/basic_node.adoc[`basic_node`]
|
||||
|
||||
xref:reference:boost/redis/resp3/node.adoc[`node`]
|
||||
|
||||
xref:reference:boost/redis/resp3/node_view.adoc[`node_view`]
|
||||
|
||||
xref:reference:boost/redis/resp3/boost_redis_to_bulk-08.adoc[`boost_redis_to_bulk`]
|
||||
|
||||
xref:reference:boost/redis/resp3/type.adoc[`type`]
|
||||
|
||||
xref:reference:boost/redis/resp3/is_aggregate.adoc[`is_aggregate`]
|
||||
|
||||
|
||||
|
|
||||
|
||||
xref:reference:boost/redis/adapter/adapt2.adoc[`adapter::adapt2`]
|
||||
|
||||
xref:reference:boost/redis/resp3/parser.adoc[`parser`]
|
||||
|
||||
xref:reference:boost/redis/resp3/parse.adoc[`parse`]
|
||||
|
||||
|===
|
||||
302
doc/modules/ROOT/pages/requests_responses.adoc
Normal file
302
doc/modules/ROOT/pages/requests_responses.adoc
Normal file
@@ -0,0 +1,302 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
= Requests and responses
|
||||
|
||||
== Requests
|
||||
|
||||
Redis requests are composed of one or more commands. In the
|
||||
Redis documentation, requests are called
|
||||
https://redis.io/topics/pipelining[pipelines]. For example:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Some example containers.
|
||||
std::list<std::string> list {...};
|
||||
std::map<std::string, mystruct> map { ...};
|
||||
|
||||
// The request can contain multiple commands.
|
||||
request req;
|
||||
|
||||
// Command with variable length of arguments.
|
||||
req.push("SET", "key", "some value", "EX", "2");
|
||||
|
||||
// Pushes a list.
|
||||
req.push_range("SUBSCRIBE", list);
|
||||
|
||||
// Same as above but as an iterator range.
|
||||
req.push_range("SUBSCRIBE", std::cbegin(list), std::cend(list));
|
||||
|
||||
// Pushes a map.
|
||||
req.push_range("HSET", "key", map);
|
||||
----
|
||||
|
||||
Sending a request to Redis is performed by
|
||||
xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`connection::async_exec`]
|
||||
as already stated. Requests accept a xref:reference:boost/redis/request/config[`boost::redis::request::config`]
|
||||
object when constructed that dictates how requests are handled in situations like
|
||||
reconnection. The reader is advised to read it carefully.
|
||||
|
||||
## Responses
|
||||
|
||||
Boost.Redis uses the following strategy to deal with Redis responses:
|
||||
|
||||
* xref:reference:boost/redis/response.adoc[`boost::redis::response`] should be used
|
||||
when the request's number of commands is known at compile-time.
|
||||
* xref:reference:boost/redis/generic_response.adoc[`boost::redis::generic_response`] should be
|
||||
used when the number of commands is dynamic.
|
||||
|
||||
For example, the request below has three commands:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("INCR", "key");
|
||||
req.push("QUIT");
|
||||
----
|
||||
|
||||
Therefore, its response will also contain three elements.
|
||||
The following response object can be used:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
response<std::string, int, std::string>
|
||||
----
|
||||
|
||||
The response behaves as a `std::tuple` and must
|
||||
have as many elements as the request has commands (exceptions below).
|
||||
It is also necessary that each tuple element is capable of storing the
|
||||
response to the command it refers to, otherwise an error will occur.
|
||||
|
||||
To ignore responses to individual commands in the request use the tag
|
||||
xref:reference:boost/redis/ignore_t.adoc[`boost::redis::ignore_t`]. For example:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Ignore the second and last responses.
|
||||
response<std::string, ignore_t, std::string, ignore_t>
|
||||
----
|
||||
|
||||
The following table provides the RESP3-types returned by some Redis
|
||||
commands:
|
||||
|
||||
[cols="3*"]
|
||||
|===
|
||||
|
||||
| *Command* | *RESP3 type* | *Documentation*
|
||||
|
||||
| `lpush` | Number | https://redis.io/commands/lpush[]
|
||||
| `lrange` | Array | https://redis.io/commands/lrange[]
|
||||
| `set` | Simple-string, null or blob-string | https://redis.io/commands/set[]
|
||||
| `get` | Blob-string | https://redis.io/commands/get[]
|
||||
| `smembers` | Set | https://redis.io/commands/smembers[]
|
||||
| `hgetall` | Map | https://redis.io/commands/hgetall[]
|
||||
|
||||
|===
|
||||
|
||||
To map these RESP3 types into a pass:[C++] data structure use the table below:
|
||||
|
||||
[cols="3*"]
|
||||
|===
|
||||
|
||||
| *RESP3 type* | *Possible pass:[C++] type* | *Type*
|
||||
|
||||
| Simple-string | `std::string` | Simple
|
||||
| Simple-error | `std::string` | Simple
|
||||
| Blob-string | `std::string`, `std::vector` | Simple
|
||||
| Blob-error | `std::string`, `std::vector` | Simple
|
||||
| Number | `long long`, `int`, `std::size_t`, `std::string` | Simple
|
||||
| Double | `double`, `std::string` | Simple
|
||||
| Null | `std::optional<T>` | Simple
|
||||
| Array | `std::vector`, `std::list`, `std::array`, `std::deque` | Aggregate
|
||||
| Map | `std::vector`, `std::map`, `std::unordered_map` | Aggregate
|
||||
| Set | `std::vector`, `std::set`, `std::unordered_set` | Aggregate
|
||||
| Push | `std::vector`, `std::map`, `std::unordered_map` | Aggregate
|
||||
|
||||
|===
|
||||
|
||||
For example, the response to the request
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("RPUSH", "key1", vec);
|
||||
req.push_range("HSET", "key2", map);
|
||||
req.push("LRANGE", "key3", 0, -1);
|
||||
req.push("HGETALL", "key4");
|
||||
req.push("QUIT");
|
||||
----
|
||||
|
||||
Can be read in the following response object:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
response<
|
||||
redis::ignore_t, // hello
|
||||
int, // rpush
|
||||
int, // hset
|
||||
std::vector<T>, // lrange
|
||||
std::map<U, V>, // hgetall
|
||||
std::string // quit
|
||||
> resp;
|
||||
----
|
||||
|
||||
To execute the request and read the response use
|
||||
xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`async_exec`]:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
co_await conn->async_exec(req, resp);
|
||||
----
|
||||
|
||||
If the intention is to ignore responses altogether, use
|
||||
xref:reference:boost/redis/ignore.adoc[`ignore`]:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Ignores the response
|
||||
co_await conn->async_exec(req, ignore);
|
||||
----
|
||||
|
||||
Responses that contain nested aggregates or heterogeneous data
|
||||
types will be given special treatment later in xref:#the-general-case[the general case]. As
|
||||
of this writing, not all RESP3 types are used by the Redis server,
|
||||
which means in practice users will be concerned with a reduced
|
||||
subset of the RESP3 specification.
|
||||
|
||||
### Pushes
|
||||
|
||||
Commands that have no response, like
|
||||
|
||||
* `"SUBSCRIBE"`
|
||||
* `"PSUBSCRIBE"`
|
||||
* `"UNSUBSCRIBE"`
|
||||
|
||||
must **NOT** be included in the response tuple. For example, the following request
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("QUIT");
|
||||
----
|
||||
|
||||
must be read in the response object `response<std::string, std::string>`.
|
||||
|
||||
### Null
|
||||
|
||||
It is not uncommon for apps to access keys that do not exist or that
|
||||
have already expired in the Redis server. To deal with these use cases,
|
||||
wrap the type within a `std::optional`:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
response<
|
||||
std::optional<A>,
|
||||
std::optional<B>,
|
||||
...
|
||||
> resp;
|
||||
----
|
||||
|
||||
Everything else stays the same.
|
||||
|
||||
### Transactions
|
||||
|
||||
To read responses to transactions we must first observe that Redis
|
||||
will queue the transaction commands and send their individual
|
||||
responses as elements of an array. The array itself is the response to
|
||||
the `EXEC` command. For example, to read the response to this request
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
req.push("MULTI");
|
||||
req.push("GET", "key1");
|
||||
req.push("LRANGE", "key2", 0, -1);
|
||||
req.push("HGETALL", "key3");
|
||||
req.push("EXEC");
|
||||
----
|
||||
|
||||
Use the following response type:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
response<
|
||||
ignore_t, // multi
|
||||
ignore_t, // QUEUED
|
||||
ignore_t, // QUEUED
|
||||
ignore_t, // QUEUED
|
||||
response<
|
||||
std::optional<std::string>, // get
|
||||
std::optional<std::vector<std::string>>, // lrange
|
||||
std::optional<std::map<std::string, std::string>> // hgetall
|
||||
> // exec
|
||||
> resp;
|
||||
----
|
||||
|
||||
For a complete example, see {site-url}/example/cpp20_containers.cpp[cpp20_containers.cpp].
|
||||
|
||||
[#the-general-case]
|
||||
### The general case
|
||||
|
||||
There are cases where responses to Redis
|
||||
commands won't fit in the model presented above. Some examples are:
|
||||
|
||||
* Commands (like `set`) whose responses don't have a fixed
|
||||
RESP3 type. Expecting an `int` and receiving a blob-string
|
||||
results in an error.
|
||||
* RESP3 aggregates that contain nested aggregates can't be read in STL containers.
|
||||
* Transactions with a dynamic number of commands can't be read in a `response`.
|
||||
|
||||
To deal with these cases Boost.Redis provides the xref:reference:boost/redis/resp3/node.adoc[`boost::redis::resp3::node`] type
|
||||
abstraction, that is the most general form of an element in a
|
||||
response, be it a simple RESP3 type or the element of an aggregate. It
|
||||
is defined like:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
template <class String>
|
||||
struct basic_node {
|
||||
// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
// The number of elements of an aggregate (or 1 for simple data).
|
||||
std::size_t aggregate_size;
|
||||
|
||||
// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
// The actual data. For aggregate types this is always empty.
|
||||
String value;
|
||||
};
|
||||
----
|
||||
|
||||
Any response to a Redis command can be parsed into a
|
||||
xref:reference:boost/redis/generic_response.adoc[boost::redis::generic_response].
|
||||
The vector can be seen as a pre-order view of the response tree.
|
||||
Using it is not different than using other types:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Receives any RESP3 simple or aggregate data type.
|
||||
boost::redis::generic_response resp;
|
||||
co_await conn->async_exec(req, resp);
|
||||
----
|
||||
|
||||
For example, suppose we want to retrieve a hash data structure
|
||||
from Redis with `HGETALL`, some of the options are
|
||||
|
||||
* `boost::redis::generic_response`: always works.
|
||||
* `std::vector<std::string>`: efficient and flat, all elements as string.
|
||||
* `std::map<std::string, std::string>`: efficient if you need the data as a `std::map`.
|
||||
* `std::map<U, V>`: efficient if you are storing serialized data. Avoids temporaries and requires `boost_redis_from_bulk` for `U` and `V`.
|
||||
|
||||
In addition to the above users can also use unordered versions of the
|
||||
containers. The same reasoning applies to sets e.g. `SMEMBERS`
|
||||
and other data structures in general.
|
||||
26
doc/modules/ROOT/pages/serialization.adoc
Normal file
26
doc/modules/ROOT/pages/serialization.adoc
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
= Serializing and parsing into custom types
|
||||
|
||||
Boost.Redis supports serialization of user defined types by means of
|
||||
the following customization points
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Serialize
|
||||
void boost_redis_to_bulk(std::string& to, mystruct const& obj);
|
||||
|
||||
// Deserialize
|
||||
void boost_redis_from_bulk(mystruct& u, node_view const& node, boost::system::error_code&);
|
||||
----
|
||||
|
||||
These functions are accessed over ADL and therefore they must be
|
||||
imported in the global namespace by the user. The following examples might be of interest:
|
||||
|
||||
* {site-url}/example/cpp20_json.cpp[cpp20_json.cpp]: serializes and parses JSON objects.
|
||||
* {site-url}/example/cpp20_protobuf.cpp[cpp20_protobuf.cpp]: serializes and parses https://protobuf.dev/[protobuf] objects.
|
||||
10
doc/mrdocs.cpp
Normal file
10
doc/mrdocs.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#define BOOST_ALLOW_DEPRECATED // avoid mrdocs errors with the BOOST_DEPRECATED macro
|
||||
|
||||
#include <boost/redis.hpp>
|
||||
31
doc/mrdocs.yml
Normal file
31
doc/mrdocs.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
source-root: ../include
|
||||
compilation-database: ./CMakeLists.txt
|
||||
include-symbols:
|
||||
- "boost::redis::**"
|
||||
exclude-symbols:
|
||||
- "boost::redis::detail::**"
|
||||
- "boost::redis::adapter::detail::**"
|
||||
- "boost::redis::resp3::detail::**"
|
||||
- "boost::redis::basic_connection::run_is_canceled"
|
||||
- "boost::redis::basic_connection::this_type"
|
||||
- "boost::redis::any_adapter::impl_t"
|
||||
- "boost::redis::any_adapter::fn_type"
|
||||
- "boost::redis::any_adapter::create_impl"
|
||||
- "boost::redis::any_adapter::impl_"
|
||||
- "boost::redis::request::payload"
|
||||
- "boost::redis::request::has_hello_priority"
|
||||
see-below:
|
||||
- "boost::redis::adapter::ignore"
|
||||
sort-members: false
|
||||
base-url: https://github.com/boostorg/redis/blob/master/include/
|
||||
use-system-libc: true
|
||||
warn-as-error: true
|
||||
warn-if-undocumented: false
|
||||
warn-no-paramdoc: false
|
||||
671
doc/on-the-costs-of-async-abstractions.md
Normal file
671
doc/on-the-costs-of-async-abstractions.md
Normal file
@@ -0,0 +1,671 @@
|
||||
# On the costs of asynchronous abstractions
|
||||
|
||||
The biggest force behind the evolution of
|
||||
[Boost.Redis](https://github.com/boostorg/redis) was my struggling in
|
||||
coming up with a high-level connection abstraction that was capable of
|
||||
multiplexing Redis commands from independent sources while
|
||||
concurrently handling server pushes. This journey taught me many
|
||||
important lessons, many of which are related to the design and
|
||||
performance of asynchronous programs based on Boost.Asio.
|
||||
|
||||
In this article I will share some of the lessons learned, specially
|
||||
those related to the performance costs of _abstractions_ such as
|
||||
`async_read_until` that tend to overschedule into the event-loop. In
|
||||
this context I will also briefly comment on how the topics discussed
|
||||
here influenced my views on the proposed
|
||||
[P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
|
||||
(a.k.a. Senders and Receivers), which is likely to become the basis of
|
||||
networking in upcoming C++ standards.
|
||||
|
||||
Although the analysis presented in this article uses the Redis communication
|
||||
protocol for illustration I expect it to be useful in general since
|
||||
[RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) shares
|
||||
many similarities with other widely used protocols such as HTTP.
|
||||
|
||||
## Parsing `\r\n`-delimited messages
|
||||
|
||||
The Redis server communicates with its clients by exchanging data
|
||||
serialized in
|
||||
[RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) format.
|
||||
Among the data types supported by this specification, the
|
||||
`\r\n`-delimited messages are some of the most frequent in a typical
|
||||
session. The table below shows some examples
|
||||
|
||||
Command | Response | Wire format | RESP3 name
|
||||
---------|----------|---------------|---------------------
|
||||
PING | PONG | `+PONG\r\n` | simple-string
|
||||
INCR | 42 | `:42\r\n` | number
|
||||
GET | null | `_\r\n` | null
|
||||
|
||||
Redis also supports command pipelines, which provide a way of
|
||||
optimizing round-trip times by batching commands. A pipeline composed
|
||||
by the commands shown in the previous table look like this
|
||||
|
||||
```
|
||||
| Sent in a |
|
||||
| single write |
|
||||
+--------+ | | +-------+
|
||||
| | --------> PING + INCR + GET --------> | |
|
||||
| | | |
|
||||
| Client | | Redis |
|
||||
| | | |
|
||||
| | <-------- "+PONG\r\n:42\r\n_\r\n" <-------- | |
|
||||
+--------+ |<------>|<---->|<-->| +-------+
|
||||
| |
|
||||
| Responses |
|
||||
```
|
||||
|
||||
Messages that use delimiters are so common in networking that a
|
||||
facility called `async_read_until` for reading them incrementally from
|
||||
a socket is already part of Boost.Asio. The coroutine below uses it to
|
||||
print message contents to the screen
|
||||
|
||||
```cpp
|
||||
awaitable<void> parse_resp3_simple_msgs(tcp::socket socket)
|
||||
{
|
||||
for (std::string buffer;;) {
|
||||
auto n = co_await async_read_until(socket, dynamic_buffer(buffer), "\r\n");
|
||||
|
||||
std::cout << buffer.substr(1, n - 3) << std::endl;
|
||||
|
||||
// Consume the buffer.
|
||||
buffer.erase(0, n);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we pay attention to the buffer content as it is parsed by the code
|
||||
above we can see it is rotated fairly often, for example
|
||||
|
||||
```
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n"
|
||||
":100\r\n+OK\r\n_\r\n"
|
||||
"+OK\r\n_\r\n"
|
||||
"_\r\n"
|
||||
""
|
||||
```
|
||||
|
||||
When I first realized these, apparently excessive, buffer rotations I
|
||||
was concerned they would impact the performance of Boost.Redis in a
|
||||
severe way. To measure the magnitude of this impact I came up with an
|
||||
experimental implementation of Asio's `dynamic_buffer` that consumed
|
||||
the buffer less eagerly than the `std::string::erase` function used
|
||||
above. For that, the implementation increased a buffer offset up
|
||||
to a certain threshold and only then triggered a (larger) rotation.
|
||||
This is illustrated in the diagram below
|
||||
|
||||
```
|
||||
|<---- offset threshold ---->|
|
||||
| |
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
| # Initial offset
|
||||
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
|<------>| # After 1st message
|
||||
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
|<-------------->| # After 2nd message
|
||||
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
|<--------------------->| # After 3rd message
|
||||
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
|<-------------------------->| # Threshold crossed after the 4th message
|
||||
|
||||
"+PONG\r\n"
|
||||
| # After rotation
|
||||
```
|
||||
|
||||
After comparing the performance differences between the two versions I
|
||||
was surprised there wasn't any! But that was also very suspicious
|
||||
since some RESP3 aggregate types contain a considerable number of
|
||||
separators. For example, a map with two pairs `[(key1, value1),
|
||||
(key2, value2)]` encoded in RESP3 requires ten rotations in total
|
||||
|
||||
```
|
||||
"%2\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
|
||||
"$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
|
||||
"key1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
|
||||
"$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
|
||||
...
|
||||
```
|
||||
|
||||
It was evident something more costly was shadowing the buffer
|
||||
rotations. But it couldn't be the search for the separator since it
|
||||
performs equivalently to rotations. It is also easy to show that the
|
||||
overhead is not related to any IO operation since the problem persists
|
||||
if the buffer is never consumed (which causes the function to be
|
||||
called with the same string repeatedly). Once these two factors
|
||||
are removed from the table, we are driven into the conclusion that
|
||||
calling `async_read_until` has an intrinsic cost, let us see what
|
||||
that is.
|
||||
|
||||
### Async operations that complete synchronously considered harmful
|
||||
|
||||
Assume the scenario described earlier where `async_read_until` is used
|
||||
to parse multiple `\r\n`-delimited messages. The following is a
|
||||
detailed description of what happens behind the scenes
|
||||
|
||||
1. `async_read_until` calls `socket.async_read_some` repeatedly
|
||||
until the separator `\r\n` shows up in the buffer
|
||||
|
||||
```
|
||||
"<read1>" # Read 1: needs more data.
|
||||
"<read1><read2>" # Read 2: needs more data.
|
||||
"<read1><read2>" # Read 3: needs more data.
|
||||
"<read1><read2><read3>" # Read 4: needs more data.
|
||||
"<read1><read2><read3>\r\n<bonus bytes>" # separator found, done.
|
||||
```
|
||||
|
||||
2. The last call to `socket.async_read_some` happens to read past
|
||||
the separator `\r\n` (depicted as `<bonus bytes>` above),
|
||||
resulting in bonus (maybe incomplete) messages in the buffer
|
||||
|
||||
```
|
||||
| 1st async_read_some | 2nd async_read_some |
|
||||
| | |
|
||||
"+message content here \r\n:100\r\n+OK\r\n_\r\n+incomplete respo"
|
||||
| | | |
|
||||
| Message wanted |<-- bonus msgs --->|<--incomplete-->|
|
||||
| | msg |
|
||||
| | |
|
||||
| |<---------- bonus bytes ----------->|
|
||||
```
|
||||
|
||||
3. The buffer is consumed and `async_read_until` is called again.
|
||||
However, since the buffer already contains the next message this
|
||||
is an IO-less call
|
||||
|
||||
```
|
||||
":100\r\n+OK\r\n_\r\n+not enough byt"
|
||||
| | |
|
||||
| No IO required | Need more |
|
||||
| to parse these | data |
|
||||
| messages. | |
|
||||
```
|
||||
|
||||
The fact that step 3. doesn't perform any IO implies the operation can
|
||||
complete synchronously, but because this is an asynchronous function
|
||||
Boost.Asio by default won't call the continuation before the
|
||||
function returns. The implementation must therefore enqueue it for
|
||||
execution, as depicted below
|
||||
|
||||
```
|
||||
OP5 ---> OP4 ---> OP3 ---> OP2 ---> OP1 # Reschedules the continuation
|
||||
|
|
||||
OP1 schedules its continuation |
|
||||
+-----------------------------------+
|
||||
|
|
||||
|
|
||||
OP6 ---> OP5 ---> OP4 ---> OP3 ---> OP2 # Reschedules the continuation
|
||||
|
|
||||
OP2 schedules its continuation |
|
||||
+-----------------------------------+
|
||||
|
|
||||
|
|
||||
OP7 ---> OP6 ---> OP5 ---> OP4 ---> OP3
|
||||
```
|
||||
|
||||
When summed up, the excessive rescheduling of continuations lead to
|
||||
performance degradation at scale. But since this is an event-loop
|
||||
there is no way around rescheduling as doing otherwise would mean
|
||||
allowing a task to monopolize the event-loop, preventing other tasks
|
||||
from making progress. The best that can be done is to avoid
|
||||
_overscheduling_, so let us determine how much rescheduling is too
|
||||
much.
|
||||
|
||||
## The intrinsic latency of an event-loop
|
||||
|
||||
An event-loop is a design pattern originally used to handle events
|
||||
external to the application, such as GUIs, networking and other forms
|
||||
of IO. If we take this literally, it becomes evident that the way
|
||||
`async_read_until` works is incompatible with an event-loop since
|
||||
_searching for the separator_ is not an external event and as such
|
||||
should not have to be enqueued for execution.
|
||||
|
||||
Once we constrain ourselves to events that have an external origin,
|
||||
such as anything related to IO and including any form of IPC, the
|
||||
scheduling overhead is reduced considerably since the latency
|
||||
of the transport layer eclipses whatever time it takes to schedule the
|
||||
continuation, for example, according to
|
||||
[these](https://www.boost.org/doc/libs/develop/libs/cobalt/doc/html/index.html#posting_to_an_executor)
|
||||
benchmarks, the time it takes to schedule a task in the
|
||||
`asio::io_context ` is approximately `50ns`.
|
||||
|
||||
To give the reader an idea about the magnitude of this number, if
|
||||
rescheduling alone were to account for 1% of the runtime of an app
|
||||
that uses asynchronous IO to move around data in chunks of size 128kb,
|
||||
then this app would have a throughput of approximately 24Gbs. At such
|
||||
high throughput multiple other factors kick in before any scheduling
|
||||
overhead even starts to manifest.
|
||||
|
||||
It is therefore safe to say that only asynchronous operations that
|
||||
don't perform or are not bound to any IO are ever likely to
|
||||
overschedule in the sense described above. Those cases can be usually
|
||||
avoided, this is what worked for Boost.Redis
|
||||
|
||||
1. `async_read_until` was replaced with calls to
|
||||
`socket.async_read_some` and an incremental parser that does not
|
||||
do any IO.
|
||||
|
||||
2. Channel `try_` functions are used to check if send and receive
|
||||
operations can be called without suspension. For example,
|
||||
`try_send` before `async_send` and `try_receive` before
|
||||
`async_receive` ([see also](https://github.com/chriskohlhoff/asio/commit/fe4fd7acf145335eeefdd19708483c46caeb45e5)
|
||||
`try_send_via_dispatch` for a more aggressive optimization).
|
||||
|
||||
3. Coalescing of individual requests into a single payload to reduce
|
||||
the number of necessary writes on the socket, this is only
|
||||
possible because Redis supports pipelining (good protocols
|
||||
help!).
|
||||
|
||||
4. Increased the socket read sizes to 4kb to reduce the number of
|
||||
reads (which is outweighed by the costs of rotating data in the
|
||||
buffer).
|
||||
|
||||
5. Dropped the `resp3::async_read` abstraction. When I started
|
||||
developing Boost.Redis there was convincing precedent for having
|
||||
a `resp3::async_read` function to read complete RESP3 messages
|
||||
from a socket
|
||||
|
||||
Name | Description
|
||||
---------------------------------------|-------------------
|
||||
`asio::ip::tcp::async_read` | Reads `n` bytes from a stream.
|
||||
`beast::http::async_read` | Reads a complete HTTP message.
|
||||
`beast::websocket::stream::async_read` | Reads a complete Websocket message.
|
||||
`redis::async_read` | Reads a complete RESP3 message.
|
||||
|
||||
It turns out however that this function is also vulnerable to
|
||||
immediate completions since in command pipelines multiple
|
||||
responses show up in the buffer after a call to
|
||||
`socket.async_read_some`. When that happens each call to
|
||||
`resp3::async_read` is IO-less.
|
||||
|
||||
Sometimes it is not possible to avoid asynchronous operations that
|
||||
complete synchronously, in the following sections we will see how to
|
||||
favor throughput over fairness in Boost.Asio.
|
||||
|
||||
### Calling the continuation inline
|
||||
|
||||
In Boost.Asio it is possible to customize how an algorithm executes
|
||||
the continuation when an immediate completion occurs, this includes
|
||||
the ability of calling it inline, thereby avoiding the costs of
|
||||
excessive rescheduling. Here is how it works
|
||||
|
||||
```cpp
|
||||
// (default) The continuation is enqueued for execution, regardless of
|
||||
// whether it is immediate or not.
|
||||
async_read_until(socket, buffer, "\r\n", continuation);
|
||||
|
||||
// Immediate completions are executed in exec2 (otherwise equal to the
|
||||
// version above). The completion is called inline if exec2 is the
|
||||
// same executor that is running the operation.
|
||||
async_read_until(socket, buffer, "\r\n", bind_immediate_executor(exec2, completion));
|
||||
```
|
||||
|
||||
To compare the performance of both cases I have written a small
|
||||
function that calls `async_read_until` in a loop with a buffer that is
|
||||
never consumed so that all completions are immediate. The version
|
||||
below uses the default behaviour
|
||||
|
||||
```cpp
|
||||
void read_safe(tcp::socket& s, std::string& buffer)
|
||||
{
|
||||
auto continuation = [&s, &buffer](auto ec, auto n)
|
||||
{
|
||||
read_safe(s, buffer); // Recursive call
|
||||
};
|
||||
|
||||
// This won't cause stack exhaustion because the continuation is
|
||||
// not called inline but posted in the event loop.
|
||||
async_read_until(s, dynamic_buffer(buffer), "\r\n", continuation);
|
||||
}
|
||||
```
|
||||
|
||||
To optimize away some of the rescheduling the version below uses the
|
||||
`bind_immediate_executor` customization to call the continuation
|
||||
reentrantly and then breaks the stack from time to time to avoid
|
||||
exhausting it
|
||||
|
||||
```cpp
|
||||
void read_reentrant(tcp::socket& s, std::string& buffer)
|
||||
{
|
||||
auto cont = [&](auto, auto)
|
||||
{
|
||||
read_reentrant(s, buffer); // Recursive call
|
||||
};
|
||||
|
||||
// Breaks the callstack after 16 inline calls.
|
||||
if (counter % 16 == 0) {
|
||||
post(s.get_executor(), [cont](){cont({}, 0);});
|
||||
return;
|
||||
}
|
||||
|
||||
// Continuation called reentrantly.
|
||||
async_read_until(s, dynamic_buffer(buffer), "\r\n",
|
||||
bind_immediate_executor(s.get_executor(), cont));
|
||||
}
|
||||
```
|
||||
|
||||
The diagram below shows what the reentrant chain of calls in the code
|
||||
above look like from the event-loop point of view
|
||||
|
||||
```
|
||||
OP5 ---> OP4 ---> OP3 ---> OP2 ---> OP1a # Completes immediately
|
||||
|
|
||||
|
|
||||
... |
|
||||
OP1b # Completes immediately
|
||||
|
|
||||
Waiting for OP5 to |
|
||||
reschedule its |
|
||||
continuation OP1c # Completes immediately
|
||||
|
|
||||
|
|
||||
... |
|
||||
OP1d # Break the call-stack
|
||||
|
|
||||
+-----------------------------------+
|
||||
|
|
||||
OP6 ---> OP5 ---> OP4 ---> OP3 ---> OP2
|
||||
```
|
||||
|
||||
Unsurprisingly, the reentrant code is 3x faster than the one that
|
||||
relies on the default behaviour (don't forget that this is a best case
|
||||
scenario, in the general case not all completions are immediate).
|
||||
Although faster, this strategy has some downsides
|
||||
|
||||
- The overall operation is not as fast as possible since it still
|
||||
has to reschedule from time to time to break the call stack. The
|
||||
less it reschedules the higher the risk of exhausting it.
|
||||
|
||||
- It is too easy to forget to break the stack. For example, the
|
||||
programmer might decide to branch somewhere into another chain of
|
||||
asynchronous calls that also use this strategy. To avoid
|
||||
exhaustion all such branches would have to be safeguarded with a
|
||||
manual rescheduling i.e. `post`.
|
||||
|
||||
- Requires additional layers of complexity such as
|
||||
`bind_immediate_executor` in addition to `bind_executor`.
|
||||
|
||||
- Non-compliat with more strict
|
||||
[guidelines](https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Developing_Safety-Critical_Code)
|
||||
that prohibits reentrat code.
|
||||
|
||||
- There is no simple way of choosing the maximum allowed number of
|
||||
reentrant calls for each function in a way that covers different
|
||||
use cases and users. Library writers and users would be tempted
|
||||
into using a small value reducing the performance advantage.
|
||||
|
||||
- If the socket is always ready for reading the task will
|
||||
monopolize IO for up to `16` interactions which might cause
|
||||
stutter in unrelated tasks as depicted below
|
||||
|
||||
```
|
||||
Unfairness
|
||||
|
||||
+----+----+----+ +----+----+----+ +----+----+----+
|
||||
Socket-1 | | | | | | | | | | | |
|
||||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||||
Socket-2 | | | | | |
|
||||
+----+ +----+ +----+
|
||||
```
|
||||
|
||||
From the aesthetic point of view the code above is also unpleasant as
|
||||
it breaks the function asynchronous contract by injecting a reentrant
|
||||
behaviour. It gives me the same kind of feeling I have about
|
||||
[recursive
|
||||
mutexes](http://www.zaval.org/resources/library/butenhof1.html).
|
||||
|
||||
Note: It is worth mentioning here that a similar
|
||||
[strategy](https://github.com/NVIDIA/stdexec/blob/6f23dd5b1d523541ce28af32fc2603403ebd36ed/include/exec/trampoline_scheduler.hpp#L52)
|
||||
is used to break the call stack of repeating algorithms in
|
||||
[stdexec](https://github.com/NVIDIA/stdexec), but in this time
|
||||
based on
|
||||
[P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
|
||||
and not on Boost.Asio.
|
||||
|
||||
### Coroutine tail-calls
|
||||
|
||||
In the previous section we have seen how to avoid overscheduling by
|
||||
instructing the asynchronous operation to call the completion inline
|
||||
on immediate completion. It turns out however that coroutine support
|
||||
for _tail-calls_ provide a way to completely sidestep this problem.
|
||||
This feature is described by
|
||||
[Lewis Baker](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer)
|
||||
as follows
|
||||
|
||||
> A tail-call is one where the current stack-frame is popped before
|
||||
> the call and the current function’s return address becomes the
|
||||
> return-address for the callee. ie. the callee will return directly
|
||||
> the the [sic] caller of this function.
|
||||
|
||||
This means (at least in principle) that a library capable of using
|
||||
tail-calls when an immediate completion occurs neither has to
|
||||
reschedule the continuation nor call it inline. To test how this
|
||||
feature compares to the other styles I have used Boost.Cobalt. The
|
||||
code looks as follows
|
||||
|
||||
```cpp
|
||||
// Warning: risks unfairness and starvation of other tasks.
|
||||
task<void> read_until_unfair()
|
||||
{
|
||||
for (int i = 0; i != repeat; ++i) {
|
||||
co_await async_read_until(s, dynamic_buffer(buffer), "\r\n", cobalt::use_op);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The result of this comparison as listed in the table below
|
||||
|
||||
Time/s | Style | Configuration | Library
|
||||
-------|-----------|-----------------------------|-------------
|
||||
1,0 | Coroutine | `await_ready` optimization | Boost.Cobalt
|
||||
4.8 | Callback | Reentant | Boost.Asio
|
||||
10.3 | Coroutine | `use_op` | Boost.Cobalt
|
||||
14.9 | Callback | Regular | Boost.Asio
|
||||
15.6 | Coroutine | `asio::deferred` | Boost.Asio
|
||||
|
||||
As the reader can see, `cobalt::use_op` ranks 3rd and is considerably
|
||||
faster (10.3 vs 15.6) than the Asio equivalent that uses
|
||||
default-rescheduling. However, by trading rescheduling with tail-calls
|
||||
the code above can now monopolize the event-loop, resulting in
|
||||
unfairness if the socket happens to receive data at a higher rate
|
||||
than other tasks. If by chance data is received continuously
|
||||
on a socket that is always ready for reading, other tasks will starve
|
||||
|
||||
```
|
||||
Starvation
|
||||
|
||||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||||
Socket-1 | | | | | | | | | | | | |
|
||||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||||
|
||||
Socket-2 Starving ...
|
||||
|
||||
```
|
||||
|
||||
To avoid this problem the programmer is forced to reschedule from time
|
||||
to time, in the same way we did for the reentrant calls
|
||||
|
||||
```cpp
|
||||
task<void> read_until_fair()
|
||||
{
|
||||
for (int i = 0; i != repeat; ++i) {
|
||||
if (repeat % 16 == 0) {
|
||||
// Reschedules to address unfairness and starvation of
|
||||
// other tasks.
|
||||
co_await post(cobalt::use_op);
|
||||
continue;
|
||||
}
|
||||
|
||||
co_await async_read_until(s, dynamic_buffer(buffer), "\r\n", cobalt::use_op);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Delegating fairness-safety to applications is a dangerous game.
|
||||
This is a
|
||||
[problem](https://tokio.rs/blog/2020-04-preemption) the Tokio
|
||||
community had to deal with before Tokio runtime started enforcing
|
||||
rescheduling (after 256 successful operations)
|
||||
|
||||
> If data is received faster than it can be processed, it is possible
|
||||
> that more data will have already been received by the time the
|
||||
> processing of a data chunk completes. In this case, .await will
|
||||
> never yield control back to the scheduler, other tasks will not be
|
||||
> scheduled, resulting in starvation and large latency variance.
|
||||
|
||||
> Currently, the answer to this problem is that the user of Tokio is
|
||||
> responsible for adding yield points in both the application and
|
||||
> libraries. In practice, very few actually do this and end up being
|
||||
> vulnerable to this sort of problem.
|
||||
|
||||
### Safety in P2300 (Senders and Receivers)
|
||||
|
||||
As of this writing, the C++ standards committee (WG21) has been
|
||||
pursuing the standardization of a networking library for almost 20
|
||||
years. One of the biggest obstacles that prevented it from happening
|
||||
was a disagreement on what the _asynchronous model_ that underlies
|
||||
networking should look like. Until 2021 that model was basically
|
||||
Boost.Asio _executors_, but in this
|
||||
[poll](https://www.reddit.com/r/cpp/comments/q6tgod/c_committee_polling_results_for_asynchronous/)
|
||||
the committee decided to abandon that front and concentrate efforts on
|
||||
the new [P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
|
||||
proposal, also known as _senders and receivers_. The decision was
|
||||
quite [abrupt](https://isocpp.org/files/papers/P2464R0.html)
|
||||
|
||||
> The original plan about a week earlier than the actual writing of
|
||||
> this paper was to write a paper that makes a case for standardizing
|
||||
> the Networking TS.
|
||||
|
||||
and opinions turned out to be very strong against Boost.Asio (see
|
||||
[this](https://api.csswg.org/bikeshed/?force=1&url=https://raw.githubusercontent.com/brycelelbach/wg21_p2459_2022_january_library_evolution_poll_outcomes/main/2022_january_library_evolution_poll_outcomes.bs)
|
||||
for how each voter backed their vote)
|
||||
|
||||
> The whole concept is completely useless, there's no composed code
|
||||
> you can write with it.
|
||||
|
||||
The part of that debate that interests us most here is stated in
|
||||
[P2471](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2471r1.pdf),
|
||||
that compares Boost.Asio with P2300
|
||||
|
||||
> Yes, default rescheduling each operation and default not
|
||||
> rescheduling each operation, is a poor trade off. IMO both options
|
||||
> are poor. The one good option that I know of that can prevent stack
|
||||
> exhaustion is first-class tail-recursion in library or language
|
||||
|
||||
> ASIO has chosen to require that every async operation must schedule
|
||||
> the completion on a scheduler (every read, every write, etc..).
|
||||
|
||||
> sender/receiver has not decided to
|
||||
> require that the completion be scheduled.
|
||||
|
||||
> This is why I consider tail-call the only good solution. Scheduling
|
||||
> solutions are all inferior (give thanks to Lewis for this shift in
|
||||
> my understanding :) ).
|
||||
|
||||
Although tail-calls solve the problem of stack-exhaustion as we have
|
||||
seen above, it makes the code vulnerable to unfairness and starvation
|
||||
and therefore it is not an alternative to default-rescheduling as the
|
||||
quotation above is implying. To deal with the lack of
|
||||
default-rescheduling, libraries and applications built on top of P2300
|
||||
have to address the aforementioned problems, layer after layer. For
|
||||
example,
|
||||
[stdexec](https://github.com/NVIDIA/stdexec) has invented something
|
||||
called
|
||||
_[trampoline-scheduler](https://github.com/NVIDIA/stdexec/blob/e7cd275273525dbc693f4bf5f6dc4d4181b639e4/include/exec/trampoline_scheduler.hpp)_
|
||||
to protect repeating algorithms such as `repeat_effect_until` from
|
||||
exhausting the stack. This construct however is built around
|
||||
reentracy, allowing
|
||||
[sixteen](https://github.com/NVIDIA/stdexec/blob/83cdb92d316e8b3bca1357e2cf49fc39e9bed403/include/exec/trampoline_scheduler.hpp#L52)
|
||||
levels of inline calls by default. While in Boost.Asio it is possible to use
|
||||
reentracy as an optimization for a corner cases, here it is made its
|
||||
_modus operandi_, the downsides of this approach have already been stated in a
|
||||
previous section so I won't repeat it here.
|
||||
|
||||
Also the fact that a special scheduler is needed by specific
|
||||
algorithms is a problem on its own since it contradicts one of the
|
||||
main selling points of P2300 which is that of being _generic_. For
|
||||
example, [P2464R0](https://isocpp.org/files/papers/P2464R0.html) uses
|
||||
the code below as an example
|
||||
|
||||
```cpp
|
||||
void
|
||||
run_that_io_operation(
|
||||
scheduler auto sched,
|
||||
sender_of<network_buffer> auto wrapping_continuation)
|
||||
{
|
||||
// snip
|
||||
}
|
||||
```
|
||||
|
||||
and states
|
||||
|
||||
> I have no idea what the sched's concrete type is. I have no idea
|
||||
> what the wrapping_continuation's concrete type is. They're none of
|
||||
> my business, ...
|
||||
|
||||
Hence, by being generic, the algorithms built on top of P2300 are also
|
||||
unsafe (against stack-exhaustion, unfairness and starvation). Otherwise,
|
||||
if library writers require a specific scheduler to ensure safety, then
|
||||
the algorithms become automatically non-generic, pick your poison!
|
||||
|
||||
The proposers of P2300 claim that it doesn't address safety because it
|
||||
should be seen as the low-level building blocks of asynchronous
|
||||
programming and that its the role of higher-level libraries, to deal
|
||||
with that. This claim however does not hold since, as we have just
|
||||
seen, Boost.Asio also provides those building blocks but does so in a
|
||||
safe way. In fact during the whole development of Boost.Redis I never
|
||||
had to think about these kinds of problems because safety is built
|
||||
from the ground up.
|
||||
|
||||
### Avoiding coroutine suspension with `await_ready`
|
||||
|
||||
Now let us get back to the first place in the table above, which uses
|
||||
the `await_ready` optimization from Boost.Cobalt. This API provides
|
||||
users with the ability to avoid coroutine suspension altogether in
|
||||
case the separator is already present in the buffer. It works by
|
||||
defining a `struct` with the following interface
|
||||
|
||||
```cpp
|
||||
struct read_until : cobalt::op<error_code, std::size_t> {
|
||||
...
|
||||
|
||||
void ready(cobalt::handler<error_code, std::size_t> handler) override
|
||||
{
|
||||
// Search for the separator in buffer and call the handler if found
|
||||
}
|
||||
|
||||
void initiate(cobalt::completion_handler<error_code, std::size_t> complete) override
|
||||
{
|
||||
// Regular call to async_read_until.
|
||||
async_read_until(socket, buffer, delim, std::move(complete));
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
and the code that uses it
|
||||
|
||||
```cpp
|
||||
for (int i = 0; i != repeat; ++i) {
|
||||
co_await read_until(socket, dynamic_buffer(buffer));
|
||||
}
|
||||
```
|
||||
|
||||
In essence, what the code above does is to skip a call to
|
||||
`async_read_unil` by first checking with the ready function whether
|
||||
the forthcoming operation is going to complete immediately. The
|
||||
nice thing about it is that the programmer can use this optimization
|
||||
only when a performance bottleneck is detected, without planing for it
|
||||
in advance. The drawback however is that it requires reimplementing
|
||||
the search for the separator in the body of the `ready` function,
|
||||
defeating the purpose of using `async_read_until` in first place as
|
||||
(again) it would have been simpler to reformulate the operation in
|
||||
terms of `socket.async_read_some` directly.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Thanks to Klemens Morgenstern for answering questions about
|
||||
Boost.Cobalt.
|
||||
|
||||
1955
doc/package-lock.json
generated
Normal file
1955
doc/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
doc/package.json
Normal file
6
doc/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@cppalliance/antora-cpp-reference-extension": "^0.0.6",
|
||||
"antora": "^3.1.10"
|
||||
}
|
||||
}
|
||||
45
doc/redis-playbook.yml
Normal file
45
doc/redis-playbook.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
site:
|
||||
url: https://github.com/boostorg/redis/blob/master
|
||||
title: Boost.Redis
|
||||
robots: allow
|
||||
start_page: redis::index.adoc
|
||||
|
||||
antora:
|
||||
extensions:
|
||||
- require: '@cppalliance/antora-cpp-reference-extension'
|
||||
dependencies:
|
||||
- name: 'boost'
|
||||
repo: 'https://github.com/boostorg/boost.git'
|
||||
tag: 'develop'
|
||||
variable: 'BOOST_SRC_DIR'
|
||||
system-env: 'BOOST_SRC_DIR'
|
||||
|
||||
asciidoc:
|
||||
attributes:
|
||||
# Scrolling problems appear without this
|
||||
page-pagination: ''
|
||||
|
||||
content:
|
||||
sources:
|
||||
- url: ..
|
||||
start_path: doc
|
||||
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/boostorg/website-v2-docs/releases/download/ui-master/ui-bundle.zip
|
||||
snapshot: true
|
||||
output_dir: _
|
||||
|
||||
output:
|
||||
dir: html
|
||||
|
||||
runtime:
|
||||
log:
|
||||
failure_level: error
|
||||
62
example/CMakeLists.txt
Normal file
62
example/CMakeLists.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
add_library(examples_main STATIC main.cpp)
|
||||
target_compile_features(examples_main PRIVATE cxx_std_20)
|
||||
target_link_libraries(examples_main PRIVATE boost_redis_project_options)
|
||||
|
||||
macro(make_example EXAMPLE_NAME STANDARD)
|
||||
add_executable(${EXAMPLE_NAME} ${EXAMPLE_NAME}.cpp)
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src)
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options)
|
||||
target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD})
|
||||
if (${STANDARD} STREQUAL "20")
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main)
|
||||
endif()
|
||||
if (${EXAMPLE_NAME} STREQUAL "cpp20_json")
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE Boost::json Boost::container_hash)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(make_testable_example EXAMPLE_NAME STANDARD)
|
||||
make_example(${EXAMPLE_NAME} ${STANDARD})
|
||||
if (BOOST_REDIS_INTEGRATION_TESTS)
|
||||
add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
make_testable_example(cpp17_intro 17)
|
||||
make_testable_example(cpp17_intro_sync 17)
|
||||
|
||||
make_testable_example(cpp20_intro 20)
|
||||
make_testable_example(cpp20_containers 20)
|
||||
make_testable_example(cpp20_json 20)
|
||||
make_testable_example(cpp20_intro_tls 20)
|
||||
make_testable_example(cpp20_unix_sockets 20)
|
||||
|
||||
make_example(cpp20_subscriber 20)
|
||||
make_example(cpp20_streams 20)
|
||||
make_example(cpp20_echo_server 20)
|
||||
make_example(cpp20_resolve_with_sentinel 20)
|
||||
|
||||
# We test the protobuf example only on gcc.
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
find_package(Protobuf)
|
||||
if (Protobuf_FOUND)
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS person.proto)
|
||||
make_testable_example(cpp20_protobuf 20)
|
||||
target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES})
|
||||
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT MSVC)
|
||||
make_example(cpp20_chat_room 20)
|
||||
endif()
|
||||
|
||||
# We build and test the spdlog integration example only if the library is found
|
||||
find_package(spdlog)
|
||||
if (spdlog_FOUND)
|
||||
make_testable_example(cpp17_spdlog 17)
|
||||
target_link_libraries(cpp17_spdlog PRIVATE spdlog::spdlog)
|
||||
else()
|
||||
message(STATUS "Skipping the spdlog example because the spdlog package couldn't be found")
|
||||
endif()
|
||||
51
example/cpp17_intro.cpp
Normal file
51
example/cpp17_intro.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
|
||||
auto main(int argc, char* argv[]) -> int
|
||||
{
|
||||
try {
|
||||
config cfg;
|
||||
|
||||
if (argc == 3) {
|
||||
cfg.addr.host = argv[1];
|
||||
cfg.addr.port = argv[2];
|
||||
}
|
||||
|
||||
request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
asio::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
conn.async_run(cfg, asio::detached);
|
||||
|
||||
conn.async_exec(req, resp, [&](auto ec, auto) {
|
||||
if (!ec)
|
||||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
|
||||
conn.cancel();
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
43
example/cpp17_intro_sync.cpp
Normal file
43
example/cpp17_intro_sync.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include "sync_connection.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using boost::redis::sync_connection;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
|
||||
auto main(int argc, char* argv[]) -> int
|
||||
{
|
||||
try {
|
||||
config cfg;
|
||||
|
||||
if (argc == 3) {
|
||||
cfg.addr.host = argv[1];
|
||||
cfg.addr.port = argv[2];
|
||||
}
|
||||
|
||||
sync_connection conn;
|
||||
conn.run(cfg);
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
conn.exec(req, resp);
|
||||
conn.stop();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
102
example/cpp17_spdlog.cpp
Normal file
102
example/cpp17_spdlog.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace redis = boost::redis;
|
||||
|
||||
// Maps a Boost.Redis log level to a spdlog log level
|
||||
static spdlog::level::level_enum to_spdlog_level(redis::logger::level lvl)
|
||||
{
|
||||
switch (lvl) {
|
||||
// spdlog doesn't include the emerg and alert syslog levels,
|
||||
// so we convert them to the highest supported level.
|
||||
// Similarly, notice is similar to info
|
||||
case redis::logger::level::emerg:
|
||||
case redis::logger::level::alert:
|
||||
case redis::logger::level::crit: return spdlog::level::critical;
|
||||
case redis::logger::level::err: return spdlog::level::err;
|
||||
case redis::logger::level::warning: return spdlog::level::warn;
|
||||
case redis::logger::level::notice:
|
||||
case redis::logger::level::info: return spdlog::level::info;
|
||||
case redis::logger::level::debug:
|
||||
default: return spdlog::level::debug;
|
||||
}
|
||||
}
|
||||
|
||||
// This function glues Boost.Redis logging and spdlog.
|
||||
// It should have the signature shown here. It will be invoked
|
||||
// by Boost.Redis whenever a message is to be logged.
|
||||
static void do_log(redis::logger::level level, std::string_view msg)
|
||||
{
|
||||
spdlog::log(to_spdlog_level(level), "(Boost.Redis) {}", msg);
|
||||
}
|
||||
|
||||
auto main(int argc, char* argv[]) -> int
|
||||
{
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <server-host> <server-port>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// Create an execution context, required to create any I/O objects
|
||||
asio::io_context ioc;
|
||||
|
||||
// Create a connection to connect to Redis, and pass it a custom logger.
|
||||
// Boost.Redis will call do_log whenever it needs to log a message.
|
||||
// Note that the function will only be called for messages with level >= info
|
||||
// (i.e. filtering is done by Boost.Redis).
|
||||
redis::connection conn{
|
||||
ioc,
|
||||
redis::logger{redis::logger::level::info, do_log}
|
||||
};
|
||||
|
||||
// Configuration to connect to the server
|
||||
redis::config cfg;
|
||||
cfg.addr.host = argv[1];
|
||||
cfg.addr.port = argv[2];
|
||||
|
||||
// Run the connection with the specified configuration.
|
||||
// This will establish the connection and keep it healthy
|
||||
conn.async_run(cfg, asio::detached);
|
||||
|
||||
// Execute a request
|
||||
redis::request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
redis::response<std::string> resp;
|
||||
|
||||
conn.async_exec(req, resp, [&](boost::system::error_code ec, std::size_t /* bytes_read*/) {
|
||||
if (ec) {
|
||||
spdlog::error("Request failed: {}", ec.what());
|
||||
exit(1);
|
||||
} else {
|
||||
spdlog::info("PING: {}", std::get<0>(resp).value());
|
||||
}
|
||||
conn.cancel();
|
||||
});
|
||||
|
||||
// Actually run our example. Nothing will happen until we call run()
|
||||
ioc.run();
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
spdlog::error("Error: {}", e.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
104
example/cpp20_chat_room.cpp
Normal file
104
example/cpp20_chat_room.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using asio::posix::stream_descriptor;
|
||||
using asio::signal_set;
|
||||
using boost::asio::async_read_until;
|
||||
using boost::asio::awaitable;
|
||||
using boost::asio::co_spawn;
|
||||
using boost::asio::consign;
|
||||
using boost::asio::detached;
|
||||
using boost::asio::dynamic_buffer;
|
||||
using boost::asio::redirect_error;
|
||||
using boost::asio::use_awaitable;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::request;
|
||||
using boost::system::error_code;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// Chat over Redis pubsub. To test, run this program from multiple
|
||||
// terminals and type messages to stdin.
|
||||
|
||||
auto receiver(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
generic_response resp;
|
||||
conn->set_receive_response(resp);
|
||||
|
||||
while (conn->will_reconnect()) {
|
||||
// Subscribe to channels.
|
||||
co_await conn->async_exec(req, ignore);
|
||||
|
||||
// Loop reading Redis push messages.
|
||||
for (error_code ec;;) {
|
||||
co_await conn->async_receive(redirect_error(use_awaitable, ec));
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " "
|
||||
<< resp.value().at(3).value << std::endl;
|
||||
resp.value().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Publishes stdin messages to a Redis channel.
|
||||
auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection> conn)
|
||||
-> awaitable<void>
|
||||
{
|
||||
for (std::string msg;;) {
|
||||
auto n = co_await async_read_until(*in, dynamic_buffer(msg, 1024), "\n");
|
||||
request req;
|
||||
req.push("PUBLISH", "channel", msg);
|
||||
co_await conn->async_exec(req, ignore);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> awaitable<void>
|
||||
{
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
auto stream = std::make_shared<stream_descriptor>(ex, ::dup(STDIN_FILENO));
|
||||
|
||||
co_spawn(ex, receiver(conn), detached);
|
||||
co_spawn(ex, publisher(stream, conn), detached);
|
||||
conn->async_run(cfg, consign(detached, conn));
|
||||
|
||||
signal_set sig_set{ex, SIGINT, SIGTERM};
|
||||
co_await sig_set.async_wait();
|
||||
conn->cancel();
|
||||
stream->cancel();
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
auto co_main(config const&) -> awaitable<void>
|
||||
{
|
||||
std::cout << "Requires support for posix streams." << std::endl;
|
||||
co_return;
|
||||
}
|
||||
#endif // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
145
example/cpp20_containers.cpp
Normal file
145
example/cpp20_containers.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::asio::awaitable;
|
||||
using boost::asio::detached;
|
||||
using boost::asio::consign;
|
||||
|
||||
template <class T>
|
||||
std::ostream& operator<<(std::ostream& os, std::optional<T> const& opt)
|
||||
{
|
||||
if (opt.has_value())
|
||||
std::cout << opt.value();
|
||||
else
|
||||
std::cout << "null";
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
void print(std::map<std::string, std::string> const& cont)
|
||||
{
|
||||
for (auto const& e : cont)
|
||||
std::cout << e.first << ": " << e.second << "\n";
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void print(std::vector<T> const& cont)
|
||||
{
|
||||
for (auto const& e : cont)
|
||||
std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
// Stores the content of some STL containers in Redis.
|
||||
auto store(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
std::vector<int> vec{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::map<std::string, std::string> map{
|
||||
{"key1", "value1"},
|
||||
{"key2", "value2"},
|
||||
{"key3", "value3"}
|
||||
};
|
||||
|
||||
request req;
|
||||
req.push_range("RPUSH", "rpush-key", vec);
|
||||
req.push_range("HSET", "hset-key", map);
|
||||
req.push("SET", "key", "value");
|
||||
|
||||
co_await conn->async_exec(req, ignore);
|
||||
}
|
||||
|
||||
auto hgetall(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
// A request contains multiple commands.
|
||||
request req;
|
||||
req.push("HGETALL", "hset-key");
|
||||
|
||||
// Responses as tuple elements.
|
||||
response<std::map<std::string, std::string>> resp;
|
||||
|
||||
// Executes the request and reads the response.
|
||||
co_await conn->async_exec(req, resp);
|
||||
|
||||
print(std::get<0>(resp).value());
|
||||
}
|
||||
|
||||
auto mget(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
// A request contains multiple commands.
|
||||
request req;
|
||||
req.push("MGET", "key", "non-existing-key");
|
||||
|
||||
// Responses as tuple elements.
|
||||
response<std::vector<std::optional<std::string>>> resp;
|
||||
|
||||
// Executes the request and reads the response.
|
||||
co_await conn->async_exec(req, resp);
|
||||
|
||||
print(std::get<0>(resp).value());
|
||||
}
|
||||
|
||||
// Retrieves in a transaction.
|
||||
auto transaction(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("MULTI");
|
||||
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
|
||||
req.push("HGETALL", "hset-key"); // Retrieves
|
||||
req.push("MGET", "key", "non-existing-key");
|
||||
req.push("EXEC");
|
||||
|
||||
response<
|
||||
ignore_t, // multi
|
||||
ignore_t, // lrange
|
||||
ignore_t, // hgetall
|
||||
ignore_t, // mget
|
||||
response<
|
||||
std::optional<std::vector<int>>,
|
||||
std::optional<std::map<std::string, std::string>>,
|
||||
std::optional<std::vector<std::optional<std::string>>>> // exec
|
||||
>
|
||||
resp;
|
||||
|
||||
co_await conn->async_exec(req, resp);
|
||||
|
||||
print(std::get<0>(std::get<4>(resp).value()).value().value());
|
||||
print(std::get<1>(std::get<4>(resp).value()).value().value());
|
||||
print(std::get<2>(std::get<4>(resp).value()).value().value());
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
awaitable<void> co_main(config cfg)
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, consign(detached, conn));
|
||||
|
||||
co_await store(conn);
|
||||
co_await transaction(conn);
|
||||
co_await hgetall(conn);
|
||||
co_await mget(conn);
|
||||
conn->cancel();
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
70
example/cpp20_echo_server.cpp
Normal file
70
example/cpp20_echo_server.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::asio::signal_set;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::system::error_code;
|
||||
using boost::redis::connection;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto echo_server_session(asio::ip::tcp::socket socket, std::shared_ptr<connection> conn)
|
||||
-> asio::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
response<std::string> resp;
|
||||
|
||||
for (std::string buffer;;) {
|
||||
auto n = co_await asio::async_read_until(socket, asio::dynamic_buffer(buffer, 1024), "\n");
|
||||
req.push("PING", buffer);
|
||||
co_await conn->async_exec(req, resp);
|
||||
co_await asio::async_write(socket, asio::buffer(std::get<0>(resp).value()));
|
||||
std::get<0>(resp).value().clear();
|
||||
req.clear();
|
||||
buffer.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
// Listens for tcp connections.
|
||||
auto listener(std::shared_ptr<connection> conn) -> asio::awaitable<void>
|
||||
{
|
||||
try {
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
asio::ip::tcp::acceptor acc(ex, {asio::ip::tcp::v4(), 55555});
|
||||
for (;;)
|
||||
asio::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), asio::detached);
|
||||
} catch (std::exception const& e) {
|
||||
std::clog << "Listener: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
asio::co_spawn(ex, listener(conn), asio::detached);
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
conn->cancel();
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
43
example/cpp20_intro.cpp
Normal file
43
example/cpp20_intro.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
// A request containing only a ping command.
|
||||
request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
// Response where the PONG response will be stored.
|
||||
response<std::string> resp;
|
||||
|
||||
// Executes the request.
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
57
example/cpp20_intro_tls.cpp
Normal file
57
example/cpp20_intro_tls.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto verify_certificate(bool, asio::ssl::verify_context&) -> bool
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
cfg.use_ssl = true;
|
||||
cfg.username = "aedis";
|
||||
cfg.password = "aedis";
|
||||
cfg.addr.host = "db.occase.de";
|
||||
cfg.addr.port = "6380";
|
||||
|
||||
asio::ssl::context ctx{asio::ssl::context::tlsv12_client};
|
||||
ctx.set_verify_mode(asio::ssl::verify_peer);
|
||||
ctx.set_verify_callback(verify_certificate);
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor, std::move(ctx));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
82
example/cpp20_json.cpp
Normal file
82
example/cpp20_json.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/describe.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/json/value_from.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace resp3 = boost::redis::resp3;
|
||||
using namespace boost::describe;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::resp3::node_view;
|
||||
|
||||
// Struct that will be stored in Redis using json serialization.
|
||||
struct user {
|
||||
std::string name;
|
||||
std::string age;
|
||||
std::string country;
|
||||
};
|
||||
|
||||
// The type must be described for serialization to work.
|
||||
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
|
||||
|
||||
// Boost.Redis customization points (example/json.hpp)
|
||||
void boost_redis_to_bulk(std::string& to, user const& u)
|
||||
{
|
||||
resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u)));
|
||||
}
|
||||
|
||||
void boost_redis_from_bulk(user& u, node_view const& node, boost::system::error_code&)
|
||||
{
|
||||
u = boost::json::value_to<user>(boost::json::parse(node.value));
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
// user object that will be stored in Redis in json format.
|
||||
user const u{"Joao", "58", "Brazil"};
|
||||
|
||||
// Stores and retrieves in the same request.
|
||||
request req;
|
||||
req.push("SET", "json-key", u); // Stores in Redis.
|
||||
req.push("GET", "json-key"); // Retrieves from Redis.
|
||||
|
||||
response<ignore_t, user> resp;
|
||||
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
// Prints the first ping
|
||||
std::cout << "Name: " << std::get<1>(resp).value().name << "\n"
|
||||
<< "Age: " << std::get<1>(resp).value().age << "\n"
|
||||
<< "Country: " << std::get<1>(resp).value().country << "\n";
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
89
example/cpp20_protobuf.cpp
Normal file
89
example/cpp20_protobuf.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// See the definition in person.proto. This header is automatically
|
||||
// generated by CMakeLists.txt.
|
||||
#include "person.pb.h"
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace resp3 = boost::redis::resp3;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::resp3::node_view;
|
||||
|
||||
// The protobuf type described in example/person.proto
|
||||
using tutorial::person;
|
||||
|
||||
// Boost.Redis customization points (example/protobuf.hpp)
|
||||
namespace tutorial {
|
||||
|
||||
// Below I am using a Boost.Redis to indicate a protobuf error, this
|
||||
// is ok for an example, users however might want to define their own
|
||||
// error codes.
|
||||
void boost_redis_to_bulk(std::string& to, person const& u)
|
||||
{
|
||||
std::string tmp;
|
||||
if (!u.SerializeToString(&tmp))
|
||||
throw boost::system::system_error(boost::redis::error::invalid_data_type);
|
||||
|
||||
resp3::boost_redis_to_bulk(to, tmp);
|
||||
}
|
||||
|
||||
void boost_redis_from_bulk(person& u, node_view const& node, boost::system::error_code& ec)
|
||||
{
|
||||
std::string const tmp{node.value};
|
||||
if (!u.ParseFromString(tmp))
|
||||
ec = boost::redis::error::invalid_data_type;
|
||||
}
|
||||
|
||||
} // namespace tutorial
|
||||
|
||||
using tutorial::boost_redis_to_bulk;
|
||||
using tutorial::boost_redis_from_bulk;
|
||||
|
||||
asio::awaitable<void> co_main(config cfg)
|
||||
{
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
person p;
|
||||
p.set_name("Louis");
|
||||
p.set_id(3);
|
||||
p.set_email("No email yet.");
|
||||
|
||||
request req;
|
||||
req.push("SET", "protobuf-key", p);
|
||||
req.push("GET", "protobuf-key");
|
||||
|
||||
response<ignore_t, person> resp;
|
||||
|
||||
// Sends the request and receives the response.
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "Name: " << std::get<1>(resp).value().name() << "\n"
|
||||
<< "Age: " << std::get<1>(resp).value().id() << "\n"
|
||||
<< "Email: " << std::get<1>(resp).value().email() << "\n";
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
76
example/cpp20_resolve_with_sentinel.cpp
Normal file
76
example/cpp20_resolve_with_sentinel.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using endpoints = asio::ip::tcp::resolver::results_type;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::config;
|
||||
using boost::redis::address;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto redir(boost::system::error_code& ec) { return asio::redirect_error(asio::use_awaitable, ec); }
|
||||
|
||||
// For more info see
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
auto resolve_master_address(std::vector<address> const& addresses) -> asio::awaitable<address>
|
||||
{
|
||||
request req;
|
||||
req.push("SENTINEL", "get-master-addr-by-name", "mymaster");
|
||||
req.push("QUIT");
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
|
||||
response<std::optional<std::array<std::string, 2>>, ignore_t> resp;
|
||||
for (auto addr : addresses) {
|
||||
boost::system::error_code ec;
|
||||
config cfg;
|
||||
cfg.addr = addr;
|
||||
// TODO: async_run and async_exec should be lauched in
|
||||
// parallel here so we can wait for async_run completion
|
||||
// before eventually calling it again.
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
co_await conn->async_exec(req, resp, redir(ec));
|
||||
conn->cancel();
|
||||
if (!ec && std::get<0>(resp))
|
||||
co_return address{
|
||||
std::get<0>(resp).value().value().at(0),
|
||||
std::get<0>(resp).value().value().at(1)};
|
||||
}
|
||||
|
||||
co_return address{};
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
// A list of sentinel addresses from which only one is responsive.
|
||||
// This simulates sentinels that are down.
|
||||
std::vector<address> const addresses{
|
||||
address{"foo", "26379"},
|
||||
address{"bar", "26379"},
|
||||
cfg.addr
|
||||
};
|
||||
|
||||
auto const ep = co_await resolve_master_address(addresses);
|
||||
|
||||
std::clog << "Host: " << ep.host << "\n"
|
||||
<< "Port: " << ep.port << "\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
97
example/cpp20_streams.cpp
Normal file
97
example/cpp20_streams.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::config;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::request;
|
||||
using boost::redis::connection;
|
||||
using net::signal_set;
|
||||
|
||||
auto stream_reader(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
std::string redisStreamKey_;
|
||||
request req;
|
||||
generic_response resp;
|
||||
|
||||
std::string stream_id{"$"};
|
||||
std::string const field = "myfield";
|
||||
|
||||
for (;;) {
|
||||
req.push("XREAD", "BLOCK", "0", "STREAMS", "test-topic", stream_id);
|
||||
co_await conn->async_exec(req, resp);
|
||||
|
||||
//std::cout << "Response: ";
|
||||
//for (auto i = 0UL; i < resp->size(); ++i) {
|
||||
// std::cout << resp->at(i).value << ", ";
|
||||
//}
|
||||
//std::cout << std::endl;
|
||||
|
||||
// The following approach was taken in order to be able to
|
||||
// deal with the responses, as generated by redis in the case
|
||||
// that there are multiple stream 'records' within a single
|
||||
// generic_response. The nesting and number of values in
|
||||
// resp.value() are different, depending on the contents
|
||||
// of the stream in redis. Uncomment the above commented-out
|
||||
// code for examples while running the XADD command.
|
||||
|
||||
std::size_t item_index = 0;
|
||||
while (item_index < std::size(resp.value())) {
|
||||
auto const& val = resp.value().at(item_index).value;
|
||||
|
||||
if (field.compare(val) == 0) {
|
||||
// We've hit a myfield field.
|
||||
// The streamId is located at item_index - 2
|
||||
// The payload is located at item_index + 1
|
||||
stream_id = resp.value().at(item_index - 2).value;
|
||||
std::cout << "StreamId: " << stream_id << ", "
|
||||
<< "MyField: " << resp.value().at(item_index + 1).value << std::endl;
|
||||
++item_index; // We can increase so we don't read this again
|
||||
}
|
||||
|
||||
++item_index;
|
||||
}
|
||||
|
||||
req.clear();
|
||||
resp.value().clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Run this in another terminal:
|
||||
// redis-cli -r 100000 -i 0.0001 XADD "test-topic" "*" "myfield" "myfieldvalue1"
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
net::co_spawn(ex, stream_reader(conn), net::detached);
|
||||
|
||||
// Disable health checks.
|
||||
cfg.health_check_interval = std::chrono::seconds::zero();
|
||||
conn->async_run(cfg, net::consign(net::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
conn->cancel();
|
||||
}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
98
example/cpp20_subscriber.cpp
Normal file
98
example/cpp20_subscriber.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using namespace std::chrono_literals;
|
||||
using boost::redis::request;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::consume_one;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::config;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::error;
|
||||
using boost::system::error_code;
|
||||
using boost::redis::connection;
|
||||
using asio::signal_set;
|
||||
|
||||
/* This example will subscribe and read pushes indefinitely.
|
||||
*
|
||||
* To test send messages with redis-cli
|
||||
*
|
||||
* $ redis-cli -3
|
||||
* 127.0.0.1:6379> PUBLISH channel some-message
|
||||
* (integer) 3
|
||||
* 127.0.0.1:6379>
|
||||
*
|
||||
* To test reconnection try, for example, to close all clients currently
|
||||
* connected to the Redis instance
|
||||
*
|
||||
* $ redis-cli
|
||||
* > CLIENT kill TYPE pubsub
|
||||
*/
|
||||
|
||||
// Receives server pushes.
|
||||
auto receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
generic_response resp;
|
||||
conn->set_receive_response(resp);
|
||||
|
||||
// Loop while reconnection is enabled
|
||||
while (conn->will_reconnect()) {
|
||||
// Reconnect to the channels.
|
||||
co_await conn->async_exec(req, ignore);
|
||||
|
||||
// Loop reading Redis pushs messages.
|
||||
for (error_code ec;;) {
|
||||
// First tries to read any buffered pushes.
|
||||
conn->receive(ec);
|
||||
if (ec == error::sync_receive_push_failed) {
|
||||
ec = {};
|
||||
co_await conn->async_receive(asio::redirect_error(asio::use_awaitable, ec));
|
||||
}
|
||||
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
|
||||
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " "
|
||||
<< resp.value().at(3).value << std::endl;
|
||||
|
||||
consume_one(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
asio::co_spawn(ex, receiver(conn), asio::detached);
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
|
||||
conn->cancel();
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
60
example/cpp20_unix_sockets.cpp
Normal file
60
example/cpp20_unix_sockets.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/this_coro.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::connection;
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
|
||||
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
// If unix_socket is set to a non-empty string, UNIX domain sockets will be used
|
||||
// instead of TCP. Set this value to the path where your server is listening.
|
||||
// UNIX domain socket connections work in the same way as TCP connections.
|
||||
cfg.unix_socket = "/tmp/redis-socks/redis.sock";
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
auto co_main(config) -> asio::awaitable<void>
|
||||
{
|
||||
std::cout << "Sorry, your system does not support UNIX domain sockets\n";
|
||||
co_return;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user