mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
881
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80ba2a4ff6 | ||
|
|
8d09f61d33 | ||
|
|
815173f8e5 | ||
|
|
34e388619f | ||
|
|
9d5af20bcf | ||
|
|
613fde2637 | ||
|
|
22a3b10171 | ||
|
|
e3415d8d61 | ||
|
|
d1f60ac4c6 | ||
|
|
e2161502ad | ||
|
|
ab785bfba7 | ||
|
|
5f2f3f076d | ||
|
|
efb27731ad | ||
|
|
578a9aec16 | ||
|
|
5b8fc19701 | ||
|
|
c0a64d67ca | ||
|
|
6369d0f4f2 | ||
|
|
f69ee652e6 | ||
|
|
9b3f8564af | ||
|
|
9766cd644f | ||
|
|
431d4857e8 | ||
|
|
26432355d5 | ||
|
|
2f91c85ad8 | ||
|
|
951827229a | ||
|
|
383bb0a143 | ||
|
|
d2bd6f5002 | ||
|
|
6a94d2cf4f | ||
|
|
4f99470d46 | ||
|
|
3d9134d13e | ||
|
|
15dc0563b7 | ||
|
|
4e26258ac3 | ||
|
|
a59e096c47 | ||
|
|
fc1640891e | ||
|
|
de9f05d4d3 | ||
|
|
9c31797a08 | ||
|
|
d1c58b7940 | ||
|
|
5f74a99ea3 | ||
|
|
08e20fcf6a | ||
|
|
08099bb2d3 | ||
|
|
807d536846 | ||
|
|
654f9d703f | ||
|
|
a285689664 | ||
|
|
13930eff2a | ||
|
|
24fb33e012 | ||
|
|
a791f4a758 | ||
|
|
7e7a5147a3 | ||
|
|
2d56312b89 | ||
|
|
357df38861 | ||
|
|
34a9dc2d76 | ||
|
|
68d5fcb8d1 | ||
|
|
08b07afbd9 | ||
|
|
7b0c60849c | ||
|
|
6d8d388dc5 | ||
|
|
bc61bd9d3d | ||
|
|
a9585f0318 | ||
|
|
88b4ec84d7 | ||
|
|
9f0daad5ac | ||
|
|
c187cd75b6 | ||
|
|
a83f5e4ba6 | ||
|
|
4935aae164 | ||
|
|
ba1829fd26 | ||
|
|
ce7e60e396 | ||
|
|
d8b23ef852 | ||
|
|
4788d3a9e3 | ||
|
|
f1f61a3b15 | ||
|
|
d0a8e5d6f2 | ||
|
|
3b27dc31d2 | ||
|
|
37131b2114 | ||
|
|
8538c25170 | ||
|
|
7b6438a172 | ||
|
|
9de7083be8 | ||
|
|
8d55f98f6f | ||
|
|
999a600494 | ||
|
|
fb9809c068 | ||
|
|
517162792f | ||
|
|
fe90d61446 | ||
|
|
338b37884a | ||
|
|
513326e01d | ||
|
|
73a91ad7b3 | ||
|
|
930cce8677 | ||
|
|
6a125082e4 | ||
|
|
d9f9c5658f | ||
|
|
fff39c03b1 | ||
|
|
756606a58b | ||
|
|
e3b4c99a33 | ||
|
|
42fa0c28d3 | ||
|
|
f7b41c9dcc | ||
|
|
73102760cf | ||
|
|
18833a8e67 | ||
|
|
eba8bf2b4b | ||
|
|
790d649dc5 | ||
|
|
50e5d401df | ||
|
|
2e69f2a7fd | ||
|
|
962521f449 | ||
|
|
831be9c08e | ||
|
|
23c380a78f | ||
|
|
0a05332648 | ||
|
|
7268b0bb3a | ||
|
|
6412389bba | ||
|
|
53a94c025d | ||
|
|
7174c63bf9 | ||
|
|
cb2336ff3d | ||
|
|
47e2ff48d9 | ||
|
|
2f43b0a023 | ||
|
|
05d00aebb7 | ||
|
|
62733a6594 | ||
|
|
6d8cc4e475 | ||
|
|
6cbe3d4f82 | ||
|
|
337646b97f | ||
|
|
0bb015588a | ||
|
|
63377e2110 | ||
|
|
878503f965 | ||
|
|
2c01b3e0e0 | ||
|
|
ee9105d166 | ||
|
|
5a4f849bba | ||
|
|
ff41108834 | ||
|
|
6b202722ea | ||
|
|
144d980e5c | ||
|
|
81d789b88f | ||
|
|
634c19582f | ||
|
|
ff922bbe6d | ||
|
|
bf38631d6a | ||
|
|
6dda866191 | ||
|
|
03e450deb1 | ||
|
|
fbf90e6356 | ||
|
|
74168ae82f | ||
|
|
d038009e7d | ||
|
|
132e9f1da5 | ||
|
|
fe021e6c00 | ||
|
|
f311ac3d4f | ||
|
|
298be80249 | ||
|
|
79b017c773 | ||
|
|
ca0e5cc670 | ||
|
|
59bc364a0e | ||
|
|
e88d10a3cb | ||
|
|
a3b8d82711 | ||
|
|
d9688bc094 | ||
|
|
320a5fdca7 | ||
|
|
9af07ce208 | ||
|
|
1b2d997863 | ||
|
|
7a52ddcd09 | ||
|
|
9d3e5aac08 | ||
|
|
a99fa6e096 | ||
|
|
b89ed00a0d | ||
|
|
12ce75f088 | ||
|
|
a88308ed9f | ||
|
|
a298472da8 | ||
|
|
d0bb16192b | ||
|
|
a18ddb3b61 | ||
|
|
003b4d8074 | ||
|
|
6d186fe40e | ||
|
|
0d99ddd4f4 | ||
|
|
448d9d2eab | ||
|
|
0de3a69eb4 | ||
|
|
61aee5fc28 | ||
|
|
7f7f74985e | ||
|
|
e6cf976662 | ||
|
|
b14e189e44 | ||
|
|
2dde2b448f | ||
|
|
6742816e78 | ||
|
|
ab040bb498 | ||
|
|
0cfa120ba8 | ||
|
|
5774a9cd64 | ||
|
|
da49ede41e | ||
|
|
d224d6415e | ||
|
|
83273b8b50 | ||
|
|
ca708d6d87 | ||
|
|
0b75c0c53d | ||
|
|
5fbb36a060 | ||
|
|
c897a5b696 | ||
|
|
48d7f7b931 | ||
|
|
2291823181 | ||
|
|
795e02f4c6 | ||
|
|
7ac8bfc821 | ||
|
|
a79483750f | ||
|
|
3855f373d3 | ||
|
|
bd3f3270db | ||
|
|
e5525628cd | ||
|
|
f1b8a318d9 | ||
|
|
64343f1b78 | ||
|
|
82795184c1 | ||
|
|
f529928d87 | ||
|
|
461eebe612 | ||
|
|
407d15cf93 | ||
|
|
80915906d8 | ||
|
|
88e775dcf0 | ||
|
|
8b49a1e05f | ||
|
|
8fa1510d67 | ||
|
|
7797a377c3 | ||
|
|
b62d224fac | ||
|
|
d4803bc868 | ||
|
|
e07a03b3c5 | ||
|
|
7b86acb71d | ||
|
|
0651f09427 | ||
|
|
516251052d | ||
|
|
cbad83f362 | ||
|
|
bd9760e124 | ||
|
|
3e898f58a5 | ||
|
|
c531865d2c | ||
|
|
722eb257be | ||
|
|
ceed29586b | ||
|
|
70eca184f0 | ||
|
|
b2c777846e | ||
|
|
8e7d8af588 | ||
|
|
7380dd2482 | ||
|
|
af46eac583 | ||
|
|
e7488d983e | ||
|
|
e4415d986a | ||
|
|
ff906acdfb | ||
|
|
32ceccb465 | ||
|
|
0af05e7408 | ||
|
|
6b9e7bdace | ||
|
|
ed4d4a5353 | ||
|
|
1f47ed3dcc | ||
|
|
e9aaacddbd | ||
|
|
11f6b2862f | ||
|
|
8404f796ac | ||
|
|
6316aa1d0b | ||
|
|
0784dc2767 | ||
|
|
dd126c2333 | ||
|
|
3cc33dca7c | ||
|
|
dc5fa80a09 | ||
|
|
455782b964 | ||
|
|
29e417c257 | ||
|
|
9df1140340 | ||
|
|
e2b4bdb647 | ||
|
|
c6defbce4b | ||
|
|
b7846a4e2f | ||
|
|
cb8607a816 | ||
|
|
27c15471c1 | ||
|
|
0ab25878bd | ||
|
|
448302c3d4 | ||
|
|
934a1467d4 | ||
|
|
4d639e246b | ||
|
|
ff5381de8d | ||
|
|
061452dc01 | ||
|
|
a6b2682309 | ||
|
|
cf84a59e5a | ||
|
|
e90e33d5df | ||
|
|
fa5da27d98 | ||
|
|
ecaa069f0f | ||
|
|
fc15d7d4a4 | ||
|
|
a70a3b599a | ||
|
|
26faefcc34 | ||
|
|
f16725ea9f | ||
|
|
caf43221b5 | ||
|
|
93680357dd | ||
|
|
784d21ae31 | ||
|
|
0938ccfefd | ||
|
|
6fa2fdab44 | ||
|
|
d0d13d0bd0 | ||
|
|
8a1777b800 | ||
|
|
42d0f53ddb | ||
|
|
db0c4147c8 | ||
|
|
10fd2afd18 | ||
|
|
83a55601ef | ||
|
|
b37120f61c | ||
|
|
91dbf24cf4 | ||
|
|
c78fd6d6c5 | ||
|
|
025474dfbb | ||
|
|
e7dfcf90fe | ||
|
|
ecdfe4c474 | ||
|
|
37b4efb9e2 | ||
|
|
c94f607f1b | ||
|
|
76438c9e70 | ||
|
|
e0e9594f71 | ||
|
|
3b43262a10 | ||
|
|
5f18bf669f | ||
|
|
1581c8b475 | ||
|
|
d3b2efc815 | ||
|
|
f7c4e3cd84 | ||
|
|
329ad3324c | ||
|
|
2b6695d25a | ||
|
|
9290602815 | ||
|
|
827e77e71e | ||
|
|
7b3e4b98ac | ||
|
|
5e9d60e834 | ||
|
|
8270965459 | ||
|
|
e6b044a820 | ||
|
|
de9b401457 | ||
|
|
ad5179b2d5 | ||
|
|
0aceba21bd | ||
|
|
09e21eceea | ||
|
|
bdc87856f2 | ||
|
|
7987b64445 | ||
|
|
32c928b607 | ||
|
|
82aeae147d | ||
|
|
f48944c1fb | ||
|
|
f0680617ee | ||
|
|
dbc185cd3a | ||
|
|
c205e2c358 | ||
|
|
acec6356ee | ||
|
|
c11eec3908 | ||
|
|
69295fac1e | ||
|
|
b9c6eb309c | ||
|
|
56d3914675 | ||
|
|
34e71fa71a | ||
|
|
914b803429 | ||
|
|
4d11784b01 | ||
|
|
50cabae181 | ||
|
|
c101e9e11d | ||
|
|
5c5f793ef0 | ||
|
|
1384b31d60 | ||
|
|
521dc94021 | ||
|
|
ed23a06fb1 | ||
|
|
1aee22505a | ||
|
|
01b5bf6761 | ||
|
|
6638cbc80e | ||
|
|
22bc5e2738 | ||
|
|
a0c162c0ff | ||
|
|
ca51ae9651 | ||
|
|
f3f99b723f | ||
|
|
f20e7a4d2b | ||
|
|
4a92c4d4b6 | ||
|
|
9680ecc109 | ||
|
|
124391e42b | ||
|
|
6150be189f | ||
|
|
ca9586a089 | ||
|
|
89b9792931 | ||
|
|
a0cc60153a | ||
|
|
7f3f868b66 | ||
|
|
97ebb2275c | ||
|
|
849a5ad0b2 | ||
|
|
b16a8613b1 | ||
|
|
5664660156 | ||
|
|
4416ea07d8 | ||
|
|
56222c588b | ||
|
|
72179d49c5 | ||
|
|
073a1a251a | ||
|
|
31c81faf96 | ||
|
|
5e68b0d51d | ||
|
|
4bd07ac6aa | ||
|
|
f4f7bb232e | ||
|
|
413e1b78a7 | ||
|
|
649fa13a15 | ||
|
|
eef655f3b1 | ||
|
|
97508096fa | ||
|
|
06c473e628 | ||
|
|
aa99950b9c | ||
|
|
aac6998c22 | ||
|
|
df2c3cd475 | ||
|
|
36ed35c52c | ||
|
|
5925ca7720 | ||
|
|
2ac132fb46 | ||
|
|
f9a0cb8792 | ||
|
|
9fa6092e5a | ||
|
|
d4adeeef2f | ||
|
|
4f6395b31c | ||
|
|
5c0b56278b | ||
|
|
41ca9a43de | ||
|
|
8d0f102de8 | ||
|
|
5dcb379f6d | ||
|
|
970f75f830 | ||
|
|
619efed28b | ||
|
|
18ed0be851 | ||
|
|
01052f930a | ||
|
|
940f2c3431 | ||
|
|
84d5a7f5a0 | ||
|
|
1b498e8aa2 | ||
|
|
b2c53987d9 | ||
|
|
38092010c4 | ||
|
|
475dabe96d | ||
|
|
cb4aea394e | ||
|
|
79d8820050 | ||
|
|
951f2fd910 | ||
|
|
4ef736b9d5 | ||
|
|
219f24cbf1 | ||
|
|
ea282efb2e | ||
|
|
042224d33c | ||
|
|
927eb80ad4 | ||
|
|
6a8934e897 | ||
|
|
0e400af78c | ||
|
|
df702130d6 | ||
|
|
949adbb887 | ||
|
|
b2b796a228 | ||
|
|
abb014efc2 | ||
|
|
e5cf0cc717 | ||
|
|
62f34e15ce | ||
|
|
3ebca76a9a | ||
|
|
fea1f780bc | ||
|
|
712ca84033 | ||
|
|
cf06621998 | ||
|
|
7e51ab05e9 | ||
|
|
4bfa4ffcdf | ||
|
|
88b942652c | ||
|
|
5029e80a89 | ||
|
|
847fb59b17 | ||
|
|
b4fe517a16 | ||
|
|
7ae010f0f3 | ||
|
|
9144b2ff53 | ||
|
|
2c4549a18a | ||
|
|
4f819b7cd1 | ||
|
|
197f88f3bc | ||
|
|
44f65afcc6 | ||
|
|
4271a9cd8d | ||
|
|
9d8096b911 | ||
|
|
597f271c08 | ||
|
|
599955f716 | ||
|
|
91bb0f73f5 | ||
|
|
6c9d8abba9 | ||
|
|
824b7b6759 | ||
|
|
cb91dd274a | ||
|
|
ea7178b8c6 | ||
|
|
a99b8e2e0b | ||
|
|
03859a7dcd | ||
|
|
d8177f81ac | ||
|
|
ceca2a3cd6 | ||
|
|
1524ee4b60 | ||
|
|
7793d63739 | ||
|
|
e0e26bc223 | ||
|
|
9c5cad037f | ||
|
|
30330da11a | ||
|
|
6e4945025c | ||
|
|
3c8f110730 | ||
|
|
678f15bd48 | ||
|
|
b1172f8074 | ||
|
|
cdde2e7a27 | ||
|
|
85487727d4 | ||
|
|
92d51202ef | ||
|
|
cb55bf4012 | ||
|
|
987ccfc8ac | ||
|
|
1bc6d75543 | ||
|
|
27148d6110 | ||
|
|
a1871b1480 | ||
|
|
acd08eb23d | ||
|
|
90b1a01010 | ||
|
|
676824988e | ||
|
|
46149f031e | ||
|
|
5510ba6dba | ||
|
|
b8f63308d7 | ||
|
|
4313d65b38 | ||
|
|
e780fccce4 | ||
|
|
b01e71b3d8 | ||
|
|
7f911b6b70 | ||
|
|
d88aba8d1c | ||
|
|
6fbef0a528 | ||
|
|
9be5f3f9ff | ||
|
|
e28856cffe | ||
|
|
85e3bd34af | ||
|
|
db4019d84a | ||
|
|
195c4b0496 | ||
|
|
619d3b163b | ||
|
|
5ff6e37c59 | ||
|
|
43d69d77e2 | ||
|
|
dbb04e310c | ||
|
|
0e2e07812a | ||
|
|
047d0b821c | ||
|
|
70f4fc481c | ||
|
|
7039f02bb2 | ||
|
|
4985e0c608 | ||
|
|
fd22090df8 | ||
|
|
02a5091885 | ||
|
|
80162306e7 | ||
|
|
ab595d0825 | ||
|
|
41a2245b85 | ||
|
|
7ca4f3ec4b | ||
|
|
0da649727c | ||
|
|
1cf5f73651 | ||
|
|
beb639a030 | ||
|
|
75ab7c9e9b | ||
|
|
cec9efeb7a | ||
|
|
f9345f99bb | ||
|
|
ab206b976c | ||
|
|
3d787b16c7 | ||
|
|
f513558076 | ||
|
|
d0cdcff8aa | ||
|
|
e3115231dd | ||
|
|
2d5aa82341 | ||
|
|
dd66096ea0 | ||
|
|
c08e73c8d4 | ||
|
|
d1d72dc1c8 | ||
|
|
27a42b980c | ||
|
|
7a50e09495 | ||
|
|
d7a556fe8b | ||
|
|
860ca79d62 | ||
|
|
7b98bf7da3 | ||
|
|
49774f6af1 | ||
|
|
ec22fb9843 | ||
|
|
ce2147d2b6 | ||
|
|
fca41d4e73 | ||
|
|
a69aca850c | ||
|
|
13c96187f8 | ||
|
|
61d4aa98e4 | ||
|
|
9d6d142bed | ||
|
|
95b0eec8af | ||
|
|
e1a07ce50c | ||
|
|
11e2af66a8 | ||
|
|
a4aae1459c | ||
|
|
12546d1d9c | ||
|
|
fbad6297c5 | ||
|
|
0ec8986b0b | ||
|
|
c6f8bdb249 | ||
|
|
c6f9a069a5 | ||
|
|
9f356d6244 | ||
|
|
13083153aa | ||
|
|
91f20e33a4 | ||
|
|
983e9d1b67 | ||
|
|
4c8f274db9 | ||
|
|
c980837581 | ||
|
|
eec370cae8 | ||
|
|
733d432b80 | ||
|
|
74c473d68f | ||
|
|
d95c697781 | ||
|
|
25e835c5b7 | ||
|
|
961aae41c4 | ||
|
|
74c73b218e | ||
|
|
7a49ebb65e | ||
|
|
a687922746 | ||
|
|
df299ced45 | ||
|
|
78d1fe0eb0 | ||
|
|
fc8cde383a | ||
|
|
76198f63d7 | ||
|
|
39dc5706b7 | ||
|
|
cbecb87797 | ||
|
|
f0bdf1980c | ||
|
|
5e2d93f060 | ||
|
|
9a8d087c69 | ||
|
|
30f59670c8 | ||
|
|
9e4ddaeaf3 | ||
|
|
03e2e864f3 | ||
|
|
c8a990eda4 | ||
|
|
1a5026324f | ||
|
|
fdf4aba621 | ||
|
|
201b6ce53a | ||
|
|
db69275202 | ||
|
|
af85cb3430 | ||
|
|
36f1a19ac8 | ||
|
|
6aa990ea75 | ||
|
|
760a7667d6 | ||
|
|
2283b63e9e | ||
|
|
8263e5f18d | ||
|
|
b3e57b60d0 | ||
|
|
1cd0ebfc5e | ||
|
|
4797d79950 | ||
|
|
e7d9ba7e51 | ||
|
|
527dc0a66f | ||
|
|
b117fc1d65 | ||
|
|
272e09d349 | ||
|
|
3235749006 | ||
|
|
9c037044c4 | ||
|
|
3658e10045 | ||
|
|
ed3ece266b | ||
|
|
9b1a45cc6a | ||
|
|
477fa5580a | ||
|
|
bb6cca8ff0 | ||
|
|
e166c4d912 | ||
|
|
b7506cf663 | ||
|
|
dc4a29359f | ||
|
|
d3dca4552b | ||
|
|
42a0df1ea4 | ||
|
|
a98eab6eff | ||
|
|
5a5dde70b3 | ||
|
|
d0963774a3 | ||
|
|
c83355235c | ||
|
|
33a216e4c1 | ||
|
|
09f2ac85bf | ||
|
|
32a152630f | ||
|
|
9153067d66 | ||
|
|
d246964bdf | ||
|
|
e700607554 | ||
|
|
5321550534 | ||
|
|
32e1cafb57 | ||
|
|
49bc4025dd | ||
|
|
51e36e41bc | ||
|
|
d011b92b9a | ||
|
|
f929576f0e | ||
|
|
b0f001a05a | ||
|
|
2291ba9d0d | ||
|
|
7f84f6b4ca | ||
|
|
5f61bd5252 | ||
|
|
bffa3ed558 | ||
|
|
7b5ef61aeb | ||
|
|
753336de8e | ||
|
|
65aea16ad1 | ||
|
|
796fee6364 | ||
|
|
adb0ba71d4 | ||
|
|
bfa6766f3c | ||
|
|
a2f457fa48 | ||
|
|
1879bc49ce | ||
|
|
678f6382b8 | ||
|
|
e27b0a46ba | ||
|
|
d35d0518f5 | ||
|
|
886511c0a6 | ||
|
|
d06bd6b216 | ||
|
|
2c85cd0991 | ||
|
|
1e45237a28 | ||
|
|
3a88d85538 | ||
|
|
b47ad24268 | ||
|
|
cab9a44e01 | ||
|
|
964afb2ce3 | ||
|
|
2f690d30bc | ||
|
|
331a88cee6 | ||
|
|
46353737e7 | ||
|
|
ffd73a64e7 | ||
|
|
1119d572ee | ||
|
|
20ca59114a | ||
|
|
3dd95a9ff1 | ||
|
|
be67eda117 | ||
|
|
e267a1922d | ||
|
|
9b456f48d9 | ||
|
|
0ca973a7eb | ||
|
|
df6acf0c2a | ||
|
|
98d23b8b29 | ||
|
|
85f8522536 | ||
|
|
d275341fb2 | ||
|
|
d735e5d527 | ||
|
|
4019198706 | ||
|
|
24dc85dc5e | ||
|
|
aaa5adb7fd | ||
|
|
5f68b3aaa1 | ||
|
|
2f69acbe9f | ||
|
|
7e12f5c39e | ||
|
|
462882b356 | ||
|
|
35687f1d18 | ||
|
|
e7dc3a1091 | ||
|
|
b594e240f9 | ||
|
|
605708dca6 | ||
|
|
cc40a4e7f0 | ||
|
|
4ae6c997ee | ||
|
|
0f44adf5f6 | ||
|
|
19d5565442 | ||
|
|
98e76d9bc6 | ||
|
|
b7f5bc95fe | ||
|
|
90ea2f6c5b | ||
|
|
837c3934d5 | ||
|
|
bcd42d11d9 | ||
|
|
8ad66d296f | ||
|
|
d9edc26e97 | ||
|
|
f0ac62151b | ||
|
|
214722a296 | ||
|
|
b8f8145b62 | ||
|
|
32d3e0e1f9 | ||
|
|
266919add6 | ||
|
|
c9ffd98b1e | ||
|
|
2d4bfa1485 | ||
|
|
b6ecfa251c | ||
|
|
3a81d7746a | ||
|
|
6e1a833825 | ||
|
|
3f80953dee | ||
|
|
4374f5be70 | ||
|
|
b23ab94cd5 | ||
|
|
40c77bd17e | ||
|
|
31b51004f2 | ||
|
|
9a4e4f2308 | ||
|
|
484fda7a23 | ||
|
|
45ba6e2652 | ||
|
|
e73b8a0cc9 | ||
|
|
209232befd | ||
|
|
f759e4d70f | ||
|
|
1fdff707b8 | ||
|
|
e97e8cb7fe | ||
|
|
2848df9b6c | ||
|
|
7a24ed7509 | ||
|
|
9c9760cfbb | ||
|
|
5ecd929b1a | ||
|
|
8ddebf4309 | ||
|
|
0df1882f21 | ||
|
|
4fa1b2b58c | ||
|
|
a4895fe364 | ||
|
|
7ab97f99c8 | ||
|
|
9bf3228f73 | ||
|
|
d41c60e21d | ||
|
|
934c596133 | ||
|
|
72e2209bd8 | ||
|
|
6a7092b9f7 | ||
|
|
1c893ef6d3 | ||
|
|
20e7598e8d | ||
|
|
3db92496f6 | ||
|
|
da47cfbd58 | ||
|
|
f5eadc74f1 | ||
|
|
175f9afea9 | ||
|
|
48eda3fe2f | ||
|
|
010c2223ca | ||
|
|
36b58d8fa8 | ||
|
|
c601f68c9f | ||
|
|
78ca103f3a | ||
|
|
7da00f3832 | ||
|
|
edba77e8df | ||
|
|
cf1afd2d90 | ||
|
|
8e58a9d8d4 | ||
|
|
4afef9391a | ||
|
|
b6bacc1ca3 | ||
|
|
955cd2836d | ||
|
|
70e4ed67ad | ||
|
|
699b9ab89e | ||
|
|
ba0921a01d | ||
|
|
367cb56e02 | ||
|
|
04d949c380 | ||
|
|
ed4359bb26 | ||
|
|
4142dc2fae | ||
|
|
468b037e4e | ||
|
|
b33ae3cdd6 | ||
|
|
4519ac8e17 | ||
|
|
92b93ee176 | ||
|
|
635149e3ab | ||
|
|
e28c350e31 | ||
|
|
1a122018a2 | ||
|
|
f3f8ee431e | ||
|
|
61c4047c6a | ||
|
|
3486a61a0f | ||
|
|
8291c3d462 | ||
|
|
1b6893b6f6 | ||
|
|
eca7f0760f | ||
|
|
89d969d518 | ||
|
|
16664189c1 | ||
|
|
6828870608 | ||
|
|
89d0cda2e2 | ||
|
|
8052a9b348 | ||
|
|
a5ac6c8b72 | ||
|
|
3a59526523 | ||
|
|
322a94f72f | ||
|
|
c03b23355b | ||
|
|
bc91bc5022 | ||
|
|
a7b053372f | ||
|
|
673fdb5cb3 | ||
|
|
97618746de | ||
|
|
d8f8b59df9 | ||
|
|
0745a9b88a | ||
|
|
a7f5ba28ba | ||
|
|
cc3b6af7a3 | ||
|
|
07e30ae923 | ||
|
|
69d90ac7ee | ||
|
|
d16032cf06 | ||
|
|
b479ce78d3 | ||
|
|
6e45e0ac61 | ||
|
|
82c5baa09b | ||
|
|
7dc6404726 | ||
|
|
2e88e29fe9 | ||
|
|
07203408de | ||
|
|
8bf2e9aeb0 | ||
|
|
cf184eb326 | ||
|
|
d822b721b4 | ||
|
|
c66b56c3fb | ||
|
|
89639ec48b | ||
|
|
2b1b0ac858 | ||
|
|
6b84c73f12 | ||
|
|
767b370c21 | ||
|
|
28010b5962 | ||
|
|
2e343f9e42 | ||
|
|
31f71dedee | ||
|
|
989262fe6e | ||
|
|
d91c775f36 | ||
|
|
96b556fbff | ||
|
|
fd36054ae4 | ||
|
|
89d6bfc5cb | ||
|
|
decc83e959 | ||
|
|
afcfefd7e3 | ||
|
|
c89b0b4c8c | ||
|
|
6b1e4ab0a3 | ||
|
|
4153cc4076 | ||
|
|
5304557d1d | ||
|
|
fdb2f61357 | ||
|
|
e964c4136c | ||
|
|
0490280d66 | ||
|
|
0f76470172 | ||
|
|
9352249c3e | ||
|
|
e5b2681513 | ||
|
|
629c9f0698 | ||
|
|
5d0d2a2e12 | ||
|
|
ad4693a18f | ||
|
|
c85bde3170 | ||
|
|
1e90e27720 | ||
|
|
f212a2ab9d | ||
|
|
84db325628 | ||
|
|
365efec24a | ||
|
|
491f15827b | ||
|
|
c17ecb53e7 | ||
|
|
6ba8e7621d | ||
|
|
39c95d6206 | ||
|
|
78b6bd4ca5 | ||
|
|
b3ff9e315c | ||
|
|
990186ec9d | ||
|
|
19da6ff59a | ||
|
|
36c817f0c3 | ||
|
|
35123f7ae4 | ||
|
|
54b7c1b10d | ||
|
|
e6fc3d209d | ||
|
|
f98b81e527 | ||
|
|
dc7202cfa9 | ||
|
|
f1a7caea3f | ||
|
|
b019532bc2 | ||
|
|
7fb579c667 | ||
|
|
dbefa67058 | ||
|
|
24d99c029e | ||
|
|
3fecd0154c | ||
|
|
0440343a11 | ||
|
|
3cf56b7bfa | ||
|
|
7153d8d6ce | ||
|
|
ecfe2f6a05 | ||
|
|
5753553ba3 | ||
|
|
04a4bfd455 | ||
|
|
b2f77dcebe | ||
|
|
85cf47de86 | ||
|
|
45bcea6c4f | ||
|
|
3fac7ce68c | ||
|
|
71c8f561e3 | ||
|
|
011ebf44eb | ||
|
|
c25ea78ec9 | ||
|
|
2e0cd292d2 | ||
|
|
4ebaf18c27 | ||
|
|
ab07733d66 | ||
|
|
d1f825ca13 | ||
|
|
4cf7d73b22 | ||
|
|
2cd854c2c7 | ||
|
|
ba05c39d65 | ||
|
|
64b8884911 | ||
|
|
d391e63418 | ||
|
|
8d8c895a1c | ||
|
|
dba5c27296 | ||
|
|
db620b42ec | ||
|
|
9013ed9bd4 | ||
|
|
06325fa63b | ||
|
|
0d41ba7a08 | ||
|
|
c07a7b26d3 | ||
|
|
f723d10087 | ||
|
|
3d7263d3a0 | ||
|
|
9caec1c15d | ||
|
|
703f07ca17 | ||
|
|
db9371126d | ||
|
|
eb1cf8fc9b | ||
|
|
4af6109398 | ||
|
|
96f3ec903c | ||
|
|
8c791fd0bf | ||
|
|
c0747a5fc1 | ||
|
|
c8e710d39e | ||
|
|
e281e4f4cb | ||
|
|
6598334021 | ||
|
|
35f3351c97 | ||
|
|
1f5bb121e2 | ||
|
|
88801bb613 | ||
|
|
a850063211 | ||
|
|
14ec268b8a | ||
|
|
363b207f2b | ||
|
|
06b2c40222 | ||
|
|
68b82f5721 | ||
|
|
7cca6499a9 | ||
|
|
8235eefbf0 | ||
|
|
14b31bdba5 | ||
|
|
f768163982 | ||
|
|
7a2b5db15c | ||
|
|
2465483845 | ||
|
|
6a0ecef81a | ||
|
|
6defeeb2ba | ||
|
|
b36a73059d | ||
|
|
d1d4fe4d07 | ||
|
|
9aaa8f06d1 | ||
|
|
6ea00162b9 | ||
|
|
61d635e8ad | ||
|
|
372400ed34 | ||
|
|
ba9d849ef0 | ||
|
|
5b677934fe | ||
|
|
dbcd8353b0 | ||
|
|
3be6b69e1b | ||
|
|
0bcf9b0ae6 | ||
|
|
c715739599 | ||
|
|
6aea9c43e8 | ||
|
|
82f6a52d1a | ||
|
|
a6b307cfbe | ||
|
|
dcb20b289c | ||
|
|
79afc7ee68 | ||
|
|
3ba5595233 | ||
|
|
7232ba6d55 | ||
|
|
a14de909eb | ||
|
|
d8789cd379 | ||
|
|
8d4be0361e | ||
|
|
3f2710397d | ||
|
|
8895a7d3ab | ||
|
|
10cb9dd468 | ||
|
|
2ca214bd2c | ||
|
|
2a01c26d58 | ||
|
|
ea172537aa | ||
|
|
baa2502ec6 | ||
|
|
d4d17392fe | ||
|
|
6807363efd | ||
|
|
1c5d131245 | ||
|
|
19500f7df8 | ||
|
|
ad189826f4 | ||
|
|
a612736f54 | ||
|
|
bda8dd5113 | ||
|
|
415a786049 | ||
|
|
2edc35a45d |
@@ -1,16 +0,0 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
install:
|
||||
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init.exe -y --default-host x86_64-pc-windows-msvc
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- if NOT "%TARGET%" == "x86_64-pc-windows-msvc" rustup target add %TARGET%
|
||||
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --all --target %TARGET%
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-0-release-amd64
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 12.0
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
# TODO: switch back to nightly
|
||||
- sh rustup.sh -y --default-toolchain nightly-2019-08-21
|
||||
- . $HOME/.cargo/env
|
||||
- rustup target add i686-unknown-freebsd
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
|
||||
# Remove any existing patch statements
|
||||
mv Cargo.toml Cargo.toml.bck
|
||||
sed -n '/\[patch.crates-io\]/q;p' Cargo.toml.bck > Cargo.toml
|
||||
|
||||
# Patch all crates
|
||||
cat ci/patch.toml >> Cargo.toml
|
||||
|
||||
# Print `Cargo.toml` for debugging
|
||||
echo "~~~~ Cargo.toml ~~~~"
|
||||
cat Cargo.toml
|
||||
echo "~~~~~~~~~~~~~~~~~~~~"
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all
|
||||
- cargo doc --all --no-deps
|
||||
# TODO: Re-enable
|
||||
# i686_test_script:
|
||||
# - . $HOME/.cargo/env
|
||||
# - |
|
||||
# cargo test --all --exclude tokio-tls --exclude tokio-macros --target i686-unknown-freebsd
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
Thank you for reporting an issue.
|
||||
|
||||
Please fill in as much of the template below as you're able.
|
||||
-->
|
||||
|
||||
## Version
|
||||
|
||||
<!--
|
||||
List the versions of all `tokio` crates you are using. The easiest way to get
|
||||
this information is using `cargo-tree`.
|
||||
|
||||
`cargo install cargo-tree`
|
||||
(see install here: https://github.com/sfackler/cargo-tree)
|
||||
|
||||
Then:
|
||||
|
||||
`cargo tree | grep tokio`
|
||||
-->
|
||||
|
||||
## Platform
|
||||
|
||||
<!---
|
||||
Output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows)
|
||||
-->
|
||||
|
||||
## Subcrates
|
||||
|
||||
<!--
|
||||
If known, please specify the affected Tokio sub crates. Otherwise, delete this
|
||||
section.
|
||||
-->
|
||||
|
||||
## Description
|
||||
|
||||
<!--
|
||||
|
||||
Enter your issue details below this comment.
|
||||
|
||||
One way to structure the description:
|
||||
|
||||
<short summary of the bug>
|
||||
|
||||
I tried this code:
|
||||
|
||||
<code sample that causes the bug>
|
||||
|
||||
I expected to see this happen: <explanation>
|
||||
|
||||
Instead, this happened: <explanation>
|
||||
-->
|
||||
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
Thank you for your Pull Request. Please provide a description above and review
|
||||
the requirements below.
|
||||
|
||||
Bug fixes and new features should include tests.
|
||||
|
||||
Contributors guide: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
## Motivation
|
||||
|
||||
<!--
|
||||
Explain the context and why you're making that change. What is the problem
|
||||
you're trying to solve? In some cases there is not a problem and this can be
|
||||
thought of as being the motivation for your change.
|
||||
-->
|
||||
|
||||
## Solution
|
||||
|
||||
<!--
|
||||
Summarize the solution and provide any necessary context needed to understand
|
||||
the code change.
|
||||
-->
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
---
|
||||
language: rust
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: 1.21.0
|
||||
- rust: stable
|
||||
- os: osx
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
- env: TARGET=x86_64-unknown-freebsd
|
||||
|
||||
script:
|
||||
- |
|
||||
set -e
|
||||
if [[ "$TRAVIS_RUST_VERSION" == nightly ]]
|
||||
then
|
||||
cargo build --benches --all
|
||||
fi
|
||||
- |
|
||||
set -e
|
||||
if [[ "$TARGET" ]]
|
||||
then
|
||||
rustup target add $TARGET
|
||||
cargo check --all --target $TARGET
|
||||
cargo check --tests --all --target $TARGET
|
||||
else
|
||||
cargo test --all
|
||||
cargo test --features unstable-futures
|
||||
cargo test --manifest-path tokio-threadpool/Cargo.toml --features unstable-futures
|
||||
cargo test --manifest-path tokio-reactor/Cargo.toml --features unstable-futures
|
||||
fi
|
||||
|
||||
before_deploy:
|
||||
- cargo doc --all --no-deps
|
||||
|
||||
deploy:
|
||||
provider: pages
|
||||
skip_cleanup: true
|
||||
github_token: $GH_TOKEN
|
||||
target_branch: gh-pages
|
||||
local_dir: target/doc
|
||||
on:
|
||||
branch: master
|
||||
repo: tokio-rs/tokio
|
||||
rust: stable
|
||||
condition: $TRAVIS_OS_NAME = linux
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: iwlN1zfUCp/5BAAheqIRSFIqiM9zSwfIGcVDw/V7jHveqXyNzmCs7H58/cd90WLqonqpPX0t5GF66oTjms4v0DFjgXr/k4358qeSZaV082V3baNrVpCDHeCQV0SvKsfiYxDDJGSUL1WIUP+tqqDm4+ksZQP3LnwZojkABjWz5CBNt4kX+Wz5ZbYqtQoxyuZba5UyPY2CXJtubvCVPGMJULuUpklYxXZ4dWM2olzGgVJ8rE8udhSZ4ER4JgxB0KUx3/5TwHHzgyPEsWR4bKN6JzBjIczQofXUcUXXdoZBs23H/VhCpzKcn3/oJ8btVYPzwtdj5FmVB1aVR/gjPo2bSGi/sofq+LwL/1HJXkM+kjl8m2dLLcDBKqNYNERtVA1++LhkMWAFRgGYe8v8Ryxjiue1NF5LgAIA/fjK0uI1DELTzTf/TKrM+AtPDNTvhOft4/YD+hoImjwk6nv6PBb2TiTYnc79Qf4AZ65tv1qtsAUPuw4plLaccHQAO4ldYVXn4u9c+iisJwvovs6jo06bF3U3qtdI5gXsrI9+T25TrXvYb+IREo0MHzYEM0KlPFnscEArzC3eajuSd36ARFP3lDc+gp2RPs89iJjowms0eRyepp7Cu6XO3Cd2pfAX8AqvnmttZf4Nm51ONeiBPXPXItUkJm49MCpMJywU1IZcWZg=
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
@@ -1,30 +0,0 @@
|
||||
# 0.1.4 (March 22, 2018)
|
||||
|
||||
* Fix build on FreeBSD (#218)
|
||||
* Shutdown the Runtime when the handle is dropped (#214)
|
||||
* Set Runtime thread name prefix for worker threads (#232)
|
||||
* Add builder for Runtime (#234)
|
||||
* Extract TCP and UDP types into separate crates (#224)
|
||||
* Optionally support futures 0.2.
|
||||
|
||||
# 0.1.3 (March 09, 2018)
|
||||
|
||||
* Fix `CurrentThread::turn` to block on idle (#212).
|
||||
|
||||
# 0.1.2 (March 09, 2018)
|
||||
|
||||
* Introduce Tokio Runtime (#141)
|
||||
* Provide `CurrentThread` for more flexible usage of current thread executor (#141).
|
||||
* Add Lio for platforms that support it (#142).
|
||||
* I/O resources now lazily bind to the reactor (#160).
|
||||
* Extract Reactor to dedicated crate (#169)
|
||||
* Add facade to sub crates and add prelude (#166).
|
||||
* Switch TCP/UDP fns to poll_ -> Poll<...> style (#175)
|
||||
|
||||
# 0.1.1 (February 09, 2018)
|
||||
|
||||
* Doc fixes
|
||||
|
||||
# 0.1.0 (February 07, 2018)
|
||||
|
||||
* Initial crate released based on [RFC](https://github.com/tokio-rs/tokio-rfcs/pull/3).
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
# Contributing to Tokio
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
you!
|
||||
|
||||
There are opportunities to contribute to Tokio at any level. It doesn't matter if
|
||||
you are just getting started with Rust or are the most weathered expert, we can
|
||||
use your help.
|
||||
|
||||
**No contribution is too small and all contributions are valued.**
|
||||
|
||||
This guide will help you get started. **Do not let this guide intimidate you**.
|
||||
It should be considered a map to help you navigate the process.
|
||||
|
||||
You may also get help with contributing in the [dev channel][dev], please join
|
||||
us!
|
||||
|
||||
[dev]: https://gitter.im/tokio-rs/dev
|
||||
|
||||
## Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
|
||||
the _minimum_ behavior expected from all contributors.
|
||||
|
||||
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
## Contributing in Issues
|
||||
|
||||
For any issue, there are fundamentally three ways an individual can contribute:
|
||||
|
||||
1. By opening the issue for discussion: For instance, if you believe that you
|
||||
have uncovered a bug in Tokio, creating a new issue in the tokio-rs/tokio
|
||||
issue tracker is the way to report it.
|
||||
|
||||
2. By helping to triage the issue: This can be done by providing
|
||||
supporting details (a test case that demonstrates a bug), providing
|
||||
suggestions on how to address the issue, or ensuring that the issue is tagged
|
||||
correctly.
|
||||
|
||||
3. By helping to resolve the issue: Typically this is done either in the form of
|
||||
demonstrating that the issue reported is not a problem after all, or more
|
||||
often, by opening a Pull Request that changes some bit of something in
|
||||
Tokio in a concrete and reviewable manner.
|
||||
|
||||
**Anybody can participate in any stage of contribution**. We urge you to
|
||||
participate in the discussion around bugs and participate in reviewing PRs.
|
||||
|
||||
### Asking for General Help
|
||||
|
||||
If you have reviewed existing documentation and still have questions or are
|
||||
having problems, you can open an issue asking for help.
|
||||
|
||||
In exchange for receiving help, we ask that you contribute back a documentation
|
||||
PR that helps others avoid the problems that you encountered.
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, users will be presented
|
||||
with a [basic template][template] that should be filled in. If you believe that you have
|
||||
uncovered a bug, please fill out this form, following the template to the best
|
||||
of your ability. Do not worry if you cannot answer every detail, just fill in
|
||||
what you can.
|
||||
|
||||
The two most important pieces of information we need in order to properly
|
||||
evaluate the report is a description of the behavior you are seeing and a simple
|
||||
test case we can use to recreate the problem on our own. If we cannot recreate
|
||||
the issue, it becomes impossible for us to fix.
|
||||
|
||||
In order to rule out the possibility of bugs introduced by userland code, test
|
||||
cases should be limited, as much as possible, to using only Tokio APIs.
|
||||
|
||||
See [How to create a Minimal, Complete, and Verifiable example][mcve].
|
||||
|
||||
[mcve]: https://stackoverflow.com/help/mcve
|
||||
[template]: .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Triaging a Bug Report
|
||||
|
||||
Once an issue has been opened, it is not uncommon for there to be discussion
|
||||
around it. Some contributors may have differing opinions about the issue,
|
||||
including whether the behavior being seen is a bug or a feature. This discussion
|
||||
is part of the process and should be kept focused, helpful, and professional.
|
||||
|
||||
Short, clipped responses—that provide neither additional context nor supporting
|
||||
detail—are not helpful or professional. To many, such responses are simply
|
||||
annoying and unfriendly.
|
||||
|
||||
Contributors are encouraged to help one another make forward progress as much as
|
||||
possible, empowering one another to solve issues collaboratively. If you choose
|
||||
to comment on an issue that you feel either is not a problem that needs to be
|
||||
fixed, or if you encounter information in an issue that you feel is incorrect,
|
||||
explain why you feel that way with additional supporting context, and be willing
|
||||
to be convinced that you may be wrong. By doing so, we can often reach the
|
||||
correct outcome much faster.
|
||||
|
||||
### Resolving a Bug Report
|
||||
|
||||
In the majority of cases, issues are resolved by opening a Pull Request. The
|
||||
process for opening and reviewing a Pull Request is similar to that of opening
|
||||
and triaging issues, but carries with it a necessary review and approval
|
||||
workflow that ensures that the proposed changes meet the minimal quality and
|
||||
functional guidelines of the Tokio project.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull Requests are the way concrete changes are made to the code, documentation,
|
||||
and dependencies in the Tokio repository.
|
||||
|
||||
Even tiny pull requests (e.g., one character pull request fixing a typo in API
|
||||
documentation) are greatly appreciated. Before making a large change, it is
|
||||
usually a good idea to first open an issue describing the change to solicit
|
||||
feedback and guidance. This will increase the likelihood of the PR getting
|
||||
merged.
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: integration tests and documentation tests
|
||||
(Tokio avoids unit tests as much as possible).
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Integration tests go in the same crate as the code they are testing. Each sub
|
||||
crate should have a `dev-dependency` on `tokio` itself. This makes all Tokio
|
||||
utilities available to use in tests, no matter the crate being tested.
|
||||
|
||||
The best strategy for writing a new integration test is to look at existing
|
||||
integration tests in the crate and follow the style.
|
||||
|
||||
#### Documentation tests
|
||||
|
||||
Ideally, every API has at least one [documentation test] that demonstrates how to
|
||||
use the API. Documentation tests are run with `cargo test --doc`. This ensures
|
||||
that the example is correct and provides additional test coverage.
|
||||
|
||||
The trick to documentation tests is striking a balance between being succinct
|
||||
for a reader to understand and actually testing the API.
|
||||
|
||||
Same as with integration tests, when writing a documentation test, the full
|
||||
`tokio` crate is available. This is especially useful for getting access to the
|
||||
runtime to run the example.
|
||||
|
||||
The documentation tests will be visible from both the crate specific
|
||||
documentation **and** the `tokio` facade documentation via the re-export. The
|
||||
example should be written from the point of view of a user that is using the
|
||||
`tokio` crate. As such, the example should use the API via the facade and not by
|
||||
directly referencing the crate.
|
||||
|
||||
The type level example for `tokio_timer::Timeout` provides a good example of a
|
||||
documentation test:
|
||||
|
||||
```
|
||||
/// // import the `timeout` function, usually this is done
|
||||
/// // with `use tokio::prelude::*`
|
||||
/// use tokio::prelude::FutureExt;
|
||||
/// use futures::Stream;
|
||||
/// use futures::sync::mpsc;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let (tx, rx) = mpsc::unbounded();
|
||||
/// # tx.unbounded_send(()).unwrap();
|
||||
/// # drop(tx);
|
||||
///
|
||||
/// let process = rx.for_each(|item| {
|
||||
/// // do something with `item`
|
||||
/// # drop(item);
|
||||
/// # Ok(())
|
||||
/// });
|
||||
///
|
||||
/// # tokio::runtime::current_thread::block_on_all(
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// process.timeout(Duration::from_millis(10))
|
||||
/// # ).unwrap();
|
||||
/// # }
|
||||
```
|
||||
|
||||
Given that this is a *type* level documentation test and the primary way users
|
||||
of `tokio` will create an instance of `Timeout` is by using
|
||||
`FutureExt::timeout`, this is how the documentation test is structured.
|
||||
|
||||
Lines that start with `/// #` are removed when the documentation is generated.
|
||||
They are only there to get the test to run. The `block_on_all` function is the
|
||||
easiest way to execute a future from a test.
|
||||
|
||||
If this were a documentation test for the `Timeout::new` function, then the
|
||||
example would explicitly use `Timeout::new`. For example:
|
||||
|
||||
```
|
||||
/// use tokio::timer::Timeout;
|
||||
/// use futures::Future;
|
||||
/// use futures::sync::oneshot;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
/// # tx.send(()).unwrap();
|
||||
///
|
||||
/// # tokio::runtime::current_thread::block_on_all(
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// Timeout::new(rx, Duration::from_millis(10))
|
||||
/// # ).unwrap();
|
||||
/// # }
|
||||
```
|
||||
|
||||
### Commits
|
||||
|
||||
It is a recommended best practice to keep your changes as logically grouped as
|
||||
possible within individual commits. There is no limit to the number of commits
|
||||
any single Pull Request may have, and many contributors find it easier to review
|
||||
changes that are split across multiple commits.
|
||||
|
||||
That said, if you have a number of commits that are "checkpoints" and don't
|
||||
represent a single logical change, please squash those together.
|
||||
|
||||
Note that multiple commits often get squashed when they are landed (see the
|
||||
notes about [commit squashing]).
|
||||
|
||||
#### Commit message guidelines
|
||||
|
||||
A good commit message should describe what changed and why.
|
||||
|
||||
1. The first line should:
|
||||
|
||||
* contain a short description of the change (preferably 50 characters or less,
|
||||
and no more than 72 characters)
|
||||
* be entirely in lowercase with the exception of proper nouns, acronyms, and
|
||||
the words that refer to code, like function/variable names
|
||||
* be prefixed with the name of the sub crate being changed (without the `tokio-`
|
||||
prefix) and start with an imperative verb. If modifying `tokio` proper,
|
||||
omit the crate prefix.
|
||||
|
||||
Examples:
|
||||
|
||||
* timer: introduce `Timeout` and deprecate `Deadline`
|
||||
* export `Encoder`, `Decoder`, `Framed*` from tokio_codec
|
||||
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns (except for long URLs).
|
||||
4. If your patch fixes an open issue, you can add a reference to it at the end
|
||||
of the log. Use the `Fixes: #` prefix and the issue number. For other
|
||||
references use `Refs: #`. `Refs` may include multiple issues, separated by a
|
||||
comma.
|
||||
|
||||
Examples:
|
||||
|
||||
- `Fixes: #1337`
|
||||
- `Refs: #1234`
|
||||
|
||||
Sample complete commit message:
|
||||
|
||||
```txt
|
||||
subcrate: explain the commit in one line
|
||||
|
||||
Body of commit message is a few lines of text, explaining things
|
||||
in more detail, possibly giving some background about the issue
|
||||
being fixed, etc.
|
||||
|
||||
The body of the commit message can be several paragraphs, and
|
||||
please do proper word-wrap and keep columns shorter than about
|
||||
72 characters or so. That way, `git log` will show things
|
||||
nicely even when it is indented.
|
||||
|
||||
Fixes: #1337
|
||||
Refs: #453, #154
|
||||
```
|
||||
|
||||
### Opening the Pull Request
|
||||
|
||||
From within GitHub, opening a new Pull Request will present you with a
|
||||
[template] that should be filled out. Please try to do your best at filling out
|
||||
the details, but feel free to skip parts if you're not sure what to put.
|
||||
|
||||
[template]: .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Discuss and update
|
||||
|
||||
You will probably get feedback or requests for changes to your Pull Request.
|
||||
This is a big part of the submission process so don't be discouraged! Some
|
||||
contributors may sign off on the Pull Request right away, others may have
|
||||
more detailed comments or feedback. This is a necessary part of the process
|
||||
in order to evaluate whether the changes are correct and necessary.
|
||||
|
||||
**Any community member can review a PR and you might get conflicting feedback**.
|
||||
Keep an eye out for comments from code owners to provide guidance on conflicting
|
||||
feedback.
|
||||
|
||||
**Once the PR is open, do not rebase the commits**. See [Commit Squashing] for
|
||||
more details.
|
||||
|
||||
### Commit Squashing
|
||||
|
||||
In most cases, **do not squash commits that you add to your Pull Request during
|
||||
the review process**. When the commits in your Pull Request land, they may be
|
||||
squashed into one commit per logical change. Metadata will be added to the
|
||||
commit message (including links to the Pull Request, links to relevant issues,
|
||||
and the names of the reviewers). The commit history of your Pull Request,
|
||||
however, will stay intact on the Pull Request page.
|
||||
|
||||
## Reviewing Pull Requests
|
||||
|
||||
**Any Tokio community member is welcome to review any pull request**.
|
||||
|
||||
All Tokio contributors who choose to review and provide feedback on Pull
|
||||
Requests have a responsibility to both the project and the individual making the
|
||||
contribution. Reviews and feedback must be helpful, insightful, and geared
|
||||
towards improving the contribution as opposed to simply blocking it. If there
|
||||
are reasons why you feel the PR should not land, explain what those are. Do not
|
||||
expect to be able to block a Pull Request from advancing simply because you say
|
||||
"No" without giving an explanation. Be open to having your mind changed. Be open
|
||||
to working with the contributor to make the Pull Request better.
|
||||
|
||||
Reviews that are dismissive or disrespectful of the contributor or any other
|
||||
reviewers are strictly counter to the Code of Conduct.
|
||||
|
||||
When reviewing a Pull Request, the primary goals are for the codebase to improve
|
||||
and for the person submitting the request to succeed. **Even if a Pull Request
|
||||
does not land, the submitters should come away from the experience feeling like
|
||||
their effort was not wasted or unappreciated**. Every Pull Request from a new
|
||||
contributor is an opportunity to grow the community.
|
||||
|
||||
### Review a bit at a time.
|
||||
|
||||
Do not overwhelm new contributors.
|
||||
|
||||
It is tempting to micro-optimize and make everything about relative performance,
|
||||
perfect grammar, or exact style matches. Do not succumb to that temptation.
|
||||
|
||||
Focus first on the most significant aspects of the change:
|
||||
|
||||
1. Does this change make sense for Tokio?
|
||||
2. Does this change make Tokio better, even if only incrementally?
|
||||
3. Are there clear bugs or larger scale issues that need attending to?
|
||||
4. Is the commit message readable and correct? If it contains a breaking change
|
||||
is it clear enough?
|
||||
|
||||
Note that only **incremental** improvement is needed to land a PR. This means
|
||||
that the PR does not need to be perfect, only better than the status quo. Follow
|
||||
up PRs may be opened to continue iterating.
|
||||
|
||||
When changes are necessary, *request* them, do not *demand* them, and **do not
|
||||
assume that the submitter already knows how to add a test or run a benchmark**.
|
||||
|
||||
Specific performance optimization techniques, coding styles and conventions
|
||||
change over time. The first impression you give to a new contributor never does.
|
||||
|
||||
Nits (requests for small changes that are not essential) are fine, but try to
|
||||
avoid stalling the Pull Request. Most nits can typically be fixed by the Tokio
|
||||
Collaborator landing the Pull Request but they can also be an opportunity for
|
||||
the contributor to learn a bit more about the project.
|
||||
|
||||
It is always good to clearly indicate nits when you comment: e.g.
|
||||
`Nit: change foo() to bar(). But this is not blocking.`
|
||||
|
||||
If your comments were addressed but were not folded automatically after new
|
||||
commits or if they proved to be mistaken, please, [hide them][hiding-a-comment]
|
||||
with the appropriate reason to keep the conversation flow concise and relevant.
|
||||
|
||||
### Be aware of the person behind the code
|
||||
|
||||
Be aware that *how* you communicate requests and reviews in your feedback can
|
||||
have a significant impact on the success of the Pull Request. Yes, we may land
|
||||
a particular change that makes Tokio better, but the individual might just not
|
||||
want to have anything to do with Tokio ever again. The goal is not just having
|
||||
good code.
|
||||
|
||||
### Abandoned or Stalled Pull Requests
|
||||
|
||||
If a Pull Request appears to be abandoned or stalled, it is polite to first
|
||||
check with the contributor to see if they intend to continue the work before
|
||||
checking if they would mind if you took it over (especially if it just has nits
|
||||
left). When doing so, it is courteous to give the original contributor credit
|
||||
for the work they started (either by preserving their name and email address in
|
||||
the commit log, or by using an `Author: ` meta-data tag in the commit.
|
||||
|
||||
_Adapted from the [Node.js contributing guide][node]_.
|
||||
|
||||
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
|
||||
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
|
||||
[documentation test]: https://doc.rust-lang.org/rustdoc/documentation-tests.html
|
||||
|
||||
## Releasing
|
||||
|
||||
Since the Tokio project consists of a number of crates, many of which depend on
|
||||
each other, releasing new versions to crates.io can involve some complexities.
|
||||
When releasing a new version of a crate, follow these steps:
|
||||
|
||||
1. **Ensure that the release crate has no path dependencies.** When the HEAD
|
||||
version of a Tokio crate requires unreleased changes in another Tokio crate,
|
||||
the crates.io dependency on the second crate will be replaced with a path
|
||||
dependency. Crates with path dependencies cannot be published, so before
|
||||
publishing the dependent crate, any path dependencies must also be published.
|
||||
This should be done through a form of depth-first tree traversal:
|
||||
|
||||
1. Starting with the first path dependency in the crate to be released,
|
||||
inspect the `Cargo.toml` for the dependency. If the dependency has any
|
||||
path dependencies of its own, repeat this step with the first such
|
||||
dependency.
|
||||
2. Begin the release process for the path dependency.
|
||||
3. Once the path dependency has been published to crates.io, update the
|
||||
dependent crate to depend on the crates.io version.
|
||||
4. When all path dependencies have been published, the dependent crate may
|
||||
be published.
|
||||
|
||||
To verify that a crate is ready to publish, run:
|
||||
|
||||
```bash
|
||||
bin/publish --dry-run <CRATE NAME> <CRATE VERSION>
|
||||
```
|
||||
|
||||
2. **Update Cargo metadata.** After releasing any path dependencies, update the
|
||||
`version` field in `Cargo.toml` to the new version, and the `documentation`
|
||||
field to the docs.rs URL of the new version.
|
||||
3. **Update other documentation links.** Update the `#![doc(html_root_url)]`
|
||||
attribute in the crate's `lib.rs` and the "Documentation" link in the crate's
|
||||
`README.md` to point to the docs.rs URL of the new version.
|
||||
4. **Update the changelog for the crate.** Each crate in the Tokio repository
|
||||
has its own `CHANGELOG.md` in that crate's subdirectory. Any changes to that
|
||||
crate since the last release should be added to the changelog. Change
|
||||
descriptions may be taken from the Git history, but should be edited to
|
||||
ensure a consistent format, based on [Keep A Changelog][keep-a-changelog].
|
||||
Other entries in that crate's changelog may also be used for reference.
|
||||
5. **Perform a final audit for breaking changes.** Compare the HEAD version of
|
||||
crate with the Git tag for the most recent release version. If there are any
|
||||
breaking API changes, determine if those changes can be made without breaking
|
||||
existing APIs. If so, resolve those issues. Otherwise, if it is necessary to
|
||||
make a breaking release, update the version numbers to reflect this.
|
||||
6. **Open a pull request with your changes.** Once that pull request has been
|
||||
approved by a maintainer and the pull request has been merged, continue to
|
||||
the next step.
|
||||
7. **Release the crate.** Run the following command:
|
||||
|
||||
```bash
|
||||
bin/publish <NAME OF CRATE> <VERSION>
|
||||
```
|
||||
|
||||
Your editor and prompt you to edit a message for the tag. Copy the changelog
|
||||
entry for that release version into your editor and close the window.
|
||||
|
||||
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog/blob/master/CHANGELOG.md
|
||||
+10
-72
@@ -1,78 +1,16 @@
|
||||
[package]
|
||||
name = "tokio"
|
||||
|
||||
# When releasing to crates.io:
|
||||
# - Update html_root_url.
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.4"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio/0.1"
|
||||
description = """
|
||||
An event-driven, non-blocking I/O platform for writing asynchronous I/O
|
||||
backed applications.
|
||||
"""
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
keywords = ["io", "async", "non-blocking", "futures"]
|
||||
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"./",
|
||||
"tokio",
|
||||
"tokio-codec",
|
||||
"tokio-executor",
|
||||
"tokio-fs",
|
||||
"tokio-io",
|
||||
"tokio-reactor",
|
||||
"tokio-threadpool",
|
||||
"tokio-tcp",
|
||||
"tokio-udp",
|
||||
"futures2",
|
||||
"tokio-macros",
|
||||
"tokio-net",
|
||||
"tokio-sync",
|
||||
"tokio-test",
|
||||
"tokio-timer",
|
||||
"tokio-tls",
|
||||
"build-tests",
|
||||
]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "tokio-rs/tokio" }
|
||||
appveyor = { repository = "carllerche/tokio" }
|
||||
|
||||
[dependencies]
|
||||
tokio-io = { version = "0.1.6", path = "tokio-io" }
|
||||
tokio-executor = { version = "0.1.1", path = "tokio-executor" }
|
||||
tokio-reactor = { version = "0.1.1", path = "tokio-reactor" }
|
||||
tokio-threadpool = { version = "0.1.1", path = "tokio-threadpool" }
|
||||
tokio-tcp = { version = "0.1.0", path = "tokio-tcp" }
|
||||
tokio-udp = { version = "0.1.0", path = "tokio-udp" }
|
||||
mio = "0.6.14"
|
||||
futures = "0.1.19"
|
||||
|
||||
# Futures 0.2 integration
|
||||
futures2 = { version = "0.1.0", path = "futures2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = "0.4"
|
||||
env_logger = { version = "0.4", default-features = false }
|
||||
flate2 = { version = "1", features = ["tokio"] }
|
||||
futures-cpupool = "0.1"
|
||||
http = "0.1"
|
||||
httparse = "1.0"
|
||||
libc = "0.2"
|
||||
num_cpus = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1"
|
||||
|
||||
[patch.crates-io]
|
||||
tokio-io = { path = "tokio-io" }
|
||||
|
||||
[features]
|
||||
unstable-futures = [
|
||||
"futures2",
|
||||
"tokio-reactor/unstable-futures",
|
||||
"tokio-threadpool/unstable-futures",
|
||||
"tokio-executor/unstable-futures",
|
||||
"tokio-tcp/unstable-futures",
|
||||
"tokio-udp/unstable-futures"
|
||||
]
|
||||
default = []
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 Tokio Contributors
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Tokio
|
||||
|
||||
**NOTE**: Tokio's [`master`](https://github.com/tokio-rs/tokio) is currently undergoing heavy development. This branch and the alpha releases will see API breaking changes and there are currently significant performance regressions that still need to be fixed before the final release. Use the [`v0.1.x`](https://github.com/tokio-rs/tokio/tree/v0.1.x) branch for stable releases.
|
||||
|
||||
A runtime for writing reliable, asynchronous, and slim applications with
|
||||
the Rust programming language. It is:
|
||||
|
||||
@@ -14,25 +16,22 @@ the Rust programming language. It is:
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Travis Build Status][travis-badge]][travis-url]
|
||||
[![Appveyor Build Status][appveyor-badge]][appveyor-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Gitter chat][gitter-badge]][gitter-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: LICENSE-MIT
|
||||
[travis-badge]: https://travis-ci.org/tokio-rs/tokio.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/tokio-rs/tokio
|
||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/s83yxhy9qeb58va7/branch/master?svg=true
|
||||
[appveyor-url]: https://ci.appveyor.com/project/carllerche/tokio/branch/master
|
||||
[mit-url]: LICENSE
|
||||
[azure-badge]: https://dev.azure.com/tokio-rs/Tokio/_apis/build/status/tokio-rs.tokio?branchName=master
|
||||
[azure-url]: https://dev.azure.com/tokio-rs/Tokio/_build/latest?definitionId=1&branchName=master
|
||||
[gitter-badge]: https://img.shields.io/gitter/room/tokio-rs/tokio.svg
|
||||
[gitter-url]: https://gitter.im/tokio-rs/tokio
|
||||
|
||||
[Website](https://tokio.rs) |
|
||||
[Guides](https://tokio.rs/docs/getting-started/hello-world/) |
|
||||
[API Docs](https://docs.rs/tokio)
|
||||
|
||||
The API docs for the master branch are published [here][master-dox].
|
||||
|
||||
[master-dox]: https://tokio-rs.github.io/tokio/tokio/
|
||||
[Guides](https://tokio.rs/docs/) |
|
||||
[API Docs](https://docs.rs/tokio/latest/tokio) |
|
||||
[Chat](https://gitter.im/tokio-rs/tokio)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -41,63 +40,85 @@ asynchronous applications with the Rust programming language. At a high
|
||||
level, it provides a few major components:
|
||||
|
||||
* A multithreaded, work-stealing based task [scheduler].
|
||||
* A [reactor] backed by the operating system's event queue (epoll, kqueue,
|
||||
* A reactor backed by the operating system's event queue (epoll, kqueue,
|
||||
IOCP, etc...).
|
||||
* Asynchronous [TCP and UDP][net] sockets.
|
||||
|
||||
These components provide the runtime components necessary for building
|
||||
an asynchronous application.
|
||||
|
||||
[net]: https://docs.rs/tokio/0.1/tokio/net/index.html
|
||||
[reactor]: https://docs.rs/tokio/0.1.1/tokio/reactor/index.html
|
||||
[scheduler]: https://tokio-rs.github.io/tokio/tokio/runtime/index.html
|
||||
[net]: https://docs.rs/tokio/latest/tokio/net/index.html
|
||||
[scheduler]: https://docs.rs/tokio/latest/tokio/runtime/index.html
|
||||
|
||||
## Example
|
||||
|
||||
A basic TCP echo server with Tokio:
|
||||
|
||||
```rust
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::prelude::*;
|
||||
use tokio::io::copy;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
fn main() {
|
||||
// Bind the server's socket.
|
||||
let addr = "127.0.0.1:12345".parse().unwrap();
|
||||
let listener = TcpListener::bind(&addr)
|
||||
.expect("unable to bind TCP listener");
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "127.0.0.1:8080".parse::<SocketAddr>()?;
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
// Pull out a stream of sockets for incoming connections
|
||||
let server = listener.incoming()
|
||||
.map_err(|e| eprintln!("accept failed = {:?}", e))
|
||||
.for_each(|sock| {
|
||||
// Split up the reading and writing parts of the
|
||||
// socket.
|
||||
let (reader, writer) = sock.split();
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
// A future that echos the data and returns how
|
||||
// many bytes were copied...
|
||||
let bytes_copied = copy(reader, writer);
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
// ... after which we'll print what happened.
|
||||
let handle_conn = bytes_copied.map(|amt| {
|
||||
println!("wrote {:?} bytes", amt)
|
||||
}).map_err(|err| {
|
||||
eprintln!("IO error {:?}", err)
|
||||
});
|
||||
// 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) => {
|
||||
println!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Spawn the future as a concurrent task.
|
||||
tokio::spawn(handle_conn)
|
||||
// Write the data back
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
println!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start the Tokio runtime
|
||||
tokio::run(server);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
More examples can be found [here](examples).
|
||||
More examples can be found [here](tokio/examples). Note that the `master` branch
|
||||
is currently being updated to use `async` / `await`. The examples are
|
||||
not fully ported. Examples for stable Tokio can be found
|
||||
[here](https://github.com/tokio-rs/tokio/tree/v0.1.x/tokio/examples).
|
||||
|
||||
|
||||
## Getting Help
|
||||
|
||||
First, see if the answer to your question can be found in the [Guides] or the
|
||||
[API documentation]. If the answer is not there, there is an active community in
|
||||
the [Tokio Gitter channel][chat]. We would be happy to try to answer your
|
||||
question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
|
||||
[Guides]: https://tokio.rs/docs/
|
||||
[API documentation]: https://docs.rs/tokio/latest/tokio
|
||||
[chat]: https://gitter.im/tokio-rs/tokio
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues/new
|
||||
|
||||
## Contributing
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
you! We have a [contributing guide][guide] to help you get involved in the Tokio
|
||||
project.
|
||||
|
||||
[guide]: CONTRIBUTING.md
|
||||
|
||||
## Project layout
|
||||
|
||||
@@ -107,26 +128,55 @@ have greater guarantees of stability.
|
||||
|
||||
The crates included as part of Tokio are:
|
||||
|
||||
* [`tokio-executor`]: Task execution related traits and utilities.
|
||||
* [`tokio-executor`]: Task executors and related utilities. Includes a
|
||||
single-threaded executor and a multi-threaded, work-stealing, executor.
|
||||
|
||||
* [`tokio-fs`]: Filesystem (and standard in / out) APIs.
|
||||
|
||||
* [`tokio-codec`]: Utilities for encoding and decoding protocol frames.
|
||||
|
||||
* [`tokio-io`]: Asynchronous I/O related traits and utilities.
|
||||
|
||||
* [`tokio-reactor`]: Event loop that drives I/O resources (like TCP and UDP
|
||||
sockets).
|
||||
* [`tokio-macros`]: Macros for usage with Tokio.
|
||||
|
||||
* [`tokio-threadpool`]: Schedules the execution of futures across a pool of
|
||||
threads.
|
||||
* [`tokio-net`]: Event loop that drives I/O resources as well as TCP, UDP, and
|
||||
unix domain socket apis.
|
||||
|
||||
* [`tokio-tcp`]: TCP bindings for use with `tokio-io` and `tokio-reactor`.
|
||||
|
||||
* [`tokio-udp`]: UDP bindings for use with `tokio-io` and `tokio-reactor`.
|
||||
* [ `tokio-timer`]: Time related APIs.
|
||||
|
||||
[`tokio-codec`]: tokio-codec
|
||||
[`tokio-current-thread`]: tokio-current-thread
|
||||
[`tokio-executor`]: tokio-executor
|
||||
[`tokio-fs`]: tokio-fs
|
||||
[`tokio-io`]: tokio-io
|
||||
[`tokio-reactor`]: tokio-reactor
|
||||
[`tokio-threadpool`]: tokio-threadpool
|
||||
[`tokio-tcp`]: tokio-tcp
|
||||
[`tokio-udp`]: tokio-udp
|
||||
[`tokio-macros`]: tokio-macros
|
||||
[`tokio-net`]: tokio-net
|
||||
[`tokio-timer`]: tokio-timer
|
||||
|
||||
## Related Projects
|
||||
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level
|
||||
tracing and async-aware diagnostics.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
Tokio is built against the latest stable, nightly, and beta Rust releases. The
|
||||
minimum version supported is the stable release from three months before the
|
||||
current stable release version. For example, if the latest stable Rust is 1.29,
|
||||
the minimum version supported is 1.26. The current Tokio version is not
|
||||
guaranteed to build on Rust versions earlier than the minimum supported version.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
trigger: ["master", "std-future"]
|
||||
pr: ["master", "std-future"]
|
||||
|
||||
variables:
|
||||
nightly: nightly-2019-08-21
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
# Check formatting
|
||||
- template: ci/azure-rustfmt.yml
|
||||
parameters:
|
||||
rust: $(nightly)
|
||||
name: rustfmt
|
||||
|
||||
# Apply clippy lints to all crates
|
||||
- template: ci/azure-clippy.yml
|
||||
parameters:
|
||||
rust: $(nightly)
|
||||
name: clippy
|
||||
|
||||
# Test top level crate
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_tokio
|
||||
rust: $(nightly)
|
||||
displayName: Test tokio
|
||||
cross: true
|
||||
crates:
|
||||
tokio:
|
||||
- codec
|
||||
- fs
|
||||
- io
|
||||
- rt-full
|
||||
- net
|
||||
- sync
|
||||
- tcp
|
||||
- timer
|
||||
- udp
|
||||
- uds
|
||||
|
||||
# Test crates that are platform specific
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_sub_cross
|
||||
displayName: Test sub crates (cross) -
|
||||
cross: true
|
||||
rust: $(nightly)
|
||||
crates:
|
||||
tokio-fs: []
|
||||
tokio-net:
|
||||
- process
|
||||
- signal
|
||||
- tcp
|
||||
- udp
|
||||
- uds
|
||||
|
||||
# Test crates that are NOT platform specific
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_linux
|
||||
displayName: Test sub crates -
|
||||
rust: $(nightly)
|
||||
crates:
|
||||
tokio-codec: []
|
||||
tokio-executor:
|
||||
- current-thread
|
||||
- threadpool
|
||||
tokio-io:
|
||||
- util
|
||||
tokio-sync:
|
||||
- async-traits
|
||||
tokio-macros: []
|
||||
tokio-timer:
|
||||
- async-traits
|
||||
tokio-test: []
|
||||
|
||||
# Test compilation failure
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_features
|
||||
displayName: Test feature flags
|
||||
rust: $(nightly)
|
||||
crates:
|
||||
build-tests:
|
||||
- tokio-executor
|
||||
- tokio-net
|
||||
- executor-without-current-thread
|
||||
- net-no-features
|
||||
- net-with-tcp
|
||||
- net-with-udp
|
||||
- net-with-uds
|
||||
- tokio-no-features
|
||||
- tokio-with-net
|
||||
|
||||
# Try cross compiling
|
||||
- template: ci/azure-cross-compile.yml
|
||||
parameters:
|
||||
name: cross
|
||||
rust: $(nightly)
|
||||
|
||||
# # This represents the minimum Rust version supported by
|
||||
# # Tokio. Updating this should be done in a dedicated PR and
|
||||
# # cannot be greater than two 0.x releases prior to the
|
||||
# # current stable.
|
||||
# #
|
||||
# # Tests are not run as tests may require newer versions of
|
||||
# # rust.
|
||||
# - template: ci/azure-check-minrust.yml
|
||||
# parameters:
|
||||
# name: minrust
|
||||
# rust_version: 1.34.0
|
||||
#
|
||||
# - template: ci/azure-tsan.yml
|
||||
# parameters:
|
||||
# name: tsan
|
||||
# rust: $(nightly)
|
||||
|
||||
- template: ci/azure-deploy-docs.yml
|
||||
parameters:
|
||||
rust: $(nightly)
|
||||
dependsOn:
|
||||
- rustfmt
|
||||
- clippy
|
||||
- test_tokio
|
||||
- test_sub_cross
|
||||
- test_linux
|
||||
- test_features
|
||||
# - test_nightly
|
||||
- cross
|
||||
# - minrust
|
||||
# - tsan
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Publish a new release of a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-v, --verbose Use verbose Cargo output
|
||||
-d, --dry-run Perform a dry run (do not publish or tag the release)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
DRY_RUN=""
|
||||
VERBOSE=""
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
verify() {
|
||||
status "Verifying" "if $CRATE v$VERSION can be released"
|
||||
ACTUAL=$(cargo pkgid | sed -n 's/.*#\(.*\)/\1/p')
|
||||
|
||||
if [ "$ACTUAL" != "$VERSION" ]; then
|
||||
err "expected to release version $VERSION, but Cargo.toml contained $ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git tag -l | grep -Fxq "$TAG" ; then
|
||||
err "git tag \`$TAG\` already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATH_DEPS=$(grep -F "path = \"" Cargo.toml | sed -e 's/^/ /')
|
||||
if [ -n "$PATH_DEPS" ]; then
|
||||
err "crate \`$CRATE\` contained path dependencies:\n$PATH_DEPS"
|
||||
echo "path dependencies must be removed prior to release"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
release() {
|
||||
status "Releasing" "$CRATE v$VERSION"
|
||||
cargo package $VERBOSE
|
||||
cargo publish $VERBOSE $DRY_RUN
|
||||
|
||||
status "Tagging" "$TAG"
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
echo "# git tag $TAG && git push --tags"
|
||||
else
|
||||
git tag "$TAG" && git push --tags
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE="--verbose"
|
||||
set +x
|
||||
shift
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
(cd "$CRATE" && verify && release )
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Update links to docs.rs in a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-d, --dry-run Perform a dry run (do not modify any file)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
c1grep() { grep "$@" || test $? = 1; }
|
||||
|
||||
update_versions_in_doc() {
|
||||
# Print what is being/would be done
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
local MSG="Would change:"
|
||||
else
|
||||
local MSG="Updating:"
|
||||
fi
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed --quiet \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|gp" \
|
||||
| sed -e "s/^/$MSG /"
|
||||
|
||||
# Apply changes if not in dry run
|
||||
if [ -z "$DRY_RUN" ]; then
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed -i \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|g"
|
||||
fi
|
||||
}
|
||||
|
||||
update() {
|
||||
update_versions_in_doc
|
||||
}
|
||||
|
||||
show_outdated() {
|
||||
OUTDATED=$(git grep -rn "docs.rs/$CRATE/" \
|
||||
| c1grep -v "$VERSION" \
|
||||
| sed -e 's/^/ - /')
|
||||
if [[ -n "$OUTDATED" ]]; then
|
||||
echo "Found the following links to docs.rs with an outdated version:"
|
||||
echo "$OUTDATED"
|
||||
echo
|
||||
else
|
||||
echo "Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
# Does not cd in order to update everywhere
|
||||
show_outdated && update
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "build-tests"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
executor-without-current-thread = ["tokio-executor"]
|
||||
net-no-features = ["tokio-net"]
|
||||
net-with-tcp = ["tokio-net/tcp"]
|
||||
net-with-udp = ["tokio-net/udp"]
|
||||
net-with-uds = ["tokio-net/uds"]
|
||||
net-with-process = ["tokio-net/process"]
|
||||
tokio-no-features = ["tokio"]
|
||||
tokio-with-net = ["tokio/net"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio-executor = { path = "../tokio-executor", optional = true }
|
||||
tokio-net = { path = "../tokio-net", optional = true }
|
||||
tokio = { path = "../tokio", optional = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
@@ -0,0 +1,2 @@
|
||||
Tests the various combination of feature flags. This is broken out to a separate
|
||||
crate to work around limitations with cargo features.
|
||||
@@ -0,0 +1,8 @@
|
||||
#[cfg(feature = "tokio-executor")]
|
||||
pub use tokio_executor;
|
||||
|
||||
#[cfg(feature = "tokio-net")]
|
||||
pub use tokio_net;
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
pub use tokio;
|
||||
@@ -0,0 +1,3 @@
|
||||
use build_tests::tokio_executor::current_thread;
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,7 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio_executor::current_thread`
|
||||
--> $DIR/executor_without_current_thread.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio_executor::current_thread;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `current_thread` in `tokio_executor`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -0,0 +1,4 @@
|
||||
use build_tests::tokio_net::tcp;
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio_net::tcp`
|
||||
--> $DIR/net_without_tcp_missing_tcp.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio_net::tcp;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `tcp` in `tokio_net`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -0,0 +1,4 @@
|
||||
use build_tests::tokio_net::udp;
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio_net::udp`
|
||||
--> $DIR/net_without_udp_missing_udp.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio_net::udp;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `udp` in `tokio_net`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -0,0 +1,4 @@
|
||||
use build_tests::tokio_net::udp;
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio_net::udp`
|
||||
--> $DIR/net_without_uds_missing_uds.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio_net::udp;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `udp` in `tokio_net`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -0,0 +1,3 @@
|
||||
use build_tests::tokio::net;
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,7 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio::net`
|
||||
--> $DIR/tokio_without_net_missing_net.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio::net;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ no `net` in `tokio`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -0,0 +1,58 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "tokio-net")]
|
||||
fn net_default() {
|
||||
use build_tests::tokio_net::driver::{set_default, Handle, Reactor, Registration};
|
||||
use build_tests::tokio_net::util::PollEvented;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "net-with-tcp")]
|
||||
fn net_with_tcp() {
|
||||
use build_tests::tokio_net::tcp;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "net-with-udp")]
|
||||
fn net_with_udp() {
|
||||
use build_tests::tokio_net::udp;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "net-with-uds")]
|
||||
fn net_with_udp() {
|
||||
use build_tests::tokio_net::uds;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "net-with-process")]
|
||||
fn net_with_process() {
|
||||
use build_tests::tokio_net::process;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "tokio-with-net")]
|
||||
fn tokio_with_net() {
|
||||
// net is present
|
||||
use build_tests::tokio::net;
|
||||
}
|
||||
#[test]
|
||||
fn compile_fail() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
#[cfg(feature = "executor-without-current-thread")]
|
||||
t.compile_fail("tests/fail/executor_without_current_thread.rs");
|
||||
|
||||
#[cfg(feature = "net-no-features")]
|
||||
{
|
||||
t.compile_fail("tests/fail/net_without_tcp_missing_tcp.rs");
|
||||
t.compile_fail("tests/fail/net_without_udp_missing_udp.rs");
|
||||
t.compile_fail("tests/fail/net_without_uds_missing_uds.rs");
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio-no-features")]
|
||||
t.compile_fail("tests/fail/tokio_without_net_missing_net.rs");
|
||||
|
||||
drop(t);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
parameters:
|
||||
noDefaultFeatures: '--no-default-features'
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-is-release.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- ${{ each feature in crate.value }}:
|
||||
- script: cargo check ${{ parameters.noDefaultFeatures }} --features ${{ feature }}
|
||||
displayName: Check `${{ crate.key }}`, features = ${{ feature }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
condition: and(succeeded(), not(variables['isRelease']))
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- ${{ each feature in crate.value }}:
|
||||
- script: cargo check ${{ parameters.noDefaultFeatures }} --features ${{ feature }}
|
||||
displayName: Check `${{ crate.key }}`, features = ${{ feature }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
@@ -0,0 +1,14 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Min supported Rust version
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust_version }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo check --all
|
||||
displayName: cargo check --all
|
||||
@@ -0,0 +1,16 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Clippy
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
- script: |
|
||||
rustup component add clippy
|
||||
cargo clippy --version
|
||||
displayName: Install clippy
|
||||
- script: |
|
||||
cargo clippy --all --all-features -- -A clippy::mutex-atomic
|
||||
displayName: cargo clippy --all
|
||||
@@ -0,0 +1,41 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
strategy:
|
||||
matrix:
|
||||
i686:
|
||||
vmImage: ubuntu-16.04
|
||||
target: i686-unknown-linux-gnu
|
||||
powerpc:
|
||||
vmImage: ubuntu-16.04
|
||||
target: powerpc-unknown-linux-gnu
|
||||
powerpc64:
|
||||
vmImage: ubuntu-16.04
|
||||
target: powerpc64-unknown-linux-gnu
|
||||
mips:
|
||||
vmImage: ubuntu-16.04
|
||||
target: mips-unknown-linux-gnu
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: sudo apt-get update
|
||||
displayName: apt-get update
|
||||
|
||||
- script: sudo apt-get install gcc-multilib
|
||||
displayName: Install gcc-multilib
|
||||
|
||||
- script: cargo install cross
|
||||
displayName: Install cross
|
||||
|
||||
# Always patch
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cross check --all --exclude tokio-tls --target $(target)
|
||||
displayName: Check source
|
||||
|
||||
# - script: cross check --tests --all --exclude tokio-tls --target $(target)
|
||||
# displayName: Check tests
|
||||
@@ -0,0 +1,39 @@
|
||||
parameters:
|
||||
dependsOn: []
|
||||
|
||||
jobs:
|
||||
- job: documentation
|
||||
displayName: 'Deploy API Documentation'
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
dependsOn:
|
||||
- ${{ parameters.dependsOn }}
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
# rust_version: stable
|
||||
rust_version: ${{ parameters.rust }}
|
||||
- script: |
|
||||
cargo doc --all --no-deps --all-features
|
||||
cp -R target/doc '$(Build.BinariesDirectory)'
|
||||
displayName: 'Generate Documentation'
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
git --version
|
||||
ls -la
|
||||
git init
|
||||
git config user.name 'Deployment Bot (from Azure Pipelines)'
|
||||
git config user.email '[email protected]'
|
||||
git config --global credential.helper 'store --file ~/.my-credentials'
|
||||
printf "protocol=https\nhost=github.com\nusername=carllerche\npassword=%s\n\n" "$GITHUB_TOKEN" | git credential-store --file ~/.my-credentials store
|
||||
git remote add origin https://github.com/tokio-rs/tokio
|
||||
git checkout -b gh-pages
|
||||
git add .
|
||||
git commit -m 'Deploy Tokio API documentation'
|
||||
git push -f origin gh-pages
|
||||
env:
|
||||
GITHUB_TOKEN: $(githubPersonalToken)
|
||||
workingDirectory: '$(Build.BinariesDirectory)'
|
||||
displayName: 'Deploy Documentation'
|
||||
@@ -0,0 +1,33 @@
|
||||
steps:
|
||||
# Linux and macOS.
|
||||
- script: |
|
||||
set -e
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none
|
||||
export PATH=$PATH:$HOME/.cargo/bin
|
||||
rustup toolchain install $RUSTUP_TOOLCHAIN
|
||||
rustup default $RUSTUP_TOOLCHAIN
|
||||
echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin"
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: ${{parameters.rust_version}}
|
||||
displayName: "Install rust (*nix)"
|
||||
condition: not(eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Windows.
|
||||
- script: |
|
||||
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||
rustup-init.exe -y --default-toolchain none
|
||||
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
rustup toolchain install %RUSTUP_TOOLCHAIN%
|
||||
rustup default %RUSTUP_TOOLCHAIN%
|
||||
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: ${{parameters.rust_version}}
|
||||
displayName: "Install rust (windows)"
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
|
||||
# All platforms.
|
||||
- script: |
|
||||
rustup toolchain list
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
displayName: Query rust and cargo versions
|
||||
@@ -0,0 +1,9 @@
|
||||
steps:
|
||||
- bash: |
|
||||
set -e
|
||||
|
||||
if git log --no-merges -1 --format='%B' | grep -qF '[ci-release]'; then
|
||||
echo "##vso[task.setvariable variable=isRelease]true"
|
||||
fi
|
||||
failOnStderr: true
|
||||
displayName: Check if release commit
|
||||
@@ -0,0 +1,16 @@
|
||||
steps:
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
# Remove any existing patch statements
|
||||
mv Cargo.toml Cargo.toml.bck
|
||||
sed -n '/\[patch.crates-io\]/q;p' Cargo.toml.bck > Cargo.toml
|
||||
|
||||
# Patch all crates
|
||||
cat ci/patch.toml >> Cargo.toml
|
||||
|
||||
# Print `Cargo.toml` for debugging
|
||||
echo "~~~~ Cargo.toml ~~~~"
|
||||
cat Cargo.toml
|
||||
echo "~~~~~~~~~~~~~~~~~~~~"
|
||||
displayName: Patch Cargo.toml
|
||||
@@ -0,0 +1,17 @@
|
||||
jobs:
|
||||
# Check formatting
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Check rustfmt
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
- script: |
|
||||
rustup component add rustfmt
|
||||
cargo fmt --version
|
||||
displayName: Install rustfmt
|
||||
- script: |
|
||||
cargo fmt --all -- --check
|
||||
displayName: Check formatting
|
||||
@@ -0,0 +1,19 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo check --all
|
||||
displayName: cargo check --all
|
||||
|
||||
# Check benches
|
||||
- script: cargo check --benches --all
|
||||
displayName: Check benchmarks
|
||||
@@ -0,0 +1,61 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
${{ if parameters.cross }}:
|
||||
MacOS:
|
||||
vmImage: macOS-10.13
|
||||
Windows:
|
||||
vmImage: vs2017-win2016
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
# rust_version: stable
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-is-release.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
# Run with default crate features
|
||||
- script: cargo test
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate.key }} - cargo test
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
|
||||
# Run with each specified feature
|
||||
- ${{ each feature in crate.value }}:
|
||||
- script: cargo test --no-default-features --features ${{ feature }}
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate.key }} - cargo test --features ${{ feature }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
# Run with default crate features
|
||||
- script: cargo test
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate.key }} - cargo test
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
|
||||
# Run with each specified feature
|
||||
- ${{ each feature in crate.value }}:
|
||||
- script: cargo test --no-default-features --features ${{ feature }}
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate.key }} - cargo test --features ${{ feature }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
@@ -0,0 +1,36 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: TSAN
|
||||
strategy:
|
||||
matrix:
|
||||
Timer:
|
||||
cmd: cargo test -p tokio-timer --test hammer
|
||||
Threadpool:
|
||||
cmd: cargo test -p tokio-executor --tests --features threadpool
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
# Make sure the benchmarks compile
|
||||
export ASAN_OPTIONS="detect_odr_violation=0 detect_leaks=0"
|
||||
export TSAN_OPTIONS="suppressions=`pwd`/ci/tsan"
|
||||
export RUST_BACKTRACE=1
|
||||
|
||||
# Run address sanitizer
|
||||
RUSTFLAGS="-Z sanitizer=address" \
|
||||
$(cmd) --target x86_64-unknown-linux-gnu
|
||||
|
||||
# Run thread sanitizer
|
||||
RUSTFLAGS="-Z sanitizer=thread" \
|
||||
$(cmd) --target x86_64-unknown-linux-gnu
|
||||
displayName: TSAN / MSAN
|
||||
env:
|
||||
TSAN: yes
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Patch dependencies to run all tests against versions of the crate in the
|
||||
# repository.
|
||||
[patch.crates-io]
|
||||
tokio = { path = "tokio" }
|
||||
tokio-codec = { path = "tokio-codec" }
|
||||
tokio-executor = { path = "tokio-executor" }
|
||||
tokio-fs = { path = "tokio-fs" }
|
||||
tokio-io = { path = "tokio-io" }
|
||||
tokio-macros = { path = "tokio-macros" }
|
||||
tokio-net = { path = "tokio-net" }
|
||||
tokio-sync = { path = "tokio-sync" }
|
||||
tokio-timer = { path = "tokio-timer" }
|
||||
tokio-tls = { path = "tokio-tls" }
|
||||
@@ -0,0 +1,39 @@
|
||||
# TSAN suppressions file for Tokio
|
||||
|
||||
# TSAN does not understand fences and `Arc::drop` is implemented using a fence.
|
||||
# This causes many false positives.
|
||||
race:Arc*drop
|
||||
race:Weak*drop
|
||||
|
||||
# `std` mpsc is not used in any Tokio code base. This race is triggered by some
|
||||
# rust runtime logic.
|
||||
race:std*mpsc_queue
|
||||
race:std*lang_start
|
||||
race:drop*std::thread*
|
||||
|
||||
# Probably more fences in std.
|
||||
race:__call_tls_dtors
|
||||
|
||||
# The epoch-based GC uses fences.
|
||||
race:crossbeam_epoch
|
||||
|
||||
# Push and steal operations in crossbeam-deque may cause data races, but such
|
||||
# data races are safe. If a data race happens, the value read by `steal` is
|
||||
# forgotten and the steal operation is then retried.
|
||||
race:crossbeam_deque*push
|
||||
race:crossbeam_deque*steal
|
||||
|
||||
# This filters out expected data race in the Treiber stack implementations.
|
||||
# Treiber stacks are inherently racy. The pop operation will attempt to access
|
||||
# the "next" pointer on the node it is attempting to pop. However, at this
|
||||
# point it has not gained ownership of the node and another thread might beat
|
||||
# it and take ownership of the node first (touching the next pointer). The
|
||||
# original pop operation will fail due to the ABA guard, but tsan still picks
|
||||
# up the access on the next pointer.
|
||||
race:Backup::next_sleeper
|
||||
race:Backup::set_next_sleeper
|
||||
race:WorkerEntry::set_next_sleeper
|
||||
|
||||
# This ignores a false positive caused by `thread::park()`/`thread::unpark()`.
|
||||
# See: https://github.com/rust-lang/rust/pull/54806#issuecomment-436193353
|
||||
race:pthread_cond_destroy
|
||||
@@ -1,54 +0,0 @@
|
||||
## Examples of how to use Tokio
|
||||
|
||||
This directory contains a number of examples showcasing various capabilities of
|
||||
the `tokio` crate.
|
||||
|
||||
All examples can be executed with:
|
||||
|
||||
```
|
||||
cargo run --example $name
|
||||
```
|
||||
|
||||
A high level description of each example is:
|
||||
|
||||
* [`hello_world`](hello_world.rs) - a tiny server that writes "hello world" to
|
||||
all connected clients and then terminates the connection, should help see how
|
||||
to create and initialize `tokio`.
|
||||
|
||||
* [`echo`](echo.rs) - this is your standard TCP "echo server" which accepts
|
||||
connections and then echos back any contents that are read from each connected
|
||||
client.
|
||||
|
||||
* [`echo-udp`](echo-udp.rs) - again your standard "echo server", except for UDP
|
||||
instead of TCP. This will echo back any packets received to the original
|
||||
sender.
|
||||
|
||||
* [`connect`](connect.rs) - this is a `nc`-like clone which can be used to
|
||||
interact with most other examples. The program creates a TCP connection or UDP
|
||||
socket to sends all information read on stdin to the remote peer, displaying
|
||||
any data received on stdout. Often quite useful when interacting with the
|
||||
various other servers here!
|
||||
|
||||
* [`chat`](chat.rs) - this spins up a local TCP server which will broadcast from
|
||||
any connected client to all other connected clients. You can connect to this
|
||||
in multiple terminals and use it to chat between the terminals.
|
||||
|
||||
* [`chat-combinator`](chat-combinator.rs) - Similar to `chat`, but this uses a
|
||||
much more functional programming approch using combinators.
|
||||
|
||||
* [`proxy`](proxy.rs) - an example proxy server that will forward all connected
|
||||
TCP clients to the remote address specified when starting the program.
|
||||
|
||||
* [`tinyhttp`](tinyhttp.rs) - a tiny HTTP/1.1 server which doesn't support HTTP
|
||||
request bodies showcasing running on multiple cores, working with futures and
|
||||
spawning tasks, and finally framing a TCP connection to discrete
|
||||
request/response objects.
|
||||
|
||||
* [`tinydb`](tinydb.rs) - an in-memory database which shows sharing state
|
||||
between all connected clients, notably the key/value store of this database.
|
||||
|
||||
* [`udp-client`](udp-client.rs) - a simple `send_dgram`/`recv_dgram` example.
|
||||
|
||||
If you've got an example you'd like to see here, please feel free to open an
|
||||
issue. Otherwise if you've got an example you'd like to add, please feel free
|
||||
to make a PR!
|
||||
@@ -1,150 +0,0 @@
|
||||
//! A chat server that broadcasts a message to all connections.
|
||||
//!
|
||||
//! This is a line-based server which accepts connections, reads lines from
|
||||
//! those connections, and broadcasts the lines to all other connected clients.
|
||||
//!
|
||||
//! This example is similar to chat.rs, but uses combinators and a much more
|
||||
//! functional style.
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! cargo run --example chat
|
||||
//!
|
||||
//! And then in another window run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! You can run the second command in multiple windows and then chat between the
|
||||
//! two, seeing the messages from the other client as they're received. For all
|
||||
//! connected clients they'll all join the same room and see everyone else's
|
||||
//! messages.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
extern crate futures;
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::iter;
|
||||
use std::env;
|
||||
use std::io::{BufReader};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
fn main() {
|
||||
// Create the TCP listener we'll accept connections on.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = addr.parse().unwrap();
|
||||
|
||||
let socket = TcpListener::bind(&addr).unwrap();
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// This is running on the Tokio runtime, so it will be multi-threaded. The
|
||||
// `Arc<Mutex<...>>` allows state to be shared across the threads.
|
||||
let connections = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
// The server task asynchronously iterates over and processes each incoming
|
||||
// connection.
|
||||
let srv = socket.incoming()
|
||||
.map_err(|e| println!("failed to accept socket; error = {:?}", e))
|
||||
.for_each(move |stream| {
|
||||
// The client's socket address
|
||||
let addr = stream.peer_addr().unwrap();
|
||||
|
||||
println!("New Connection: {}", addr);
|
||||
|
||||
// Split the TcpStream into two separate handles. One handle for reading
|
||||
// and one handle for writing. This lets us use separate tasks for
|
||||
// reading and writing.
|
||||
let (reader, writer) = stream.split();
|
||||
|
||||
// Create a channel for our stream, which other sockets will use to
|
||||
// send us messages. Then register our address with the stream to send
|
||||
// data to us.
|
||||
let (tx, rx) = futures::sync::mpsc::unbounded();
|
||||
connections.lock().unwrap().insert(addr, tx);
|
||||
|
||||
// Define here what we do for the actual I/O. That is, read a bunch of
|
||||
// lines from the socket and dispatch them while we also write any lines
|
||||
// from other sockets.
|
||||
let connections_inner = connections.clone();
|
||||
let reader = BufReader::new(reader);
|
||||
|
||||
// Model the read portion of this socket by mapping an infinite
|
||||
// iterator to each line off the socket. This "loop" is then
|
||||
// terminated with an error once we hit EOF on the socket.
|
||||
let iter = stream::iter_ok::<_, io::Error>(iter::repeat(()));
|
||||
|
||||
let socket_reader = iter.fold(reader, move |reader, _| {
|
||||
// Read a line off the socket, failing if we're at EOF
|
||||
let line = io::read_until(reader, b'\n', Vec::new());
|
||||
let line = line.and_then(|(reader, vec)| {
|
||||
if vec.len() == 0 {
|
||||
Err(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe"))
|
||||
} else {
|
||||
Ok((reader, vec))
|
||||
}
|
||||
});
|
||||
|
||||
// Convert the bytes we read into a string, and then send that
|
||||
// string to all other connected clients.
|
||||
let line = line.map(|(reader, vec)| {
|
||||
(reader, String::from_utf8(vec))
|
||||
});
|
||||
|
||||
// Move the connection state into the closure below.
|
||||
let connections = connections_inner.clone();
|
||||
|
||||
line.map(move |(reader, message)| {
|
||||
println!("{}: {:?}", addr, message);
|
||||
let mut conns = connections.lock().unwrap();
|
||||
|
||||
if let Ok(msg) = message {
|
||||
// For each open connection except the sender, send the
|
||||
// string via the channel.
|
||||
let iter = conns.iter_mut()
|
||||
.filter(|&(&k, _)| k != addr)
|
||||
.map(|(_, v)| v);
|
||||
for tx in iter {
|
||||
tx.unbounded_send(format!("{}: {}", addr, msg)).unwrap();
|
||||
}
|
||||
} else {
|
||||
let tx = conns.get_mut(&addr).unwrap();
|
||||
tx.unbounded_send("You didn't send valid UTF-8.".to_string()).unwrap();
|
||||
}
|
||||
|
||||
reader
|
||||
})
|
||||
});
|
||||
|
||||
// Whenever we receive a string on the Receiver, we write it to
|
||||
// `WriteHalf<TcpStream>`.
|
||||
let socket_writer = rx.fold(writer, |writer, msg| {
|
||||
let amt = io::write_all(writer, msg.into_bytes());
|
||||
let amt = amt.map(|(writer, _)| writer);
|
||||
amt.map_err(|_| ())
|
||||
});
|
||||
|
||||
// Now that we've got futures representing each half of the socket, we
|
||||
// use the `select` combinator to wait for either half to be done to
|
||||
// tear down the other. Then we spawn off the result.
|
||||
let connections = connections.clone();
|
||||
let socket_reader = socket_reader.map_err(|_| ());
|
||||
let connection = socket_reader.map(|_| ()).select(socket_writer.map(|_| ()));
|
||||
|
||||
// Spawn a task to process the connection
|
||||
tokio::spawn(connection.then(move |_| {
|
||||
connections.lock().unwrap().remove(&addr);
|
||||
println!("Connection {} closed.", addr);
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// execute server
|
||||
tokio::run(srv);
|
||||
}
|
||||
@@ -1,474 +0,0 @@
|
||||
//! A chat server that broadcasts a message to all connections.
|
||||
//!
|
||||
//! This example is explicitly more verbose than it has to be. This is to
|
||||
//! illustrate more concepts.
|
||||
//!
|
||||
//! A chat server for telnet clients. After a telnet client connects, the first
|
||||
//! line should contain the client's name. After that, all lines send by a
|
||||
//! client are broadcasted to all other connected clients.
|
||||
//!
|
||||
//! Because the client is telnet, lines are delimited by "\r\n".
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! cargo run --example chat
|
||||
//!
|
||||
//! And then in another terminal run:
|
||||
//!
|
||||
//! telnet localhost 6142
|
||||
//!
|
||||
//! You can run the `telnet` command in any number of additional windows.
|
||||
//!
|
||||
//! You can run the second command in multiple windows and then chat between the
|
||||
//! two, seeing the messages from the other client as they're received. For all
|
||||
//! connected clients they'll all join the same room and see everyone else's
|
||||
//! messages.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate bytes;
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
use futures::future::{self, Either};
|
||||
use bytes::{BytesMut, Bytes, BufMut};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Shorthand for the transmit half of the message channel.
|
||||
type Tx = mpsc::UnboundedSender<Bytes>;
|
||||
|
||||
/// Shorthand for the receive half of the message channel.
|
||||
type Rx = mpsc::UnboundedReceiver<Bytes>;
|
||||
|
||||
/// Data that is shared between all peers in the chat server.
|
||||
///
|
||||
/// This is the set of `Tx` handles for all connected clients. Whenever a
|
||||
/// message is received from a client, it is broadcasted to all peers by
|
||||
/// iterating over the `peers` entries and sending a copy of the message on each
|
||||
/// `Tx`.
|
||||
struct Shared {
|
||||
peers: HashMap<SocketAddr, Tx>,
|
||||
}
|
||||
|
||||
/// The state for each connected client.
|
||||
struct Peer {
|
||||
/// Name of the peer.
|
||||
///
|
||||
/// When a client connects, the first line sent is treated as the client's
|
||||
/// name (like alice or bob). The name is used to preface all messages that
|
||||
/// arrive from the client so that we can simulate a real chat server:
|
||||
///
|
||||
/// ```text
|
||||
/// alice: Hello everyone.
|
||||
/// bob: Welcome to telnet chat!
|
||||
/// ```
|
||||
name: BytesMut,
|
||||
|
||||
/// The TCP socket wrapped with the `Lines` codec, defined below.
|
||||
///
|
||||
/// This handles sending and receiving data on the socket. When using
|
||||
/// `Lines`, we can work at the line level instead of having to manage the
|
||||
/// raw byte operations.
|
||||
lines: Lines,
|
||||
|
||||
/// Handle to the shared chat state.
|
||||
///
|
||||
/// This is used to broadcast messages read off the socket to all connected
|
||||
/// peers.
|
||||
state: Arc<Mutex<Shared>>,
|
||||
|
||||
/// Receive half of the message channel.
|
||||
///
|
||||
/// This is used to receive messages from peers. When a message is received
|
||||
/// off of this `Rx`, it will be written to the socket.
|
||||
rx: Rx,
|
||||
|
||||
/// Client socket address.
|
||||
///
|
||||
/// The socket address is used as the key in the `peers` HashMap. The
|
||||
/// address is saved so that the `Peer` drop implementation can clean up its
|
||||
/// entry.
|
||||
addr: SocketAddr,
|
||||
}
|
||||
|
||||
/// Line based codec
|
||||
///
|
||||
/// This decorates a socket and presents a line based read / write interface.
|
||||
///
|
||||
/// As a user of `Lines`, we can focus on working at the line level. So, we send
|
||||
/// and receive values that represent entire lines. The `Lines` codec will
|
||||
/// handle the encoding and decoding as well as reading from and writing to the
|
||||
/// socket.
|
||||
#[derive(Debug)]
|
||||
struct Lines {
|
||||
/// The TCP socket.
|
||||
socket: TcpStream,
|
||||
|
||||
/// Buffer used when reading from the socket. Data is not returned from this
|
||||
/// buffer until an entire line has been read.
|
||||
rd: BytesMut,
|
||||
|
||||
/// Buffer used to stage data before writing it to the socket.
|
||||
wr: BytesMut,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
/// Create a new, empty, instance of `Shared`.
|
||||
fn new() -> Self {
|
||||
Shared {
|
||||
peers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Peer {
|
||||
/// Create a new instance of `Peer`.
|
||||
fn new(name: BytesMut,
|
||||
state: Arc<Mutex<Shared>>,
|
||||
lines: Lines) -> Peer
|
||||
{
|
||||
// Get the client socket address
|
||||
let addr = lines.socket.peer_addr().unwrap();
|
||||
|
||||
// Create a channel for this peer
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
// Add an entry for this `Peer` in the shared state map.
|
||||
state.lock().unwrap()
|
||||
.peers.insert(addr, tx);
|
||||
|
||||
Peer {
|
||||
name,
|
||||
lines,
|
||||
state,
|
||||
rx,
|
||||
addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is where a connected client is managed.
|
||||
///
|
||||
/// A `Peer` is also a future representing completly processing the client.
|
||||
///
|
||||
/// When a `Peer` is created, the first line (representing the client's name)
|
||||
/// has already been read. When the socket closes, the `Peer` future completes.
|
||||
///
|
||||
/// While processing, the peer future implementation will:
|
||||
///
|
||||
/// 1) Receive messages on its message channel and write them to the socket.
|
||||
/// 2) Receive messages from the socket and broadcast them to all peers.
|
||||
///
|
||||
impl Future for Peer {
|
||||
type Item = ();
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<(), io::Error> {
|
||||
// Tokio (and futures) use cooperative scheduling without any
|
||||
// preemption. If a task never yields execution back to the executor,
|
||||
// then other tasks may be starved.
|
||||
//
|
||||
// To deal with this, robust applications should not have any unbounded
|
||||
// loops. In this example, we will read at most `LINES_PER_TICK` lines
|
||||
// from the client on each tick.
|
||||
//
|
||||
// If the limit is hit, the current task is notified, informing the
|
||||
// executor to schedule the task again asap.
|
||||
const LINES_PER_TICK: usize = 10;
|
||||
|
||||
// Receive all messages from peers.
|
||||
for i in 0..LINES_PER_TICK {
|
||||
// Polling an `UnboundedReceiver` cannot fail, so `unwrap` here is
|
||||
// safe.
|
||||
match self.rx.poll().unwrap() {
|
||||
Async::Ready(Some(v)) => {
|
||||
// Buffer the line. Once all lines are buffered, they will
|
||||
// be flushed to the socket (right below).
|
||||
self.lines.buffer(&v);
|
||||
|
||||
// If this is the last iteration, the loop will break even
|
||||
// though there could still be lines to read. Because we did
|
||||
// not reach `Async::NotReady`, we have to notify ourselves
|
||||
// in order to tell the executor to schedule the task again.
|
||||
if i+1 == LINES_PER_TICK {
|
||||
task::current().notify();
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the write buffer to the socket
|
||||
let _ = self.lines.poll_flush()?;
|
||||
|
||||
// Read new lines from the socket
|
||||
while let Async::Ready(line) = self.lines.poll()? {
|
||||
println!("Received line ({:?}) : {:?}", self.name, line);
|
||||
|
||||
if let Some(message) = line {
|
||||
// Append the peer's name to the front of the line:
|
||||
let mut line = self.name.clone();
|
||||
line.put(": ");
|
||||
line.put(&message);
|
||||
line.put("\r\n");
|
||||
|
||||
// We're using `Bytes`, which allows zero-copy clones (by
|
||||
// storing the data in an Arc internally).
|
||||
//
|
||||
// However, before cloning, we must freeze the data. This
|
||||
// converts it from mutable -> immutable, allowing zero copy
|
||||
// cloning.
|
||||
let line = line.freeze();
|
||||
|
||||
// Now, send the line to all other peers
|
||||
for (addr, tx) in &self.state.lock().unwrap().peers {
|
||||
// Don't send the message to ourselves
|
||||
if *addr != self.addr {
|
||||
// The send only fails if the rx half has been dropped,
|
||||
// however this is impossible as the `tx` half will be
|
||||
// removed from the map before the `rx` is dropped.
|
||||
tx.unbounded_send(line.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// EOF was reached. The remote client has disconnected. There is
|
||||
// nothing more to do.
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
}
|
||||
|
||||
// As always, it is important to not just return `NotReady` without
|
||||
// ensuring an inner future also returned `NotReady`.
|
||||
//
|
||||
// We know we got a `NotReady` from either `self.rx` or `self.lines`, so
|
||||
// the contract is respected.
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Peer {
|
||||
fn drop(&mut self) {
|
||||
self.state.lock().unwrap().peers
|
||||
.remove(&self.addr);
|
||||
}
|
||||
}
|
||||
|
||||
impl Lines {
|
||||
/// Create a new `Lines` codec backed by the socket
|
||||
fn new(socket: TcpStream) -> Self {
|
||||
Lines {
|
||||
socket,
|
||||
rd: BytesMut::new(),
|
||||
wr: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Buffer a line.
|
||||
///
|
||||
/// This writes the line to an internal buffer. Calls to `poll_flush` will
|
||||
/// attempt to flush this buffer to the socket.
|
||||
fn buffer(&mut self, line: &[u8]) {
|
||||
// Ensure the buffer has capacity. Ideally this would not be unbounded,
|
||||
// but to keep the example simple, we will not limit this.
|
||||
self.wr.reserve(line.len());
|
||||
|
||||
// Push the line onto the end of the write buffer.
|
||||
//
|
||||
// The `put` function is from the `BufMut` trait.
|
||||
self.wr.put(line);
|
||||
}
|
||||
|
||||
/// Flush the write buffer to the socket
|
||||
fn poll_flush(&mut self) -> Poll<(), io::Error> {
|
||||
// As long as there is buffered data to write, try to write it.
|
||||
while !self.wr.is_empty() {
|
||||
// Try to read some bytes from the socket
|
||||
let n = try_ready!(self.socket.poll_write(&self.wr));
|
||||
|
||||
// As long as the wr is not empty, a successful write should
|
||||
// never write 0 bytes.
|
||||
assert!(n > 0);
|
||||
|
||||
// This discards the first `n` bytes of the buffer.
|
||||
let _ = self.wr.split_to(n);
|
||||
}
|
||||
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
/// Read data from the socket.
|
||||
///
|
||||
/// This only returns `Ready` when the socket has closed.
|
||||
fn fill_read_buf(&mut self) -> Poll<(), io::Error> {
|
||||
loop {
|
||||
// Ensure the read buffer has capacity.
|
||||
//
|
||||
// This might result in an internal allocation.
|
||||
self.rd.reserve(1024);
|
||||
|
||||
// Read data into the buffer.
|
||||
let n = try_ready!(self.socket.read_buf(&mut self.rd));
|
||||
|
||||
if n == 0 {
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Lines {
|
||||
type Item = BytesMut;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
// First, read any new data that might have been received off the socket
|
||||
let sock_closed = self.fill_read_buf()?.is_ready();
|
||||
|
||||
// Now, try finding lines
|
||||
let pos = self.rd.windows(2).enumerate()
|
||||
.find(|&(_, bytes)| bytes == b"\r\n")
|
||||
.map(|(i, _)| i);
|
||||
|
||||
if let Some(pos) = pos {
|
||||
// Remove the line from the read buffer and set it to `line`.
|
||||
let mut line = self.rd.split_to(pos + 2);
|
||||
|
||||
// Drop the trailing \r\n
|
||||
line.split_off(pos);
|
||||
|
||||
// Return the line
|
||||
return Ok(Async::Ready(Some(line)));
|
||||
}
|
||||
|
||||
if sock_closed {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a task to manage the socket.
|
||||
///
|
||||
/// This will read the first line from the socket to identify the client, then
|
||||
/// add the client to the set of connected peers in the chat service.
|
||||
fn process(socket: TcpStream, state: Arc<Mutex<Shared>>) {
|
||||
// Wrap the socket with the `Lines` codec that we wrote above.
|
||||
//
|
||||
// By doing this, we can operate at the line level instead of doing raw byte
|
||||
// manipulation.
|
||||
let lines = Lines::new(socket);
|
||||
|
||||
// The first line is treated as the client's name. The client is not added
|
||||
// to the set of connected peers until this line is received.
|
||||
//
|
||||
// We use the `into_future` combinator to extract the first item from the
|
||||
// lines stream. `into_future` takes a `Stream` and converts it to a future
|
||||
// of `(first, rest)` where `rest` is the original stream instance.
|
||||
let connection = lines.into_future()
|
||||
// `into_future` doesn't have the right error type, so map the error to
|
||||
// make it work.
|
||||
.map_err(|(e, _)| e)
|
||||
// Process the first received line as the client's name.
|
||||
.and_then(|(name, lines)| {
|
||||
// If `name` is `None`, then the client disconnected without
|
||||
// actually sending a line of data.
|
||||
//
|
||||
// Since the connection is closed, there is no further work that we
|
||||
// need to do. So, we just terminate processing by returning
|
||||
// `future::ok()`.
|
||||
//
|
||||
// The problem is that only a single future type can be returned
|
||||
// from a combinator closure, but we want to return both
|
||||
// `future::ok()` and `Peer` (below).
|
||||
//
|
||||
// This is a common problem, so the `futures` crate solves this by
|
||||
// providing the `Either` helper enum that allows creating a single
|
||||
// return type that covers two concrete future types.
|
||||
let name = match name {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
// The remote client closed the connection without sending
|
||||
// any data.
|
||||
return Either::A(future::ok(()));
|
||||
}
|
||||
};
|
||||
|
||||
println!("`{:?}` is joining the chat", name);
|
||||
|
||||
// Create the peer.
|
||||
//
|
||||
// This is also a future that processes the connection, only
|
||||
// completing when the socket closes.
|
||||
let peer = Peer::new(
|
||||
name,
|
||||
state,
|
||||
lines);
|
||||
|
||||
// Wrap `peer` with `Either::B` to make the return type fit.
|
||||
Either::B(peer)
|
||||
})
|
||||
// Task futures have an error of type `()`, this ensures we handle the
|
||||
// error. We do this by printing the error to STDOUT.
|
||||
.map_err(|e| {
|
||||
println!("connection error = {:?}", e);
|
||||
});
|
||||
|
||||
// Spawn the task. Internally, this submits the task to a thread pool.
|
||||
tokio::spawn(connection);
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
// Create the shared state. This is how all the peers communicate.
|
||||
//
|
||||
// The server task will hold a handle to this. For every new client, the
|
||||
// `state` handle is cloned and passed into the task that processes the
|
||||
// client connection.
|
||||
let state = Arc::new(Mutex::new(Shared::new()));
|
||||
|
||||
let addr = "127.0.0.1:6142".parse().unwrap();
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
|
||||
// The server task asynchronously iterates over and processes each
|
||||
// incoming connection.
|
||||
let server = listener.incoming().for_each(move |socket| {
|
||||
// Spawn a task to process the connection
|
||||
process(socket, state.clone());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
// All tasks must have an `Error` type of `()`. This forces error
|
||||
// handling and helps avoid silencing failures.
|
||||
//
|
||||
// In our example, we are only going to log the error to STDOUT.
|
||||
println!("accept error = {:?}", err);
|
||||
});
|
||||
|
||||
println!("server running on localhost:6142");
|
||||
|
||||
// Start the Tokio runtime.
|
||||
//
|
||||
// The Tokio is a pre-configured "out of the box" runtime for building
|
||||
// asynchronous applications. It includes both a reactor and a task
|
||||
// scheduler. This means applications are multithreaded by default.
|
||||
//
|
||||
// This function blocks until the runtime reaches an idle state. Idle is
|
||||
// defined as all spawned tasks have completed and all I/O resources (TCP
|
||||
// sockets in our case) have been dropped.
|
||||
//
|
||||
// In our example, we have not defined a shutdown strategy, so this will
|
||||
// block until `ctrl-c` is pressed at the terminal.
|
||||
tokio::run(server);
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
//! An example of hooking up stdin/stdout to either a TCP or UDP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. An optional `--udp` argument can be passed to specify
|
||||
//! that the connection should be made over UDP instead of TCP, translating each
|
||||
//! line entered on stdin to a UDP packet to be sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate futures;
|
||||
extern crate bytes;
|
||||
|
||||
use std::env;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::SocketAddr;
|
||||
use std::thread;
|
||||
|
||||
use tokio::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
|
||||
fn main() {
|
||||
// Determine if we're going to run in TCP or UDP mode
|
||||
let mut args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let tcp = match args.iter().position(|a| a == "--udp") {
|
||||
Some(i) => {
|
||||
args.remove(i);
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
// Parse what address we're going to connect to
|
||||
let addr = args.first().unwrap_or_else(|| {
|
||||
panic!("this program requires at least one argument")
|
||||
});
|
||||
let addr = addr.parse::<SocketAddr>().unwrap();
|
||||
|
||||
// Right now Tokio doesn't support a handle to stdin running on the event
|
||||
// loop, so we farm out that work to a separate thread. This thread will
|
||||
// read data (with blocking I/O) from stdin and then send it to the event
|
||||
// loop over a standard futures channel.
|
||||
let (stdin_tx, stdin_rx) = mpsc::channel(0);
|
||||
thread::spawn(|| read_stdin(stdin_tx));
|
||||
let stdin_rx = stdin_rx.map_err(|_| panic!()); // errors not possible on rx
|
||||
|
||||
// Now that we've got our stdin read we either set up our TCP connection or
|
||||
// our UDP connection to get a stream of bytes we're going to emit to
|
||||
// stdout.
|
||||
let stdout = if tcp {
|
||||
tcp::connect(&addr, Box::new(stdin_rx))
|
||||
} else {
|
||||
udp::connect(&addr, Box::new(stdin_rx))
|
||||
};
|
||||
|
||||
// And now with our stream of bytes to write to stdout, we execute that in
|
||||
// the event loop! Note that this is doing blocking I/O to emit data to
|
||||
// stdout, and in general it's a no-no to do that sort of work on the event
|
||||
// loop. In this case, though, we know it's ok as the event loop isn't
|
||||
// otherwise running anything useful.
|
||||
let mut out = io::stdout();
|
||||
|
||||
tokio::run({
|
||||
stdout
|
||||
.for_each(move |chunk| {
|
||||
out.write_all(&chunk)
|
||||
})
|
||||
.map_err(|e| println!("error reading stdout; error = {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
mod codec {
|
||||
use std::io;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use tokio_io::codec::{Encoder, Decoder};
|
||||
|
||||
/// A simple `Codec` implementation that just ships bytes around.
|
||||
///
|
||||
/// This type is used for "framing" a TCP/UDP stream of bytes but it's really
|
||||
/// just a convenient method for us to work with streams/sinks for now.
|
||||
/// This'll just take any data read and interpret it as a "frame" and
|
||||
/// conversely just shove data into the output location without looking at
|
||||
/// it.
|
||||
pub struct Bytes;
|
||||
|
||||
impl Decoder for Bytes {
|
||||
type Item = BytesMut;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
||||
if buf.len() > 0 {
|
||||
let len = buf.len();
|
||||
Ok(Some(buf.split_to(len)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for Bytes {
|
||||
type Item = Vec<u8>;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, data: Vec<u8>, buf: &mut BytesMut) -> io::Result<()> {
|
||||
buf.put(&data[..]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod tcp {
|
||||
use tokio;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use bytes::BytesMut;
|
||||
use codec::Bytes;
|
||||
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub fn connect(addr: &SocketAddr,
|
||||
stdin: Box<Stream<Item = Vec<u8>, Error = io::Error> + Send>)
|
||||
-> Box<Stream<Item = BytesMut, Error = io::Error> + Send>
|
||||
{
|
||||
let tcp = TcpStream::connect(addr);
|
||||
|
||||
// After the TCP connection has been established, we set up our client
|
||||
// to start forwarding data.
|
||||
//
|
||||
// First we use the `Io::framed` method with a simple implementation of
|
||||
// a `Codec` (listed below) that just ships bytes around. We then split
|
||||
// that in two to work with the stream and sink separately.
|
||||
//
|
||||
// Half of the work we're going to do is to take all data we receive on
|
||||
// `stdin` and send that along the TCP stream (`sink`). The second half
|
||||
// is to take all the data we receive (`stream`) and then write that to
|
||||
// stdout. We'll be passing this handle back out from this method.
|
||||
//
|
||||
// You'll also note that we *spawn* the work to read stdin and write it
|
||||
// to the TCP stream. This is done to ensure that happens concurrently
|
||||
// with us reading data from the stream.
|
||||
Box::new(tcp.map(move |stream| {
|
||||
let (sink, stream) = stream.framed(Bytes).split();
|
||||
|
||||
tokio::spawn(stdin.forward(sink).then(|result| {
|
||||
if let Err(e) = result {
|
||||
panic!("failed to write to socket: {}", e)
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
stream
|
||||
}).flatten_stream())
|
||||
}
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio;
|
||||
use tokio::net::{UdpSocket, UdpFramed};
|
||||
use tokio::prelude::*;
|
||||
use bytes::BytesMut;
|
||||
|
||||
use codec::Bytes;
|
||||
|
||||
pub fn connect(&addr: &SocketAddr,
|
||||
stdin: Box<Stream<Item = Vec<u8>, Error = io::Error> + Send>)
|
||||
-> Box<Stream<Item = BytesMut, Error = io::Error> + Send>
|
||||
{
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
let addr_to_bind = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0".parse().unwrap()
|
||||
} else {
|
||||
"[::]:0".parse().unwrap()
|
||||
};
|
||||
let udp = UdpSocket::bind(&addr_to_bind)
|
||||
.expect("failed to bind socket");
|
||||
|
||||
// Like above with TCP we use an instance of `Bytes` codec to transform
|
||||
// this UDP socket into a framed sink/stream which operates over
|
||||
// discrete values. In this case we're working with *pairs* of socket
|
||||
// addresses and byte buffers.
|
||||
let (sink, stream) = UdpFramed::new(udp, Bytes).split();
|
||||
|
||||
// All bytes from `stdin` will go to the `addr` specified in our
|
||||
// argument list. Like with TCP this is spawned concurrently
|
||||
let forward_stdin = stdin.map(move |chunk| {
|
||||
(chunk, addr)
|
||||
}).forward(sink).then(|result| {
|
||||
if let Err(e) = result {
|
||||
panic!("failed to write to socket: {}", e)
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// With UDP we could receive data from any source, so filter out
|
||||
// anything coming from a different address
|
||||
let receive = stream.filter_map(move |(chunk, src)| {
|
||||
if src == addr {
|
||||
Some(chunk.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
Box::new(future::lazy(|| {
|
||||
tokio::spawn(forward_stdin);
|
||||
future::ok(receive)
|
||||
}).flatten_stream())
|
||||
}
|
||||
}
|
||||
|
||||
// Our helper method which will read data from stdin and send it along the
|
||||
// sender provided.
|
||||
fn read_stdin(mut tx: mpsc::Sender<Vec<u8>>) {
|
||||
let mut stdin = io::stdin();
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = match stdin.read(&mut buf) {
|
||||
Err(_) |
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
};
|
||||
buf.truncate(n);
|
||||
tx = match tx.send(buf).wait() {
|
||||
Ok(tx) => tx,
|
||||
Err(_) => break,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
//! A "hello world" echo server with Tokio
|
||||
//!
|
||||
//! This server will create a TCP listener, accept connections in a loop, and
|
||||
//! write back everything that's read off of each TCP connection.
|
||||
//!
|
||||
//! Because the Tokio runtime uses a thread poool, each TCP connection is
|
||||
//! processed concurrently with all other TCP connections across multiple
|
||||
//! threads.
|
||||
//!
|
||||
//! To see this server in action, you can run this in one terminal:
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be echo'd back to
|
||||
//! you! If you open up multiple terminals running the `connect` example you
|
||||
//! should be able to see them all make progress simultaneously.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use std::env;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
fn main() {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
// program, but otherwise we'll just set up our TCP listener on
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = addr.parse::<SocketAddr>().unwrap();
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop, so we pass in a handle
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let socket = TcpListener::bind(&addr).unwrap();
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Here we convert the `TcpListener` to a stream of incoming connections
|
||||
// with the `incoming` method. We then define how to process each element in
|
||||
// the stream with the `for_each` method.
|
||||
//
|
||||
// This combinator, defined on the `Stream` trait, will allow us to define a
|
||||
// computation to happen for all items on the stream (in this case TCP
|
||||
// connections made to the server). The return value of the `for_each`
|
||||
// method is itself a future representing processing the entire stream of
|
||||
// connections, and ends up being our server.
|
||||
let done = socket.incoming()
|
||||
.map_err(|e| println!("failed to accept socket; error = {:?}", e))
|
||||
.for_each(move |socket| {
|
||||
// Once we're inside this closure this represents an accepted client
|
||||
// from our server. The `socket` is the client connection (similar to
|
||||
// how the standard library operates).
|
||||
//
|
||||
// We just want to copy all data read from the socket back onto the
|
||||
// socket itself (e.g. "echo"). We can use the standard `io::copy`
|
||||
// combinator in the `tokio-core` crate to do precisely this!
|
||||
//
|
||||
// The `copy` function takes two arguments, where to read from and where
|
||||
// to write to. We only have one argument, though, with `socket`.
|
||||
// Luckily there's a method, `Io::split`, which will split an Read/Write
|
||||
// stream into its two halves. This operation allows us to work with
|
||||
// each stream independently, such as pass them as two arguments to the
|
||||
// `copy` function.
|
||||
//
|
||||
// The `copy` function then returns a future, and this future will be
|
||||
// resolved when the copying operation is complete, resolving to the
|
||||
// amount of data that was copied.
|
||||
let (reader, writer) = socket.split();
|
||||
let amt = io::copy(reader, writer);
|
||||
|
||||
// After our copy operation is complete we just print out some helpful
|
||||
// information.
|
||||
let msg = amt.then(move |result| {
|
||||
match result {
|
||||
Ok((amt, _, _)) => println!("wrote {} bytes", amt),
|
||||
Err(e) => println!("error: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
|
||||
// And this is where much of the magic of this server happens. We
|
||||
// crucially want all clients to make progress concurrently, rather than
|
||||
// blocking one on completion of another. To achieve this we use the
|
||||
// `tokio::spawn` function to execute the work in the background.
|
||||
//
|
||||
// This function will transfer ownership of the future (`msg` in this
|
||||
// case) to the Tokio runtime thread pool that. The thread pool will
|
||||
// drive the future to completion.
|
||||
//
|
||||
// Essentially here we're executing a new task to run concurrently,
|
||||
// which will allow all of our clients to be processed concurrently.
|
||||
tokio::spawn(msg)
|
||||
});
|
||||
|
||||
// And finally now that we've define what our server is, we run it!
|
||||
//
|
||||
// This starts the Tokio runtime, spawns the server task, and blocks the
|
||||
// current thread until all tasks complete execution. Since the `done` task
|
||||
// never completes (it just keeps accepting sockets), `tokio::run` blocks
|
||||
// forever (until ctrl-c is pressed).
|
||||
tokio::run(done);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
//! Hello world server.
|
||||
//!
|
||||
//! A simple server that accepts connections, writes "hello world\n", and closes
|
||||
//! the connection.
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! cargo run --example hello_world
|
||||
//!
|
||||
//! And then in another terminal run:
|
||||
//!
|
||||
//! telnet localhost 6142
|
||||
//!
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
|
||||
pub fn main() {
|
||||
let addr = "127.0.0.1:6142".parse().unwrap();
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
|
||||
// The server task asynchronously iterates over and processes each
|
||||
// incoming connection.
|
||||
let server = listener.incoming().for_each(|socket| {
|
||||
println!("accepted socket; addr={:?}", socket.peer_addr().unwrap());
|
||||
|
||||
let connection = io::write_all(socket, "hello world\n")
|
||||
.then(|res| {
|
||||
println!("wrote message; success={:?}", res.is_ok());
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Spawn a new task that processes the socket:
|
||||
tokio::spawn(connection);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
// All tasks must have an `Error` type of `()`. This forces error
|
||||
// handling and helps avoid silencing failures.
|
||||
//
|
||||
// In our example, we are only going to log the error to STDOUT.
|
||||
println!("accept error = {:?}", err);
|
||||
});
|
||||
|
||||
println!("server running on localhost:6142");
|
||||
|
||||
// Start the Tokio runtime.
|
||||
//
|
||||
// The Tokio is a pre-configured "out of the box" runtime for building
|
||||
// asynchronous applications. It includes both a reactor and a task
|
||||
// scheduler. This means applications are multithreaded by default.
|
||||
//
|
||||
// This function blocks until the runtime reaches an idle state. Idle is
|
||||
// defined as all spawned tasks have completed and all I/O resources (TCP
|
||||
// sockets in our case) have been dropped.
|
||||
//
|
||||
// In our example, we have not defined a shutdown strategy, so this will
|
||||
// block until `ctrl-c` is pressed at the terminal.
|
||||
tokio::run(server);
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
//! A proxy that forwards data to another server and forwards that server's
|
||||
//! responses back to clients.
|
||||
//!
|
||||
//! Because the Tokio runtime uses a thread poool, each TCP connection is
|
||||
//! processed concurrently with all other TCP connections across multiple
|
||||
//! threads.
|
||||
//!
|
||||
//! You can showcase this by running this in one terminal:
|
||||
//!
|
||||
//! cargo run --example proxy
|
||||
//!
|
||||
//! This in another terminal
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//!
|
||||
//! And finally this in another terminal
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8081
|
||||
//!
|
||||
//! This final terminal will connect to our proxy, which will in turn connect to
|
||||
//! the echo server, and you'll be able to see data flowing between them.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::env;
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use tokio::io::{copy, shutdown};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let listen_addr = env::args().nth(1).unwrap_or("127.0.0.1:8081".to_string());
|
||||
let listen_addr = listen_addr.parse::<SocketAddr>().unwrap();
|
||||
|
||||
let server_addr = env::args().nth(2).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let server_addr = server_addr.parse::<SocketAddr>().unwrap();
|
||||
|
||||
// Create a TCP listener which will listen for incoming connections.
|
||||
let socket = TcpListener::bind(&listen_addr).unwrap();
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
let done = socket.incoming()
|
||||
.map_err(|e| println!("error accepting socket; error = {:?}", e))
|
||||
.for_each(move |client| {
|
||||
let server = TcpStream::connect(&server_addr);
|
||||
let amounts = server.and_then(move |server| {
|
||||
// Create separate read/write handles for the TCP clients that we're
|
||||
// proxying data between. Note that typically you'd use
|
||||
// `AsyncRead::split` for this operation, but we want our writer
|
||||
// handles to have a custom implementation of `shutdown` which
|
||||
// actually calls `TcpStream::shutdown` to ensure that EOF is
|
||||
// transmitted properly across the proxied connection.
|
||||
//
|
||||
// As a result, we wrap up our client/server manually in arcs and
|
||||
// use the impls below on our custom `MyTcpStream` type.
|
||||
let client_reader = MyTcpStream(Arc::new(Mutex::new(client)));
|
||||
let client_writer = client_reader.clone();
|
||||
let server_reader = MyTcpStream(Arc::new(Mutex::new(server)));
|
||||
let server_writer = server_reader.clone();
|
||||
|
||||
// Copy the data (in parallel) between the client and the server.
|
||||
// After the copy is done we indicate to the remote side that we've
|
||||
// finished by shutting down the connection.
|
||||
let client_to_server = copy(client_reader, server_writer)
|
||||
.and_then(|(n, _, server_writer)| {
|
||||
shutdown(server_writer).map(move |_| n)
|
||||
});
|
||||
|
||||
let server_to_client = copy(server_reader, client_writer)
|
||||
.and_then(|(n, _, client_writer)| {
|
||||
shutdown(client_writer).map(move |_| n)
|
||||
});
|
||||
|
||||
client_to_server.join(server_to_client)
|
||||
});
|
||||
|
||||
let msg = amounts.map(move |(from_client, from_server)| {
|
||||
println!("client wrote {} bytes and received {} bytes",
|
||||
from_client, from_server);
|
||||
}).map_err(|e| {
|
||||
// Don't panic. Maybe the client just disconnected too soon.
|
||||
println!("error: {}", e);
|
||||
});
|
||||
|
||||
tokio::spawn(msg);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
tokio::run(done);
|
||||
}
|
||||
|
||||
// This is a custom type used to have a custom implementation of the
|
||||
// `AsyncWrite::shutdown` method which actually calls `TcpStream::shutdown` to
|
||||
// notify the remote end that we're done writing.
|
||||
#[derive(Clone)]
|
||||
struct MyTcpStream(Arc<Mutex<TcpStream>>);
|
||||
|
||||
impl Read for MyTcpStream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.lock().unwrap().read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for MyTcpStream {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.lock().unwrap().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for MyTcpStream {}
|
||||
|
||||
impl AsyncWrite for MyTcpStream {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
try!(self.0.lock().unwrap().shutdown(Shutdown::Write));
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
//! A "tiny database" and accompanying protocol
|
||||
//!
|
||||
//! This example shows the usage of shared state amongst all connected clients,
|
||||
//! namely a database of key/value pairs. Each connected client can send a
|
||||
//! series of GET/SET commands to query the current value of a key or set the
|
||||
//! value of a key.
|
||||
//!
|
||||
//! This example has a simple protocol you can use to interact with the server.
|
||||
//! To run, first run this in one terminal window:
|
||||
//!
|
||||
//! cargo run --example tinydb
|
||||
//!
|
||||
//! and next in another windows run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! In the `connect` window you can type in commands where when you hit enter
|
||||
//! you'll get a response from the server for that command. An example session
|
||||
//! is:
|
||||
//!
|
||||
//!
|
||||
//! $ cargo run --example connect 127.0.0.1:8080
|
||||
//! GET foo
|
||||
//! foo = bar
|
||||
//! GET FOOBAR
|
||||
//! error: no key FOOBAR
|
||||
//! SET FOOBAR my awesome string
|
||||
//! set FOOBAR = `my awesome string`, previous: None
|
||||
//! SET foo tokio
|
||||
//! set foo = `tokio`, previous: Some("bar")
|
||||
//! GET foo
|
||||
//! foo = tokio
|
||||
//!
|
||||
//! Namely you can issue two forms of commands:
|
||||
//!
|
||||
//! * `GET $key` - this will fetch the value of `$key` from the database and
|
||||
//! return it. The server's database is initially populated with the key `foo`
|
||||
//! set to the value `bar`
|
||||
//! * `SET $key $value` - this will set the value of `$key` to `$value`,
|
||||
//! returning the previous value, if any.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::BufReader;
|
||||
use std::env;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use tokio::io::{lines, write_all};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
|
||||
/// The in-memory database shared amongst all clients.
|
||||
///
|
||||
/// This database will be shared via `Arc`, so to mutate the internal map we're
|
||||
/// also going to use a `RefCell` for interior mutability.
|
||||
struct Database {
|
||||
map: Mutex<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// Possible requests our clients can send us
|
||||
enum Request {
|
||||
Get { key: String },
|
||||
Set { key: String, value: String },
|
||||
}
|
||||
|
||||
/// Responses to the `Request` commands above
|
||||
enum Response {
|
||||
Value { key: String, value: String },
|
||||
Set { key: String, value: String, previous: Option<String> },
|
||||
Error { msg: String },
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Parse the address we're going to run this server on
|
||||
// and set up our TCP listener to accept connections.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = addr.parse::<SocketAddr>().unwrap();
|
||||
let listener = TcpListener::bind(&addr).expect("failed to bind");
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Create the shared state of this server that will be shared amongst all
|
||||
// clients. We populate the initial database and then create the `Database`
|
||||
// structure. Note the usage of `Arc` here which will be used to ensure that
|
||||
// each independently spawned client will have a reference to the in-memory
|
||||
// database.
|
||||
let mut initial_db = HashMap::new();
|
||||
initial_db.insert("foo".to_string(), "bar".to_string());
|
||||
let db = Arc::new(Database {
|
||||
map: Mutex::new(initial_db),
|
||||
});
|
||||
|
||||
let done = listener.incoming()
|
||||
.map_err(|e| println!("error accepting socket; error = {:?}", e))
|
||||
.for_each(move |socket| {
|
||||
// As with many other small examples, the first thing we'll do is
|
||||
// *split* this TCP stream into two separately owned halves. This'll
|
||||
// allow us to work with the read and write halves independently.
|
||||
let (reader, writer) = socket.split();
|
||||
|
||||
// Since our protocol is line-based we use `tokio_io`'s `lines` utility
|
||||
// to convert our stream of bytes, `reader`, into a `Stream` of lines.
|
||||
let lines = lines(BufReader::new(reader));
|
||||
|
||||
// Here's where the meat of the processing in this server happens. First
|
||||
// we see a clone of the database being created, which is creating a
|
||||
// new reference for this connected client to use. Also note the `move`
|
||||
// keyword on the closure here which moves ownership of the reference
|
||||
// into the closure, which we'll need for spawning the client below.
|
||||
//
|
||||
// The `map` function here means that we'll run some code for all
|
||||
// requests (lines) we receive from the client. The actual handling here
|
||||
// is pretty simple, first we parse the request and if it's valid we
|
||||
// generate a response based on the values in the database.
|
||||
let db = db.clone();
|
||||
let responses = lines.map(move |line| {
|
||||
let request = match Request::parse(&line) {
|
||||
Ok(req) => req,
|
||||
Err(e) => return Response::Error { msg: e },
|
||||
};
|
||||
|
||||
let mut db = db.map.lock().unwrap();
|
||||
match request {
|
||||
Request::Get { key } => {
|
||||
match db.get(&key) {
|
||||
Some(value) => Response::Value { key, value: value.clone() },
|
||||
None => Response::Error { msg: format!("no key {}", key) },
|
||||
}
|
||||
}
|
||||
Request::Set { key, value } => {
|
||||
let previous = db.insert(key.clone(), value.clone());
|
||||
Response::Set { key, value, previous }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// At this point `responses` is a stream of `Response` types which we
|
||||
// now want to write back out to the client. To do that we use
|
||||
// `Stream::fold` to perform a loop here, serializing each response and
|
||||
// then writing it out to the client.
|
||||
let writes = responses.fold(writer, |writer, response| {
|
||||
let mut response = response.serialize();
|
||||
response.push('\n');
|
||||
write_all(writer, response.into_bytes()).map(|(w, _)| w)
|
||||
});
|
||||
|
||||
// Like with other small servers, we'll `spawn` this client to ensure it
|
||||
// runs concurrently with all other clients, for now ignoring any errors
|
||||
// that we see.
|
||||
let msg = writes.then(move |_| Ok(()));
|
||||
|
||||
tokio::spawn(msg)
|
||||
});
|
||||
|
||||
tokio::run(done);
|
||||
}
|
||||
|
||||
impl Request {
|
||||
fn parse(input: &str) -> Result<Request, String> {
|
||||
let mut parts = input.splitn(3, " ");
|
||||
match parts.next() {
|
||||
Some("GET") => {
|
||||
let key = match parts.next() {
|
||||
Some(key) => key,
|
||||
None => return Err(format!("GET must be followed by a key")),
|
||||
};
|
||||
if parts.next().is_some() {
|
||||
return Err(format!("GET's key must not be followed by anything"))
|
||||
}
|
||||
Ok(Request::Get { key: key.to_string() })
|
||||
}
|
||||
Some("SET") => {
|
||||
let key = match parts.next() {
|
||||
Some(key) => key,
|
||||
None => return Err(format!("SET must be followed by a key")),
|
||||
};
|
||||
let value = match parts.next() {
|
||||
Some(value) => value,
|
||||
None => return Err(format!("SET needs a value")),
|
||||
};
|
||||
Ok(Request::Set { key: key.to_string(), value: value.to_string() })
|
||||
}
|
||||
Some(cmd) => Err(format!("unknown command: {}", cmd)),
|
||||
None => Err(format!("empty input")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
fn serialize(&self) -> String {
|
||||
match *self {
|
||||
Response::Value { ref key, ref value } => {
|
||||
format!("{} = {}", key, value)
|
||||
}
|
||||
Response::Set { ref key, ref value, ref previous } => {
|
||||
format!("set {} = `{}`, previous: {:?}", key, value, previous)
|
||||
}
|
||||
Response::Error { ref msg } => {
|
||||
format!("error: {}", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//! This example leverages `BytesCodec` to create a UDP client and server which
|
||||
//! speak a custom protocol.
|
||||
//!
|
||||
//! Here we're using the codec from tokio-io to convert a UDP socket to a stream of
|
||||
//! client messages. These messages are then processed and returned back as a
|
||||
//! new message with a new destination. Overall, we then use this to construct a
|
||||
//! "ping pong" pair where two sockets are sending messages back and forth.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio::prelude::*;
|
||||
use tokio::net::{UdpSocket, UdpFramed};
|
||||
use tokio_io::codec::BytesCodec;
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
|
||||
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
|
||||
// Bind both our sockets and then figure out what ports we got.
|
||||
let a = UdpSocket::bind(&addr).unwrap();
|
||||
let b = UdpSocket::bind(&addr).unwrap();
|
||||
let b_addr = b.local_addr().unwrap();
|
||||
|
||||
// We're parsing each socket with the `BytesCodec` included in `tokio_io`, and then we
|
||||
// `split` each codec into the sink/stream halves.
|
||||
let (a_sink, a_stream) = UdpFramed::new(a, BytesCodec::new()).split();
|
||||
let (b_sink, b_stream) = UdpFramed::new(b, BytesCodec::new()).split();
|
||||
|
||||
// Start off by sending a ping from a to b, afterwards we just print out
|
||||
// what they send us and continually send pings
|
||||
// let pings = stream::iter((0..5).map(Ok));
|
||||
let a = a_sink.send(("PING".into(), b_addr)).and_then(|a_sink| {
|
||||
let mut i = 0;
|
||||
let a_stream = a_stream.take(4).map(move |(msg, addr)| {
|
||||
i += 1;
|
||||
println!("[a] recv: {}", String::from_utf8_lossy(&msg));
|
||||
(format!("PING {}", i).into(), addr)
|
||||
});
|
||||
a_sink.send_all(a_stream)
|
||||
});
|
||||
|
||||
// The second client we have will receive the pings from `a` and then send
|
||||
// back pongs.
|
||||
let b_stream = b_stream.map(|(msg, addr)| {
|
||||
println!("[b] recv: {}", String::from_utf8_lossy(&msg));
|
||||
("PONG".into(), addr)
|
||||
});
|
||||
let b = b_sink.send_all(b_stream);
|
||||
|
||||
// Spawn the sender of pongs and then wait for our pinger to finish.
|
||||
tokio::run({
|
||||
b.join(a)
|
||||
.map(|_| ())
|
||||
.map_err(|e| println!("error = {:?}", e))
|
||||
});
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "futures2"
|
||||
|
||||
version = "0.1.0"
|
||||
authors = ["Aaron Turon <[email protected]>"]
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
|
||||
[dependencies]
|
||||
futures = "=0.2.0-beta"
|
||||
@@ -1,2 +0,0 @@
|
||||
extern crate futures;
|
||||
pub use futures::*;
|
||||
@@ -0,0 +1 @@
|
||||
nightly-2019-08-21
|
||||
@@ -0,0 +1 @@
|
||||
edition = "2018"
|
||||
@@ -1,730 +0,0 @@
|
||||
//! Execute many tasks concurrently on the current thread.
|
||||
//!
|
||||
//! [`CurrentThread`] is an executor that keeps tasks on the same thread that
|
||||
//! they were spawned from. This allows it to execute futures that are not
|
||||
//! `Send`.
|
||||
//!
|
||||
//! A single [`CurrentThread`] instance is able to efficiently manage a large
|
||||
//! number of tasks and will attempt to schedule all tasks fairly.
|
||||
//!
|
||||
//! All tasks that are being managed by a [`CurrentThread`] executor are able to
|
||||
//! spawn additional tasks by calling [`spawn`]. This function only works from
|
||||
//! within the context of a running [`CurrentThread`] instance.
|
||||
//!
|
||||
//! The easiest way to start a new [`CurrentThread`] executor is to call
|
||||
//! [`block_on_all`] with an initial task to seed the executor.
|
||||
//!
|
||||
//! For example:
|
||||
//!
|
||||
//! ```
|
||||
//! # extern crate tokio;
|
||||
//! # extern crate futures;
|
||||
//! # use tokio::executor::current_thread;
|
||||
//! use futures::future::lazy;
|
||||
//!
|
||||
//! // Calling execute here results in a panic
|
||||
//! // current_thread::spawn(my_future);
|
||||
//!
|
||||
//! # pub fn main() {
|
||||
//! current_thread::block_on_all(lazy(|| {
|
||||
//! // The execution context is setup, futures may be executed.
|
||||
//! current_thread::spawn(lazy(|| {
|
||||
//! println!("called from the current thread executor");
|
||||
//! Ok(())
|
||||
//! }));
|
||||
//!
|
||||
//! Ok::<_, ()>(())
|
||||
//! }));
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! The `block_on_all` function will block the current thread until **all**
|
||||
//! tasks that have been spawned onto the [`CurrentThread`] instance have
|
||||
//! completed.
|
||||
//!
|
||||
//! More fine-grain control can be achieved by using [`CurrentThread`] directly.
|
||||
//!
|
||||
//! ```
|
||||
//! # extern crate tokio;
|
||||
//! # extern crate futures;
|
||||
//! # use tokio::executor::current_thread::CurrentThread;
|
||||
//! use futures::future::{lazy, empty};
|
||||
//! use std::time::Duration;
|
||||
//!
|
||||
//! // Calling execute here results in a panic
|
||||
//! // current_thread::spawn(my_future);
|
||||
//!
|
||||
//! # pub fn main() {
|
||||
//! let mut current_thread = CurrentThread::new();
|
||||
//!
|
||||
//! // Spawn a task, the task is not executed yet.
|
||||
//! current_thread.spawn(lazy(|| {
|
||||
//! println!("Spawning a task");
|
||||
//! Ok(())
|
||||
//! }));
|
||||
//!
|
||||
//! // Spawn a task that never completes
|
||||
//! current_thread.spawn(empty());
|
||||
//!
|
||||
//! // Run the executor, but only until the provided future completes. This
|
||||
//! // provides the opportunity to start executing previously spawned tasks.
|
||||
//! let res = current_thread.block_on(lazy(|| {
|
||||
//! Ok::<_, ()>("Hello")
|
||||
//! })).unwrap();
|
||||
//!
|
||||
//! // Now, run the executor for *at most* 1 second. Since a task was spawned
|
||||
//! // that never completes, this function will return with an error.
|
||||
//! current_thread.run_timeout(Duration::from_secs(1)).unwrap_err();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! # Execution model
|
||||
//!
|
||||
//! Internally, [`CurrentThread`] maintains a queue. When one of its tasks is
|
||||
//! notified, the task gets added to the queue. The executor will pop tasks from
|
||||
//! the queue and call [`Future::poll`]. If the task gets notified while it is
|
||||
//! being executed, it won't get re-executed until all other tasks currently in
|
||||
//! the queue get polled.
|
||||
//!
|
||||
//! Before the task is polled, a thread-local variable referencing the current
|
||||
//! [`CurrentThread`] instance is set. This enables [`spawn`] to spawn new tasks
|
||||
//! onto the same executor without having to thread through a handle value.
|
||||
//!
|
||||
//! If the [`CurrentThread`] instance still has uncompleted tasks, but none of
|
||||
//! these tasks are ready to be polled, the current thread is put to sleep. When
|
||||
//! a task is notified, the thread is woken up and processing resumes.
|
||||
//!
|
||||
//! All tasks managed by [`CurrentThread`] remain on the current thread. When a
|
||||
//! task completes, it is dropped.
|
||||
//!
|
||||
//! [`spawn`]: fn.spawn.html
|
||||
//! [`block_on_all`]: fn.block_on_all.html
|
||||
//! [`CurrentThread`]: struct.CurrentThread.html
|
||||
//! [`Future::poll`]: https://docs.rs/futures/0.1/futures/future/trait.Future.html#tymethod.poll
|
||||
|
||||
#![allow(deprecated)]
|
||||
|
||||
mod scheduler;
|
||||
use self::scheduler::Scheduler;
|
||||
|
||||
use tokio_executor::{self, Enter, SpawnError};
|
||||
use tokio_executor::park::{Park, Unpark, ParkThread};
|
||||
|
||||
use futures::{executor, Async, Future};
|
||||
use futures::future::{self, Executor, ExecuteError, ExecuteErrorKind};
|
||||
|
||||
use std::fmt;
|
||||
use std::cell::Cell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
use futures2;
|
||||
|
||||
/// Executes tasks on the current thread
|
||||
pub struct CurrentThread<P: Park = ParkThread> {
|
||||
/// Execute futures and receive unpark notifications.
|
||||
scheduler: Scheduler<P::Unpark>,
|
||||
|
||||
/// Current number of futures being executed
|
||||
num_futures: usize,
|
||||
|
||||
/// Thread park handle
|
||||
park: P,
|
||||
}
|
||||
|
||||
/// Executes futures on the current thread.
|
||||
///
|
||||
/// All futures executed using this executor will be executed on the current
|
||||
/// thread. As such, `run` will wait for these futures to complete before
|
||||
/// returning.
|
||||
///
|
||||
/// For more details, see the [module level](index.html) documentation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TaskExecutor {
|
||||
// Prevent the handle from moving across threads.
|
||||
_p: ::std::marker::PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
/// Returned by the `turn` function
|
||||
#[derive(Debug)]
|
||||
pub struct Turn(());
|
||||
|
||||
/// A `CurrentThread` instance bound to a supplied execution conext.
|
||||
pub struct Entered<'a, P: Park + 'a> {
|
||||
executor: &'a mut CurrentThread<P>,
|
||||
enter: &'a mut Enter,
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.1.2", note = "use block_on_all instead")]
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct Context<'a> {
|
||||
cancel: Cell<bool>,
|
||||
_p: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
/// Error returned by the `run` function.
|
||||
#[derive(Debug)]
|
||||
pub struct RunError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
/// Error returned by the `run_timeout` function.
|
||||
#[derive(Debug)]
|
||||
pub struct RunTimeoutError {
|
||||
timeout: bool,
|
||||
}
|
||||
|
||||
/// Error returned by the `turn` function.
|
||||
#[derive(Debug)]
|
||||
pub struct TurnError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
/// Error returned by the `block_on` function.
|
||||
#[derive(Debug)]
|
||||
pub struct BlockError<T> {
|
||||
inner: Option<T>,
|
||||
}
|
||||
|
||||
/// This is mostly split out to make the borrow checker happy.
|
||||
struct Borrow<'a, U: 'a> {
|
||||
scheduler: &'a mut Scheduler<U>,
|
||||
num_futures: &'a mut usize,
|
||||
}
|
||||
|
||||
trait SpawnLocal {
|
||||
fn spawn_local(&mut self, future: Box<Future<Item = (), Error = ()>>);
|
||||
}
|
||||
|
||||
struct CurrentRunner {
|
||||
spawn: Cell<Option<*mut SpawnLocal>>,
|
||||
}
|
||||
|
||||
/// Current thread's task runner. This is set in `TaskRunner::with`
|
||||
thread_local!(static CURRENT: CurrentRunner = CurrentRunner {
|
||||
spawn: Cell::new(None),
|
||||
});
|
||||
|
||||
#[deprecated(since = "0.1.2", note = "use block_on_all instead")]
|
||||
#[doc(hidden)]
|
||||
#[allow(deprecated)]
|
||||
pub fn run<F, R>(f: F) -> R
|
||||
where F: FnOnce(&mut Context) -> R
|
||||
{
|
||||
let mut context = Context {
|
||||
cancel: Cell::new(false),
|
||||
_p: PhantomData,
|
||||
};
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let ret = current_thread
|
||||
.block_on(future::lazy(|| Ok::<_, ()>(f(&mut context))))
|
||||
.unwrap();
|
||||
|
||||
if context.cancel.get() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
current_thread.run().unwrap();
|
||||
ret
|
||||
}
|
||||
|
||||
/// Run the executor bootstrapping the execution with the provided future.
|
||||
///
|
||||
/// This creates a new [`CurrentThread`] executor, spawns the provided future,
|
||||
/// and blocks the current thread until the provided future and **all**
|
||||
/// subsequently spawned futures complete. In other words:
|
||||
///
|
||||
/// * If the provided boostrap future does **not** spawn any additional tasks,
|
||||
/// `block_on_all` returns once `future` completes.
|
||||
/// * If the provided bootstrap future **does** spawn additional tasks, then
|
||||
/// `block_on_all` returns once **all** spawned futures complete.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [`CurrentThread`]: struct.CurrentThread.html
|
||||
/// [mod]: index.html
|
||||
pub fn block_on_all<F>(future: F) -> Result<F::Item, F::Error>
|
||||
where F: Future,
|
||||
{
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let ret = current_thread.block_on(future);
|
||||
current_thread.run().unwrap();
|
||||
|
||||
ret.map_err(|e| e.into_inner().expect("unexpected execution error"))
|
||||
}
|
||||
|
||||
/// Executes a future on the current thread.
|
||||
///
|
||||
/// The provided future must complete or be canceled before `run` will return.
|
||||
///
|
||||
/// Unlike [`tokio::spawn`], this function will always spawn on a
|
||||
/// `CurrentThread` executor and is able to spawn futures that are not `Send`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function can only be invoked from the context of a `run` call; any
|
||||
/// other use will result in a panic.
|
||||
///
|
||||
/// [`tokio::spawn`]: ../fn.spawn.html
|
||||
pub fn spawn<F>(future: F)
|
||||
where F: Future<Item = (), Error = ()> + 'static
|
||||
{
|
||||
TaskExecutor::current()
|
||||
.spawn_local(Box::new(future))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// ===== impl CurrentThread =====
|
||||
|
||||
impl CurrentThread<ParkThread> {
|
||||
/// Create a new instance of `CurrentThread`.
|
||||
pub fn new() -> Self {
|
||||
CurrentThread::new_with_park(ParkThread::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Park> CurrentThread<P> {
|
||||
/// Create a new instance of `CurrentThread` backed by the given park
|
||||
/// handle.
|
||||
pub fn new_with_park(park: P) -> Self {
|
||||
let unpark = park.unpark();
|
||||
|
||||
CurrentThread {
|
||||
scheduler: Scheduler::new(unpark),
|
||||
num_futures: 0,
|
||||
park,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the executor is currently idle.
|
||||
///
|
||||
/// An idle executor is defined by not currently having any spawned tasks.
|
||||
pub fn is_idle(&self) -> bool {
|
||||
self.num_futures == 0
|
||||
}
|
||||
|
||||
/// Spawn the future on the executor.
|
||||
///
|
||||
/// This internally queues the future to be executed once `run` is called.
|
||||
pub fn spawn<F>(&mut self, future: F) -> &mut Self
|
||||
where F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
self.borrow().spawn_local(Box::new(future));
|
||||
self
|
||||
}
|
||||
|
||||
/// Synchronously waits for the provided `future` to complete.
|
||||
///
|
||||
/// This function can be used to synchronously block the current thread
|
||||
/// until the provided `future` has resolved either successfully or with an
|
||||
/// error. The result of the future is then returned from this function
|
||||
/// call.
|
||||
///
|
||||
/// Note that this function will **also** execute any spawned futures on the
|
||||
/// current thread, but will **not** block until these other spawned futures
|
||||
/// have completed.
|
||||
///
|
||||
/// The caller is responsible for ensuring that other spawned futures
|
||||
/// complete execution.
|
||||
pub fn block_on<F>(&mut self, future: F)
|
||||
-> Result<F::Item, BlockError<F::Error>>
|
||||
where F: Future
|
||||
{
|
||||
let mut enter = tokio_executor::enter().unwrap();
|
||||
self.enter(&mut enter).block_on(future)
|
||||
}
|
||||
|
||||
/// Run the executor to completion, blocking the thread until **all**
|
||||
/// spawned futures have completed.
|
||||
pub fn run(&mut self) -> Result<(), RunError> {
|
||||
let mut enter = tokio_executor::enter().unwrap();
|
||||
self.enter(&mut enter).run()
|
||||
}
|
||||
|
||||
/// Run the executor to completion, blocking the thread until all
|
||||
/// spawned futures have completed **or** `duration` time has elapsed.
|
||||
pub fn run_timeout(&mut self, duration: Duration)
|
||||
-> Result<(), RunTimeoutError>
|
||||
{
|
||||
let mut enter = tokio_executor::enter().unwrap();
|
||||
self.enter(&mut enter).run_timeout(duration)
|
||||
}
|
||||
|
||||
/// Perform a single iteration of the event loop.
|
||||
///
|
||||
/// This function blocks the current thread even if the executor is idle.
|
||||
pub fn turn(&mut self, duration: Option<Duration>)
|
||||
-> Result<Turn, TurnError>
|
||||
{
|
||||
let mut enter = tokio_executor::enter().unwrap();
|
||||
self.enter(&mut enter).turn(duration)
|
||||
}
|
||||
|
||||
/// Bind `CurrentThread` instance with an execution context.
|
||||
pub fn enter<'a>(&'a mut self, enter: &'a mut Enter) -> Entered<'a, P> {
|
||||
Entered {
|
||||
executor: self,
|
||||
enter,
|
||||
}
|
||||
}
|
||||
|
||||
fn borrow(&mut self) -> Borrow<P::Unpark> {
|
||||
Borrow {
|
||||
scheduler: &mut self.scheduler,
|
||||
num_futures: &mut self.num_futures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_executor::Executor for CurrentThread {
|
||||
fn spawn(&mut self, future: Box<Future<Item = (), Error = ()> + Send>)
|
||||
-> Result<(), SpawnError>
|
||||
{
|
||||
self.borrow().spawn_local(future);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
fn spawn2(&mut self, _future: Box<futures2::Future<Item = (), Error = futures2::Never> + Send>)
|
||||
-> Result<(), futures2::executor::SpawnError>
|
||||
{
|
||||
panic!("Futures 0.2 integration is not available for current_thread");
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Park> fmt::Debug for CurrentThread<P> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("CurrentThread")
|
||||
.field("scheduler", &self.scheduler)
|
||||
.field("num_futures", &self.num_futures)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Entered =====
|
||||
|
||||
impl<'a, P: Park> Entered<'a, P> {
|
||||
/// Spawn the future on the executor.
|
||||
///
|
||||
/// This internally queues the future to be executed once `run` is called.
|
||||
pub fn spawn<F>(&mut self, future: F) -> &mut Self
|
||||
where F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
self.executor.borrow().spawn_local(Box::new(future));
|
||||
self
|
||||
}
|
||||
|
||||
/// Synchronously waits for the provided `future` to complete.
|
||||
///
|
||||
/// This function can be used to synchronously block the current thread
|
||||
/// until the provided `future` has resolved either successfully or with an
|
||||
/// error. The result of the future is then returned from this function
|
||||
/// call.
|
||||
///
|
||||
/// Note that this function will **also** execute any spawned futures on the
|
||||
/// current thread, but will **not** block until these other spawned futures
|
||||
/// have completed.
|
||||
///
|
||||
/// The caller is responsible for ensuring that other spawned futures
|
||||
/// complete execution.
|
||||
pub fn block_on<F>(&mut self, future: F)
|
||||
-> Result<F::Item, BlockError<F::Error>>
|
||||
where F: Future
|
||||
{
|
||||
let mut future = executor::spawn(future);
|
||||
let notify = self.executor.scheduler.notify();
|
||||
|
||||
loop {
|
||||
let res = self.executor.borrow().enter(self.enter, || {
|
||||
future.poll_future_notify(¬ify, 0)
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(Async::Ready(e)) => return Ok(e),
|
||||
Err(e) => return Err(BlockError { inner: Some(e) }),
|
||||
Ok(Async::NotReady) => {}
|
||||
}
|
||||
|
||||
self.tick();
|
||||
|
||||
if let Err(_) = self.executor.park.park() {
|
||||
return Err(BlockError { inner: None });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor to completion, blocking the thread until **all**
|
||||
/// spawned futures have completed.
|
||||
pub fn run(&mut self) -> Result<(), RunError> {
|
||||
self.run_timeout2(None)
|
||||
.map_err(|_| RunError { _p: () })
|
||||
}
|
||||
|
||||
/// Run the executor to completion, blocking the thread until all
|
||||
/// spawned futures have completed **or** `duration` time has elapsed.
|
||||
pub fn run_timeout(&mut self, duration: Duration)
|
||||
-> Result<(), RunTimeoutError>
|
||||
{
|
||||
self.run_timeout2(Some(duration))
|
||||
}
|
||||
|
||||
/// Perform a single iteration of the event loop.
|
||||
///
|
||||
/// This function blocks the current thread even if the executor is idle.
|
||||
pub fn turn(&mut self, duration: Option<Duration>)
|
||||
-> Result<Turn, TurnError>
|
||||
{
|
||||
if !self.tick() {
|
||||
let res = match duration {
|
||||
Some(duration) => self.executor.park.park_timeout(duration),
|
||||
None => self.executor.park.park(),
|
||||
};
|
||||
|
||||
if res.is_err() {
|
||||
return Err(TurnError { _p: () });
|
||||
}
|
||||
|
||||
self.tick();
|
||||
}
|
||||
|
||||
Ok(Turn(()))
|
||||
}
|
||||
|
||||
fn run_timeout2(&mut self, dur: Option<Duration>)
|
||||
-> Result<(), RunTimeoutError>
|
||||
{
|
||||
if self.executor.is_idle() {
|
||||
// Nothing to do
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut time = dur.map(|dur| (Instant::now() + dur, dur));
|
||||
|
||||
loop {
|
||||
self.tick();
|
||||
|
||||
if self.executor.is_idle() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match time {
|
||||
Some((until, rem)) => {
|
||||
if let Err(_) = self.executor.park.park_timeout(rem) {
|
||||
return Err(RunTimeoutError::new(false));
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
if now >= until {
|
||||
return Err(RunTimeoutError::new(true));
|
||||
}
|
||||
|
||||
time = Some((until, until - now));
|
||||
}
|
||||
None => {
|
||||
if let Err(_) = self.executor.park.park() {
|
||||
return Err(RunTimeoutError::new(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if any futures were processed
|
||||
fn tick(&mut self) -> bool {
|
||||
self.executor.scheduler.tick(
|
||||
&mut *self.enter,
|
||||
&mut self.executor.num_futures)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: Park> fmt::Debug for Entered<'a, P> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Entered")
|
||||
.field("executor", &self.executor)
|
||||
.field("enter", &self.enter)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl TaskExecutor =====
|
||||
|
||||
#[deprecated(since = "0.1.2", note = "use TaskExecutor::current instead")]
|
||||
#[doc(hidden)]
|
||||
pub fn task_executor() -> TaskExecutor {
|
||||
TaskExecutor {
|
||||
_p: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskExecutor {
|
||||
/// Returns an executor that executes futures on the current thread.
|
||||
///
|
||||
/// The user of `TaskExecutor` must ensure that when a future is submitted,
|
||||
/// that it is done within the context of a call to `run`.
|
||||
///
|
||||
/// For more details, see the [module level](index.html) documentation.
|
||||
pub fn current() -> TaskExecutor {
|
||||
TaskExecutor {
|
||||
_p: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a future onto the current `CurrentThread` instance.
|
||||
pub fn spawn_local(&mut self, future: Box<Future<Item = (), Error = ()>>)
|
||||
-> Result<(), SpawnError>
|
||||
{
|
||||
CURRENT.with(|current| {
|
||||
match current.spawn.get() {
|
||||
Some(spawn) => {
|
||||
unsafe { (*spawn).spawn_local(future) };
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
Err(SpawnError::shutdown())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_executor::Executor for TaskExecutor {
|
||||
fn spawn(&mut self, future: Box<Future<Item = (), Error = ()> + Send>)
|
||||
-> Result<(), SpawnError>
|
||||
{
|
||||
self.spawn_local(future)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
fn spawn2(&mut self, _future: Box<futures2::Future<Item = (), Error = futures2::Never> + Send>)
|
||||
-> Result<(), futures2::executor::SpawnError>
|
||||
{
|
||||
panic!("Futures 0.2 integration is not available for current_thread");
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
CURRENT.with(|current| {
|
||||
if current.spawn.get().is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SpawnError::shutdown())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Executor<F> for TaskExecutor
|
||||
where F: Future<Item = (), Error = ()> + 'static
|
||||
{
|
||||
fn execute(&self, future: F) -> Result<(), ExecuteError<F>> {
|
||||
CURRENT.with(|current| {
|
||||
match current.spawn.get() {
|
||||
Some(spawn) => {
|
||||
unsafe { (*spawn).spawn_local(Box::new(future)) };
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
Err(ExecuteError::new(ExecuteErrorKind::Shutdown, future))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Context =====
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
/// Cancels *all* executing futures.
|
||||
pub fn cancel_all_spawned(&self) {
|
||||
self.cancel.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Borrow =====
|
||||
|
||||
impl<'a, U: Unpark> Borrow<'a, U> {
|
||||
fn enter<F, R>(&mut self, _: &mut Enter, f: F) -> R
|
||||
where F: FnOnce() -> R,
|
||||
{
|
||||
CURRENT.with(|current| {
|
||||
current.set_spawn(self, || {
|
||||
f()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, U: Unpark> SpawnLocal for Borrow<'a, U> {
|
||||
fn spawn_local(&mut self, future: Box<Future<Item = (), Error = ()>>) {
|
||||
*self.num_futures += 1;
|
||||
self.scheduler.schedule(future);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl CurrentRunner =====
|
||||
|
||||
impl CurrentRunner {
|
||||
fn set_spawn<F, R>(&self, spawn: &mut SpawnLocal, f: F) -> R
|
||||
where F: FnOnce() -> R
|
||||
{
|
||||
struct Reset<'a>(&'a CurrentRunner);
|
||||
|
||||
impl<'a> Drop for Reset<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.0.spawn.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
let _reset = Reset(self);
|
||||
|
||||
let spawn = unsafe { hide_lt(spawn as *mut SpawnLocal) };
|
||||
self.spawn.set(Some(spawn));
|
||||
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn hide_lt<'a>(p: *mut (SpawnLocal + 'a)) -> *mut (SpawnLocal + 'static) {
|
||||
use std::mem;
|
||||
mem::transmute(p)
|
||||
}
|
||||
|
||||
// ===== impl RunTimeoutError =====
|
||||
|
||||
impl RunTimeoutError {
|
||||
fn new(timeout: bool) -> Self {
|
||||
RunTimeoutError { timeout }
|
||||
}
|
||||
|
||||
/// Returns `true` if the error was caused by the operation timeing out.
|
||||
pub fn is_timeout(&self) -> bool {
|
||||
self.timeout
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tokio_executor::EnterError> for RunTimeoutError {
|
||||
fn from(_: tokio_executor::EnterError) -> Self {
|
||||
RunTimeoutError::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl BlockError =====
|
||||
|
||||
impl<T> BlockError<T> {
|
||||
/// Returns the error yielded by the future being blocked on
|
||||
pub fn into_inner(self) -> Option<T> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<tokio_executor::EnterError> for BlockError<T> {
|
||||
fn from(_: tokio_executor::EnterError) -> Self {
|
||||
BlockError { inner: None }
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
//! Task execution utilities.
|
||||
//!
|
||||
//! In the Tokio execution model, futures are lazy. When a future is created, no
|
||||
//! work is performed. In order for the work defined by the future to happen,
|
||||
//! the future must be submitted to an executor. A future that is submitted to
|
||||
//! an executor is called a "task".
|
||||
//!
|
||||
//! The executor executor is responsible for ensuring that [`Future::poll`] is
|
||||
//! called whenever the task is [notified]. Notification happens when the
|
||||
//! internal state of a task transitions from "not ready" to ready. For
|
||||
//! example, a socket might have received data and a call to `read` will now be
|
||||
//! able to succeed.
|
||||
//!
|
||||
//! The specific strategy used to manage the tasks is left up to the
|
||||
//! executor. There are two main flavors of executors: single-threaded and
|
||||
//! multithreaded. This module provides both.
|
||||
//!
|
||||
//! * **[`current_thread`]**: A single-threaded executor that support spawning
|
||||
//! tasks that are not `Send`. It guarantees that tasks will be executed on
|
||||
//! the same thread from which they are spawned.
|
||||
//!
|
||||
//! * **[`thread_pool`]**: A multi-threaded executor that maintains a pool of
|
||||
//! threads. Tasks are spawned to one of the threads in the pool and executed.
|
||||
//! The pool employes a [work-stealing] strategy for optimizing how tasks get
|
||||
//! spread across the available threads.
|
||||
//!
|
||||
//! # `Executor` trait.
|
||||
//!
|
||||
//! This module provides the [`Executor`] trait (re-exported from
|
||||
//! [`tokio-executor`]), which describes the API that all executors must
|
||||
//! implement.
|
||||
//!
|
||||
//! A free [`spawn`] function is provided that allows spawning futures onto the
|
||||
//! default executor (tracked via a thread-local variable) without referencing a
|
||||
//! handle. It is expected that all executors will set a value for the default
|
||||
//! executor. This value will often be set to the executor itself, but it is
|
||||
//! possible that the default executor might be set to a different executor.
|
||||
//!
|
||||
//! For example, the [`current_thread`] executor might set the default executor
|
||||
//! to a thread pool instead of itself, allowing futures to spawn new tasks onto
|
||||
//! the thread pool when those tasks are `Send`.
|
||||
//!
|
||||
//! [`Future::poll`]: https://docs.rs/futures/0.1/futures/future/trait.Future.html#tymethod.poll
|
||||
//! [notified]: https://docs.rs/futures/0.1/futures/executor/trait.Notify.html#tymethod.notify
|
||||
//! [`current_thread`]: current_thread/index.html
|
||||
//! [`thread_pool`]: thread_pool/index.html
|
||||
//! [work-stealing]: https://en.wikipedia.org/wiki/Work_stealing
|
||||
//! [`tokio-executor`]: #
|
||||
//! [`Executor`]: #
|
||||
//! [`spawn`]: #
|
||||
|
||||
pub mod current_thread;
|
||||
|
||||
pub mod thread_pool {
|
||||
//! Maintains a pool of threads across which the set of spawned tasks are
|
||||
//! executed.
|
||||
//!
|
||||
//! [`ThreadPool`] is an executor that uses a thread pool for executing
|
||||
//! tasks concurrently across multiple cores. It uses a thread pool that is
|
||||
//! optimized for use cases that involve multiplexing large number of
|
||||
//! independent tasks that perform short(ish) amounts of computation and are
|
||||
//! mainly waiting on I/O, i.e. the Tokio use case.
|
||||
//!
|
||||
//! Usually, users of [`ThreadPool`] will not create pool instances.
|
||||
//! Instead, they will create a [`Runtime`] instance, which comes with a
|
||||
//! pre-configured thread pool.
|
||||
//!
|
||||
//! At the core, [`ThreadPool`] uses a work-stealing based scheduling
|
||||
//! strategy. When spawning a task while *external* to the thread pool
|
||||
//! (i.e., from a thread that is not part of the thread pool), the task is
|
||||
//! randomly assigned to a worker thread. When spawning a task while
|
||||
//! *internal* to the thread pool, the task is assigned to the current
|
||||
//! worker.
|
||||
//!
|
||||
//! Each worker maintains its own queue and first focuses on processing all
|
||||
//! tasks in its queue. When the worker's queue is empty, the worker will
|
||||
//! attempt to *steal* tasks from other worker queues. This strategy helps
|
||||
//! ensure that work is evenly distributed across threads while minimizing
|
||||
//! synchronization between worker threads.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Thread pool instances are created using [`ThreadPool::new`] or
|
||||
//! [`Builder::new`]. The first option returns a thread pool with default
|
||||
//! configuration values. The second option allows configuring the thread
|
||||
//! pool before instantiating it.
|
||||
//!
|
||||
//! Once an instance is obtained, futures may be spawned onto it using the
|
||||
//! [`spawn`] function.
|
||||
//!
|
||||
//! A handle to the thread pool is obtained using [`ThreadPool::sender`].
|
||||
//! This handle is **only** able to spawn futures onto the thread pool. It
|
||||
//! is unable to affect the lifecycle of the thread pool in any way. This
|
||||
//! handle can be passed into functions or stored in structs as a way to
|
||||
//! grant the capability of spawning futures.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate tokio;
|
||||
//! # extern crate futures;
|
||||
//! # use tokio::executor::thread_pool::ThreadPool;
|
||||
//! use futures::future::{Future, lazy};
|
||||
//!
|
||||
//! # pub fn main() {
|
||||
//! // Create a thread pool with default configuration values
|
||||
//! let thread_pool = ThreadPool::new();
|
||||
//!
|
||||
//! thread_pool.spawn(lazy(|| {
|
||||
//! println!("called from a worker thread");
|
||||
//! Ok(())
|
||||
//! }));
|
||||
//!
|
||||
//! // Gracefully shutdown the threadpool
|
||||
//! thread_pool.shutdown().wait().unwrap();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! [`ThreadPool`]: struct.ThreadPool.html
|
||||
//! [`ThreadPool::new`]: struct.ThreadPool.html#method.new
|
||||
//! [`ThreadPool::sender`]: struct.ThreadPool.html#method.sender
|
||||
//! [`spawn`]: struct.ThreadPool.html#method.spawn
|
||||
//! [`Builder::new`]: struct.Builder.html#method.new
|
||||
//! [`Runtime`]: ../../runtime/struct.Runtime.html
|
||||
|
||||
pub use tokio_threadpool::{
|
||||
Builder,
|
||||
Sender,
|
||||
Shutdown,
|
||||
ThreadPool,
|
||||
};
|
||||
}
|
||||
|
||||
pub use tokio_executor::{Executor, DefaultExecutor, SpawnError};
|
||||
|
||||
use futures::{Future, IntoFuture};
|
||||
use futures::future::{self, FutureResult};
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
use futures2;
|
||||
|
||||
/// Return value from the `spawn` function.
|
||||
///
|
||||
/// Currently this value doesn't actually provide any functionality. However, it
|
||||
/// provides a way to add functionality later without breaking backwards
|
||||
/// compatibility.
|
||||
///
|
||||
/// This also implements `IntoFuture` so that it can be used as the return value
|
||||
/// in a `for_each` loop.
|
||||
///
|
||||
/// See [`spawn`] for more details.
|
||||
///
|
||||
/// [`spawn`]: fn.spawn.html
|
||||
#[derive(Debug)]
|
||||
pub struct Spawn(());
|
||||
|
||||
/// Spawns a future on the default executor.
|
||||
///
|
||||
/// In order for a future to do work, it must be spawned on an executor. The
|
||||
/// `spawn` function is the easiest way to do this. It spawns a future on the
|
||||
/// [default executor] for the current execution context (tracked using a
|
||||
/// thread-local variable).
|
||||
///
|
||||
/// The default executor is **usually** a thread pool.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// In this example, a server is started and `spawn` is used to start a new task
|
||||
/// that processes each received connection.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::{Future, Stream};
|
||||
/// use tokio::net::TcpListener;
|
||||
///
|
||||
/// # fn process<T>(_: T) -> Box<Future<Item = (), Error = ()> + Send> {
|
||||
/// # unimplemented!();
|
||||
/// # }
|
||||
/// # fn dox() {
|
||||
/// # let addr = "127.0.0.1:8080".parse().unwrap();
|
||||
/// let listener = TcpListener::bind(&addr).unwrap();
|
||||
///
|
||||
/// let server = listener.incoming()
|
||||
/// .map_err(|e| println!("error = {:?}", e))
|
||||
/// .for_each(|socket| {
|
||||
/// tokio::spawn(process(socket))
|
||||
/// });
|
||||
///
|
||||
/// tokio::run(server);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// [default executor]: struct.DefaultExecutor.html
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the default executor is not set or if spawning
|
||||
/// onto the default executor returns an error. To avoid the panic, use
|
||||
/// [`DefaultExecutor`].
|
||||
///
|
||||
/// [`DefaultExecutor`]: struct.DefaultExecutor.html
|
||||
pub fn spawn<F>(f: F) -> Spawn
|
||||
where F: Future<Item = (), Error = ()> + 'static + Send
|
||||
{
|
||||
::tokio_executor::spawn(f);
|
||||
Spawn(())
|
||||
}
|
||||
|
||||
/// Like `spawn`, but compatible with futures 0.2
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
pub fn spawn2<F>(f: F) -> Spawn
|
||||
where F: futures2::Future<Item = (), Error = futures2::Never> + 'static + Send
|
||||
{
|
||||
::tokio_executor::spawn2(f);
|
||||
Spawn(())
|
||||
}
|
||||
|
||||
impl IntoFuture for Spawn {
|
||||
type Future = FutureResult<(), ()>;
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn into_future(self) -> Self::Future {
|
||||
future::ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
impl futures2::IntoFuture for Spawn {
|
||||
type Future = futures2::future::FutureResult<(), ()>;
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn into_future(self) -> Self::Future {
|
||||
futures2::future::ok(())
|
||||
}
|
||||
}
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
//! A runtime for writing reliable, asynchronous, and slim applications.
|
||||
//!
|
||||
//! Tokio is an event-driven, non-blocking I/O platform for writing asynchronous
|
||||
//! applications with the Rust programming language. At a high level, it
|
||||
//! provides a few major components:
|
||||
//!
|
||||
//! * A multi threaded, work-stealing based task [scheduler][runtime].
|
||||
//! * A [reactor][reactor] backed by the operating system's event queue (epoll, kqueue,
|
||||
//! IOCP, etc...).
|
||||
//! * Asynchronous [TCP and UDP][net] sockets.
|
||||
//!
|
||||
//! Tokio is built using futures (provided by the [futures] crate) as the
|
||||
//! abstraction for managing the complexity of asynchronous programming.
|
||||
//!
|
||||
//! Guide level documentation is found on the [website].
|
||||
//!
|
||||
//! [website]: https://tokio.rs/docs/getting-started/hello-world/
|
||||
//! [futures]: http://docs.rs/futures
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! A simple TCP echo server:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! extern crate tokio;
|
||||
//!
|
||||
//! use tokio::prelude::*;
|
||||
//! use tokio::io::copy;
|
||||
//! use tokio::net::TcpListener;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! // Bind the server's socket.
|
||||
//! let addr = "127.0.0.1:12345".parse().unwrap();
|
||||
//! let listener = TcpListener::bind(&addr)
|
||||
//! .expect("unable to bind TCP listener");
|
||||
//!
|
||||
//! // Pull out a stream of sockets for incoming connections
|
||||
//! let server = listener.incoming()
|
||||
//! .map_err(|e| eprintln!("accept failed = {:?}", e))
|
||||
//! .for_each(|sock| {
|
||||
//! // Split up the reading and writing parts of the
|
||||
//! // socket.
|
||||
//! let (reader, writer) = sock.split();
|
||||
//!
|
||||
//! // A future that echos the data and returns how
|
||||
//! // many bytes were copied...
|
||||
//! let bytes_copied = copy(reader, writer);
|
||||
//!
|
||||
//! // ... after which we'll print what happened.
|
||||
//! let handle_conn = bytes_copied.map(|amt| {
|
||||
//! println!("wrote {:?} bytes", amt)
|
||||
//! }).map_err(|err| {
|
||||
//! eprintln!("IO error {:?}", err)
|
||||
//! });
|
||||
//!
|
||||
//! // Spawn the future as a concurrent task.
|
||||
//! tokio::spawn(handle_conn)
|
||||
//! });
|
||||
//!
|
||||
//! // Start the Tokio runtime
|
||||
//! tokio::run(server);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/tokio/0.1.4")]
|
||||
#![deny(missing_docs, warnings, missing_debug_implementations)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate mio;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_executor;
|
||||
extern crate tokio_reactor;
|
||||
extern crate tokio_threadpool;
|
||||
extern crate tokio_tcp;
|
||||
extern crate tokio_udp;
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
extern crate futures2;
|
||||
|
||||
pub mod executor;
|
||||
pub mod net;
|
||||
pub mod reactor;
|
||||
pub mod runtime;
|
||||
|
||||
pub use executor::spawn;
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
pub use executor::spawn2;
|
||||
|
||||
pub use runtime::run;
|
||||
|
||||
pub mod io {
|
||||
//! Asynchronous I/O.
|
||||
//!
|
||||
//! This module is the asynchronous version of `std::io`. Primarily, it
|
||||
//! defines two traits, [`AsyncRead`] and [`AsyncWrite`], which extend the
|
||||
//! `Read` and `Write` traits of the standard library.
|
||||
//!
|
||||
//! [`AsyncRead`] and [`AsyncWrite`] must only be implemented for
|
||||
//! non-blocking I/O types that integrate with the futures type system. In
|
||||
//! other words, these types must never block the thread, and instead the
|
||||
//! current task is notified when the I/O resource is ready.
|
||||
//!
|
||||
//! Utilities functions are provided for working with [`AsyncRead`] /
|
||||
//! [`AsyncWrite`] types. For example, [`copy`] asynchronously copies all
|
||||
//! data from a source to a destination.
|
||||
//!
|
||||
//! Additionally, [`Read`], [`Write`], [`Error`], [`ErrorKind`], and
|
||||
//! [`Result`] are re-exported from `std::io` for ease of use.
|
||||
//!
|
||||
//! [`AsyncRead`]: trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: trait.AsyncWrite.html
|
||||
//! [`copy`]: fn.copy.html
|
||||
//! [`Read`]: trait.Read.html
|
||||
//! [`Write`]: trait.Write.html
|
||||
//! [`Error`]: struct.Error.html
|
||||
//! [`ErrorKind`]: enum.ErrorKind.html
|
||||
//! [`Result`]: type.Result.html
|
||||
|
||||
pub use tokio_io::{
|
||||
AsyncRead,
|
||||
AsyncWrite,
|
||||
};
|
||||
|
||||
// Utils
|
||||
pub use tokio_io::io::{
|
||||
copy,
|
||||
Copy,
|
||||
flush,
|
||||
Flush,
|
||||
lines,
|
||||
Lines,
|
||||
read_exact,
|
||||
ReadExact,
|
||||
read_to_end,
|
||||
ReadToEnd,
|
||||
read_until,
|
||||
ReadUntil,
|
||||
shutdown,
|
||||
Shutdown,
|
||||
write_all,
|
||||
WriteAll,
|
||||
};
|
||||
|
||||
// Re-export io::Error so that users don't have to deal
|
||||
// with conflicts when `use`ing `futures::io` and `std::io`.
|
||||
pub use ::std::io::{
|
||||
Error,
|
||||
ErrorKind,
|
||||
Result,
|
||||
Read,
|
||||
Write,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
//! A "prelude" for users of the `tokio` crate.
|
||||
//!
|
||||
//! This prelude is similar to the standard library's prelude in that you'll
|
||||
//! almost always want to import its entire contents, but unlike the standard
|
||||
//! library's prelude you'll have to do so manually:
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::prelude::*;
|
||||
//! ```
|
||||
//!
|
||||
//! The prelude may grow over time as additional items see ubiquitous use.
|
||||
|
||||
pub use tokio_io::{
|
||||
AsyncRead,
|
||||
AsyncWrite,
|
||||
};
|
||||
|
||||
pub use ::std::io::{
|
||||
Read,
|
||||
Write,
|
||||
};
|
||||
|
||||
pub use futures::{
|
||||
Future,
|
||||
future,
|
||||
Stream,
|
||||
stream,
|
||||
Sink,
|
||||
IntoFuture,
|
||||
Async,
|
||||
AsyncSink,
|
||||
Poll,
|
||||
task,
|
||||
};
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
//! TCP/UDP bindings for `tokio`.
|
||||
//!
|
||||
//! This module contains the TCP/UDP networking types, similar to the standard
|
||||
//! library, which can be used to implement networking protocols.
|
||||
//!
|
||||
//! # TCP
|
||||
//!
|
||||
//! Connecting to an address, via TCP, can be done using [`TcpStream`]'s
|
||||
//! [`connect`] method, which returns [`ConnectFuture`]. `ConnectFuture`
|
||||
//! implements a future which returns a `TcpStream`.
|
||||
//!
|
||||
//! To listen on an address [`TcpListener`] can be used. `TcpListener`'s
|
||||
//! [`incoming`][incoming_method] method can be used to accept new connections.
|
||||
//! It return the [`Incoming`] struct, which implements a stream which returns
|
||||
//! `TcpStream`s.
|
||||
//!
|
||||
//! [`TcpStream`]: struct.TcpStream.html
|
||||
//! [`connect`]: struct.TcpStream.html#method.connect
|
||||
//! [`ConnectFuture`]: struct.ConnectFuture.html
|
||||
//! [`TcpListener`]: struct.TcpListener.html
|
||||
//! [incoming_method]: struct.TcpListener.html#method.incoming
|
||||
//! [`Incoming`]: struct.Incoming.html
|
||||
//!
|
||||
//! # UDP
|
||||
//!
|
||||
//! The main struct for UDP is the [`UdpSocket`], which represents a UDP socket.
|
||||
//! Reading and writing to it can be done using futures, which return the
|
||||
//! [`RecvDgram`] and [`SendDgram`] structs respectively.
|
||||
//!
|
||||
//! For convience it's also possible to convert raw datagrams into higher-level
|
||||
//! frames.
|
||||
//!
|
||||
//! [`UdpSocket`]: struct.UdpSocket.html
|
||||
//! [`RecvDgram`]: struct.RecvDgram.html
|
||||
//! [`SendDgram`]: struct.SendDgram.html
|
||||
//! [`UdpFramed`]: struct.UdpFramed.html
|
||||
//! [`framed`]: struct.UdpSocket.html#method.framed
|
||||
|
||||
pub use tokio_tcp::{TcpStream, ConnectFuture};
|
||||
pub use tokio_tcp::{TcpListener, Incoming};
|
||||
pub use tokio_udp::{UdpSocket, UdpFramed, SendDgram, RecvDgram};
|
||||
@@ -1,539 +0,0 @@
|
||||
//! Readiness tracking streams, backing I/O objects.
|
||||
//!
|
||||
//! This module contains the core type which is used to back all I/O on object
|
||||
//! in `tokio-core`. The `PollEvented` type is the implementation detail of
|
||||
//! all I/O. Each `PollEvented` manages registration with a reactor,
|
||||
//! acquisition of a token, and tracking of the readiness state on the
|
||||
//! underlying I/O primitive.
|
||||
|
||||
#![allow(deprecated, warnings)]
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
use futures::{task, Async, Poll};
|
||||
use mio::event::Evented;
|
||||
use mio::Ready;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use reactor::{Handle, Registration};
|
||||
|
||||
#[deprecated(since = "0.1.2", note = "PollEvented2 instead")]
|
||||
#[doc(hidden)]
|
||||
pub struct PollEvented<E> {
|
||||
io: E,
|
||||
inner: Inner,
|
||||
handle: Handle,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
registration: Mutex<Registration>,
|
||||
|
||||
/// Currently visible read readiness
|
||||
read_readiness: AtomicUsize,
|
||||
|
||||
/// Currently visible write readiness
|
||||
write_readiness: AtomicUsize,
|
||||
}
|
||||
|
||||
impl<E: fmt::Debug> fmt::Debug for PollEvented<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("PollEvented")
|
||||
.field("io", &self.io)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> PollEvented<E> {
|
||||
/// Creates a new readiness stream associated with the provided
|
||||
/// `loop_handle` and for the given `source`.
|
||||
pub fn new(io: E, handle: &Handle) -> io::Result<PollEvented<E>>
|
||||
where E: Evented,
|
||||
{
|
||||
let registration = Registration::new();
|
||||
registration.register(&io)?;
|
||||
|
||||
Ok(PollEvented {
|
||||
io: io,
|
||||
inner: Inner {
|
||||
registration: Mutex::new(registration),
|
||||
read_readiness: AtomicUsize::new(0),
|
||||
write_readiness: AtomicUsize::new(0),
|
||||
},
|
||||
handle: handle.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Tests to see if this source is ready to be read from or not.
|
||||
///
|
||||
/// If this stream is not ready for a read then `Async::NotReady` will be
|
||||
/// returned and the current task will be scheduled to receive a
|
||||
/// notification when the stream is readable again. In other words, this
|
||||
/// method is only safe to call from within the context of a future's task,
|
||||
/// typically done in a `Future::poll` method.
|
||||
///
|
||||
/// This is mostly equivalent to `self.poll_ready(Ready::readable())`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if called outside the context of a future's
|
||||
/// task.
|
||||
pub fn poll_read(&mut self) -> Async<()> {
|
||||
if self.poll_read2().is_ready() {
|
||||
return ().into();
|
||||
}
|
||||
|
||||
Async::NotReady
|
||||
}
|
||||
|
||||
fn poll_read2(&self) -> Async<Ready> {
|
||||
let r = self.inner.registration.lock().unwrap();
|
||||
|
||||
// Load the cached readiness
|
||||
match self.inner.read_readiness.load(Relaxed) {
|
||||
0 => {}
|
||||
mut n => {
|
||||
// Check what's new with the reactor.
|
||||
if let Some(ready) = r.take_read_ready().unwrap() {
|
||||
n |= ready2usize(ready);
|
||||
self.inner.read_readiness.store(n, Relaxed);
|
||||
}
|
||||
|
||||
return usize2ready(n).into();
|
||||
}
|
||||
}
|
||||
|
||||
let ready = match r.poll_read_ready().unwrap() {
|
||||
Async::Ready(r) => r,
|
||||
_ => return Async::NotReady,
|
||||
};
|
||||
|
||||
// Cache the value
|
||||
self.inner.read_readiness.store(ready2usize(ready), Relaxed);
|
||||
|
||||
ready.into()
|
||||
}
|
||||
|
||||
/// Tests to see if this source is ready to be written to or not.
|
||||
///
|
||||
/// If this stream is not ready for a write then `Async::NotReady` will be returned
|
||||
/// and the current task will be scheduled to receive a notification when
|
||||
/// the stream is writable again. In other words, this method is only safe
|
||||
/// to call from within the context of a future's task, typically done in a
|
||||
/// `Future::poll` method.
|
||||
///
|
||||
/// This is mostly equivalent to `self.poll_ready(Ready::writable())`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if called outside the context of a future's
|
||||
/// task.
|
||||
pub fn poll_write(&mut self) -> Async<()> {
|
||||
let r = self.inner.registration.lock().unwrap();
|
||||
|
||||
match self.inner.write_readiness.load(Relaxed) {
|
||||
0 => {}
|
||||
mut n => {
|
||||
// Check what's new with the reactor.
|
||||
if let Some(ready) = r.take_write_ready().unwrap() {
|
||||
n |= ready2usize(ready);
|
||||
self.inner.write_readiness.store(n, Relaxed);
|
||||
}
|
||||
|
||||
return ().into();
|
||||
}
|
||||
}
|
||||
|
||||
let ready = match r.poll_write_ready().unwrap() {
|
||||
Async::Ready(r) => r,
|
||||
_ => return Async::NotReady,
|
||||
};
|
||||
|
||||
// Cache the value
|
||||
self.inner.write_readiness.store(ready2usize(ready), Relaxed);
|
||||
|
||||
().into()
|
||||
}
|
||||
|
||||
/// Test to see whether this source fulfills any condition listed in `mask`
|
||||
/// provided.
|
||||
///
|
||||
/// The `mask` given here is a mio `Ready` set of possible events. This can
|
||||
/// contain any events like read/write but also platform-specific events
|
||||
/// such as hup and error. The `mask` indicates events that are interested
|
||||
/// in being ready.
|
||||
///
|
||||
/// If any event in `mask` is ready then it is returned through
|
||||
/// `Async::Ready`. The `Ready` set returned is guaranteed to not be empty
|
||||
/// and contains all events that are currently ready in the `mask` provided.
|
||||
///
|
||||
/// If no events are ready in the `mask` provided then the current task is
|
||||
/// scheduled to receive a notification when any of them become ready. If
|
||||
/// the `writable` event is contained within `mask` then this
|
||||
/// `PollEvented`'s `write` task will be blocked and otherwise the `read`
|
||||
/// task will be blocked. This is generally only relevant if you're working
|
||||
/// with this `PollEvented` object on multiple tasks.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if called outside the context of a future's
|
||||
/// task.
|
||||
pub fn poll_ready(&mut self, mask: Ready) -> Async<Ready> {
|
||||
let mut ret = Ready::empty();
|
||||
|
||||
if mask.is_empty() {
|
||||
return ret.into();
|
||||
}
|
||||
|
||||
if mask.is_writable() {
|
||||
if self.poll_write().is_ready() {
|
||||
ret = Ready::writable();
|
||||
}
|
||||
}
|
||||
|
||||
let mask = mask - Ready::writable();
|
||||
|
||||
if !mask.is_empty() {
|
||||
if let Async::Ready(v) = self.poll_read2() {
|
||||
ret |= v & mask;
|
||||
}
|
||||
}
|
||||
|
||||
if ret.is_empty() {
|
||||
if mask.is_writable() {
|
||||
let _ = self.need_write();
|
||||
}
|
||||
|
||||
if mask.is_readable() {
|
||||
let _ = self.need_read();
|
||||
}
|
||||
|
||||
Async::NotReady
|
||||
} else {
|
||||
ret.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates to this source of events that the corresponding I/O object is
|
||||
/// no longer readable, but it needs to be.
|
||||
///
|
||||
/// This function, like `poll_read`, is only safe to call from the context
|
||||
/// of a future's task (typically in a `Future::poll` implementation). It
|
||||
/// informs this readiness stream that the underlying object is no longer
|
||||
/// readable, typically because a "would block" error was seen.
|
||||
///
|
||||
/// *All* readiness bits associated with this stream except the writable bit
|
||||
/// will be reset when this method is called. The current task is then
|
||||
/// scheduled to receive a notification whenever anything changes other than
|
||||
/// the writable bit. Note that this typically just means the readable bit
|
||||
/// is used here, but if you're using a custom I/O object for events like
|
||||
/// hup/error this may also be relevant.
|
||||
///
|
||||
/// Note that it is also only valid to call this method if `poll_read`
|
||||
/// previously indicated that the object is readable. That is, this function
|
||||
/// must always be paired with calls to `poll_read` previously.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the `Reactor` that this `PollEvented`
|
||||
/// is associated with has gone away (been destroyed). The error means that
|
||||
/// the ambient futures task could not be scheduled to receive a
|
||||
/// notification and typically means that the error should be propagated
|
||||
/// outwards.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if called outside the context of a future's
|
||||
/// task.
|
||||
pub fn need_read(&mut self) -> io::Result<()> {
|
||||
self.inner.read_readiness.store(0, Relaxed);
|
||||
|
||||
if self.poll_read().is_ready() {
|
||||
// Notify the current task
|
||||
task::current().notify();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Indicates to this source of events that the corresponding I/O object is
|
||||
/// no longer writable, but it needs to be.
|
||||
///
|
||||
/// This function, like `poll_write`, is only safe to call from the context
|
||||
/// of a future's task (typically in a `Future::poll` implementation). It
|
||||
/// informs this readiness stream that the underlying object is no longer
|
||||
/// writable, typically because a "would block" error was seen.
|
||||
///
|
||||
/// The flag indicating that this stream is writable is unset and the
|
||||
/// current task is scheduled to receive a notification when the stream is
|
||||
/// then again writable.
|
||||
///
|
||||
/// Note that it is also only valid to call this method if `poll_write`
|
||||
/// previously indicated that the object is writable. That is, this function
|
||||
/// must always be paired with calls to `poll_write` previously.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if the `Reactor` that this `PollEvented`
|
||||
/// is associated with has gone away (been destroyed). The error means that
|
||||
/// the ambient futures task could not be scheduled to receive a
|
||||
/// notification and typically means that the error should be propagated
|
||||
/// outwards.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if called outside the context of a future's
|
||||
/// task.
|
||||
pub fn need_write(&mut self) -> io::Result<()> {
|
||||
self.inner.write_readiness.store(0, Relaxed);
|
||||
|
||||
if self.poll_write().is_ready() {
|
||||
// Notify the current task
|
||||
task::current().notify();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a reference to the event loop handle that this readiness stream
|
||||
/// is associated with.
|
||||
pub fn handle(&self) -> &Handle {
|
||||
&self.handle
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the underlying I/O object this readiness
|
||||
/// stream is wrapping.
|
||||
pub fn get_ref(&self) -> &E {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O object this readiness
|
||||
/// stream is wrapping.
|
||||
pub fn get_mut(&mut self) -> &mut E {
|
||||
&mut self.io
|
||||
}
|
||||
|
||||
/// Consumes the `PollEvented` and returns the underlying I/O object
|
||||
pub fn into_inner(self) -> E {
|
||||
self.io
|
||||
}
|
||||
|
||||
/// Deregisters this source of events from the reactor core specified.
|
||||
///
|
||||
/// This method can optionally be called to unregister the underlying I/O
|
||||
/// object with the event loop that the `handle` provided points to.
|
||||
/// Typically this method is not required as this automatically happens when
|
||||
/// `E` is dropped, but for some use cases the `E` object doesn't represent
|
||||
/// an owned reference, so dropping it won't automatically unregister with
|
||||
/// the event loop.
|
||||
///
|
||||
/// This consumes `self` as it will no longer provide events after the
|
||||
/// method is called, and will likely return an error if this `PollEvented`
|
||||
/// was created on a separate event loop from the `handle` specified.
|
||||
pub fn deregister(&self) -> io::Result<()>
|
||||
where E: Evented,
|
||||
{
|
||||
self.inner.registration.lock().unwrap()
|
||||
.deregister(&self.io)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Read> Read for PollEvented<E> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if let Async::NotReady = self.poll_read() {
|
||||
return Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
|
||||
let r = self.get_mut().read(buf);
|
||||
|
||||
if is_wouldblock(&r) {
|
||||
self.need_read()?;
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Write> Write for PollEvented<E> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
if let Async::NotReady = self.poll_write() {
|
||||
return Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
|
||||
let r = self.get_mut().write(buf);
|
||||
|
||||
if is_wouldblock(&r) {
|
||||
self.need_write()?;
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
if let Async::NotReady = self.poll_write() {
|
||||
return Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
|
||||
let r = self.get_mut().flush();
|
||||
|
||||
if is_wouldblock(&r) {
|
||||
self.need_write()?;
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Read> AsyncRead for PollEvented<E> {
|
||||
}
|
||||
|
||||
impl<E: Write> AsyncWrite for PollEvented<E> {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_wouldblock<T>(r: &io::Result<T>) -> bool {
|
||||
match *r {
|
||||
Ok(_) => false,
|
||||
Err(ref e) => e.kind() == io::ErrorKind::WouldBlock,
|
||||
}
|
||||
}
|
||||
|
||||
const READ: usize = 1 << 0;
|
||||
const WRITE: usize = 1 << 1;
|
||||
|
||||
fn ready2usize(ready: Ready) -> usize {
|
||||
let mut bits = 0;
|
||||
if ready.is_readable() {
|
||||
bits |= READ;
|
||||
}
|
||||
if ready.is_writable() {
|
||||
bits |= WRITE;
|
||||
}
|
||||
bits | platform::ready2usize(ready)
|
||||
}
|
||||
|
||||
fn usize2ready(bits: usize) -> Ready {
|
||||
let mut ready = Ready::empty();
|
||||
if bits & READ != 0 {
|
||||
ready.insert(Ready::readable());
|
||||
}
|
||||
if bits & WRITE != 0 {
|
||||
ready.insert(Ready::writable());
|
||||
}
|
||||
ready | platform::usize2ready(bits)
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
||||
mod platform {
|
||||
use mio::Ready;
|
||||
use mio::unix::UnixReady;
|
||||
|
||||
const HUP: usize = 1 << 2;
|
||||
const ERROR: usize = 1 << 3;
|
||||
const AIO: usize = 1 << 4;
|
||||
const LIO: usize = 1 << 5;
|
||||
|
||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
|
||||
fn is_aio(ready: &Ready) -> bool {
|
||||
UnixReady::from(*ready).is_aio()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "dragonfly", target_os = "freebsd")))]
|
||||
fn is_aio(_ready: &Ready) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fn is_lio(ready: &Ready) -> bool {
|
||||
UnixReady::from(*ready).is_lio()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
fn is_lio(_ready: &Ready) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn ready2usize(ready: Ready) -> usize {
|
||||
let ready = UnixReady::from(ready);
|
||||
let mut bits = 0;
|
||||
if is_aio(&ready) {
|
||||
bits |= AIO;
|
||||
}
|
||||
if is_lio(&ready) {
|
||||
bits |= LIO;
|
||||
}
|
||||
if ready.is_error() {
|
||||
bits |= ERROR;
|
||||
}
|
||||
if ready.is_hup() {
|
||||
bits |= HUP;
|
||||
}
|
||||
bits
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "ios",
|
||||
target_os = "macos"))]
|
||||
fn usize2ready_aio(ready: &mut UnixReady) {
|
||||
ready.insert(UnixReady::aio());
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "dragonfly",
|
||||
target_os = "freebsd", target_os = "ios", target_os = "macos")))]
|
||||
fn usize2ready_aio(_ready: &mut UnixReady) {
|
||||
// aio not available here → empty
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
fn usize2ready_lio(ready: &mut UnixReady) {
|
||||
ready.insert(UnixReady::lio());
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
fn usize2ready_lio(_ready: &mut UnixReady) {
|
||||
// lio not available here → empty
|
||||
}
|
||||
|
||||
pub fn usize2ready(bits: usize) -> Ready {
|
||||
let mut ready = UnixReady::from(Ready::empty());
|
||||
if bits & AIO != 0 {
|
||||
usize2ready_aio(&mut ready);
|
||||
}
|
||||
if bits & LIO != 0 {
|
||||
usize2ready_lio(&mut ready);
|
||||
}
|
||||
if bits & HUP != 0 {
|
||||
ready.insert(UnixReady::hup());
|
||||
}
|
||||
if bits & ERROR != 0 {
|
||||
ready.insert(UnixReady::error());
|
||||
}
|
||||
ready.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "fuchsia"))]
|
||||
mod platform {
|
||||
use mio::Ready;
|
||||
|
||||
pub fn all() -> Ready {
|
||||
// No platform-specific Readinesses for Windows
|
||||
Ready::empty()
|
||||
}
|
||||
|
||||
pub fn hup() -> Ready {
|
||||
Ready::empty()
|
||||
}
|
||||
|
||||
pub fn ready2usize(_r: Ready) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub fn usize2ready(_r: usize) -> Ready {
|
||||
Ready::empty()
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
use runtime::{Inner, Runtime};
|
||||
|
||||
use reactor::Reactor;
|
||||
|
||||
use std::io;
|
||||
|
||||
use tokio_threadpool::Builder as ThreadPoolBuilder;
|
||||
|
||||
|
||||
|
||||
/// Builds Tokio Runtime with custom configuration values.
|
||||
///
|
||||
/// Methods can be chanined in order to set the configuration values. The
|
||||
/// Runtime is constructed by calling [`build`].
|
||||
///
|
||||
/// New instances of `Builder` are obtained via [`Builder::new`].
|
||||
///
|
||||
/// See function level documentation for details on the various configuration
|
||||
/// settings.
|
||||
///
|
||||
/// [`build`]: #method.build
|
||||
/// [`Builder::new`]: #method.new
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate tokio_threadpool;
|
||||
/// # use tokio::runtime::Builder;
|
||||
///
|
||||
/// # pub fn main() {
|
||||
/// // create and configure ThreadPool
|
||||
/// let mut threadpool_builder = tokio_threadpool::Builder::new();
|
||||
/// threadpool_builder
|
||||
/// .name_prefix("my-runtime-worker-")
|
||||
/// .pool_size(4);
|
||||
///
|
||||
/// // build Runtime
|
||||
/// let runtime = Builder::new()
|
||||
/// .threadpool_builder(threadpool_builder)
|
||||
/// .build();
|
||||
/// // ... call runtime.run(...)
|
||||
/// # let _ = runtime;
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Builder {
|
||||
/// Thread pool specific builder
|
||||
threadpool_builder: ThreadPoolBuilder,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Returns a new runtime builder initialized with default configuration
|
||||
/// values.
|
||||
///
|
||||
/// Configuration methods can be chained on the return value.
|
||||
pub fn new() -> Builder {
|
||||
let mut threadpool_builder = ThreadPoolBuilder::new();
|
||||
threadpool_builder.name_prefix("tokio-runtime-worker-");
|
||||
|
||||
Builder { threadpool_builder }
|
||||
}
|
||||
|
||||
/// Set builder to set up the thread pool instance.
|
||||
pub fn threadpool_builder(&mut self, val: ThreadPoolBuilder) -> &mut Self {
|
||||
self.threadpool_builder = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Create the configured `Runtime`.
|
||||
///
|
||||
/// The returned `ThreadPool` instance is ready to spawn tasks.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate tokio;
|
||||
/// # use tokio::runtime::Builder;
|
||||
/// # pub fn main() {
|
||||
/// let runtime = Builder::new().build();
|
||||
/// // ... call runtime.run(...)
|
||||
/// # let _ = runtime;
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn build(&mut self) -> io::Result<Runtime> {
|
||||
// Spawn a reactor on a background thread.
|
||||
let reactor = Reactor::new()?.background()?;
|
||||
|
||||
// Get a handle to the reactor.
|
||||
let handle = reactor.handle().clone();
|
||||
|
||||
let pool = self.threadpool_builder
|
||||
.around_worker(move |w, enter| {
|
||||
::tokio_reactor::with_default(&handle, enter, |_| {
|
||||
w.run();
|
||||
});
|
||||
})
|
||||
.build();
|
||||
|
||||
Ok(Runtime {
|
||||
inner: Some(Inner {
|
||||
reactor,
|
||||
pool,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
//! A batteries included runtime for applications using Tokio.
|
||||
//!
|
||||
//! Applications using Tokio require some runtime support in order to work:
|
||||
//!
|
||||
//! * A [reactor] to drive I/O resources.
|
||||
//! * An [executor] to execute tasks that use these I/O resources.
|
||||
//!
|
||||
//! While it is possible to setup each component manually, this involves a bunch
|
||||
//! of boilerplate.
|
||||
//!
|
||||
//! [`Runtime`] bundles all of these various runtime components into a single
|
||||
//! handle that can be started and shutdown together, eliminating the necessary
|
||||
//! boilerplate to run a Tokio application.
|
||||
//!
|
||||
//! Most applications wont need to use [`Runtime`] directly. Instead, they will
|
||||
//! use the [`run`] function, which uses [`Runtime`] under the hood.
|
||||
//!
|
||||
//! Creating a [`Runtime`] does the following:
|
||||
//!
|
||||
//! * Spawn a background thread running a [`Reactor`] instance.
|
||||
//! * Start a [`ThreadPool`] for executing futures.
|
||||
//!
|
||||
//! The thread pool uses a work-stealing strategy and is configured to start a
|
||||
//! worker thread for each CPU core available on the system. This tends to be
|
||||
//! the ideal setup for Tokio applications.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Most applications will use the [`run`] function. This takes a future to
|
||||
//! "seed" the application, blocking the thread until the runtime becomes
|
||||
//! [idle].
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate tokio;
|
||||
//! # extern crate futures;
|
||||
//! # use futures::{Future, Stream};
|
||||
//! use tokio::net::TcpListener;
|
||||
//!
|
||||
//! # fn process<T>(_: T) -> Box<Future<Item = (), Error = ()> + Send> {
|
||||
//! # unimplemented!();
|
||||
//! # }
|
||||
//! # fn dox() {
|
||||
//! # let addr = "127.0.0.1:8080".parse().unwrap();
|
||||
//! let listener = TcpListener::bind(&addr).unwrap();
|
||||
//!
|
||||
//! let server = listener.incoming()
|
||||
//! .map_err(|e| println!("error = {:?}", e))
|
||||
//! .for_each(|socket| {
|
||||
//! tokio::spawn(process(socket))
|
||||
//! });
|
||||
//!
|
||||
//! tokio::run(server);
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! In this function, the `run` function blocks until the runtime becomes idle.
|
||||
//! See [`shutdown_on_idle`][idle] for more shutdown details.
|
||||
//!
|
||||
//! From within the context of the runtime, additional tasks are spawned using
|
||||
//! the [`tokio::spawn`] function. Futures spawned using this function will be
|
||||
//! executed on the same thread pool used by the [`Runtime`].
|
||||
//!
|
||||
//! A [`Runtime`] instance can also be used directly.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate tokio;
|
||||
//! # extern crate futures;
|
||||
//! # use futures::{Future, Stream};
|
||||
//! use tokio::runtime::Runtime;
|
||||
//! use tokio::net::TcpListener;
|
||||
//!
|
||||
//! # fn process<T>(_: T) -> Box<Future<Item = (), Error = ()> + Send> {
|
||||
//! # unimplemented!();
|
||||
//! # }
|
||||
//! # fn dox() {
|
||||
//! # let addr = "127.0.0.1:8080".parse().unwrap();
|
||||
//! let listener = TcpListener::bind(&addr).unwrap();
|
||||
//!
|
||||
//! let server = listener.incoming()
|
||||
//! .map_err(|e| println!("error = {:?}", e))
|
||||
//! .for_each(|socket| {
|
||||
//! tokio::spawn(process(socket))
|
||||
//! });
|
||||
//!
|
||||
//! // Create the runtime
|
||||
//! let mut rt = Runtime::new().unwrap();
|
||||
//!
|
||||
//! // Spawn the server task
|
||||
//! rt.spawn(server);
|
||||
//!
|
||||
//! // Wait until the runtime becomes idle and shut it down.
|
||||
//! rt.shutdown_on_idle()
|
||||
//! .wait().unwrap();
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! [reactor]: ../reactor/struct.Reactor.html
|
||||
//! [executor]: https://tokio.rs/docs/getting-started/runtime-model/#executors
|
||||
//! [`Runtime`]: struct.Runtime.html
|
||||
//! [`ThreadPool`]: ../executor/thread_pool/struct.ThreadPool.html
|
||||
//! [`run`]: fn.run.html
|
||||
//! [idle]: struct.Runtime.html#method.shutdown_on_idle
|
||||
//! [`tokio::spawn`]: ../executor/fn.spawn.html
|
||||
|
||||
mod builder;
|
||||
mod shutdown;
|
||||
mod task_executor;
|
||||
|
||||
pub use self::builder::Builder;
|
||||
pub use self::shutdown::Shutdown;
|
||||
pub use self::task_executor::TaskExecutor;
|
||||
|
||||
use reactor::{Background, Handle};
|
||||
|
||||
use std::io;
|
||||
|
||||
use tokio_threadpool as threadpool;
|
||||
|
||||
use futures::future::Future;
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
use futures2;
|
||||
|
||||
/// Handle to the Tokio runtime.
|
||||
///
|
||||
/// The Tokio runtime includes a reactor as well as an executor for running
|
||||
/// tasks.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: index.html
|
||||
#[derive(Debug)]
|
||||
pub struct Runtime {
|
||||
inner: Option<Inner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
/// Reactor running on a background thread.
|
||||
reactor: Background,
|
||||
|
||||
/// Task execution pool.
|
||||
pool: threadpool::ThreadPool,
|
||||
}
|
||||
|
||||
// ===== impl Runtime =====
|
||||
|
||||
/// Start the Tokio runtime using the supplied future to bootstrap execution.
|
||||
///
|
||||
/// This function is used to bootstrap the execution of a Tokio application. It
|
||||
/// does the following:
|
||||
///
|
||||
/// * Start the Tokio runtime using a default configuration.
|
||||
/// * Spawn the given future onto the thread pool.
|
||||
/// * Block the current thread until the runtime shuts down.
|
||||
///
|
||||
/// Note that the function will not return immediately once `future` has
|
||||
/// completed. Instead it waits for the entire runtime to become idle.
|
||||
///
|
||||
/// See the [module level][mod] documentation for more details.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::{Future, Stream};
|
||||
/// use tokio::net::TcpListener;
|
||||
///
|
||||
/// # fn process<T>(_: T) -> Box<Future<Item = (), Error = ()> + Send> {
|
||||
/// # unimplemented!();
|
||||
/// # }
|
||||
/// # fn dox() {
|
||||
/// # let addr = "127.0.0.1:8080".parse().unwrap();
|
||||
/// let listener = TcpListener::bind(&addr).unwrap();
|
||||
///
|
||||
/// let server = listener.incoming()
|
||||
/// .map_err(|e| println!("error = {:?}", e))
|
||||
/// .for_each(|socket| {
|
||||
/// tokio::spawn(process(socket))
|
||||
/// });
|
||||
///
|
||||
/// tokio::run(server);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if called from the context of an executor.
|
||||
///
|
||||
/// [mod]: ../index.html
|
||||
pub fn run<F>(future: F)
|
||||
where F: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
runtime.spawn(future);
|
||||
runtime.shutdown_on_idle().wait().unwrap();
|
||||
}
|
||||
|
||||
/// Start the Tokio runtime using the supplied future to bootstrap execution.
|
||||
///
|
||||
/// Identical to `run` but works with futures 0.2-style futures.
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
pub fn run2<F>(future: F)
|
||||
where F: futures2::Future<Item = (), Error = futures2::Never> + Send + 'static,
|
||||
{
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
runtime.spawn2(future);
|
||||
runtime.shutdown_on_idle().wait().unwrap();
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
/// Create a new runtime instance with default configuration values.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: index.html
|
||||
pub fn new() -> io::Result<Self> {
|
||||
Builder::new().build()
|
||||
}
|
||||
|
||||
/// Return a reference to the reactor handle for this runtime instance.
|
||||
pub fn handle(&self) -> &Handle {
|
||||
self.inner().reactor.handle()
|
||||
}
|
||||
|
||||
/// Return a handle to the runtime's executor.
|
||||
pub fn executor(&self) -> TaskExecutor {
|
||||
let inner = self.inner().pool.sender().clone();
|
||||
TaskExecutor { inner }
|
||||
}
|
||||
|
||||
/// Spawn a future onto the Tokio runtime.
|
||||
///
|
||||
/// This spawns the given future onto the runtime's executor, usually a
|
||||
/// thread pool. The thread pool is then responsible for polling the future
|
||||
/// until it completes.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: index.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::{future, Future, Stream};
|
||||
/// use tokio::runtime::Runtime;
|
||||
///
|
||||
/// # fn dox() {
|
||||
/// // Create the runtime
|
||||
/// let mut rt = Runtime::new().unwrap();
|
||||
///
|
||||
/// // Spawn a future onto the runtime
|
||||
/// rt.spawn(future::lazy(|| {
|
||||
/// println!("now running on a worker thread");
|
||||
/// Ok(())
|
||||
/// }));
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the spawn fails. Failure occurs if the executor
|
||||
/// is currently at capacity and is unable to spawn a new future.
|
||||
pub fn spawn<F>(&mut self, future: F) -> &mut Self
|
||||
where F: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
self.inner_mut().pool.sender().spawn(future).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Spawn a futures 0.2-style future onto the Tokio runtime.
|
||||
///
|
||||
/// Otherwise identical to `spawn`
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
pub fn spawn2<F>(&mut self, future: F) -> &mut Self
|
||||
where F: futures2::Future<Item = (), Error = futures2::Never> + Send + 'static,
|
||||
{
|
||||
futures2::executor::Executor::spawn(
|
||||
self.inner_mut().pool.sender_mut(), Box::new(future)
|
||||
).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Signals the runtime to shutdown once it becomes idle.
|
||||
///
|
||||
/// Returns a future that completes once the shutdown operation has
|
||||
/// completed.
|
||||
///
|
||||
/// This function can be used to perform a graceful shutdown of the runtime.
|
||||
///
|
||||
/// The runtime enters an idle state once **all** of the following occur.
|
||||
///
|
||||
/// * The thread pool has no tasks to execute, i.e., all tasks that were
|
||||
/// spawned have completed.
|
||||
/// * The reactor is not managing any I/O resources.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: index.html
|
||||
pub fn shutdown_on_idle(mut self) -> Shutdown {
|
||||
let inner = self.inner.take().unwrap();
|
||||
|
||||
let inner = Box::new({
|
||||
let pool = inner.pool;
|
||||
let reactor = inner.reactor;
|
||||
|
||||
pool.shutdown_on_idle().and_then(|_| {
|
||||
reactor.shutdown_on_idle()
|
||||
})
|
||||
});
|
||||
|
||||
Shutdown { inner }
|
||||
}
|
||||
|
||||
/// Signals the runtime to shutdown immediately.
|
||||
///
|
||||
/// Returns a future that completes once the shutdown operation has
|
||||
/// completed.
|
||||
///
|
||||
/// This function will forcibly shutdown the runtime, causing any
|
||||
/// in-progress work to become canceled. The shutdown steps are:
|
||||
///
|
||||
/// * Drain any scheduled work queues.
|
||||
/// * Drop any futures that have not yet completed.
|
||||
/// * Drop the reactor.
|
||||
///
|
||||
/// Once the reactor has dropped, any outstanding I/O resources bound to
|
||||
/// that reactor will no longer function. Calling any method on them will
|
||||
/// result in an error.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: index.html
|
||||
pub fn shutdown_now(mut self) -> Shutdown {
|
||||
let inner = self.inner.take().unwrap();
|
||||
Shutdown::shutdown_now(inner)
|
||||
}
|
||||
|
||||
fn inner(&self) -> &Inner {
|
||||
self.inner.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn inner_mut(&mut self) -> &mut Inner {
|
||||
self.inner.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Runtime {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.inner.take() {
|
||||
let shutdown = Shutdown::shutdown_now(inner);
|
||||
let _ = shutdown.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use runtime::Inner;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use futures::{Future, Poll};
|
||||
|
||||
/// A future that resolves when the Tokio `Runtime` is shut down.
|
||||
pub struct Shutdown {
|
||||
pub(super) inner: Box<Future<Item = (), Error = ()> + Send>,
|
||||
}
|
||||
|
||||
impl Shutdown {
|
||||
pub(super) fn shutdown_now(inner: Inner) -> Self {
|
||||
let inner = Box::new({
|
||||
let pool = inner.pool;
|
||||
let reactor = inner.reactor;
|
||||
|
||||
pool.shutdown_now().and_then(|_| {
|
||||
reactor.shutdown_now()
|
||||
.then(|_| {
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
Shutdown { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Shutdown {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
try_ready!(self.inner.poll());
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Shutdown {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Shutdown")
|
||||
.field("inner", &"Box<Future<Item = (), Error = ()>>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
|
||||
use tokio_threadpool::Sender;
|
||||
|
||||
use futures::future::{self, Future};
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
use futures2;
|
||||
|
||||
/// Executes futures on the runtime
|
||||
///
|
||||
/// All futures spawned using this executor will be submitted to the associated
|
||||
/// Runtime's executor. This executor is usually a thread pool.
|
||||
///
|
||||
/// For more details, see the [module level](index.html) documentation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TaskExecutor {
|
||||
pub(super) inner: Sender,
|
||||
}
|
||||
|
||||
impl TaskExecutor {
|
||||
/// Spawn a future onto the Tokio runtime.
|
||||
///
|
||||
/// This spawns the given future onto the runtime's executor, usually a
|
||||
/// thread pool. The thread pool is then responsible for polling the future
|
||||
/// until it completes.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [mod]: index.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::{future, Future, Stream};
|
||||
/// use tokio::runtime::Runtime;
|
||||
///
|
||||
/// # fn dox() {
|
||||
/// // Create the runtime
|
||||
/// let mut rt = Runtime::new().unwrap();
|
||||
/// let executor = rt.executor();
|
||||
///
|
||||
/// // Spawn a future onto the runtime
|
||||
/// executor.spawn(future::lazy(|| {
|
||||
/// println!("now running on a worker thread");
|
||||
/// Ok(())
|
||||
/// }));
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the spawn fails. Failure occurs if the executor
|
||||
/// is currently at capacity and is unable to spawn a new future.
|
||||
pub fn spawn<F>(&self, future: F)
|
||||
where F: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
self.inner.spawn(future).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> future::Executor<T> for TaskExecutor
|
||||
where T: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
fn execute(&self, future: T) -> Result<(), future::ExecuteError<T>> {
|
||||
self.inner.execute(future)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::executor::Executor for TaskExecutor {
|
||||
fn spawn(&mut self, future: Box<Future<Item = (), Error = ()> + Send>)
|
||||
-> Result<(), ::executor::SpawnError>
|
||||
{
|
||||
self.inner.spawn(future)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
fn spawn2(&mut self, future: Box<futures2::Future<Item = (), Error = futures2::Never> + Send>)
|
||||
-> Result<(), futures2::executor::SpawnError>
|
||||
{
|
||||
self.inner.spawn2(future)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
type Task2 = Box<futures2::Future<Item = (), Error = futures2::Never> + Send>;
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
impl futures2::executor::Executor for TaskExecutor {
|
||||
fn spawn(&mut self, f: Task2) -> Result<(), futures2::executor::SpawnError> {
|
||||
futures2::executor::Executor::spawn(&mut self.inner, f)
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<(), futures2::executor::SpawnError> {
|
||||
futures2::executor::Executor::status(&self.inner)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
|
||||
use std::net::TcpStream;
|
||||
use std::thread;
|
||||
use std::io::{Read, Write, BufReader, BufWriter};
|
||||
|
||||
use futures::Future;
|
||||
use futures::stream::Stream;
|
||||
use tokio_io::io::copy;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo_server() {
|
||||
const N: usize = 1024;
|
||||
drop(env_logger::init());
|
||||
|
||||
let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse())));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let msg = "foo bar baz";
|
||||
let t = thread::spawn(move || {
|
||||
let mut s = t!(TcpStream::connect(&addr));
|
||||
|
||||
let t2 = thread::spawn(move || {
|
||||
let mut s = t!(TcpStream::connect(&addr));
|
||||
let mut b = vec![0; msg.len() * N];
|
||||
t!(s.read_exact(&mut b));
|
||||
b
|
||||
});
|
||||
|
||||
let mut expected = Vec::<u8>::new();
|
||||
for _i in 0..N {
|
||||
expected.extend(msg.as_bytes());
|
||||
assert_eq!(t!(s.write(msg.as_bytes())), msg.len());
|
||||
}
|
||||
(expected, t2)
|
||||
});
|
||||
|
||||
let clients = srv.incoming().take(2).collect();
|
||||
let copied = clients.and_then(|clients| {
|
||||
let mut clients = clients.into_iter();
|
||||
let a = BufReader::new(clients.next().unwrap());
|
||||
let b = BufWriter::new(clients.next().unwrap());
|
||||
copy(a, b)
|
||||
});
|
||||
|
||||
let (amt, _, _) = t!(copied.wait());
|
||||
let (expected, t2) = t.join().unwrap();
|
||||
let actual = t2.join().unwrap();
|
||||
|
||||
assert!(expected == actual);
|
||||
assert_eq!(amt, msg.len() as u64 * 1024);
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
#![cfg(not(feature = "unstable-futures"))]
|
||||
|
||||
extern crate tokio;
|
||||
extern crate tokio_executor;
|
||||
extern crate futures;
|
||||
|
||||
use tokio::executor::current_thread::{self, block_on_all, CurrentThread};
|
||||
|
||||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::task;
|
||||
use futures::future::{self, lazy};
|
||||
use futures::prelude::*;
|
||||
use futures::sync::oneshot;
|
||||
|
||||
#[test]
|
||||
fn spawn_from_block_on_all() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
let msg = current_thread::block_on_all(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
|
||||
// Spawn!
|
||||
current_thread::spawn(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
|
||||
Ok::<_, ()>("hello")
|
||||
})).unwrap();
|
||||
|
||||
assert_eq!(2, cnt.get());
|
||||
assert_eq!(msg, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_waits() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
thread::spawn(|| {
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let cnt2 = cnt.clone();
|
||||
|
||||
block_on_all(rx.then(move |_| {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok::<_, ()>(())
|
||||
})).unwrap();
|
||||
|
||||
assert_eq!(1, cnt2.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_many() {
|
||||
const ITER: usize = 200;
|
||||
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
for _ in 0..ITER {
|
||||
let cnt = cnt.clone();
|
||||
current_thread.spawn(lazy(move || {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
}
|
||||
|
||||
current_thread.run().unwrap();
|
||||
|
||||
assert_eq!(cnt.get(), ITER);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_set_global_executor_by_default() {
|
||||
use tokio_executor::Executor;
|
||||
|
||||
block_on_all(lazy(|| {
|
||||
tokio_executor::DefaultExecutor::current()
|
||||
.spawn(Box::new(lazy(|| ok())))
|
||||
.unwrap_err();
|
||||
|
||||
ok()
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_from_block_on_future() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
current_thread.block_on(lazy(|| {
|
||||
let cnt = cnt.clone();
|
||||
|
||||
current_thread::spawn(lazy(move || {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
Ok::<_, ()>(())
|
||||
})).unwrap();
|
||||
|
||||
current_thread.run().unwrap();
|
||||
|
||||
assert_eq!(1, cnt.get());
|
||||
}
|
||||
|
||||
struct Never(Rc<()>);
|
||||
|
||||
impl Future for Never {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outstanding_tasks_are_dropped_when_executor_is_dropped() {
|
||||
let mut rc = Rc::new(());
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
current_thread.spawn(Never(rc.clone()));
|
||||
|
||||
drop(current_thread);
|
||||
|
||||
// Ensure the daemon is dropped
|
||||
assert!(Rc::get_mut(&mut rc).is_some());
|
||||
|
||||
// Using the global spawn fn
|
||||
|
||||
let mut rc = Rc::new(());
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
current_thread.block_on(lazy(|| {
|
||||
current_thread::spawn(Never(rc.clone()));
|
||||
Ok::<_, ()>(())
|
||||
})).unwrap();
|
||||
|
||||
drop(current_thread);
|
||||
|
||||
// Ensure the daemon is dropped
|
||||
assert!(Rc::get_mut(&mut rc).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn nesting_run() {
|
||||
block_on_all(lazy(|| {
|
||||
block_on_all(lazy(|| {
|
||||
ok()
|
||||
})).unwrap();
|
||||
|
||||
ok()
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn run_in_future() {
|
||||
block_on_all(lazy(|| {
|
||||
current_thread::spawn(lazy(|| {
|
||||
block_on_all(lazy(|| {
|
||||
ok()
|
||||
})).unwrap();
|
||||
ok()
|
||||
}));
|
||||
ok()
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_on_infini_future() {
|
||||
let num = Rc::new(Cell::new(0));
|
||||
|
||||
struct Infini {
|
||||
num: Rc<Cell<usize>>,
|
||||
}
|
||||
|
||||
impl Future for Infini {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.num.set(1 + self.num.get());
|
||||
task::current().notify();
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
CurrentThread::new()
|
||||
.spawn(Infini {
|
||||
num: num.clone(),
|
||||
})
|
||||
.turn(None)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, num.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tasks_are_scheduled_fairly() {
|
||||
let state = Rc::new(RefCell::new([0, 0]));
|
||||
|
||||
struct Spin {
|
||||
state: Rc<RefCell<[i32; 2]>>,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl Future for Spin {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
if self.idx == 0 {
|
||||
let diff = state[0] - state[1];
|
||||
|
||||
assert!(diff.abs() <= 1);
|
||||
|
||||
if state[0] >= 50 {
|
||||
return Ok(().into());
|
||||
}
|
||||
}
|
||||
|
||||
state[self.idx] += 1;
|
||||
|
||||
if state[self.idx] >= 100 {
|
||||
return Ok(().into());
|
||||
}
|
||||
|
||||
task::current().notify();
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
block_on_all(lazy(|| {
|
||||
current_thread::spawn(Spin {
|
||||
state: state.clone(),
|
||||
idx: 0,
|
||||
});
|
||||
|
||||
current_thread::spawn(Spin {
|
||||
state: state,
|
||||
idx: 1,
|
||||
});
|
||||
|
||||
ok()
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_and_turn() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
// Spawn a basic task to get the executor to turn
|
||||
current_thread.spawn(lazy(move || {
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
// Turn once...
|
||||
current_thread.turn(None).unwrap();
|
||||
|
||||
current_thread.spawn(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
|
||||
// Spawn!
|
||||
current_thread::spawn(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
// This does not run the newly spawned thread
|
||||
current_thread.turn(None).unwrap();
|
||||
assert_eq!(1, cnt.get());
|
||||
|
||||
// This runs the newly spawned thread
|
||||
current_thread.turn(None).unwrap();
|
||||
assert_eq!(2, cnt.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_in_drop() {
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
current_thread.spawn({
|
||||
struct OnDrop<F: FnOnce()>(Option<F>);
|
||||
|
||||
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.0.take().unwrap())();
|
||||
}
|
||||
}
|
||||
|
||||
struct MyFuture {
|
||||
_data: Box<Any>,
|
||||
}
|
||||
|
||||
impl Future for MyFuture {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
MyFuture {
|
||||
_data: Box::new(OnDrop(Some(move || {
|
||||
current_thread::spawn(lazy(move || {
|
||||
tx.send(()).unwrap();
|
||||
Ok(())
|
||||
}));
|
||||
}))),
|
||||
}
|
||||
});
|
||||
|
||||
current_thread.block_on(rx).unwrap();
|
||||
current_thread.run().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_turn() {
|
||||
use futures::sync::mpsc;
|
||||
|
||||
const ITER: usize = 100;
|
||||
const N: usize = 100;
|
||||
const THREADS: usize = 4;
|
||||
|
||||
for _ in 0..ITER {
|
||||
let mut ths = vec![];
|
||||
|
||||
// Add some jitter
|
||||
for _ in 0..THREADS {
|
||||
let th = thread::spawn(|| {
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
current_thread.spawn({
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
rx.for_each(move |_| {
|
||||
c.set(1 + c.get());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| panic!("err={:?}", e))
|
||||
.map(move |v| {
|
||||
assert_eq!(N, cnt.get());
|
||||
v
|
||||
})
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
for _ in 0..N {
|
||||
tx.unbounded_send(()).unwrap();
|
||||
thread::yield_now();
|
||||
}
|
||||
});
|
||||
|
||||
while !current_thread.is_idle() {
|
||||
current_thread.turn(None).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ths.push(th);
|
||||
}
|
||||
|
||||
for th in ths {
|
||||
th.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ok() -> future::FutureResult<(), ()> {
|
||||
future::ok(())
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
extern crate tokio;
|
||||
extern crate futures;
|
||||
|
||||
use std::thread;
|
||||
use std::net;
|
||||
|
||||
use futures::future;
|
||||
use futures::prelude::*;
|
||||
use futures::sync::oneshot;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::reactor::Reactor;
|
||||
|
||||
#[test]
|
||||
fn tcp_doesnt_block() {
|
||||
let core = Reactor::new().unwrap();
|
||||
let handle = core.handle();
|
||||
let listener = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let listener = TcpListener::from_std(listener, &handle).unwrap();
|
||||
drop(core);
|
||||
assert!(listener.incoming().wait().next().unwrap().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_wakes() {
|
||||
let core = Reactor::new().unwrap();
|
||||
let handle = core.handle();
|
||||
let listener = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let listener = TcpListener::from_std(listener, &handle).unwrap();
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
let t = thread::spawn(move || {
|
||||
let incoming = listener.incoming();
|
||||
let new_socket = incoming.into_future().map_err(|_| ());
|
||||
let drop_tx = future::lazy(|| {
|
||||
drop(tx);
|
||||
future::ok(())
|
||||
});
|
||||
assert!(new_socket.join(drop_tx).wait().is_err());
|
||||
});
|
||||
drop(rx.wait());
|
||||
drop(core);
|
||||
t.join().unwrap();
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#![cfg(feature = "unstable-futures")]
|
||||
|
||||
// This test is the same as `echo.rs`, but ported to futures 0.2
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate futures2;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::thread;
|
||||
|
||||
use futures2::prelude::*;
|
||||
use futures2::executor::block_on;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo_server() {
|
||||
drop(env_logger::init());
|
||||
|
||||
let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse())));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let msg = "foo bar baz";
|
||||
let t = thread::spawn(move || {
|
||||
let mut s = TcpStream::connect(&addr).unwrap();
|
||||
|
||||
for _i in 0..1024 {
|
||||
assert_eq!(t!(s.write(msg.as_bytes())), msg.len());
|
||||
let mut buf = [0; 1024];
|
||||
assert_eq!(t!(s.read(&mut buf)), msg.len());
|
||||
assert_eq!(&buf[..msg.len()], msg.as_bytes());
|
||||
}
|
||||
});
|
||||
|
||||
let clients = srv.incoming();
|
||||
let client = clients.next().map(|e| e.0.unwrap()).map_err(|e| e.0);
|
||||
let halves = client.map(|s| s.split());
|
||||
let copied = halves.and_then(|(a, b)| a.copy_into(b));
|
||||
|
||||
let (amt, _, _) = t!(block_on(copied));
|
||||
t.join().unwrap();
|
||||
|
||||
assert_eq!(amt, msg.len() as u64 * 1024);
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::{io, thread};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
use futures::prelude::*;
|
||||
use tokio::net::{TcpStream, TcpListener};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_old() {
|
||||
let _ = env_logger::init();
|
||||
|
||||
let threads = (0..10).map(|_| {
|
||||
thread::spawn(|| {
|
||||
let srv = t!(TcpListener::bind(&"127.0.0.1:0".parse().unwrap()));
|
||||
let addr = t!(srv.local_addr());
|
||||
let mine = TcpStream::connect(&addr);
|
||||
let theirs = srv.incoming().into_future()
|
||||
.map(|(s, _)| s.unwrap())
|
||||
.map_err(|(s, _)| s);
|
||||
let (mine, theirs) = t!(mine.join(theirs).wait());
|
||||
|
||||
assert_eq!(t!(mine.local_addr()), t!(theirs.peer_addr()));
|
||||
assert_eq!(t!(theirs.local_addr()), t!(mine.peer_addr()));
|
||||
})
|
||||
}).collect::<Vec<_>>();
|
||||
for thread in threads {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct Rd(Arc<TcpStream>);
|
||||
struct Wr(Arc<TcpStream>);
|
||||
|
||||
impl io::Read for Rd {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
<&TcpStream>::read(&mut &*self.0, dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_io::AsyncRead for Rd {
|
||||
}
|
||||
|
||||
impl io::Write for Wr {
|
||||
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
|
||||
<&TcpStream>::write(&mut &*self.0, src)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_io::AsyncWrite for Wr {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_split() {
|
||||
use tokio_io::io;
|
||||
|
||||
const N: usize = 100;
|
||||
const ITER: usize = 10;
|
||||
|
||||
let _ = env_logger::init();
|
||||
|
||||
for _ in 0..ITER {
|
||||
let srv = t!(TcpListener::bind(&"127.0.0.1:0".parse().unwrap()));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let cnt = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
|
||||
fn split(socket: TcpStream, cnt: Arc<AtomicUsize>) {
|
||||
let socket = Arc::new(socket);
|
||||
let rd = Rd(socket.clone());
|
||||
let wr = Wr(socket);
|
||||
|
||||
let cnt2 = cnt.clone();
|
||||
|
||||
let rd = io::read(rd, vec![0; 1])
|
||||
.map(move |_| {
|
||||
cnt2.fetch_add(1, Relaxed);
|
||||
})
|
||||
.map_err(|e| panic!("read error = {:?}", e));
|
||||
|
||||
let wr = io::write_all(wr, b"1")
|
||||
.map(move |_| {
|
||||
cnt.fetch_add(1, Relaxed);
|
||||
})
|
||||
.map_err(move |e| panic!("write error = {:?}", e));
|
||||
|
||||
tokio::spawn(rd);
|
||||
tokio::spawn(wr);
|
||||
}
|
||||
|
||||
rt.spawn({
|
||||
let cnt = cnt.clone();
|
||||
srv.incoming()
|
||||
.map_err(|e| panic!("accept error = {:?}", e))
|
||||
.take(N as u64)
|
||||
.for_each(move |socket| {
|
||||
split(socket, cnt.clone());
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
for _ in 0..N {
|
||||
rt.spawn({
|
||||
let cnt = cnt.clone();
|
||||
TcpStream::connect(&addr)
|
||||
.map_err(move |e| panic!("connect error = {:?}", e))
|
||||
.map(move |socket| split(socket, cnt))
|
||||
});
|
||||
}
|
||||
|
||||
rt.shutdown_on_idle().wait().unwrap();
|
||||
assert_eq!(N * 4, cnt.load(Relaxed));
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
#![cfg(feature = "unstable-futures")]
|
||||
|
||||
// This test is the same as `global.rs`, but ported to futures 0.2
|
||||
|
||||
extern crate futures;
|
||||
extern crate futures2;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::{io, thread};
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures2::prelude::*;
|
||||
use futures2::executor::block_on;
|
||||
use futures2::task;
|
||||
|
||||
use tokio::net::{TcpStream, TcpListener};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer() {
|
||||
let _ = env_logger::init();
|
||||
|
||||
let threads = (0..10).map(|_| {
|
||||
thread::spawn(|| {
|
||||
let srv = t!(TcpListener::bind(&"127.0.0.1:0".parse().unwrap()));
|
||||
let addr = t!(srv.local_addr());
|
||||
let mine = TcpStream::connect(&addr);
|
||||
let theirs = srv.incoming().next()
|
||||
.map(|(s, _)| s.unwrap())
|
||||
.map_err(|(s, _)| s);
|
||||
let (mine, theirs) = t!(block_on(mine.join(theirs)));
|
||||
|
||||
assert_eq!(t!(mine.local_addr()), t!(theirs.peer_addr()));
|
||||
assert_eq!(t!(theirs.local_addr()), t!(mine.peer_addr()));
|
||||
})
|
||||
}).collect::<Vec<_>>();
|
||||
for thread in threads {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct Rd(Arc<TcpStream>);
|
||||
struct Wr(Arc<TcpStream>);
|
||||
|
||||
impl AsyncRead for Rd {
|
||||
fn poll_read(&mut self, cx: &mut task::Context, dst: &mut [u8]) -> Poll<usize, io::Error> {
|
||||
<&TcpStream>::poll_read(&mut &*self.0, cx, dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Wr {
|
||||
fn poll_write(&mut self, cx: &mut task::Context, src: &[u8]) -> Poll<usize, io::Error> {
|
||||
<&TcpStream>::poll_write(&mut &*self.0, cx, src)
|
||||
}
|
||||
|
||||
fn poll_flush(&mut self, _cx: &mut task::Context) -> Poll<(), io::Error> {
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
fn poll_close(&mut self, _cx: &mut task::Context) -> Poll<(), io::Error> {
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_split() {
|
||||
const N: usize = 100;
|
||||
|
||||
let _ = env_logger::init();
|
||||
|
||||
let srv = t!(TcpListener::bind(&"127.0.0.1:0".parse().unwrap()));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
|
||||
fn split(socket: TcpStream) {
|
||||
let socket = Arc::new(socket);
|
||||
let rd = Rd(socket.clone());
|
||||
let wr = Wr(socket);
|
||||
|
||||
let rd = rd.read(vec![0; 1])
|
||||
.map(|_| ())
|
||||
.map_err(|e| panic!("read error = {:?}", e));
|
||||
|
||||
let wr = wr.write_all(b"1")
|
||||
.map(|_| ())
|
||||
.map_err(|e| panic!("write error = {:?}", e));
|
||||
|
||||
tokio::spawn2(rd);
|
||||
tokio::spawn2(wr);
|
||||
}
|
||||
|
||||
rt.spawn2({
|
||||
srv.incoming()
|
||||
.map_err(|e| panic!("accept error = {:?}", e))
|
||||
.take(N as u64)
|
||||
.for_each(|socket| {
|
||||
split(socket);
|
||||
Ok(())
|
||||
})
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
for _ in 0..N {
|
||||
rt.spawn2({
|
||||
TcpStream::connect(&addr)
|
||||
.map_err(|e| panic!("connect error = {:?}", e))
|
||||
.map(|socket| split(socket))
|
||||
});
|
||||
}
|
||||
|
||||
futures::Future::wait(rt.shutdown_on_idle()).unwrap();
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_threadpool;
|
||||
extern crate bytes;
|
||||
|
||||
use std::io;
|
||||
use std::net::Shutdown;
|
||||
|
||||
use bytes::{BytesMut, BufMut};
|
||||
use futures::{Future, Stream, Sink};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_io::codec::{Encoder, Decoder};
|
||||
use tokio_io::io::{write_all, read};
|
||||
use tokio_io::AsyncRead;
|
||||
use tokio_threadpool::Builder;
|
||||
|
||||
pub struct LineCodec;
|
||||
|
||||
impl Decoder for LineCodec {
|
||||
type Item = BytesMut;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, io::Error> {
|
||||
match buf.iter().position(|&b| b == b'\n') {
|
||||
Some(i) => Ok(Some(buf.split_to(i + 1).into())),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, buf: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
||||
if buf.len() == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
let amt = buf.len();
|
||||
Ok(Some(buf.split_to(amt)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for LineCodec {
|
||||
type Item = BytesMut;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: BytesMut, into: &mut BytesMut) -> io::Result<()> {
|
||||
into.put(&item[..]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo() {
|
||||
drop(env_logger::init());
|
||||
|
||||
let pool = Builder::new()
|
||||
.pool_size(1)
|
||||
.build();
|
||||
|
||||
let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let sender = pool.sender().clone();
|
||||
let srv = listener.incoming().for_each(move |socket| {
|
||||
let (sink, stream) = socket.framed(LineCodec).split();
|
||||
sender.spawn(sink.send_all(stream).map(|_| ()).map_err(|_| ())).unwrap();
|
||||
Ok(())
|
||||
});
|
||||
|
||||
pool.sender().spawn(srv.map_err(|e| panic!("srv error: {}", e))).unwrap();
|
||||
|
||||
let client = TcpStream::connect(&addr);
|
||||
let client = client.wait().unwrap();
|
||||
let (client, _) = write_all(client, b"a\n").wait().unwrap();
|
||||
let (client, buf, amt) = read(client, vec![0; 1024]).wait().unwrap();
|
||||
assert_eq!(amt, 2);
|
||||
assert_eq!(&buf[..2], b"a\n");
|
||||
|
||||
let (client, _) = write_all(client, b"\n").wait().unwrap();
|
||||
let (client, buf, amt) = read(client, buf).wait().unwrap();
|
||||
assert_eq!(amt, 1);
|
||||
assert_eq!(&buf[..1], b"\n");
|
||||
|
||||
let (client, _) = write_all(client, b"b").wait().unwrap();
|
||||
client.shutdown(Shutdown::Write).unwrap();
|
||||
let (_client, buf, amt) = read(client, buf).wait().unwrap();
|
||||
assert_eq!(amt, 1);
|
||||
assert_eq!(&buf[..1], b"b");
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
#![cfg(unix)]
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate libc;
|
||||
extern crate mio;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use mio::event::Evented;
|
||||
use mio::unix::{UnixReady, EventedFd};
|
||||
use mio::{PollOpt, Ready, Token};
|
||||
use tokio::reactor::{Handle, PollEvented2};
|
||||
use tokio_io::io::read_to_end;
|
||||
use futures::Future;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
struct MyFile(File);
|
||||
|
||||
impl MyFile {
|
||||
fn new(file: File) -> MyFile {
|
||||
unsafe {
|
||||
let r = libc::fcntl(file.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
|
||||
assert!(r != -1, "fcntl error: {}", io::Error::last_os_error());
|
||||
}
|
||||
MyFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for MyFile {
|
||||
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.read(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Evented for MyFile {
|
||||
fn register(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt)
|
||||
-> io::Result<()> {
|
||||
let hup: Ready = UnixReady::hup().into();
|
||||
EventedFd(&self.0.as_raw_fd()).register(poll, token, interest | hup, opts)
|
||||
}
|
||||
fn reregister(&self, poll: &mio::Poll, token: Token, interest: Ready, opts: PollOpt)
|
||||
-> io::Result<()> {
|
||||
let hup: Ready = UnixReady::hup().into();
|
||||
EventedFd(&self.0.as_raw_fd()).reregister(poll, token, interest | hup, opts)
|
||||
}
|
||||
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
|
||||
EventedFd(&self.0.as_raw_fd()).deregister(poll)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hup() {
|
||||
drop(env_logger::init());
|
||||
|
||||
let handle = Handle::default();
|
||||
unsafe {
|
||||
let mut pipes = [0; 2];
|
||||
assert!(libc::pipe(pipes.as_mut_ptr()) != -1,
|
||||
"pipe error: {}", io::Error::last_os_error());
|
||||
let read = File::from_raw_fd(pipes[0]);
|
||||
let mut write = File::from_raw_fd(pipes[1]);
|
||||
let t = thread::spawn(move || {
|
||||
write.write_all(b"Hello!\n").unwrap();
|
||||
write.write_all(b"Good bye!\n").unwrap();
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
});
|
||||
|
||||
let source = PollEvented2::new_with_handle(MyFile::new(read), &handle).unwrap();
|
||||
|
||||
let reader = read_to_end(source, Vec::new());
|
||||
let (_, content) = t!(reader.wait());
|
||||
assert_eq!(&b"Hello!\nGood bye!\n"[..], &content[..]);
|
||||
t.join().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate env_logger;
|
||||
|
||||
use futures::prelude::*;
|
||||
use tokio::net::{TcpStream, TcpListener};
|
||||
use tokio_io::io;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_runtime_usage() {
|
||||
let _ = env_logger::init();
|
||||
|
||||
// TODO: Don't require the lazy wrapper
|
||||
tokio::run(::futures::future::lazy(|| {
|
||||
let server = t!(TcpListener::bind(&"127.0.0.1:0".parse().unwrap()));
|
||||
let addr = t!(server.local_addr());
|
||||
let client = TcpStream::connect(&addr);
|
||||
|
||||
let server = server.incoming().take(1)
|
||||
.map_err(|e| println!("accept err = {:?}", e))
|
||||
.for_each(|socket| {
|
||||
tokio::spawn({
|
||||
io::write_all(socket, b"hello")
|
||||
.map(|_| println!("write done"))
|
||||
.map_err(|e| println!("write err = {:?}", e))
|
||||
})
|
||||
})
|
||||
.map(|_| println!("accept done"));
|
||||
|
||||
let client = client
|
||||
.map_err(|e| println!("connect err = {:?}", e))
|
||||
.and_then(|client| {
|
||||
// Read all
|
||||
io::read_to_end(client, vec![])
|
||||
.map(|_| println!("read done"))
|
||||
.map_err(|e| println!("read err = {:?}", e))
|
||||
});
|
||||
|
||||
tokio::spawn({
|
||||
server.join(client)
|
||||
.map(|_| println!("done"))
|
||||
})
|
||||
}));
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
#![cfg(feature = "unstable-futures")]
|
||||
|
||||
// This test is the same as `tcp.rs`, but ported to futures 0.2
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate tokio;
|
||||
extern crate mio;
|
||||
extern crate futures2;
|
||||
|
||||
use std::{net, thread};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use futures2::executor::block_on;
|
||||
use futures2::prelude::*;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect() {
|
||||
drop(env_logger::init());
|
||||
let srv = t!(net::TcpListener::bind("127.0.0.1:0"));
|
||||
let addr = t!(srv.local_addr());
|
||||
let t = thread::spawn(move || {
|
||||
t!(srv.accept()).0
|
||||
});
|
||||
|
||||
let stream = TcpStream::connect(&addr);
|
||||
let mine = t!(block_on(stream));
|
||||
let theirs = t.join().unwrap();
|
||||
|
||||
assert_eq!(t!(mine.local_addr()), t!(theirs.peer_addr()));
|
||||
assert_eq!(t!(theirs.local_addr()), t!(mine.peer_addr()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accept() {
|
||||
drop(env_logger::init());
|
||||
let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse())));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let client = srv.incoming().map(move |t| {
|
||||
tx.send(()).unwrap();
|
||||
t
|
||||
}).next().map_err(|e| e.0);
|
||||
assert!(rx.try_recv().is_err());
|
||||
let t = thread::spawn(move || {
|
||||
net::TcpStream::connect(&addr).unwrap()
|
||||
});
|
||||
|
||||
let (mine, _remaining) = t!(block_on(client));
|
||||
let mine = mine.unwrap();
|
||||
let theirs = t.join().unwrap();
|
||||
|
||||
assert_eq!(t!(mine.local_addr()), t!(theirs.peer_addr()));
|
||||
assert_eq!(t!(theirs.local_addr()), t!(mine.peer_addr()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accept2() {
|
||||
drop(env_logger::init());
|
||||
let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse())));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let t = thread::spawn(move || {
|
||||
net::TcpStream::connect(&addr).unwrap()
|
||||
});
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let client = srv.incoming().map(move |t| {
|
||||
tx.send(()).unwrap();
|
||||
t
|
||||
}).next().map_err(|e| e.0);
|
||||
assert!(rx.try_recv().is_err());
|
||||
|
||||
let (mine, _remaining) = t!(block_on(client));
|
||||
mine.unwrap();
|
||||
t.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix {
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use env_logger;
|
||||
use futures2::future;
|
||||
use futures2::executor::block_on;
|
||||
use futures2::io::AsyncRead;
|
||||
use mio::unix::UnixReady;
|
||||
|
||||
use std::{net, thread};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn poll_hup() {
|
||||
drop(env_logger::init());
|
||||
|
||||
let srv = t!(net::TcpListener::bind("127.0.0.1:0"));
|
||||
let addr = t!(srv.local_addr());
|
||||
let t = thread::spawn(move || {
|
||||
let mut client = t!(srv.accept()).0;
|
||||
client.write(b"hello world").unwrap();
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
});
|
||||
|
||||
let mut stream = t!(block_on(TcpStream::connect(&addr)));
|
||||
|
||||
// Poll for HUP before reading.
|
||||
block_on(future::poll_fn(|cx| {
|
||||
stream.poll_read_ready2(cx, UnixReady::hup().into())
|
||||
})).unwrap();
|
||||
|
||||
// Same for write half
|
||||
block_on(future::poll_fn(|cx| {
|
||||
stream.poll_write_ready2(cx)
|
||||
})).unwrap();
|
||||
|
||||
let mut buf = vec![0; 11];
|
||||
|
||||
// Read the data
|
||||
block_on(future::poll_fn(|cx| {
|
||||
stream.poll_read(cx, &mut buf)
|
||||
})).unwrap();
|
||||
|
||||
assert_eq!(b"hello world", &buf[..]);
|
||||
|
||||
t.join().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# 0.2.0-alpha.5 (September 19, 2019)
|
||||
|
||||
- Track tokio release
|
||||
|
||||
# 0.2.0-alpha.4 (August 29, 2019)
|
||||
|
||||
- Track tokio release.
|
||||
|
||||
# 0.2.0-alpha.3 (August 28, 2019)
|
||||
|
||||
### Fix
|
||||
- Infinite loop in `LinesCodec` (#1489).
|
||||
|
||||
# 0.2.0-alpha.2 (August 17, 2019)
|
||||
|
||||
### Changed
|
||||
- Update `futures` dependency to 0.3.0-alpha.18.
|
||||
|
||||
# 0.2.0-alpha.1 (August 8, 2019)
|
||||
|
||||
### Changed
|
||||
- Switch to `async`, `await`, and `std::future`.
|
||||
|
||||
# 0.1.1 (September 26, 2018)
|
||||
|
||||
* Allow setting max line length with `LinesCodec` (#632)
|
||||
|
||||
# 0.1.0 (June 13, 2018)
|
||||
|
||||
* Initial release (#353)
|
||||
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "tokio-codec"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0-alpha.5"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-codec/0.2.0-alpha.5/tokio_codec"
|
||||
description = """
|
||||
Utilities for encoding and decoding frames.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
tokio-io = { version = "=0.2.0-alpha.5", path = "../tokio-io" }
|
||||
|
||||
bytes = "0.4.7"
|
||||
futures-core-preview = "=0.3.0-alpha.18"
|
||||
futures-sink-preview = "=0.3.0-alpha.18"
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "=0.2.0-alpha.5", path = "../tokio" }
|
||||
tokio-test = { version = "=0.2.0-alpha.5", path = "../tokio-test" }
|
||||
|
||||
futures-util-preview = "=0.3.0-alpha.18"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 Tokio Contributors
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
@@ -1,12 +1,10 @@
|
||||
# tokio-udp
|
||||
# tokio-codec
|
||||
|
||||
UDP bindings for `tokio`.
|
||||
|
||||
[Documentation](https://tokio-rs.github.io/tokio/tokio_udp/)
|
||||
Utilities for encoding and decoding frames.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](./LICENSE).
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
use bytes::{Bytes, BufMut, BytesMut};
|
||||
use codec::{Encoder, Decoder};
|
||||
use crate::decoder::Decoder;
|
||||
use crate::encoder::Encoder;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use std::io;
|
||||
|
||||
/// A simple `Codec` implementation that just ships bytes around.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
|
||||
pub struct BytesCodec(());
|
||||
|
||||
impl BytesCodec {
|
||||
/// Creates a new `BytesCodec` for shipping around raw bytes.
|
||||
pub fn new() -> BytesCodec { BytesCodec(()) }
|
||||
pub fn new() -> BytesCodec {
|
||||
BytesCodec(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for BytesCodec {
|
||||
@@ -16,7 +19,7 @@ impl Decoder for BytesCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, io::Error> {
|
||||
if buf.len() > 0 {
|
||||
if !buf.is_empty() {
|
||||
let len = buf.len();
|
||||
Ok(Some(buf.split_to(len)))
|
||||
} else {
|
||||
@@ -1,5 +1,10 @@
|
||||
use std::io;
|
||||
use bytes::BytesMut;
|
||||
use std::io;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::encoder::Encoder;
|
||||
|
||||
use super::Framed;
|
||||
|
||||
/// Decoding of frames via buffers.
|
||||
///
|
||||
@@ -49,12 +54,52 @@ pub trait Decoder {
|
||||
///
|
||||
/// Note that the bytes provided may be empty. If a previous call to
|
||||
/// `decode` consumed all the bytes in the buffer then `decode` will be
|
||||
/// called again until it returns `None`, indicating that more bytes need to
|
||||
/// called again until it returns `Ok(None)`, indicating that more bytes need to
|
||||
/// be read.
|
||||
///
|
||||
/// Finally, if the bytes in the buffer are malformed then an error is
|
||||
/// returned indicating why. This informs `Framed` that the stream is now
|
||||
/// corrupt and should be terminated.
|
||||
///
|
||||
/// # Buffer management
|
||||
///
|
||||
/// Before returning from the function, implementations should ensure that
|
||||
/// the buffer has appropriate capacity in anticipation of future calls to
|
||||
/// `decode`. Failing to do so leads to inefficiency.
|
||||
///
|
||||
/// For example, if frames have a fixed length, or if the length of the
|
||||
/// current frame is known from a header, a possible buffer management
|
||||
/// strategy is:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::io;
|
||||
/// #
|
||||
/// # use bytes::BytesMut;
|
||||
/// # use tokio_codec::Decoder;
|
||||
/// #
|
||||
/// # struct MyCodec;
|
||||
/// #
|
||||
/// impl Decoder for MyCodec {
|
||||
/// // ...
|
||||
/// # type Item = BytesMut;
|
||||
/// # type Error = io::Error;
|
||||
///
|
||||
/// fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
/// // ...
|
||||
///
|
||||
/// // Reserve enough to complete decoding of the current frame.
|
||||
/// let current_frame_len: usize = 1000; // Example.
|
||||
/// // And to start decoding the next frame.
|
||||
/// let next_frame_header_len: usize = 10; // Example.
|
||||
/// src.reserve(current_frame_len + next_frame_header_len);
|
||||
///
|
||||
/// return Ok(None);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// An optimal buffer management strategy minimizes reallocations and
|
||||
/// over-allocations.
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error>;
|
||||
|
||||
/// A default method available to be called when there are no more bytes
|
||||
@@ -71,16 +116,39 @@ pub trait Decoder {
|
||||
/// frames to yield. This behavior enables returning finalization frames
|
||||
/// that may not be based on inbound data.
|
||||
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
match try!(self.decode(buf)) {
|
||||
match self.decode(buf)? {
|
||||
Some(frame) => Ok(Some(frame)),
|
||||
None => {
|
||||
if buf.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Other,
|
||||
"bytes remaining on stream").into())
|
||||
Err(io::Error::new(io::ErrorKind::Other, "bytes remaining on stream").into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a `Stream` and `Sink` interface for reading and writing to this
|
||||
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// method layers framing on top of an I/O object, by using the `Codec`
|
||||
/// traits to handle encoding and decoding of messages frames. Note that
|
||||
/// the incoming and outgoing frame types may be distinct.
|
||||
///
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
/// calling `split` on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
fn framed<T: AsyncRead + AsyncWrite + Sized>(self, io: T) -> Framed<T, Self>
|
||||
where
|
||||
Self: Encoder + Sized,
|
||||
{
|
||||
Framed::new(io, self)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::io;
|
||||
use bytes::BytesMut;
|
||||
use std::io;
|
||||
|
||||
/// Trait of helper objects to write out messages as bytes, for use with
|
||||
/// `FramedWrite`.
|
||||
@@ -18,6 +18,5 @@ pub trait Encoder {
|
||||
/// This method will encode `item` into the byte buffer provided by `dst`.
|
||||
/// The `dst` provided is an internal buffer of the `Framed` instance and
|
||||
/// will be written out when possible.
|
||||
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut)
|
||||
-> Result<(), Self::Error>;
|
||||
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error>;
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
#![allow(deprecated)]
|
||||
|
||||
use crate::decoder::Decoder;
|
||||
use crate::encoder::Encoder;
|
||||
use crate::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2};
|
||||
use crate::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2};
|
||||
|
||||
use tokio_io::{AsyncBufRead, AsyncRead, AsyncWrite};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_core::Stream;
|
||||
use futures_sink::Sink;
|
||||
use std::fmt;
|
||||
use std::io::{self, BufRead, Read, Write};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using
|
||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
||||
///
|
||||
/// You can create a `Framed` instance by using the `AsyncRead::framed` adapter.
|
||||
pub struct Framed<T, U> {
|
||||
inner: FramedRead2<FramedWrite2<Fuse<T, U>>>,
|
||||
}
|
||||
|
||||
pub(crate) struct Fuse<T, U>(pub(crate) T, pub(crate) U);
|
||||
|
||||
impl<T, U> Framed<T, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder + Encoder,
|
||||
{
|
||||
/// Provides a `Stream` and `Sink` interface for reading and writing to this
|
||||
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// method layers framing on top of an I/O object, by using the `Codec`
|
||||
/// traits to handle encoding and decoding of messages frames. Note that
|
||||
/// the incoming and outgoing frame types may be distinct.
|
||||
///
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
/// calling `split` on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
pub fn new(inner: T, codec: U) -> Framed<T, U> {
|
||||
Framed {
|
||||
inner: framed_read2(framed_write2(Fuse(inner, codec))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U> {
|
||||
/// Provides a `Stream` and `Sink` interface for reading and writing to this
|
||||
/// `Io` object, using `Decode` and `Encode` to read and write the raw data.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// method layers framing on top of an I/O object, by using the `Codec`
|
||||
/// traits to handle encoding and decoding of messages frames. Note that
|
||||
/// the incoming and outgoing frame types may be distinct.
|
||||
///
|
||||
/// This function returns a *single* object that is both `Stream` and
|
||||
/// `Sink`; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
///
|
||||
/// This objects takes a stream and a readbuffer and a writebuffer. These field
|
||||
/// can be obtained from an existing `Framed` with the `into_parts` method.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
/// calling `split` on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
||||
Framed {
|
||||
inner: framed_read2_with_buffer(
|
||||
framed_write2_with_buffer(Fuse(parts.io, parts.codec), parts.write_buf),
|
||||
parts.read_buf,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `Frame`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner.get_ref().get_ref().0
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
/// `Frame`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.get_mut().get_mut().0
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying codec wrapped by
|
||||
/// `Frame`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec(&self) -> &U {
|
||||
&self.inner.get_ref().get_ref().1
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying codec wrapped by
|
||||
/// `Frame`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec_mut(&mut self) -> &mut U {
|
||||
&mut self.inner.get_mut().get_mut().1
|
||||
}
|
||||
|
||||
/// Consumes the `Frame`, returning its underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.into_inner().into_inner().0
|
||||
}
|
||||
|
||||
/// Consumes the `Frame`, returning its underlying I/O stream, the buffer
|
||||
/// with unprocessed data, and the codec.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_parts(self) -> FramedParts<T, U> {
|
||||
let (inner, read_buf) = self.inner.into_parts();
|
||||
let (inner, write_buf) = inner.into_parts();
|
||||
|
||||
FramedParts {
|
||||
io: inner.0,
|
||||
codec: inner.1,
|
||||
read_buf,
|
||||
write_buf,
|
||||
_priv: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Stream for Framed<T, U>
|
||||
where
|
||||
T: AsyncRead + Unpin,
|
||||
U: Decoder + Unpin,
|
||||
{
|
||||
type Item = Result<U::Item, U::Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
pin!(self.get_mut().inner).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I, U> Sink<I> for Framed<T, U>
|
||||
where
|
||||
T: AsyncWrite + Unpin,
|
||||
U: Encoder<Item = I> + Unpin,
|
||||
U::Error: From<io::Error>,
|
||||
{
|
||||
type Error = U::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Pin::new(Pin::get_mut(self).inner.get_mut()).poll_ready(cx)
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
Pin::new(Pin::get_mut(self).inner.get_mut()).start_send(item)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Pin::new(Pin::get_mut(self).inner.get_mut()).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Pin::new(Pin::get_mut(self).inner.get_mut()).poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> fmt::Debug for Framed<T, U>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
U: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Framed")
|
||||
.field("io", &self.inner.get_ref().get_ref().0)
|
||||
.field("codec", &self.inner.get_ref().get_ref().1)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Fuse =====
|
||||
|
||||
impl<T: Read, U> Read for Fuse<T, U> {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.read(dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BufRead, U> BufRead for Fuse<T, U> {
|
||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||
self.0.fill_buf()
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.0.consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + Unpin, U: Unpin> AsyncRead for Fuse<T, U> {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
|
||||
self.0.prepare_uninitialized_buffer(buf)
|
||||
}
|
||||
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
pin!(self.get_mut().0).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncBufRead + Unpin, U: Unpin> AsyncBufRead for Fuse<T, U> {
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
pin!(self.get_mut().0).poll_fill_buf(cx)
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
pin!(self.get_mut().0).consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write, U> Write for Fuse<T, U> {
|
||||
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
|
||||
self.0.write(src)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.0.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite + Unpin, U: Unpin> AsyncWrite for Fuse<T, U> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
pin!(self.get_mut().0).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
pin!(self.get_mut().0).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
pin!(self.get_mut().0).poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: Decoder> Decoder for Fuse<T, U> {
|
||||
type Item = U::Item;
|
||||
type Error = U::Error;
|
||||
|
||||
fn decode(&mut self, buffer: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
self.1.decode(buffer)
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, buffer: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
self.1.decode_eof(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: Encoder> Encoder for Fuse<T, U> {
|
||||
type Item = U::Item;
|
||||
type Error = U::Error;
|
||||
|
||||
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
self.1.encode(item, dst)
|
||||
}
|
||||
}
|
||||
|
||||
/// `FramedParts` contains an export of the data of a Framed transport.
|
||||
/// It can be used to construct a new `Framed` with a different codec.
|
||||
/// It contains all current buffers and the inner transport.
|
||||
#[derive(Debug)]
|
||||
pub struct FramedParts<T, U> {
|
||||
/// The inner transport used to read bytes to and write bytes to
|
||||
pub io: T,
|
||||
|
||||
/// The codec
|
||||
pub codec: U,
|
||||
|
||||
/// The buffer with read but unprocessed data.
|
||||
pub read_buf: BytesMut,
|
||||
|
||||
/// A buffer with unprocessed data which are not written yet.
|
||||
pub write_buf: BytesMut,
|
||||
|
||||
/// This private field allows us to add additional fields in the future in a
|
||||
/// backwards compatible way.
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
impl<T, U> FramedParts<T, U> {
|
||||
/// Create a new, default, `FramedParts`
|
||||
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
||||
FramedParts {
|
||||
io,
|
||||
codec,
|
||||
read_buf: BytesMut::new(),
|
||||
write_buf: BytesMut::new(),
|
||||
_priv: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
use std::fmt;
|
||||
use super::framed::Fuse;
|
||||
use super::Decoder;
|
||||
|
||||
use AsyncRead;
|
||||
use codec::Decoder;
|
||||
use framed::Fuse;
|
||||
use tokio_io::AsyncRead;
|
||||
|
||||
use futures::{Async, Poll, Stream, Sink, StartSend};
|
||||
use bytes::BytesMut;
|
||||
use futures_core::Stream;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// A `Stream` of messages decoded from an `AsyncRead`.
|
||||
pub struct FramedRead<T, D> {
|
||||
inner: FramedRead2<Fuse<T, D>>,
|
||||
}
|
||||
|
||||
pub struct FramedRead2<T> {
|
||||
pub(crate) struct FramedRead2<T> {
|
||||
inner: T,
|
||||
eof: bool,
|
||||
is_readable: bool,
|
||||
@@ -24,8 +28,9 @@ const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
// ===== impl FramedRead =====
|
||||
|
||||
impl<T, D> FramedRead<T, D>
|
||||
where T: AsyncRead,
|
||||
D: Decoder,
|
||||
where
|
||||
T: AsyncRead,
|
||||
D: Decoder,
|
||||
{
|
||||
/// Creates a new `FramedRead` with the given `decoder`.
|
||||
pub fn new(inner: T, decoder: D) -> FramedRead<T, D> {
|
||||
@@ -77,44 +82,48 @@ impl<T, D> FramedRead<T, D> {
|
||||
}
|
||||
|
||||
impl<T, D> Stream for FramedRead<T, D>
|
||||
where T: AsyncRead,
|
||||
D: Decoder,
|
||||
where
|
||||
T: AsyncRead + Unpin,
|
||||
D: Decoder + Unpin,
|
||||
{
|
||||
type Item = D::Item;
|
||||
type Error = D::Error;
|
||||
type Item = Result<D::Item, D::Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
self.inner.poll()
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
pin!(self.get_mut().inner).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> Sink for FramedRead<T, D>
|
||||
where T: Sink,
|
||||
// This impl just defers to the underlying T: Sink
|
||||
impl<T, I, D> Sink<I> for FramedRead<T, D>
|
||||
where
|
||||
T: Sink<I> + Unpin,
|
||||
D: Unpin,
|
||||
{
|
||||
type SinkItem = T::SinkItem;
|
||||
type SinkError = T::SinkError;
|
||||
type Error = T::Error;
|
||||
|
||||
fn start_send(&mut self,
|
||||
item: Self::SinkItem)
|
||||
-> StartSend<Self::SinkItem, Self::SinkError>
|
||||
{
|
||||
self.inner.inner.0.start_send(item)
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
pin!(Pin::get_mut(self).inner.inner.0).poll_ready(cx)
|
||||
}
|
||||
|
||||
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
|
||||
self.inner.inner.0.poll_complete()
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
pin!(Pin::get_mut(self).inner.inner.0).start_send(item)
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Poll<(), Self::SinkError> {
|
||||
self.inner.inner.0.close()
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
pin!(Pin::get_mut(self).inner.inner.0).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
pin!(Pin::get_mut(self).inner.inner.0).poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> fmt::Debug for FramedRead<T, D>
|
||||
where T: fmt::Debug,
|
||||
D: fmt::Debug,
|
||||
where
|
||||
T: fmt::Debug,
|
||||
D: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("FramedRead")
|
||||
.field("inner", &self.inner.inner.0)
|
||||
.field("decoder", &self.inner.inner.1)
|
||||
@@ -127,86 +136,91 @@ impl<T, D> fmt::Debug for FramedRead<T, D>
|
||||
|
||||
// ===== impl FramedRead2 =====
|
||||
|
||||
pub fn framed_read2<T>(inner: T) -> FramedRead2<T> {
|
||||
pub(crate) fn framed_read2<T>(inner: T) -> FramedRead2<T> {
|
||||
FramedRead2 {
|
||||
inner: inner,
|
||||
inner,
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn framed_read2_with_buffer<T>(inner: T, mut buf: BytesMut) -> FramedRead2<T> {
|
||||
pub(crate) fn framed_read2_with_buffer<T>(inner: T, mut buf: BytesMut) -> FramedRead2<T> {
|
||||
if buf.capacity() < INITIAL_CAPACITY {
|
||||
let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity();
|
||||
buf.reserve(bytes_to_reserve);
|
||||
}
|
||||
FramedRead2 {
|
||||
inner: inner,
|
||||
inner,
|
||||
eof: false,
|
||||
is_readable: buf.len() > 0,
|
||||
is_readable: !buf.is_empty(),
|
||||
buffer: buf,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FramedRead2<T> {
|
||||
pub fn get_ref(&self) -> &T {
|
||||
pub(crate) fn get_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
pub(crate) fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub fn into_parts(self) -> (T, BytesMut) {
|
||||
pub(crate) fn into_parts(self) -> (T, BytesMut) {
|
||||
(self.inner, self.buffer)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
pub(crate) fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Stream for FramedRead2<T>
|
||||
where T: AsyncRead + Decoder,
|
||||
where
|
||||
T: AsyncRead + Decoder + Unpin,
|
||||
{
|
||||
type Item = T::Item;
|
||||
type Error = T::Error;
|
||||
type Item = Result<T::Item, T::Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let pinned = Pin::get_mut(self);
|
||||
loop {
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
||||
// "readable". Readable is defined as not having returned `None`. If
|
||||
// the upstream has returned EOF, and the decoder is no longer
|
||||
// readable, it can be assumed that the decoder will never become
|
||||
// readable again, at which point the stream is terminated.
|
||||
if self.is_readable {
|
||||
if self.eof {
|
||||
let frame = try!(self.inner.decode_eof(&mut self.buffer));
|
||||
return Ok(Async::Ready(frame));
|
||||
if pinned.is_readable {
|
||||
if pinned.eof {
|
||||
let frame = pinned.inner.decode_eof(&mut pinned.buffer)?;
|
||||
return Poll::Ready(frame.map(Ok));
|
||||
}
|
||||
|
||||
trace!("attempting to decode a frame");
|
||||
|
||||
if let Some(frame) = try!(self.inner.decode(&mut self.buffer)) {
|
||||
if let Some(frame) = pinned.inner.decode(&mut pinned.buffer)? {
|
||||
trace!("frame decoded from buffer");
|
||||
return Ok(Async::Ready(Some(frame)));
|
||||
return Poll::Ready(Some(Ok(frame)));
|
||||
}
|
||||
|
||||
self.is_readable = false;
|
||||
pinned.is_readable = false;
|
||||
}
|
||||
|
||||
assert!(!self.eof);
|
||||
assert!(!pinned.eof);
|
||||
|
||||
// Otherwise, try to read more data and try again. Make sure we've
|
||||
// got room for at least one byte to read to ensure that we don't
|
||||
// get a spurious 0 that looks like EOF
|
||||
self.buffer.reserve(1);
|
||||
if 0 == try_ready!(self.inner.read_buf(&mut self.buffer)) {
|
||||
self.eof = true;
|
||||
pinned.buffer.reserve(1);
|
||||
let bytect = match pin!(pinned.inner).poll_read_buf(cx, &mut pinned.buffer)? {
|
||||
Poll::Ready(ct) => ct,
|
||||
Poll::Pending => return Poll::Pending,
|
||||
};
|
||||
if bytect == 0 {
|
||||
pinned.eof = true;
|
||||
}
|
||||
|
||||
self.is_readable = true;
|
||||
pinned.is_readable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
#![allow(deprecated)]
|
||||
|
||||
use super::framed::Fuse;
|
||||
use crate::decoder::Decoder;
|
||||
use crate::encoder::Encoder;
|
||||
|
||||
use tokio_io::{AsyncBufRead, AsyncRead, AsyncWrite};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
use std::fmt;
|
||||
use std::io::{self, BufRead, Read};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// A `Sink` of frames encoded to an `AsyncWrite`.
|
||||
pub struct FramedWrite<T, E> {
|
||||
inner: FramedWrite2<Fuse<T, E>>,
|
||||
}
|
||||
|
||||
pub(crate) struct FramedWrite2<T> {
|
||||
inner: T,
|
||||
buffer: BytesMut,
|
||||
}
|
||||
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY;
|
||||
|
||||
impl<T, E> FramedWrite<T, E>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
E: Encoder,
|
||||
{
|
||||
/// Creates a new `FramedWrite` with the given `encoder`.
|
||||
pub fn new(inner: T, encoder: E) -> FramedWrite<T, E> {
|
||||
FramedWrite {
|
||||
inner: framed_write2(Fuse(inner, encoder)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> FramedWrite<T, E> {
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `FramedWrite`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
/// `FramedWrite`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Consumes the `FramedWrite`, returning its underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.inner.0
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying decoder.
|
||||
pub fn encoder(&self) -> &E {
|
||||
&self.inner.inner.1
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying decoder.
|
||||
pub fn encoder_mut(&mut self) -> &mut E {
|
||||
&mut self.inner.inner.1
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedWrite2
|
||||
impl<T, I, E> Sink<I> for FramedWrite<T, E>
|
||||
where
|
||||
T: AsyncWrite + Unpin,
|
||||
E: Encoder<Item = I> + Unpin,
|
||||
E::Error: From<io::Error>,
|
||||
{
|
||||
type Error = E::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
pin!(Pin::get_mut(self).inner).poll_ready(cx)
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
pin!(Pin::get_mut(self).inner).start_send(item)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
pin!(Pin::get_mut(self).inner).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
pin!(Pin::get_mut(self).inner).poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> Stream for FramedWrite<T, D>
|
||||
where
|
||||
T: Stream + Unpin,
|
||||
D: Unpin,
|
||||
{
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Pin::new(Pin::get_mut(self).get_mut()).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> fmt::Debug for FramedWrite<T, U>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
U: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("FramedWrite")
|
||||
.field("inner", &self.inner.get_ref().0)
|
||||
.field("encoder", &self.inner.get_ref().1)
|
||||
.field("buffer", &self.inner.buffer)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FramedWrite2 =====
|
||||
|
||||
pub(crate) fn framed_write2<T>(inner: T) -> FramedWrite2<T> {
|
||||
FramedWrite2 {
|
||||
inner,
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn framed_write2_with_buffer<T>(inner: T, mut buf: BytesMut) -> FramedWrite2<T> {
|
||||
if buf.capacity() < INITIAL_CAPACITY {
|
||||
let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity();
|
||||
buf.reserve(bytes_to_reserve);
|
||||
}
|
||||
FramedWrite2 { inner, buffer: buf }
|
||||
}
|
||||
|
||||
impl<T> FramedWrite2<T> {
|
||||
pub(crate) fn get_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn into_parts(self) -> (T, BytesMut) {
|
||||
(self.inner, self.buffer)
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> Sink<I> for FramedWrite2<T>
|
||||
where
|
||||
T: AsyncWrite + Encoder<Item = I> + Unpin,
|
||||
{
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// If the buffer is already over 8KiB, then attempt to flush it. If after flushing it's
|
||||
// *still* over 8KiB, then apply backpressure (reject the send).
|
||||
if self.buffer.len() >= BACKPRESSURE_BOUNDARY {
|
||||
match self.as_mut().poll_flush(cx) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
|
||||
Poll::Ready(Ok(())) => (),
|
||||
};
|
||||
|
||||
if self.buffer.len() >= BACKPRESSURE_BOUNDARY {
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
let pinned = Pin::get_mut(self);
|
||||
pinned.inner.encode(item, &mut pinned.buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
trace!("flushing framed transport");
|
||||
let pinned = Pin::get_mut(self);
|
||||
|
||||
while !pinned.buffer.is_empty() {
|
||||
trace!("writing; remaining={}", pinned.buffer.len());
|
||||
|
||||
let buf = &pinned.buffer;
|
||||
let n = ready!(pin!(pinned.inner).poll_write(cx, &buf))?;
|
||||
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::WriteZero,
|
||||
"failed to \
|
||||
write frame to transport",
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
|
||||
// TODO: Add a way to `bytes` to do this w/o returning the drained data.
|
||||
let _ = pinned.buffer.split_to(n);
|
||||
}
|
||||
|
||||
// Try flushing the underlying IO
|
||||
ready!(pin!(pinned.inner).poll_flush(cx))?;
|
||||
|
||||
trace!("framed transport flushed");
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(pin!(self).poll_flush(cx))?;
|
||||
ready!(pin!(self.inner).poll_shutdown(cx))?;
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decoder> Decoder for FramedWrite2<T> {
|
||||
type Item = T::Item;
|
||||
type Error = T::Error;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<T::Item>, T::Error> {
|
||||
self.inner.decode(src)
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, src: &mut BytesMut) -> Result<Option<T::Item>, T::Error> {
|
||||
self.inner.decode_eof(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read> Read for FramedWrite2<T> {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
self.inner.read(dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BufRead> BufRead for FramedWrite2<T> {
|
||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||
self.inner.fill_buf()
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.inner.consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + Unpin> AsyncRead for FramedWrite2<T> {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
|
||||
self.inner.prepare_uninitialized_buffer(buf)
|
||||
}
|
||||
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
pin!(self.get_mut().inner).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncBufRead + Unpin> AsyncBufRead for FramedWrite2<T> {
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
pin!(self.get_mut().inner).poll_fill_buf(cx)
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
pin!(self.get_mut().inner).consume(amt)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,960 @@
|
||||
//! Frame a stream of bytes based on a length prefix
|
||||
//!
|
||||
//! Many protocols delimit their frames by prefacing frame data with a
|
||||
//! frame head that specifies the length of the frame. The
|
||||
//! `length_delimited` module provides utilities for handling the length
|
||||
//! based framing. This allows the consumer to work with entire frames
|
||||
//! without having to worry about buffering or other framing logic.
|
||||
//!
|
||||
//! # Getting started
|
||||
//!
|
||||
//! If implementing a protocol from scratch, using length delimited framing
|
||||
//! is an easy way to get started. [`LengthDelimitedCodec::new()`] will
|
||||
//! return a length delimited codec using default configuration values.
|
||||
//! This can then be used to construct a framer to adapt a full-duplex
|
||||
//! byte stream into a stream of frames.
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio_io::{AsyncRead, AsyncWrite};
|
||||
//! use tokio_codec::{Framed, LengthDelimitedCodec};
|
||||
//!
|
||||
//! fn bind_transport<T: AsyncRead + AsyncWrite>(io: T)
|
||||
//! -> Framed<T, LengthDelimitedCodec>
|
||||
//! {
|
||||
//! Framed::new(io, LengthDelimitedCodec::new())
|
||||
//! }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! The returned transport implements `Sink + Stream` for `BytesMut`. It
|
||||
//! encodes the frame with a big-endian `u32` header denoting the frame
|
||||
//! payload length:
|
||||
//!
|
||||
//! ```text
|
||||
//! +----------+--------------------------------+
|
||||
//! | len: u32 | frame payload |
|
||||
//! +----------+--------------------------------+
|
||||
//! ```
|
||||
//!
|
||||
//! Specifically, given the following:
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::codec::{Framed, LengthDelimitedCodec};
|
||||
//! use tokio::prelude::*;
|
||||
//!
|
||||
//! use bytes::Bytes;
|
||||
//!
|
||||
//! async fn write_frame<T>(io: T) -> Result<(), Box<dyn std::error::Error>>
|
||||
//! where
|
||||
//! T: AsyncRead + AsyncWrite + Unpin,
|
||||
//! {
|
||||
//! let mut transport = Framed::new(io, LengthDelimitedCodec::new());
|
||||
//! let frame = Bytes::from("hello world");
|
||||
//!
|
||||
//! transport.send(frame).await?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! The encoded frame will look like this:
|
||||
//!
|
||||
//! ```text
|
||||
//! +---- len: u32 ----+---- data ----+
|
||||
//! | \x00\x00\x00\x0b | hello world |
|
||||
//! +------------------+--------------+
|
||||
//! ```
|
||||
//!
|
||||
//! # Decoding
|
||||
//!
|
||||
//! [`FramedRead`] adapts an [`AsyncRead`] into a `Stream` of [`BytesMut`],
|
||||
//! such that each yielded [`BytesMut`] value contains the contents of an
|
||||
//! entire frame. There are many configuration parameters enabling
|
||||
//! [`FramedRead`] to handle a wide range of protocols. Here are some
|
||||
//! examples that will cover the various options at a high level.
|
||||
//!
|
||||
//! ## Example 1
|
||||
//!
|
||||
//! The following will parse a `u16` length field at offset 0, including the
|
||||
//! frame head in the yielded `BytesMut`.
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_io::AsyncRead;
|
||||
//! # use tokio_codec::LengthDelimitedCodec;
|
||||
//! # fn bind_read<T: AsyncRead>(io: T) {
|
||||
//! LengthDelimitedCodec::builder()
|
||||
//! .length_field_offset(0) // default value
|
||||
//! .length_field_length(2)
|
||||
//! .length_adjustment(0) // default value
|
||||
//! .num_skip(0) // Do not strip frame header
|
||||
//! .new_read(io);
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! The following frame will be decoded as such:
|
||||
//!
|
||||
//! ```text
|
||||
//! INPUT DECODED
|
||||
//! +-- len ---+--- Payload ---+ +-- len ---+--- Payload ---+
|
||||
//! | \x00\x0B | Hello world | --> | \x00\x0B | Hello world |
|
||||
//! +----------+---------------+ +----------+---------------+
|
||||
//! ```
|
||||
//!
|
||||
//! The value of the length field is 11 (`\x0B`) which represents the length
|
||||
//! of the payload, `hello world`. By default, [`FramedRead`] assumes that
|
||||
//! the length field represents the number of bytes that **follows** the
|
||||
//! length field. Thus, the entire frame has a length of 13: 2 bytes for the
|
||||
//! frame head + 11 bytes for the payload.
|
||||
//!
|
||||
//! ## Example 2
|
||||
//!
|
||||
//! The following will parse a `u16` length field at offset 0, omitting the
|
||||
//! frame head in the yielded `BytesMut`.
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_io::AsyncRead;
|
||||
//! # use tokio_codec::LengthDelimitedCodec;
|
||||
//! # fn bind_read<T: AsyncRead>(io: T) {
|
||||
//! LengthDelimitedCodec::builder()
|
||||
//! .length_field_offset(0) // default value
|
||||
//! .length_field_length(2)
|
||||
//! .length_adjustment(0) // default value
|
||||
//! // `num_skip` is not needed, the default is to skip
|
||||
//! .new_read(io);
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! The following frame will be decoded as such:
|
||||
//!
|
||||
//! ```text
|
||||
//! INPUT DECODED
|
||||
//! +-- len ---+--- Payload ---+ +--- Payload ---+
|
||||
//! | \x00\x0B | Hello world | --> | Hello world |
|
||||
//! +----------+---------------+ +---------------+
|
||||
//! ```
|
||||
//!
|
||||
//! This is similar to the first example, the only difference is that the
|
||||
//! frame head is **not** included in the yielded `BytesMut` value.
|
||||
//!
|
||||
//! ## Example 3
|
||||
//!
|
||||
//! The following will parse a `u16` length field at offset 0, including the
|
||||
//! frame head in the yielded `BytesMut`. In this case, the length field
|
||||
//! **includes** the frame head length.
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_io::AsyncRead;
|
||||
//! # use tokio_codec::LengthDelimitedCodec;
|
||||
//! # fn bind_read<T: AsyncRead>(io: T) {
|
||||
//! LengthDelimitedCodec::builder()
|
||||
//! .length_field_offset(0) // default value
|
||||
//! .length_field_length(2)
|
||||
//! .length_adjustment(-2) // size of head
|
||||
//! .num_skip(0)
|
||||
//! .new_read(io);
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! The following frame will be decoded as such:
|
||||
//!
|
||||
//! ```text
|
||||
//! INPUT DECODED
|
||||
//! +-- len ---+--- Payload ---+ +-- len ---+--- Payload ---+
|
||||
//! | \x00\x0D | Hello world | --> | \x00\x0D | Hello world |
|
||||
//! +----------+---------------+ +----------+---------------+
|
||||
//! ```
|
||||
//!
|
||||
//! In most cases, the length field represents the length of the payload
|
||||
//! only, as shown in the previous examples. However, in some protocols the
|
||||
//! length field represents the length of the whole frame, including the
|
||||
//! head. In such cases, we specify a negative `length_adjustment` to adjust
|
||||
//! the value provided in the frame head to represent the payload length.
|
||||
//!
|
||||
//! ## Example 4
|
||||
//!
|
||||
//! The following will parse a 3 byte length field at offset 0 in a 5 byte
|
||||
//! frame head, including the frame head in the yielded `BytesMut`.
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_io::AsyncRead;
|
||||
//! # use tokio_codec::LengthDelimitedCodec;
|
||||
//! # fn bind_read<T: AsyncRead>(io: T) {
|
||||
//! LengthDelimitedCodec::builder()
|
||||
//! .length_field_offset(0) // default value
|
||||
//! .length_field_length(3)
|
||||
//! .length_adjustment(2) // remaining head
|
||||
//! .num_skip(0)
|
||||
//! .new_read(io);
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! The following frame will be decoded as such:
|
||||
//!
|
||||
//! ```text
|
||||
//! INPUT
|
||||
//! +---- len -----+- head -+--- Payload ---+
|
||||
//! | \x00\x00\x0B | \xCAFE | Hello world |
|
||||
//! +--------------+--------+---------------+
|
||||
//!
|
||||
//! DECODED
|
||||
//! +---- len -----+- head -+--- Payload ---+
|
||||
//! | \x00\x00\x0B | \xCAFE | Hello world |
|
||||
//! +--------------+--------+---------------+
|
||||
//! ```
|
||||
//!
|
||||
//! A more advanced example that shows a case where there is extra frame
|
||||
//! head data between the length field and the payload. In such cases, it is
|
||||
//! usually desirable to include the frame head as part of the yielded
|
||||
//! `BytesMut`. This lets consumers of the length delimited framer to
|
||||
//! process the frame head as needed.
|
||||
//!
|
||||
//! The positive `length_adjustment` value lets `FramedRead` factor in the
|
||||
//! additional head into the frame length calculation.
|
||||
//!
|
||||
//! ## Example 5
|
||||
//!
|
||||
//! The following will parse a `u16` length field at offset 1 of a 4 byte
|
||||
//! frame head. The first byte and the length field will be omitted from the
|
||||
//! yielded `BytesMut`, but the trailing 2 bytes of the frame head will be
|
||||
//! included.
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_io::AsyncRead;
|
||||
//! # use tokio_codec::LengthDelimitedCodec;
|
||||
//! # fn bind_read<T: AsyncRead>(io: T) {
|
||||
//! LengthDelimitedCodec::builder()
|
||||
//! .length_field_offset(1) // length of hdr1
|
||||
//! .length_field_length(2)
|
||||
//! .length_adjustment(1) // length of hdr2
|
||||
//! .num_skip(3) // length of hdr1 + LEN
|
||||
//! .new_read(io);
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! The following frame will be decoded as such:
|
||||
//!
|
||||
//! ```text
|
||||
//! INPUT
|
||||
//! +- hdr1 -+-- len ---+- hdr2 -+--- Payload ---+
|
||||
//! | \xCA | \x00\x0B | \xFE | Hello world |
|
||||
//! +--------+----------+--------+---------------+
|
||||
//!
|
||||
//! DECODED
|
||||
//! +- hdr2 -+--- Payload ---+
|
||||
//! | \xFE | Hello world |
|
||||
//! +--------+---------------+
|
||||
//! ```
|
||||
//!
|
||||
//! The length field is situated in the middle of the frame head. In this
|
||||
//! case, the first byte in the frame head could be a version or some other
|
||||
//! identifier that is not needed for processing. On the other hand, the
|
||||
//! second half of the head is needed.
|
||||
//!
|
||||
//! `length_field_offset` indicates how many bytes to skip before starting
|
||||
//! to read the length field. `length_adjustment` is the number of bytes to
|
||||
//! skip starting at the end of the length field. In this case, it is the
|
||||
//! second half of the head.
|
||||
//!
|
||||
//! ## Example 6
|
||||
//!
|
||||
//! The following will parse a `u16` length field at offset 1 of a 4 byte
|
||||
//! frame head. The first byte and the length field will be omitted from the
|
||||
//! yielded `BytesMut`, but the trailing 2 bytes of the frame head will be
|
||||
//! included. In this case, the length field **includes** the frame head
|
||||
//! length.
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_io::AsyncRead;
|
||||
//! # use tokio_codec::LengthDelimitedCodec;
|
||||
//! # fn bind_read<T: AsyncRead>(io: T) {
|
||||
//! LengthDelimitedCodec::builder()
|
||||
//! .length_field_offset(1) // length of hdr1
|
||||
//! .length_field_length(2)
|
||||
//! .length_adjustment(-3) // length of hdr1 + LEN, negative
|
||||
//! .num_skip(3)
|
||||
//! .new_read(io);
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! The following frame will be decoded as such:
|
||||
//!
|
||||
//! ```text
|
||||
//! INPUT
|
||||
//! +- hdr1 -+-- len ---+- hdr2 -+--- Payload ---+
|
||||
//! | \xCA | \x00\x0F | \xFE | Hello world |
|
||||
//! +--------+----------+--------+---------------+
|
||||
//!
|
||||
//! DECODED
|
||||
//! +- hdr2 -+--- Payload ---+
|
||||
//! | \xFE | Hello world |
|
||||
//! +--------+---------------+
|
||||
//! ```
|
||||
//!
|
||||
//! Similar to the example above, the difference is that the length field
|
||||
//! represents the length of the entire frame instead of just the payload.
|
||||
//! The length of `hdr1` and `len` must be counted in `length_adjustment`.
|
||||
//! Note that the length of `hdr2` does **not** need to be explicitly set
|
||||
//! anywhere because it already is factored into the total frame length that
|
||||
//! is read from the byte stream.
|
||||
//!
|
||||
//! # Encoding
|
||||
//!
|
||||
//! [`FramedWrite`] adapts an [`AsyncWrite`] into a `Sink` of [`BytesMut`],
|
||||
//! such that each submitted [`BytesMut`] is prefaced by a length field.
|
||||
//! There are fewer configuration options than [`FramedRead`]. Given
|
||||
//! protocols that have more complex frame heads, an encoder should probably
|
||||
//! be written by hand using [`Encoder`].
|
||||
//!
|
||||
//! Here is a simple example, given a `FramedWrite` with the following
|
||||
//! configuration:
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio_io::AsyncWrite;
|
||||
//! # use tokio_codec::LengthDelimitedCodec;
|
||||
//! # fn write_frame<T: AsyncWrite>(io: T) {
|
||||
//! # let _ =
|
||||
//! LengthDelimitedCodec::builder()
|
||||
//! .length_field_length(2)
|
||||
//! .new_write(io);
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! A payload of `hello world` will be encoded as:
|
||||
//!
|
||||
//! ```text
|
||||
//! +- len: u16 -+---- data ----+
|
||||
//! | \x00\x0b | hello world |
|
||||
//! +------------+--------------+
|
||||
//! ```
|
||||
//!
|
||||
//! [`LengthDelimitedCodec::new()`]: struct.LengthDelimitedCodec.html#method.new
|
||||
//! [`FramedRead`]: struct.FramedRead.html
|
||||
//! [`FramedWrite`]: struct.FramedWrite.html
|
||||
//! [`AsyncRead`]: ../../trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: ../../trait.AsyncWrite.html
|
||||
//! [`Encoder`]: ../trait.Encoder.html
|
||||
//! [`BytesMut`]: https://docs.rs/bytes/0.4/bytes/struct.BytesMut.html
|
||||
|
||||
use crate::{Decoder, Encoder, Framed, FramedRead, FramedWrite};
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut, IntoBuf};
|
||||
use std::error::Error as StdError;
|
||||
use std::io::{self, Cursor};
|
||||
use std::{cmp, fmt};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
/// Configure length delimited `LengthDelimitedCodec`s.
|
||||
///
|
||||
/// `Builder` enables constructing configured length delimited codecs. Note
|
||||
/// that not all configuration settings apply to both encoding and decoding. See
|
||||
/// the documentation for specific methods for more detail.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Builder {
|
||||
// Maximum frame length
|
||||
max_frame_len: usize,
|
||||
|
||||
// Number of bytes representing the field length
|
||||
length_field_len: usize,
|
||||
|
||||
// Number of bytes in the header before the length field
|
||||
length_field_offset: usize,
|
||||
|
||||
// Adjust the length specified in the header field by this amount
|
||||
length_adjustment: isize,
|
||||
|
||||
// Total number of bytes to skip before reading the payload, if not set,
|
||||
// `length_field_len + length_field_offset`
|
||||
num_skip: Option<usize>,
|
||||
|
||||
// Length field byte order (little or big endian)
|
||||
length_field_is_big_endian: bool,
|
||||
}
|
||||
|
||||
/// An error when the number of bytes read is more than max frame length.
|
||||
pub struct LengthDelimitedCodecError {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
/// A codec for frames delimited by a frame head specifying their lengths.
|
||||
///
|
||||
/// This allows the consumer to work with entire frames without having to worry
|
||||
/// about buffering or other framing logic.
|
||||
///
|
||||
/// See [module level] documentation for more detail.
|
||||
///
|
||||
/// [module level]: index.html
|
||||
#[derive(Debug)]
|
||||
pub struct LengthDelimitedCodec {
|
||||
// Configuration values
|
||||
builder: Builder,
|
||||
|
||||
// Read state
|
||||
state: DecodeState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum DecodeState {
|
||||
Head,
|
||||
Data(usize),
|
||||
}
|
||||
|
||||
// ===== impl LengthDelimitedCodec ======
|
||||
|
||||
impl LengthDelimitedCodec {
|
||||
/// Creates a new `LengthDelimitedCodec` with the default configuration values.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
builder: Builder::new(),
|
||||
state: DecodeState::Head,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new length delimited codec builder with default configuration
|
||||
/// values.
|
||||
pub fn builder() -> Builder {
|
||||
Builder::new()
|
||||
}
|
||||
|
||||
/// Returns the current max frame setting
|
||||
///
|
||||
/// This is the largest size this codec will accept from the wire. Larger
|
||||
/// frames will be rejected.
|
||||
pub fn max_frame_length(&self) -> usize {
|
||||
self.builder.max_frame_len
|
||||
}
|
||||
|
||||
/// Updates the max frame setting.
|
||||
///
|
||||
/// The change takes effect the next time a frame is decoded. In other
|
||||
/// words, if a frame is currently in process of being decoded with a frame
|
||||
/// size greater than `val` but less than the max frame length in effect
|
||||
/// before calling this function, then the frame will be allowed.
|
||||
pub fn set_max_frame_length(&mut self, val: usize) {
|
||||
self.builder.max_frame_length(val);
|
||||
}
|
||||
|
||||
fn decode_head(&mut self, src: &mut BytesMut) -> io::Result<Option<usize>> {
|
||||
let head_len = self.builder.num_head_bytes();
|
||||
let field_len = self.builder.length_field_len;
|
||||
|
||||
if src.len() < head_len {
|
||||
// Not enough data
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let n = {
|
||||
let mut src = Cursor::new(&mut *src);
|
||||
|
||||
// Skip the required bytes
|
||||
src.advance(self.builder.length_field_offset);
|
||||
|
||||
// match endianess
|
||||
let n = if self.builder.length_field_is_big_endian {
|
||||
src.get_uint_be(field_len)
|
||||
} else {
|
||||
src.get_uint_le(field_len)
|
||||
};
|
||||
|
||||
if n > self.builder.max_frame_len as u64 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
LengthDelimitedCodecError { _priv: () },
|
||||
));
|
||||
}
|
||||
|
||||
// The check above ensures there is no overflow
|
||||
let n = n as usize;
|
||||
|
||||
// Adjust `n` with bounds checking
|
||||
let n = if self.builder.length_adjustment < 0 {
|
||||
n.checked_sub(-self.builder.length_adjustment as usize)
|
||||
} else {
|
||||
n.checked_add(self.builder.length_adjustment as usize)
|
||||
};
|
||||
|
||||
// Error handling
|
||||
match n {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"provided length would overflow after adjustment",
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let num_skip = self.builder.get_num_skip();
|
||||
|
||||
if num_skip > 0 {
|
||||
let _ = src.split_to(num_skip);
|
||||
}
|
||||
|
||||
// Ensure that the buffer has enough space to read the incoming
|
||||
// payload
|
||||
src.reserve(n);
|
||||
|
||||
Ok(Some(n))
|
||||
}
|
||||
|
||||
fn decode_data(&self, n: usize, src: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
||||
// At this point, the buffer has already had the required capacity
|
||||
// reserved. All there is to do is read.
|
||||
if src.len() < n {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(src.split_to(n)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for LengthDelimitedCodec {
|
||||
type Item = BytesMut;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
||||
let n = match self.state {
|
||||
DecodeState::Head => match self.decode_head(src)? {
|
||||
Some(n) => {
|
||||
self.state = DecodeState::Data(n);
|
||||
n
|
||||
}
|
||||
None => return Ok(None),
|
||||
},
|
||||
DecodeState::Data(n) => n,
|
||||
};
|
||||
|
||||
match self.decode_data(n, src)? {
|
||||
Some(data) => {
|
||||
// Update the decode state
|
||||
self.state = DecodeState::Head;
|
||||
|
||||
// Make sure the buffer has enough space to read the next head
|
||||
src.reserve(self.builder.num_head_bytes());
|
||||
|
||||
Ok(Some(data))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for LengthDelimitedCodec {
|
||||
type Item = Bytes;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, data: Bytes, dst: &mut BytesMut) -> Result<(), io::Error> {
|
||||
let n = (&data).into_buf().remaining();
|
||||
|
||||
if n > self.builder.max_frame_len {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
LengthDelimitedCodecError { _priv: () },
|
||||
));
|
||||
}
|
||||
|
||||
// Adjust `n` with bounds checking
|
||||
let n = if self.builder.length_adjustment < 0 {
|
||||
n.checked_add(-self.builder.length_adjustment as usize)
|
||||
} else {
|
||||
n.checked_sub(self.builder.length_adjustment as usize)
|
||||
};
|
||||
|
||||
let n = n.ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"provided length would overflow after adjustment",
|
||||
)
|
||||
})?;
|
||||
|
||||
// Reserve capacity in the destination buffer to fit the frame and
|
||||
// length field (plus adjustment).
|
||||
dst.reserve(self.builder.length_field_len + n);
|
||||
|
||||
if self.builder.length_field_is_big_endian {
|
||||
dst.put_uint_be(n as u64, self.builder.length_field_len);
|
||||
} else {
|
||||
dst.put_uint_le(n as u64, self.builder.length_field_len);
|
||||
}
|
||||
|
||||
// Write the frame to the buffer
|
||||
dst.extend_from_slice(&data[..]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LengthDelimitedCodec {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Builder =====
|
||||
|
||||
impl Builder {
|
||||
/// Creates a new length delimited codec builder with default configuration
|
||||
/// values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .length_field_offset(0)
|
||||
/// .length_field_length(2)
|
||||
/// .length_adjustment(0)
|
||||
/// .num_skip(0)
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn new() -> Builder {
|
||||
Builder {
|
||||
// Default max frame length of 8MB
|
||||
max_frame_len: 8 * 1_024 * 1_024,
|
||||
|
||||
// Default byte length of 4
|
||||
length_field_len: 4,
|
||||
|
||||
// Default to the header field being at the start of the header.
|
||||
length_field_offset: 0,
|
||||
|
||||
length_adjustment: 0,
|
||||
|
||||
// Total number of bytes to skip before reading the payload, if not set,
|
||||
// `length_field_len + length_field_offset`
|
||||
num_skip: None,
|
||||
|
||||
// Default to reading the length field in network (big) endian.
|
||||
length_field_is_big_endian: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the length field as a big endian integer
|
||||
///
|
||||
/// This is the default setting.
|
||||
///
|
||||
/// This configuration option applies to both encoding and decoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .big_endian()
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn big_endian(&mut self) -> &mut Self {
|
||||
self.length_field_is_big_endian = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Read the length field as a little endian integer
|
||||
///
|
||||
/// The default setting is big endian.
|
||||
///
|
||||
/// This configuration option applies to both encoding and decoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .little_endian()
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn little_endian(&mut self) -> &mut Self {
|
||||
self.length_field_is_big_endian = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Read the length field as a native endian integer
|
||||
///
|
||||
/// The default setting is big endian.
|
||||
///
|
||||
/// This configuration option applies to both encoding and decoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .native_endian()
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn native_endian(&mut self) -> &mut Self {
|
||||
if cfg!(target_endian = "big") {
|
||||
self.big_endian()
|
||||
} else {
|
||||
self.little_endian()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the max frame length
|
||||
///
|
||||
/// This configuration option applies to both encoding and decoding. The
|
||||
/// default value is 8MB.
|
||||
///
|
||||
/// When decoding, the length field read from the byte stream is checked
|
||||
/// against this setting **before** any adjustments are applied. When
|
||||
/// encoding, the length of the submitted payload is checked against this
|
||||
/// setting.
|
||||
///
|
||||
/// When frames exceed the max length, an `io::Error` with the custom value
|
||||
/// of the `LengthDelimitedCodecError` type will be returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .max_frame_length(8 * 1024)
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn max_frame_length(&mut self, val: usize) -> &mut Self {
|
||||
self.max_frame_len = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the number of bytes used to represent the length field
|
||||
///
|
||||
/// The default value is `4`. The max value is `8`.
|
||||
///
|
||||
/// This configuration option applies to both encoding and decoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .length_field_length(4)
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn length_field_length(&mut self, val: usize) -> &mut Self {
|
||||
assert!(val > 0 && val <= 8, "invalid length field length");
|
||||
self.length_field_len = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the number of bytes in the header before the length field
|
||||
///
|
||||
/// This configuration option only applies to decoding.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .length_field_offset(1)
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn length_field_offset(&mut self, val: usize) -> &mut Self {
|
||||
self.length_field_offset = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Delta between the payload length specified in the header and the real
|
||||
/// payload length
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .length_adjustment(-2)
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn length_adjustment(&mut self, val: isize) -> &mut Self {
|
||||
self.length_adjustment = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the number of bytes to skip before reading the payload
|
||||
///
|
||||
/// Default value is `length_field_len + length_field_offset`
|
||||
///
|
||||
/// This configuration option only applies to decoding
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .num_skip(4)
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn num_skip(&mut self, val: usize) -> &mut Self {
|
||||
self.num_skip = Some(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a configured length delimited `LengthDelimitedCodec`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
/// # pub fn main() {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .length_field_offset(0)
|
||||
/// .length_field_length(2)
|
||||
/// .length_adjustment(0)
|
||||
/// .num_skip(0)
|
||||
/// .new_codec();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new_codec(&self) -> LengthDelimitedCodec {
|
||||
LengthDelimitedCodec {
|
||||
builder: *self,
|
||||
state: DecodeState::Head,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a configured length delimited `FramedRead`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncRead;
|
||||
/// use tokio_codec::LengthDelimitedCodec;
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .length_field_offset(0)
|
||||
/// .length_field_length(2)
|
||||
/// .length_adjustment(0)
|
||||
/// .num_skip(0)
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn new_read<T>(&self, upstream: T) -> FramedRead<T, LengthDelimitedCodec>
|
||||
where
|
||||
T: AsyncRead,
|
||||
{
|
||||
FramedRead::new(upstream, self.new_codec())
|
||||
}
|
||||
|
||||
/// Create a configured length delimited `FramedWrite`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::AsyncWrite;
|
||||
/// # use tokio_codec::LengthDelimitedCodec;
|
||||
/// # fn write_frame<T: AsyncWrite>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .length_field_length(2)
|
||||
/// .new_write(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn new_write<T>(&self, inner: T) -> FramedWrite<T, LengthDelimitedCodec>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
{
|
||||
FramedWrite::new(inner, self.new_codec())
|
||||
}
|
||||
|
||||
/// Create a configured length delimited `Framed`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio_io::{AsyncRead, AsyncWrite};
|
||||
/// # use tokio_codec::LengthDelimitedCodec;
|
||||
/// # fn write_frame<T: AsyncRead + AsyncWrite>(io: T) {
|
||||
/// # let _ =
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .length_field_length(2)
|
||||
/// .new_framed(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn new_framed<T>(&self, inner: T) -> Framed<T, LengthDelimitedCodec>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
Framed::new(inner, self.new_codec())
|
||||
}
|
||||
|
||||
fn num_head_bytes(&self) -> usize {
|
||||
let num = self.length_field_offset + self.length_field_len;
|
||||
cmp::max(num, self.num_skip.unwrap_or(0))
|
||||
}
|
||||
|
||||
fn get_num_skip(&self) -> usize {
|
||||
self.num_skip
|
||||
.unwrap_or(self.length_field_offset + self.length_field_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Builder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl LengthDelimitedCodecError =====
|
||||
|
||||
impl fmt::Debug for LengthDelimitedCodecError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("LengthDelimitedCodecError").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LengthDelimitedCodecError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("frame size too big")
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for LengthDelimitedCodecError {}
|
||||
@@ -0,0 +1,44 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-codec/0.2.0-alpha.5")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
|
||||
//! Utilities for encoding and decoding frames.
|
||||
//!
|
||||
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and
|
||||
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
|
||||
//! Framed streams are also known as transports.
|
||||
//!
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/*/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/*/tokio/io/trait.AsyncWrite.html
|
||||
//! [`Sink`]: https://docs.rs/futures-sink-preview/*/futures_sink/trait.Sink.html
|
||||
//! [`Stream`]: https://docs.rs/futures-core-preview/*/futures_core/stream/trait.Stream.html
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod bytes_codec;
|
||||
mod decoder;
|
||||
mod encoder;
|
||||
mod framed;
|
||||
mod framed_read;
|
||||
mod framed_write;
|
||||
pub mod length_delimited;
|
||||
mod lines_codec;
|
||||
|
||||
pub use crate::bytes_codec::BytesCodec;
|
||||
pub use crate::decoder::Decoder;
|
||||
pub use crate::encoder::Encoder;
|
||||
pub use crate::framed::{Framed, FramedParts};
|
||||
pub use crate::framed_read::FramedRead;
|
||||
pub use crate::framed_write::FramedWrite;
|
||||
pub use crate::length_delimited::{LengthDelimitedCodec, LengthDelimitedCodecError};
|
||||
pub use crate::lines_codec::{LinesCodec, LinesCodecError};
|
||||
@@ -0,0 +1,223 @@
|
||||
use crate::decoder::Decoder;
|
||||
use crate::encoder::Encoder;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use std::{cmp, fmt, io, str, usize};
|
||||
|
||||
/// A simple `Codec` implementation that splits up data into lines.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct LinesCodec {
|
||||
// Stored index of the next index to examine for a `\n` character.
|
||||
// This is used to optimize searching.
|
||||
// For example, if `decode` was called with `abc`, it would hold `3`,
|
||||
// because that is the next index to examine.
|
||||
// The next time `decode` is called with `abcde\n`, the method will
|
||||
// only look at `de\n` before returning.
|
||||
next_index: usize,
|
||||
|
||||
/// The maximum length for a given line. If `usize::MAX`, lines will be
|
||||
/// read until a `\n` character is reached.
|
||||
max_length: usize,
|
||||
|
||||
/// Are we currently discarding the remainder of a line which was over
|
||||
/// the length limit?
|
||||
is_discarding: bool,
|
||||
}
|
||||
|
||||
impl LinesCodec {
|
||||
/// Returns a `LinesCodec` for splitting up data into lines.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The returned `LinesCodec` will not have an upper bound on the length
|
||||
/// of a buffered line. See the documentation for [`new_with_max_length`]
|
||||
/// for information on why this could be a potential security risk.
|
||||
///
|
||||
/// [`new_with_max_length`]: #method.new_with_max_length
|
||||
pub fn new() -> LinesCodec {
|
||||
LinesCodec {
|
||||
next_index: 0,
|
||||
max_length: usize::MAX,
|
||||
is_discarding: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `LinesCodec` with a maximum line length limit.
|
||||
///
|
||||
/// If this is set, calls to `LinesCodec::decode` will return a
|
||||
/// [`LengthError`] when a line exceeds the length limit. Subsequent calls
|
||||
/// will discard up to `limit` bytes from that line until a newline
|
||||
/// character is reached, returning `None` until the line over the limit
|
||||
/// has been fully discarded. After that point, calls to `decode` will
|
||||
/// function as normal.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Setting a length limit is highly recommended for any `LinesCodec` which
|
||||
/// will be exposed to untrusted input. Otherwise, the size of the buffer
|
||||
/// that holds the line currently being read is unbounded. An attacker could
|
||||
/// exploit this unbounded buffer by sending an unbounded amount of input
|
||||
/// without any `\n` characters, causing unbounded memory consumption.
|
||||
///
|
||||
/// [`LengthError`]: ../struct.LengthError
|
||||
pub fn new_with_max_length(max_length: usize) -> Self {
|
||||
LinesCodec {
|
||||
max_length,
|
||||
..LinesCodec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the maximum line length when decoding.
|
||||
///
|
||||
/// ```
|
||||
/// use std::usize;
|
||||
/// use tokio_codec::LinesCodec;
|
||||
///
|
||||
/// let codec = LinesCodec::new();
|
||||
/// assert_eq!(codec.max_length(), usize::MAX);
|
||||
/// ```
|
||||
/// ```
|
||||
/// use tokio_codec::LinesCodec;
|
||||
///
|
||||
/// let codec = LinesCodec::new_with_max_length(256);
|
||||
/// assert_eq!(codec.max_length(), 256);
|
||||
/// ```
|
||||
pub fn max_length(&self) -> usize {
|
||||
self.max_length
|
||||
}
|
||||
}
|
||||
|
||||
fn utf8(buf: &[u8]) -> Result<&str, io::Error> {
|
||||
str::from_utf8(buf)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Unable to decode input as UTF8"))
|
||||
}
|
||||
|
||||
fn without_carriage_return(s: &[u8]) -> &[u8] {
|
||||
if let Some(&b'\r') = s.last() {
|
||||
&s[..s.len() - 1]
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for LinesCodec {
|
||||
type Item = String;
|
||||
type Error = LinesCodecError;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<String>, LinesCodecError> {
|
||||
loop {
|
||||
// Determine how far into the buffer we'll search for a newline. If
|
||||
// there's no max_length set, we'll read to the end of the buffer.
|
||||
let read_to = cmp::min(self.max_length.saturating_add(1), buf.len());
|
||||
|
||||
let newline_offset = buf[self.next_index..read_to]
|
||||
.iter()
|
||||
.position(|b| *b == b'\n');
|
||||
|
||||
match (self.is_discarding, newline_offset) {
|
||||
(true, Some(offset)) => {
|
||||
// If we found a newline, discard up to that offset and
|
||||
// then stop discarding. On the next iteration, we'll try
|
||||
// to read a line normally.
|
||||
buf.advance(offset + self.next_index + 1);
|
||||
self.is_discarding = false;
|
||||
self.next_index = 0;
|
||||
}
|
||||
(true, None) => {
|
||||
// Otherwise, we didn't find a newline, so we'll discard
|
||||
// everything we read. On the next iteration, we'll continue
|
||||
// discarding up to max_len bytes unless we find a newline.
|
||||
buf.advance(read_to);
|
||||
self.next_index = 0;
|
||||
if buf.is_empty() {
|
||||
return Err(LinesCodecError::MaxLineLengthExceeded);
|
||||
}
|
||||
}
|
||||
(false, Some(offset)) => {
|
||||
// Found a line!
|
||||
let newline_index = offset + self.next_index;
|
||||
self.next_index = 0;
|
||||
let line = buf.split_to(newline_index + 1);
|
||||
let line = &line[..line.len() - 1];
|
||||
let line = without_carriage_return(line);
|
||||
let line = utf8(line)?;
|
||||
return Ok(Some(line.to_string()));
|
||||
}
|
||||
(false, None) if buf.len() > self.max_length => {
|
||||
// Reached the maximum length without finding a
|
||||
// newline, return an error and start discarding on the
|
||||
// next call.
|
||||
self.is_discarding = true;
|
||||
return Err(LinesCodecError::MaxLineLengthExceeded);
|
||||
}
|
||||
(false, None) => {
|
||||
// We didn't find a line or reach the length limit, so the next
|
||||
// call will resume searching at the current offset.
|
||||
self.next_index = read_to;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<String>, LinesCodecError> {
|
||||
Ok(match self.decode(buf)? {
|
||||
Some(frame) => Some(frame),
|
||||
None => {
|
||||
// No terminating newline - return remaining data, if any
|
||||
if buf.is_empty() || buf == &b"\r"[..] {
|
||||
None
|
||||
} else {
|
||||
let line = buf.take();
|
||||
let line = without_carriage_return(&line);
|
||||
let line = utf8(line)?;
|
||||
self.next_index = 0;
|
||||
Some(line.to_string())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for LinesCodec {
|
||||
type Item = String;
|
||||
type Error = LinesCodecError;
|
||||
|
||||
fn encode(&mut self, line: String, buf: &mut BytesMut) -> Result<(), LinesCodecError> {
|
||||
buf.reserve(line.len() + 1);
|
||||
buf.put(line);
|
||||
buf.put_u8(b'\n');
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LinesCodec {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occured while encoding or decoding a line.
|
||||
#[derive(Debug)]
|
||||
pub enum LinesCodecError {
|
||||
/// The maximum line length was exceeded.
|
||||
MaxLineLengthExceeded,
|
||||
/// An IO error occured.
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for LinesCodecError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LinesCodecError::MaxLineLengthExceeded => write!(f, "max line length exceeded"),
|
||||
LinesCodecError::Io(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for LinesCodecError {
|
||||
fn from(e: io::Error) -> LinesCodecError {
|
||||
LinesCodecError::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LinesCodecError {}
|
||||
@@ -0,0 +1,7 @@
|
||||
/// A macro to reduce some of the boilerplate for projecting from
|
||||
/// `Pin<&mut T>` to `Pin<&mut T.field>`
|
||||
macro_rules! pin {
|
||||
($e:expr) => {
|
||||
std::pin::Pin::new(&mut $e)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use tokio_codec::{BytesCodec, Decoder, Encoder, LinesCodec};
|
||||
|
||||
#[test]
|
||||
fn bytes_decoder() {
|
||||
let mut codec = BytesCodec::new();
|
||||
let buf = &mut BytesMut::new();
|
||||
buf.put_slice(b"abc");
|
||||
assert_eq!("abc", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"a");
|
||||
assert_eq!("a", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bytes_encoder() {
|
||||
let mut codec = BytesCodec::new();
|
||||
|
||||
// Default capacity of BytesMut
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const INLINE_CAP: usize = 4 * 8 - 1;
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const INLINE_CAP: usize = 4 * 4 - 1;
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
codec
|
||||
.encode(Bytes::from_static(&[0; INLINE_CAP + 1]), &mut buf)
|
||||
.unwrap();
|
||||
|
||||
// Default capacity of Framed Read
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
let mut buf = BytesMut::with_capacity(INITIAL_CAPACITY);
|
||||
codec
|
||||
.encode(Bytes::from_static(&[0; INITIAL_CAPACITY + 1]), &mut buf)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder() {
|
||||
let mut codec = LinesCodec::new();
|
||||
let buf = &mut BytesMut::new();
|
||||
buf.reserve(200);
|
||||
buf.put("line 1\nline 2\r\nline 3\n\r\n\r");
|
||||
assert_eq!("line 1", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("line 2", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("line 3", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
buf.put("k");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!("\rk", codec.decode_eof(buf).unwrap().unwrap());
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length() {
|
||||
const MAX_LENGTH: usize = 6;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("line 1 is too long\nline 2\nline 3\r\nline 4\n\r\n\r");
|
||||
|
||||
assert!(codec.decode(buf).is_err());
|
||||
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("line 2", line);
|
||||
|
||||
assert!(codec.decode(buf).is_err());
|
||||
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("line 4", line);
|
||||
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("", line);
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
buf.put("k");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
let line = codec.decode_eof(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("\rk", line);
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
|
||||
// Line that's one character too long. This could cause an out of bounds
|
||||
// error if we peek at the next characters using slice indexing.
|
||||
buf.put("aaabbbc");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length_underrun() {
|
||||
const MAX_LENGTH: usize = 6;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("too l");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put("ong\n");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
buf.put("line 2");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("\n");
|
||||
assert_eq!("line 2", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length_bursts() {
|
||||
const MAX_LENGTH: usize = 10;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("too l");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("ong\n");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length_big_burst() {
|
||||
const MAX_LENGTH: usize = 10;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("too long!\n");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length_newline_between_decodes() {
|
||||
const MAX_LENGTH: usize = 5;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("hello");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
buf.put("\nworld");
|
||||
assert_eq!("hello", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
// Regression test for [infinite loop bug](https://github.com/tokio-rs/tokio/issues/1483)
|
||||
#[test]
|
||||
fn lines_decoder_discard_repeat() {
|
||||
const MAX_LENGTH: usize = 1;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("aa");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put("a");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_encoder() {
|
||||
let mut codec = LinesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
codec.encode(String::from("line 1"), &mut buf).unwrap();
|
||||
assert_eq!("line 1\n", buf);
|
||||
|
||||
codec.encode(String::from("line 2"), &mut buf).unwrap();
|
||||
assert_eq!("line 1\nline 2\n", buf);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::prelude::*;
|
||||
use tokio_codec::{Decoder, Encoder, Framed, FramedParts};
|
||||
use tokio_test::assert_ok;
|
||||
|
||||
use bytes::{Buf, BufMut, BytesMut, IntoBuf};
|
||||
use std::io::{self, Read};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
/// Encode and decode u32 values.
|
||||
struct U32Codec;
|
||||
|
||||
impl Decoder for U32Codec {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
if buf.len() < 4 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let n = buf.split_to(4).into_buf().get_u32_be();
|
||||
Ok(Some(n))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for U32Codec {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: u32, dst: &mut BytesMut) -> io::Result<()> {
|
||||
// Reserve space
|
||||
dst.reserve(4);
|
||||
dst.put_u32_be(item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This value should never be used
|
||||
struct DontReadIntoThis;
|
||||
|
||||
impl Read for DontReadIntoThis {
|
||||
fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Read into something you weren't supposed to.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for DontReadIntoThis {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
_buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_read_from_existing_buf() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
parts.read_buf = vec![0, 0, 0, 42].into();
|
||||
|
||||
let mut framed = Framed::from_parts(parts);
|
||||
let num = assert_ok!(framed.next().await.unwrap());
|
||||
|
||||
assert_eq!(num, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_buf_grows_to_init() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
parts.read_buf = vec![0, 0, 0, 42].into();
|
||||
|
||||
let framed = Framed::from_parts(parts);
|
||||
let FramedParts { read_buf, .. } = framed.into_parts();
|
||||
|
||||
assert_eq!(read_buf.capacity(), INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_buf_does_not_shrink() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
parts.read_buf = vec![0; INITIAL_CAPACITY * 2].into();
|
||||
|
||||
let framed = Framed::from_parts(parts);
|
||||
let FramedParts { read_buf, .. } = framed.into_parts();
|
||||
|
||||
assert_eq!(read_buf.capacity(), INITIAL_CAPACITY * 2);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::prelude::*;
|
||||
use tokio_codec::{Decoder, FramedRead};
|
||||
use tokio_test::assert_ready;
|
||||
use tokio_test::task::MockTask;
|
||||
|
||||
use bytes::{Buf, BytesMut, IntoBuf};
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll::{Pending, Ready};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
macro_rules! mock {
|
||||
($($x:expr,)*) => {{
|
||||
let mut v = VecDeque::new();
|
||||
v.extend(vec![$($x),*]);
|
||||
Mock { calls: v }
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_read {
|
||||
($e:expr, $n:expr) => {{
|
||||
let val = assert_ready!($e);
|
||||
assert_eq!(val.unwrap().unwrap(), $n);
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! pin {
|
||||
($id:ident) => {
|
||||
Pin::new(&mut $id)
|
||||
};
|
||||
}
|
||||
|
||||
struct U32Decoder;
|
||||
|
||||
impl Decoder for U32Decoder {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
if buf.len() < 4 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let n = buf.split_to(4).into_buf().get_u32_be();
|
||||
Ok(Some(n))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_multi_frame_in_packet() {
|
||||
let mut task = MockTask::new();
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02".to_vec()),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert_read!(pin!(framed).poll_next(cx), 0);
|
||||
assert_read!(pin!(framed).poll_next(cx), 1);
|
||||
assert_read!(pin!(framed).poll_next(cx), 2);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_multi_frame_across_packets() {
|
||||
let mut task = MockTask::new();
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x00".to_vec()),
|
||||
Ok(b"\x00\x00\x00\x01".to_vec()),
|
||||
Ok(b"\x00\x00\x00\x02".to_vec()),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert_read!(pin!(framed).poll_next(cx), 0);
|
||||
assert_read!(pin!(framed).poll_next(cx), 1);
|
||||
assert_read!(pin!(framed).poll_next(cx), 2);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_not_ready() {
|
||||
let mut task = MockTask::new();
|
||||
let mock = mock! {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "")),
|
||||
Ok(b"\x00\x00\x00\x00".to_vec()),
|
||||
Ok(b"\x00\x00\x00\x01".to_vec()),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert!(pin!(framed).poll_next(cx).is_pending());
|
||||
assert_read!(pin!(framed).poll_next(cx), 0);
|
||||
assert_read!(pin!(framed).poll_next(cx), 1);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_partial_then_not_ready() {
|
||||
let mut task = MockTask::new();
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00".to_vec()),
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "")),
|
||||
Ok(b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02".to_vec()),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert!(pin!(framed).poll_next(cx).is_pending());
|
||||
assert_read!(pin!(framed).poll_next(cx), 0);
|
||||
assert_read!(pin!(framed).poll_next(cx), 1);
|
||||
assert_read!(pin!(framed).poll_next(cx), 2);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_err() {
|
||||
let mut task = MockTask::new();
|
||||
let mock = mock! {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "")),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert_eq!(
|
||||
io::ErrorKind::Other,
|
||||
assert_ready!(pin!(framed).poll_next(cx))
|
||||
.unwrap()
|
||||
.unwrap_err()
|
||||
.kind()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_partial_then_err() {
|
||||
let mut task = MockTask::new();
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00".to_vec()),
|
||||
Err(io::Error::new(io::ErrorKind::Other, "")),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert_eq!(
|
||||
io::ErrorKind::Other,
|
||||
assert_ready!(pin!(framed).poll_next(cx))
|
||||
.unwrap()
|
||||
.unwrap_err()
|
||||
.kind()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_partial_would_block_then_err() {
|
||||
let mut task = MockTask::new();
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00".to_vec()),
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "")),
|
||||
Err(io::Error::new(io::ErrorKind::Other, "")),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert!(pin!(framed).poll_next(cx).is_pending());
|
||||
assert_eq!(
|
||||
io::ErrorKind::Other,
|
||||
assert_ready!(pin!(framed).poll_next(cx))
|
||||
.unwrap()
|
||||
.unwrap_err()
|
||||
.kind()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_size() {
|
||||
let mut task = MockTask::new();
|
||||
let data = [0; 32 * 1024];
|
||||
let mut framed = FramedRead::new(Slice(&data[..]), BigDecoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert_read!(pin!(framed).poll_next(cx), 0);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
});
|
||||
|
||||
struct BigDecoder;
|
||||
|
||||
impl Decoder for BigDecoder {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
if buf.len() < 32 * 1024 {
|
||||
return Ok(None);
|
||||
}
|
||||
buf.split_to(32 * 1024);
|
||||
Ok(Some(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_remaining_is_error() {
|
||||
let mut task = MockTask::new();
|
||||
let slice = Slice(&[0; 5]);
|
||||
let mut framed = FramedRead::new(slice, U32Decoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert_read!(pin!(framed).poll_next(cx), 0);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).unwrap().is_err());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_frames_on_eof() {
|
||||
let mut task = MockTask::new();
|
||||
struct MyDecoder(Vec<u32>);
|
||||
|
||||
impl Decoder for MyDecoder {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, _buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, _buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
if self.0.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(self.0.remove(0)))
|
||||
}
|
||||
}
|
||||
|
||||
let mut framed = FramedRead::new(mock!(), MyDecoder(vec![0, 1, 2, 3]));
|
||||
|
||||
task.enter(|cx| {
|
||||
assert_read!(pin!(framed).poll_next(cx), 0);
|
||||
assert_read!(pin!(framed).poll_next(cx), 1);
|
||||
assert_read!(pin!(framed).poll_next(cx), 2);
|
||||
assert_read!(pin!(framed).poll_next(cx), 3);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Mock ======
|
||||
|
||||
struct Mock {
|
||||
calls: VecDeque<io::Result<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
use io::ErrorKind::WouldBlock;
|
||||
|
||||
match self.calls.pop_front() {
|
||||
Some(Ok(data)) => {
|
||||
debug_assert!(buf.len() >= data.len());
|
||||
buf[..data.len()].copy_from_slice(&data[..]);
|
||||
Ready(Ok(data.len()))
|
||||
}
|
||||
Some(Err(ref e)) if e.kind() == WouldBlock => Pending,
|
||||
Some(Err(e)) => Ready(Err(e)),
|
||||
None => Ready(Ok(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this newtype is necessary because `&[u8]` does not currently implement `AsyncRead`
|
||||
struct Slice<'a>(&'a [u8]);
|
||||
|
||||
impl AsyncRead for Slice<'_> {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut self.0).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio_codec::{Encoder, FramedWrite};
|
||||
use tokio_io::AsyncWrite;
|
||||
use tokio_test::assert_ready;
|
||||
use tokio_test::task::MockTask;
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures_sink::Sink;
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{self, Write};
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll::{Pending, Ready};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
macro_rules! mock {
|
||||
($($x:expr,)*) => {{
|
||||
let mut v = VecDeque::new();
|
||||
v.extend(vec![$($x),*]);
|
||||
Mock { calls: v }
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! pin {
|
||||
($id:ident) => {
|
||||
Pin::new(&mut $id)
|
||||
};
|
||||
}
|
||||
|
||||
struct U32Encoder;
|
||||
|
||||
impl Encoder for U32Encoder {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: u32, dst: &mut BytesMut) -> io::Result<()> {
|
||||
// Reserve space
|
||||
dst.reserve(4);
|
||||
dst.put_u32_be(item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_multi_frame_in_packet() {
|
||||
let mut task = MockTask::new();
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02".to_vec()),
|
||||
};
|
||||
let mut framed = FramedWrite::new(mock, U32Encoder);
|
||||
|
||||
task.enter(|cx| {
|
||||
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
|
||||
assert!(pin!(framed).start_send(0).is_ok());
|
||||
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
|
||||
assert!(pin!(framed).start_send(1).is_ok());
|
||||
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
|
||||
assert!(pin!(framed).start_send(2).is_ok());
|
||||
|
||||
// Nothing written yet
|
||||
assert_eq!(1, framed.get_ref().calls.len());
|
||||
|
||||
// Flush the writes
|
||||
assert!(assert_ready!(pin!(framed).poll_flush(cx)).is_ok());
|
||||
|
||||
assert_eq!(0, framed.get_ref().calls.len());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_hits_backpressure() {
|
||||
const ITER: usize = 2 * 1024;
|
||||
|
||||
let mut mock = mock! {
|
||||
// Block the `ITER`th write
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "not ready")),
|
||||
Ok(b"".to_vec()),
|
||||
};
|
||||
|
||||
for i in 0..=ITER {
|
||||
let mut b = BytesMut::with_capacity(4);
|
||||
b.put_u32_be(i as u32);
|
||||
|
||||
// Append to the end
|
||||
match mock.calls.back_mut().unwrap() {
|
||||
&mut Ok(ref mut data) => {
|
||||
// Write in 2kb chunks
|
||||
if data.len() < ITER {
|
||||
data.extend_from_slice(&b[..]);
|
||||
continue;
|
||||
} // else fall through and create a new buffer
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Push a new new chunk
|
||||
mock.calls.push_back(Ok(b[..].to_vec()));
|
||||
}
|
||||
// 1 'wouldblock', 4 * 2KB buffers, 1 b-byte buffer
|
||||
assert_eq!(mock.calls.len(), 6);
|
||||
|
||||
let mut task = MockTask::new();
|
||||
let mut framed = FramedWrite::new(mock, U32Encoder);
|
||||
task.enter(|cx| {
|
||||
// Send 8KB. This fills up FramedWrite2 buffer
|
||||
for i in 0..ITER {
|
||||
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
|
||||
assert!(pin!(framed).start_send(i as u32).is_ok());
|
||||
}
|
||||
|
||||
// Now we poll_ready which forces a flush. The mock pops the front message
|
||||
// and decides to block.
|
||||
assert!(pin!(framed).poll_ready(cx).is_pending());
|
||||
|
||||
// We poll again, forcing another flush, which this time succeeds
|
||||
// The whole 8KB buffer is flushed
|
||||
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
|
||||
|
||||
// Send more data. This matches the final message expected by the mock
|
||||
assert!(pin!(framed).start_send(ITER as u32).is_ok());
|
||||
|
||||
// Flush the rest of the buffer
|
||||
assert!(assert_ready!(pin!(framed).poll_flush(cx)).is_ok());
|
||||
|
||||
// Ensure the mock is empty
|
||||
assert_eq!(0, framed.get_ref().calls.len());
|
||||
})
|
||||
}
|
||||
|
||||
// // ===== Mock ======
|
||||
|
||||
struct Mock {
|
||||
calls: VecDeque<io::Result<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Write for Mock {
|
||||
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ok(data)) => {
|
||||
assert!(src.len() >= data.len());
|
||||
assert_eq!(&data[..], &src[..data.len()]);
|
||||
Ok(data.len())
|
||||
}
|
||||
Some(Err(e)) => Err(e),
|
||||
None => panic!("unexpected write; {:?}", src),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Mock {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
match Pin::get_mut(self).write(buf) {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Pending,
|
||||
other => Ready(other),
|
||||
}
|
||||
}
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
match Pin::get_mut(self).flush() {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Pending,
|
||||
other => Ready(other),
|
||||
}
|
||||
}
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,761 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::codec::*;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::prelude::*;
|
||||
use tokio_test::task::MockTask;
|
||||
use tokio_test::{
|
||||
assert_err, assert_ok, assert_pending, assert_ready, assert_ready_err, assert_ready_ok,
|
||||
};
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures_util::pin_mut;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll::*;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
macro_rules! mock {
|
||||
($($x:expr,)*) => {{
|
||||
let mut v = VecDeque::new();
|
||||
v.extend(vec![$($x),*]);
|
||||
Mock { calls: v }
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_next_eq {
|
||||
($io:ident, $expect:expr) => {{
|
||||
MockTask::new().enter(|cx| {
|
||||
let res = assert_ready!($io.as_mut().poll_next(cx));
|
||||
match res {
|
||||
Some(Ok(v)) => assert_eq!(v, $expect.as_ref()),
|
||||
Some(Err(e)) => panic!("error = {:?}", e),
|
||||
None => panic!("none"),
|
||||
}
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_next_pending {
|
||||
($io:ident) => {{
|
||||
MockTask::new().enter(|cx| match $io.as_mut().poll_next(cx) {
|
||||
Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Ready(Some(Err(e))) => panic!("error = {:?}", e),
|
||||
Ready(None) => panic!("done"),
|
||||
Pending => {}
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_next_err {
|
||||
($io:ident) => {{
|
||||
MockTask::new().enter(|cx| match $io.as_mut().poll_next(cx) {
|
||||
Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Ready(Some(Err(_))) => {}
|
||||
Ready(None) => panic!("done"),
|
||||
Pending => panic!("pending"),
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_done {
|
||||
($io:ident) => {{
|
||||
MockTask::new().enter(|cx| {
|
||||
let res = assert_ready!($io.as_mut().poll_next(cx));
|
||||
match res {
|
||||
Some(Ok(v)) => panic!("value = {:?}", v),
|
||||
Some(Err(e)) => panic!("error = {:?}", e),
|
||||
None => {}
|
||||
}
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_empty_io_yields_nothing() {
|
||||
let io = Box::pin(FramedRead::new(mock!(), LengthDelimitedCodec::new()));
|
||||
pin_mut!(io);
|
||||
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_frame_one_packet() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00\x00\x09abcdefghi"),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_frame_one_packet_little_endian() {
|
||||
let io = length_delimited::Builder::new()
|
||||
.little_endian()
|
||||
.new_read(mock! {
|
||||
data(b"\x09\x00\x00\x00abcdefghi"),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_frame_one_packet_native_endian() {
|
||||
let d = if cfg!(target_endian = "big") {
|
||||
b"\x00\x00\x00\x09abcdefghi"
|
||||
} else {
|
||||
b"\x09\x00\x00\x00abcdefghi"
|
||||
};
|
||||
let io = length_delimited::Builder::new()
|
||||
.native_endian()
|
||||
.new_read(mock! {
|
||||
data(d),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_multi_frame_one_packet() {
|
||||
let mut d: Vec<u8> = vec![];
|
||||
d.extend_from_slice(b"\x00\x00\x00\x09abcdefghi");
|
||||
d.extend_from_slice(b"\x00\x00\x00\x03123");
|
||||
d.extend_from_slice(b"\x00\x00\x00\x0bhello world");
|
||||
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(&d),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_next_eq!(io, b"123");
|
||||
assert_next_eq!(io, b"hello world");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_frame_multi_packet() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
data(b"\x00\x09abc"),
|
||||
data(b"defghi"),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_multi_frame_multi_packet() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
data(b"\x00\x09abc"),
|
||||
data(b"defghi"),
|
||||
data(b"\x00\x00\x00\x0312"),
|
||||
data(b"3\x00\x00\x00\x0bhello world"),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_next_eq!(io, b"123");
|
||||
assert_next_eq!(io, b"hello world");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_frame_multi_packet_wait() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
data(b"\x00\x09abc"),
|
||||
Pending,
|
||||
data(b"defghi"),
|
||||
Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_pending!(io);
|
||||
assert_next_pending!(io);
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_next_pending!(io);
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_multi_frame_multi_packet_wait() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
data(b"\x00\x09abc"),
|
||||
Pending,
|
||||
data(b"defghi"),
|
||||
Pending,
|
||||
data(b"\x00\x00\x00\x0312"),
|
||||
Pending,
|
||||
data(b"3\x00\x00\x00\x0bhello world"),
|
||||
Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_pending!(io);
|
||||
assert_next_pending!(io);
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_next_pending!(io);
|
||||
assert_next_pending!(io);
|
||||
assert_next_eq!(io, b"123");
|
||||
assert_next_eq!(io, b"hello world");
|
||||
assert_next_pending!(io);
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_incomplete_head() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_err!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_incomplete_head_multi() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
Pending,
|
||||
data(b"\x00"),
|
||||
Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_pending!(io);
|
||||
assert_next_pending!(io);
|
||||
assert_next_err!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_incomplete_payload() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00\x00\x09ab"),
|
||||
Pending,
|
||||
data(b"cd"),
|
||||
Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_pending!(io);
|
||||
assert_next_pending!(io);
|
||||
assert_next_err!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_max_frame_len() {
|
||||
let io = length_delimited::Builder::new()
|
||||
.max_frame_length(5)
|
||||
.new_read(mock! {
|
||||
data(b"\x00\x00\x00\x09abcdefghi"),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_err!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_update_max_frame_len_at_rest() {
|
||||
let io = length_delimited::Builder::new().new_read(mock! {
|
||||
data(b"\x00\x00\x00\x09abcdefghi"),
|
||||
data(b"\x00\x00\x00\x09abcdefghi"),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
io.decoder_mut().set_max_frame_length(5);
|
||||
assert_next_err!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_update_max_frame_len_in_flight() {
|
||||
let io = length_delimited::Builder::new().new_read(mock! {
|
||||
data(b"\x00\x00\x00\x09abcd"),
|
||||
Pending,
|
||||
data(b"efghi"),
|
||||
data(b"\x00\x00\x00\x09abcdefghi"),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_pending!(io);
|
||||
io.decoder_mut().set_max_frame_length(5);
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_next_err!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_one_byte_length_field() {
|
||||
let io = length_delimited::Builder::new()
|
||||
.length_field_length(1)
|
||||
.new_read(mock! {
|
||||
data(b"\x09abcdefghi"),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_header_offset() {
|
||||
let io = length_delimited::Builder::new()
|
||||
.length_field_length(2)
|
||||
.length_field_offset(4)
|
||||
.new_read(mock! {
|
||||
data(b"zzzz\x00\x09abcdefghi"),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_multi_frame_one_packet_skip_none_adjusted() {
|
||||
let mut d: Vec<u8> = vec![];
|
||||
d.extend_from_slice(b"xx\x00\x09abcdefghi");
|
||||
d.extend_from_slice(b"yy\x00\x03123");
|
||||
d.extend_from_slice(b"zz\x00\x0bhello world");
|
||||
|
||||
let io = length_delimited::Builder::new()
|
||||
.length_field_length(2)
|
||||
.length_field_offset(2)
|
||||
.num_skip(0)
|
||||
.length_adjustment(4)
|
||||
.new_read(mock! {
|
||||
data(&d),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"xx\x00\x09abcdefghi");
|
||||
assert_next_eq!(io, b"yy\x00\x03123");
|
||||
assert_next_eq!(io, b"zz\x00\x0bhello world");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_multi_frame_one_packet_length_includes_head() {
|
||||
let mut d: Vec<u8> = vec![];
|
||||
d.extend_from_slice(b"\x00\x0babcdefghi");
|
||||
d.extend_from_slice(b"\x00\x05123");
|
||||
d.extend_from_slice(b"\x00\x0dhello world");
|
||||
|
||||
let io = length_delimited::Builder::new()
|
||||
.length_field_length(2)
|
||||
.length_adjustment(-2)
|
||||
.new_read(mock! {
|
||||
data(&d),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
assert_next_eq!(io, b"abcdefghi");
|
||||
assert_next_eq!(io, b"123");
|
||||
assert_next_eq!(io, b"hello world");
|
||||
assert_done!(io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_single_frame_length_adjusted() {
|
||||
let io = length_delimited::Builder::new()
|
||||
.length_adjustment(-2)
|
||||
.new_write(mock! {
|
||||
data(b"\x00\x00\x00\x0b"),
|
||||
data(b"abcdefghi"),
|
||||
flush(),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_nothing_yields_nothing() {
|
||||
let io = FramedWrite::new(mock!(), LengthDelimitedCodec::new());
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.poll_flush(cx));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_single_frame_one_packet() {
|
||||
let io = FramedWrite::new(
|
||||
mock! {
|
||||
data(b"\x00\x00\x00\x09"),
|
||||
data(b"abcdefghi"),
|
||||
flush(),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_single_multi_frame_one_packet() {
|
||||
let io = FramedWrite::new(
|
||||
mock! {
|
||||
data(b"\x00\x00\x00\x09"),
|
||||
data(b"abcdefghi"),
|
||||
data(b"\x00\x00\x00\x03"),
|
||||
data(b"123"),
|
||||
data(b"\x00\x00\x00\x0b"),
|
||||
data(b"hello world"),
|
||||
flush(),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("123")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("hello world")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_single_multi_frame_multi_packet() {
|
||||
let io = FramedWrite::new(
|
||||
mock! {
|
||||
data(b"\x00\x00\x00\x09"),
|
||||
data(b"abcdefghi"),
|
||||
flush(),
|
||||
data(b"\x00\x00\x00\x03"),
|
||||
data(b"123"),
|
||||
flush(),
|
||||
data(b"\x00\x00\x00\x0b"),
|
||||
data(b"hello world"),
|
||||
flush(),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("123")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("hello world")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_single_frame_would_block() {
|
||||
let io = FramedWrite::new(
|
||||
mock! {
|
||||
Pending,
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
data(b"\x00\x09"),
|
||||
data(b"abcdefghi"),
|
||||
flush(),
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
|
||||
|
||||
assert_pending!(io.as_mut().poll_flush(cx));
|
||||
assert_pending!(io.as_mut().poll_flush(cx));
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_single_frame_little_endian() {
|
||||
let io = length_delimited::Builder::new()
|
||||
.little_endian()
|
||||
.new_write(mock! {
|
||||
data(b"\x09\x00\x00\x00"),
|
||||
data(b"abcdefghi"),
|
||||
flush(),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_single_frame_with_short_length_field() {
|
||||
let io = length_delimited::Builder::new()
|
||||
.length_field_length(1)
|
||||
.new_write(mock! {
|
||||
data(b"\x09"),
|
||||
data(b"abcdefghi"),
|
||||
flush(),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_max_frame_len() {
|
||||
let io = length_delimited::Builder::new()
|
||||
.max_frame_length(5)
|
||||
.new_write(mock! {});
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_err!(io.as_mut().start_send(Bytes::from("abcdef")));
|
||||
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_update_max_frame_len_at_rest() {
|
||||
let io = length_delimited::Builder::new().new_write(mock! {
|
||||
data(b"\x00\x00\x00\x06"),
|
||||
data(b"abcdef"),
|
||||
flush(),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdef")));
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
|
||||
io.encoder_mut().set_max_frame_length(5);
|
||||
|
||||
assert_err!(io.as_mut().start_send(Bytes::from("abcdef")));
|
||||
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_update_max_frame_len_in_flight() {
|
||||
let io = length_delimited::Builder::new().new_write(mock! {
|
||||
data(b"\x00\x00\x00\x06"),
|
||||
data(b"ab"),
|
||||
Pending,
|
||||
data(b"cdef"),
|
||||
flush(),
|
||||
});
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdef")));
|
||||
|
||||
assert_pending!(io.as_mut().poll_flush(cx));
|
||||
|
||||
io.encoder_mut().set_max_frame_length(5);
|
||||
|
||||
assert_ready_ok!(io.as_mut().poll_flush(cx));
|
||||
|
||||
assert_err!(io.as_mut().start_send(Bytes::from("abcdef")));
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_zero() {
|
||||
let io = length_delimited::Builder::new().new_write(mock! {});
|
||||
pin_mut!(io);
|
||||
|
||||
MockTask::new().enter(|cx| {
|
||||
assert_ready_ok!(io.as_mut().poll_ready(cx));
|
||||
assert_ok!(io.as_mut().start_send(Bytes::from("abcdef")));
|
||||
|
||||
assert_ready_err!(io.as_mut().poll_flush(cx));
|
||||
|
||||
assert!(io.get_ref().calls.is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_overflow() {
|
||||
// Test reproducing tokio-rs/tokio#681.
|
||||
let mut codec = length_delimited::Builder::new().new_codec();
|
||||
let mut buf = BytesMut::with_capacity(1024);
|
||||
|
||||
// Put some data into the buffer without resizing it to hold more.
|
||||
let some_as = std::iter::repeat(b'a').take(1024).collect::<Vec<_>>();
|
||||
buf.put_slice(&some_as[..]);
|
||||
|
||||
// Trying to encode the length header should resize the buffer if it won't fit.
|
||||
codec.encode(Bytes::from("hello"), &mut buf).unwrap();
|
||||
}
|
||||
|
||||
// ===== Test utils =====
|
||||
|
||||
struct Mock {
|
||||
calls: VecDeque<Poll<io::Result<Op>>>,
|
||||
}
|
||||
|
||||
enum Op {
|
||||
Data(Vec<u8>),
|
||||
Flush,
|
||||
}
|
||||
|
||||
use self::Op::*;
|
||||
|
||||
impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
dst: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Data(data)))) => {
|
||||
debug_assert!(dst.len() >= data.len());
|
||||
dst[..data.len()].copy_from_slice(&data[..]);
|
||||
Ready(Ok(data.len()))
|
||||
}
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Mock {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
src: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Data(data)))) => {
|
||||
let len = data.len();
|
||||
assert!(src.len() >= len, "expect={:?}; actual={:?}", data, src);
|
||||
assert_eq!(&data[..], &src[..len]);
|
||||
Ready(Ok(len))
|
||||
}
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Flush))) => Ready(Ok(())),
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for Op {
|
||||
fn from(src: &'a [u8]) -> Op {
|
||||
Op::Data(src.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Op {
|
||||
fn from(src: Vec<u8>) -> Op {
|
||||
Op::Data(src)
|
||||
}
|
||||
}
|
||||
|
||||
fn data(bytes: &[u8]) -> Poll<io::Result<Op>> {
|
||||
Ready(Ok(bytes.into()))
|
||||
}
|
||||
|
||||
fn flush() -> Poll<io::Result<Op>> {
|
||||
Ready(Ok(Flush))
|
||||
}
|
||||
@@ -1,3 +1,72 @@
|
||||
# 0.2.0-alpha.5 (September 19, 2019)
|
||||
|
||||
### Fix
|
||||
- shutdown blocking pool threads when idle (#1562, #1514).
|
||||
|
||||
# 0.2.0-alpha.4 (August 29, 2019)
|
||||
|
||||
- Track tokio release.
|
||||
|
||||
# 0.2.0-alpha.3 (August 28, 2019)
|
||||
|
||||
### Changed
|
||||
- use `tracing` instead of `log`
|
||||
|
||||
### Added
|
||||
- thread pool dedicated to blocking operations (#1495).
|
||||
- `Executor::spawn_with_handle` (#1492).
|
||||
|
||||
# 0.2.0-alpha.2 (August 17, 2019)
|
||||
|
||||
### Fixed
|
||||
- allow running executor from within blocking clause (#1433).
|
||||
|
||||
### Changed
|
||||
- Update `futures` dependency to 0.3.0-alpha.18.
|
||||
|
||||
### Added
|
||||
- Import `current-thread` executor (#1447).
|
||||
- Import `threadpool` executor (#1152).
|
||||
|
||||
# 0.2.0-alpha.1 (August 8, 2019)
|
||||
|
||||
### Changed
|
||||
- Switch to `async`, `await`, and `std::future`.
|
||||
|
||||
### Removed
|
||||
- `Enter::make_permanent` and `Enter::on_exit` (#???)
|
||||
|
||||
# 0.1.7 (March 22, 2019)
|
||||
|
||||
### Added
|
||||
- `TypedExecutor` for spawning futures of a specific type (#993).
|
||||
|
||||
# 0.1.6 (January 6, 2019)
|
||||
|
||||
* Implement `Unpark` for `Arc<Unpark>` (#802).
|
||||
* Switch to crossbeam's Parker / Unparker (#528).
|
||||
|
||||
# 0.1.5 (September 26, 2018)
|
||||
|
||||
* Implement `futures::Executor` for `DefaultExecutor` (#563).
|
||||
* Add `Enter::block_on(future)` (#646)
|
||||
|
||||
# 0.1.4 (August 23, 2018)
|
||||
|
||||
* Implement `std::error::Error` for error types (#511).
|
||||
|
||||
# 0.1.3 (August 6, 2018)
|
||||
|
||||
* Implement `Executor` for `Box<E: Executor>` (#420).
|
||||
* Improve `EnterError` debug message (#410).
|
||||
* Implement `status`, `Send`, and `Sync` for `DefaultExecutor` (#463, #472).
|
||||
* Fix race in `ParkThread` (#507).
|
||||
* Handle recursive calls into `DefaultExecutor` (#473).
|
||||
|
||||
# 0.1.2 (March 30, 2018)
|
||||
|
||||
* Implement `Unpark` for `Box<Unpark>`.
|
||||
|
||||
# 0.1.1 (March 22, 2018)
|
||||
|
||||
* Optionally support futures 0.2.
|
||||
|
||||
+48
-13
@@ -1,28 +1,63 @@
|
||||
[package]
|
||||
name = "tokio-executor"
|
||||
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.1"
|
||||
documentation = "https://docs.rs/tokio-executor"
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0-alpha.5"
|
||||
edition = "2018"
|
||||
documentation = "https://docs.rs/tokio-executor/0.2.0-alpha.5/tokio_executor"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://github.com/tokio-rs/tokio"
|
||||
license = "MIT"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
description = """
|
||||
Future execution primitives
|
||||
"""
|
||||
keywords = ["futures", "tokio"]
|
||||
categories = ["concurrency", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.19"
|
||||
|
||||
# Futures 0.2 integration
|
||||
futures2 = { version = "0.1.0", path = "../futures2", optional = true }
|
||||
|
||||
[features]
|
||||
unstable-futures = ["futures2"]
|
||||
default = []
|
||||
blocking = ["tokio-sync", "lazy_static"]
|
||||
current-thread = ["crossbeam-channel"]
|
||||
threadpool = [
|
||||
"tokio-sync",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"futures-core-preview",
|
||||
"num_cpus",
|
||||
"lazy_static",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tokio-sync = { version = "=0.2.0-alpha.5", optional = true, path = "../tokio-sync" }
|
||||
|
||||
tracing = { version = "0.1.5", optional = true }
|
||||
futures-util-preview = { version = "=0.3.0-alpha.18", features = ["channel"] }
|
||||
|
||||
# current-thread dependencies
|
||||
crossbeam-channel = { version = "0.3.8", optional = true }
|
||||
|
||||
# threadpool dependencies
|
||||
crossbeam-deque = { version = "0.7.0", optional = true }
|
||||
crossbeam-queue = { version = "0.1.0", optional = true }
|
||||
crossbeam-utils = { version = "0.6.4", optional = true }
|
||||
futures-core-preview = { version = "=0.3.0-alpha.18", optional = true }
|
||||
num_cpus = { version = "1.2", optional = true }
|
||||
lazy_static = { version = "1", optional = true }
|
||||
slab = { version = "0.4.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "=0.2.0-alpha.5", path = "../tokio" }
|
||||
tokio-test = { version = "=0.2.0-alpha.5", path = "../tokio-test" }
|
||||
|
||||
futures-core-preview = "=0.3.0-alpha.18"
|
||||
rand = "0.7"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 Tokio Contributors
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -2,40 +2,6 @@
|
||||
|
||||
Task execution related traits and utilities.
|
||||
|
||||
[Documentation](https://tokio-rs.github.io/tokio/tokio_executor/)
|
||||
|
||||
## Overview
|
||||
|
||||
In the Tokio execution model, futures are lazy. When a future is created, no
|
||||
work is performed. In order for the work defined by the future to happen, the
|
||||
future must be submitted to an executor. A future that is submitted to an
|
||||
executor is called a "task".
|
||||
|
||||
The executor is responsible for ensuring that [`Future::poll`] is called
|
||||
whenever the task is [notified]. Notification happens when the internal state of
|
||||
a task transitions from "not ready" to ready. For example, a socket might have
|
||||
received data and a call to `read` will now be able to succeed.
|
||||
|
||||
This crate provides traits and utilities that are necessary for building an
|
||||
executor, including:
|
||||
|
||||
* The [`Executor`] trait describes the API for spawning a future onto an
|
||||
executor.
|
||||
|
||||
* [`enter`] marks that the the current thread is entering an execution
|
||||
context. This prevents a second executor from accidentally starting from
|
||||
within the context of one that is already running.
|
||||
|
||||
* [`DefaultExecutor`] spawns tasks onto the default executor for the current
|
||||
context.
|
||||
|
||||
* [`Park`] abstracts over blocking and unblocking the current thread.
|
||||
|
||||
[`Executor`]: https://tokio-rs.github.io/tokio/tokio_executor/trait.Executor.html
|
||||
[`enter`]: https://tokio-rs.github.io/tokio/tokio_executor/fn.enter.html
|
||||
[`DefaultExecutor`]: https://tokio-rs.github.io/tokio/tokio_executor/struct.DefaultExecutor.html
|
||||
[`Park`]: https://tokio-rs.github.io/tokio/tokio_executor/park/index.html
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
const ITER: usize = 1_000;
|
||||
|
||||
mod blocking {
|
||||
use super::*;
|
||||
use futures::future::*;
|
||||
use tokio_executor::threadpool::{blocking, Builder};
|
||||
|
||||
#[bench]
|
||||
fn cpu_bound(b: &mut test::Bencher) {
|
||||
let pool = Builder::new().pool_size(2).max_blocking(20).build();
|
||||
|
||||
b.iter(|| {
|
||||
let count_down = Arc::new(CountDown::new(ITER));
|
||||
|
||||
for _ in 0..ITER {
|
||||
let count_down = count_down.clone();
|
||||
|
||||
pool.spawn(lazy(move || {
|
||||
poll_fn(|| blocking(|| perform_complex_computation()).map_err(|_| panic!()))
|
||||
.and_then(move |_| {
|
||||
// Do something with the value
|
||||
count_down.dec();
|
||||
Ok(())
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
count_down.wait();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod message_passing {
|
||||
use super::*;
|
||||
use futures::future::*;
|
||||
use futures::sync::oneshot;
|
||||
use tokio_executor::threadpool::Builder;
|
||||
|
||||
#[bench]
|
||||
fn cpu_bound(b: &mut test::Bencher) {
|
||||
let pool = Builder::new().pool_size(2).max_blocking(20).build();
|
||||
|
||||
let blocking = threadpool::ThreadPool::new(20);
|
||||
|
||||
b.iter(|| {
|
||||
let count_down = Arc::new(CountDown::new(ITER));
|
||||
|
||||
for _ in 0..ITER {
|
||||
let count_down = count_down.clone();
|
||||
let blocking = blocking.clone();
|
||||
|
||||
pool.spawn(lazy(move || {
|
||||
// Create a channel to receive the return value.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// Spawn a task on the blocking thread pool to process the
|
||||
// computation.
|
||||
blocking.execute(move || {
|
||||
let res = perform_complex_computation();
|
||||
tx.send(res).unwrap();
|
||||
});
|
||||
|
||||
rx.and_then(move |_| {
|
||||
count_down.dec();
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|_| panic!())
|
||||
}));
|
||||
}
|
||||
|
||||
count_down.wait();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_complex_computation() -> usize {
|
||||
use rand::*;
|
||||
|
||||
// Simulate a CPU heavy computation
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.gen()
|
||||
}
|
||||
|
||||
// Util for waiting until the tasks complete
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::*;
|
||||
use std::sync::*;
|
||||
|
||||
struct CountDown {
|
||||
rem: AtomicUsize,
|
||||
mutex: Mutex<()>,
|
||||
condvar: Condvar,
|
||||
}
|
||||
|
||||
impl CountDown {
|
||||
fn new(rem: usize) -> Self {
|
||||
CountDown {
|
||||
rem: AtomicUsize::new(rem),
|
||||
mutex: Mutex::new(()),
|
||||
condvar: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dec(&self) {
|
||||
let prev = self.rem.fetch_sub(1, AcqRel);
|
||||
|
||||
if prev != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let _lock = self.mutex.lock().unwrap();
|
||||
self.condvar.notify_all();
|
||||
}
|
||||
|
||||
fn wait(&self) {
|
||||
let mut lock = self.mutex.lock().unwrap();
|
||||
|
||||
loop {
|
||||
if self.rem.load(Acquire) == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
lock = self.condvar.wait(lock).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![deny(warnings)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate tokio_threadpool;
|
||||
extern crate futures;
|
||||
extern crate futures_cpupool;
|
||||
extern crate num_cpus;
|
||||
extern crate test;
|
||||
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
@@ -13,12 +10,11 @@ const TASKS_PER_CPU: usize = 50;
|
||||
|
||||
mod threadpool {
|
||||
use futures::{future, task, Async};
|
||||
use tokio_threadpool::*;
|
||||
use num_cpus;
|
||||
use test;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use tokio_executor::threadpool::*;
|
||||
|
||||
#[bench]
|
||||
fn spawn_many(b: &mut test::Bencher) {
|
||||
@@ -90,14 +86,13 @@ mod threadpool {
|
||||
// See rust-lang-nursery/futures-rs#617
|
||||
//
|
||||
mod cpupool {
|
||||
use futures::{task, Async};
|
||||
use futures::future::{self, Executor};
|
||||
use futures::{task, Async};
|
||||
use futures_cpupool::*;
|
||||
use num_cpus;
|
||||
use test;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
#[bench]
|
||||
fn spawn_many(b: &mut test::Bencher) {
|
||||
@@ -119,7 +114,9 @@ mod cpupool {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})).ok().unwrap();
|
||||
}))
|
||||
.ok()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let _ = rx.recv().unwrap();
|
||||
@@ -151,7 +148,9 @@ mod cpupool {
|
||||
// Not ready
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
})).ok().unwrap();
|
||||
}))
|
||||
.ok()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..tasks {
|
||||
@@ -1,19 +1,15 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![deny(warnings)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate tokio_threadpool;
|
||||
extern crate futures;
|
||||
extern crate futures_cpupool;
|
||||
extern crate num_cpus;
|
||||
extern crate test;
|
||||
|
||||
const ITER: usize = 20_000;
|
||||
|
||||
mod us {
|
||||
use tokio_threadpool::*;
|
||||
use futures::future;
|
||||
use test;
|
||||
use std::sync::mpsc;
|
||||
use tokio_executor::threadpool::*;
|
||||
|
||||
#[bench]
|
||||
fn chained_spawn(b: &mut test::Bencher) {
|
||||
@@ -24,10 +20,12 @@ mod us {
|
||||
res_tx.send(()).unwrap();
|
||||
} else {
|
||||
let pool_tx2 = pool_tx.clone();
|
||||
pool_tx.spawn(future::lazy(move || {
|
||||
spawn(pool_tx2, res_tx, n - 1);
|
||||
Ok(())
|
||||
})).unwrap();
|
||||
pool_tx
|
||||
.spawn(future::lazy(move || {
|
||||
spawn(pool_tx2, res_tx, n - 1);
|
||||
Ok(())
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +42,6 @@ mod cpupool {
|
||||
use futures::future::{self, Executor};
|
||||
use futures_cpupool::*;
|
||||
use num_cpus;
|
||||
use test;
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[bench]
|
||||
@@ -59,7 +56,9 @@ mod cpupool {
|
||||
pool.execute(future::lazy(move || {
|
||||
spawn(pool2, res_tx, n - 1);
|
||||
Ok(())
|
||||
})).ok().unwrap();
|
||||
}))
|
||||
.ok()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
//! Thread pool for blocking operations
|
||||
|
||||
use tokio_sync::oneshot;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Condvar, Mutex};
|
||||
use std::task::{Context, Poll};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
struct Pool {
|
||||
shared: Mutex<Shared>,
|
||||
condvar: Condvar,
|
||||
}
|
||||
|
||||
struct Shared {
|
||||
queue: VecDeque<Box<dyn FnOnce() + Send>>,
|
||||
num_th: u32,
|
||||
num_idle: u32,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref POOL: Pool = Pool::new();
|
||||
}
|
||||
|
||||
const MAX_THREADS: u32 = 1_000;
|
||||
const KEEP_ALIVE: Duration = Duration::from_secs(10);
|
||||
|
||||
/// Result of a blocking operation running on the blocking thread pool.
|
||||
#[derive(Debug)]
|
||||
pub struct Blocking<T> {
|
||||
rx: oneshot::Receiver<T>,
|
||||
}
|
||||
|
||||
/// Run the provided function on a threadpool dedicated to blocking operations.
|
||||
pub fn run<F, R>(f: F) -> Blocking<R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let should_spawn = {
|
||||
let mut shared = POOL.shared.lock().unwrap();
|
||||
|
||||
shared.queue.push_back(Box::new(move || {
|
||||
// The receiver may have dropped
|
||||
let _ = tx.send(f());
|
||||
}));
|
||||
|
||||
if shared.num_idle == 0 {
|
||||
// No threads are able to process the task
|
||||
|
||||
if shared.num_th == MAX_THREADS {
|
||||
// At max number of threads
|
||||
false
|
||||
} else {
|
||||
shared.num_th += 1;
|
||||
true
|
||||
}
|
||||
} else {
|
||||
shared.num_idle -= 1;
|
||||
POOL.condvar.notify_one();
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if should_spawn {
|
||||
spawn_thread();
|
||||
}
|
||||
|
||||
Blocking { rx }
|
||||
}
|
||||
|
||||
impl<T> Future for Blocking<T> {
|
||||
type Output = T;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
use std::task::Poll::*;
|
||||
|
||||
match Pin::new(&mut self.rx).poll(cx) {
|
||||
Ready(Ok(v)) => Ready(v),
|
||||
Ready(Err(_)) => panic!(
|
||||
"the blocking operation has been dropped before completing. \
|
||||
This should not happen and is a bug."
|
||||
),
|
||||
Pending => Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_thread() {
|
||||
thread::Builder::new()
|
||||
.name("tokio-blocking-driver".to_string())
|
||||
.spawn(|| {
|
||||
'outer: loop {
|
||||
let mut shared = POOL.shared.lock().unwrap();
|
||||
|
||||
if let Some(task) = shared.queue.pop_front() {
|
||||
drop(shared);
|
||||
run_task(task);
|
||||
continue;
|
||||
}
|
||||
|
||||
// IDLE
|
||||
shared.num_idle += 1;
|
||||
|
||||
loop {
|
||||
let lock_result = POOL.condvar.wait_timeout(shared, KEEP_ALIVE).unwrap();
|
||||
shared = lock_result.0;
|
||||
let timeout_result = lock_result.1;
|
||||
|
||||
if let Some(task) = shared.queue.pop_front() {
|
||||
drop(shared);
|
||||
run_task(task);
|
||||
continue 'outer;
|
||||
} else if timeout_result.timed_out() {
|
||||
shared.num_idle = shared.num_idle.saturating_sub(1);
|
||||
shared.num_th -= 1;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn run_task(f: Box<dyn FnOnce() + Send>) {
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
|
||||
let _ = catch_unwind(AssertUnwindSafe(|| f()));
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
fn new() -> Pool {
|
||||
Pool {
|
||||
shared: Mutex::new(Shared {
|
||||
queue: VecDeque::new(),
|
||||
num_th: 0,
|
||||
num_idle: 0,
|
||||
}),
|
||||
condvar: Condvar::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user