mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
611
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0273e45ead | ||
|
|
e3ee4e58dc | ||
|
|
8c980ea75a | ||
|
|
e35fd6d6b7 | ||
|
|
03fe44c103 | ||
|
|
d18e5dfbb0 | ||
|
|
f21f2693f0 | ||
|
|
d81e8f0acb | ||
|
|
25e7f2641e | ||
|
|
e1a91ef114 | ||
|
|
1b11840f53 | ||
|
|
09f92b5aed | ||
|
|
e65040f061 | ||
|
|
c23735d43b | ||
|
|
d83921bc51 | ||
|
|
96f64f4ee2 | ||
|
|
00d10c22f8 | ||
|
|
a25d95a8c4 | ||
|
|
9e7e1ef7ad | ||
|
|
5bcd7c1d09 | ||
|
|
2486d49778 | ||
|
|
9dacb1c53e | ||
|
|
8167f87137 | ||
|
|
159f70bc55 | ||
|
|
d1e4db1018 | ||
|
|
530af3f331 | ||
|
|
d89e998922 | ||
|
|
306ed1c30b | ||
|
|
7e952697e8 | ||
|
|
c0943f99f0 | ||
|
|
187a2146a7 | ||
|
|
f61374f931 | ||
|
|
0d6c7af3e4 | ||
|
|
9dc9b53ae1 | ||
|
|
c3b31ba2ab | ||
|
|
b68ea4156a | ||
|
|
8f6d0864c3 | ||
|
|
63abec0525 | ||
|
|
8fd44d9bdc | ||
|
|
450fa2d09d | ||
|
|
8cfa309126 | ||
|
|
bf185b61ff | ||
|
|
27d1383581 | ||
|
|
11f9d4db73 | ||
|
|
1280cf81de | ||
|
|
67682ac2e8 | ||
|
|
b88c02c55e | ||
|
|
240cc44da8 | ||
|
|
7ed6da6733 | ||
|
|
7a2135f426 | ||
|
|
0913cad381 | ||
|
|
ff9681b3c6 | ||
|
|
f1cb007a28 | ||
|
|
2d4853ea16 | ||
|
|
d65165f7b5 | ||
|
|
71a1a3da7a | ||
|
|
df73fa2188 | ||
|
|
09ad5367b8 | ||
|
|
934f68d91c | ||
|
|
41d1877689 | ||
|
|
60b083b630 | ||
|
|
9cc02cc88d | ||
|
|
d2799d791b | ||
|
|
4d4870f291 | ||
|
|
fdb150901a | ||
|
|
426a562780 | ||
|
|
e3b89bbefa | ||
|
|
4f577b84e9 | ||
|
|
f320197693 | ||
|
|
ea6b144cd1 | ||
|
|
264e703296 | ||
|
|
dfb0f00838 | ||
|
|
4a91f197b0 | ||
|
|
601c383ab6 | ||
|
|
484cb52d8d | ||
|
|
16f20c34ed | ||
|
|
46674789ab | ||
|
|
8f4ebfd2f7 | ||
|
|
bd3940c14f | ||
|
|
c27bed36ac | ||
|
|
6d3feb581f | ||
|
|
a708ad19cb | ||
|
|
4bc2a15d28 | ||
|
|
33566434bb | ||
|
|
7388f2d2ea | ||
|
|
0a3e386269 | ||
|
|
d666068be7 | ||
|
|
1b17a7e241 | ||
|
|
5b91709edf | ||
|
|
08f40652aa | ||
|
|
6403b5370e | ||
|
|
064181f386 | ||
|
|
009a2567d0 | ||
|
|
231a3a69f9 | ||
|
|
0fa7755e97 | ||
|
|
d3fe35593d | ||
|
|
0ec0a85461 | ||
|
|
97d06ae1a6 | ||
|
|
b5054e1dff | ||
|
|
398eef8120 | ||
|
|
c8116ecd7b | ||
|
|
5471a5835e | ||
|
|
be99e7aa04 | ||
|
|
3bf2e53f0b | ||
|
|
ab3996a6dd | ||
|
|
c03a37fa0b | ||
|
|
73d733a341 | ||
|
|
749322d351 | ||
|
|
963b631754 | ||
|
|
9a1b076c00 | ||
|
|
c434ed7865 | ||
|
|
4714ca168d | ||
|
|
5e3ad02fb1 | ||
|
|
12412afea4 | ||
|
|
cae083a26f | ||
|
|
fd7a8d7c65 | ||
|
|
d709df2571 | ||
|
|
665f08b5ad | ||
|
|
d4641ba9fc | ||
|
|
2bf80f0ac6 | ||
|
|
62ecff895a | ||
|
|
d84a9e9af3 | ||
|
|
0671c205cc | ||
|
|
1ece2f1fa7 | ||
|
|
12319f26d0 | ||
|
|
454fd8c347 | ||
|
|
4421022c25 | ||
|
|
5a709e391b | ||
|
|
5efb1c3b16 | ||
|
|
f490029b8f | ||
|
|
d25778f67d | ||
|
|
b8318fa172 | ||
|
|
acfdb87e2b | ||
|
|
d060401f6c | ||
|
|
5dacc2e2a8 | ||
|
|
444d3f5c49 | ||
|
|
d23a838732 | ||
|
|
2137f7d953 | ||
|
|
51e9dc0943 | ||
|
|
3762a6a990 | ||
|
|
07f6cc7e1d | ||
|
|
308e3e6871 | ||
|
|
5a1879c2d0 | ||
|
|
de6ef21a81 | ||
|
|
90551d234f | ||
|
|
bd4c3dddca | ||
|
|
49b331855e | ||
|
|
da292dfb66 | ||
|
|
b9feac8d68 | ||
|
|
4fee6e3500 | ||
|
|
b1e69e560c | ||
|
|
556820ff84 | ||
|
|
fd1659a052 | ||
|
|
53e8acac64 | ||
|
|
9e5527d1d5 | ||
|
|
25a24de0e6 | ||
|
|
c1fa25f300 | ||
|
|
e7e02fcf0f | ||
|
|
f7a7f62959 | ||
|
|
d1f1499f63 | ||
|
|
ad6f618952 | ||
|
|
0f9ae13c31 | ||
|
|
9255d96b1b | ||
|
|
ffcc9f7c95 | ||
|
|
1a4cf319b5 | ||
|
|
3698a6f153 | ||
|
|
5b4cbbc39e | ||
|
|
c1f0c76fa0 | ||
|
|
d0953e833d | ||
|
|
b157f5da76 | ||
|
|
35470bfc6e | ||
|
|
95edd8515e | ||
|
|
a0f7f5c94a | ||
|
|
02486978d1 | ||
|
|
8ccf2fb92e | ||
|
|
bce76c515f | ||
|
|
b48586f560 | ||
|
|
eb99e476e6 | ||
|
|
b9b532485b | ||
|
|
1b98d5ad85 | ||
|
|
6d1ae62868 | ||
|
|
3b5a15dfdf | ||
|
|
2af3e4430a | ||
|
|
67869be3d7 | ||
|
|
c6b16cc861 | ||
|
|
7c197c7784 | ||
|
|
5f3f5b0be4 | ||
|
|
32a1acc85f | ||
|
|
7f455b2d93 | ||
|
|
86de2e306b | ||
|
|
6dc4f85f0b | ||
|
|
637fc1d103 | ||
|
|
7a0ca807be | ||
|
|
d8e8037de5 | ||
|
|
de978c47e0 | ||
|
|
024bd60933 | ||
|
|
7127e257a7 | ||
|
|
94b6df699b | ||
|
|
510b9ea9dc | ||
|
|
0fc23971d9 | ||
|
|
f07233f742 | ||
|
|
8efd04e382 | ||
|
|
044eaa1a41 | ||
|
|
03bb6e29b1 | ||
|
|
ac4c95972e | ||
|
|
14e739c306 | ||
|
|
9f59c6952e | ||
|
|
86400a1920 | ||
|
|
c9b4e4c110 | ||
|
|
3eb515e1a7 | ||
|
|
1ed2a1436f | ||
|
|
c6aceed643 | ||
|
|
4590828fb4 | ||
|
|
a99a351802 | ||
|
|
d1e06f831e | ||
|
|
37ca2f049c | ||
|
|
c8371d45bc | ||
|
|
adc3e19ba7 | ||
|
|
925c614c89 | ||
|
|
dd74c7c1bf | ||
|
|
23263231fd | ||
|
|
86528741f9 | ||
|
|
131afd3c53 | ||
|
|
9ed6f70b81 | ||
|
|
46f7d87962 | ||
|
|
987675e843 | ||
|
|
11d7c0486a | ||
|
|
3e84a198e4 | ||
|
|
7497561fed | ||
|
|
ef5b6af7f6 | ||
|
|
0922aa2a0b | ||
|
|
2403b91d75 | ||
|
|
f1d3b065b6 | ||
|
|
cf6b50a3fd | ||
|
|
416e36b0df | ||
|
|
9741c90f9f | ||
|
|
4e3f17bce3 | ||
|
|
f020b5c4b5 | ||
|
|
86cbf81e15 | ||
|
|
be8ee45b3f | ||
|
|
d9b19166cd | ||
|
|
db8edc620f | ||
|
|
e47565b086 | ||
|
|
4730984d66 | ||
|
|
1979615cbf | ||
|
|
1bc50825f3 | ||
|
|
f669a609cf | ||
|
|
ad2e19ffe1 | ||
|
|
5f04d14d81 | ||
|
|
01ea8f22ea | ||
|
|
9f423053fb | ||
|
|
0e5c5d64f5 | ||
|
|
1b27e17ff8 | ||
|
|
8fc62c06c7 | ||
|
|
4b96af6040 | ||
|
|
ce41896f8d | ||
|
|
c8ab78a84f | ||
|
|
3911cb8523 | ||
|
|
d545aa2601 | ||
|
|
911ab21d70 | ||
|
|
9e94fa7e15 | ||
|
|
0d234c3cf9 | ||
|
|
3754e059b6 | ||
|
|
6d868d96ce | ||
|
|
0a3fe46086 | ||
|
|
154d7d5fe6 | ||
|
|
611b793356 | ||
|
|
888ee60e41 | ||
|
|
7dd4d8a30e | ||
|
|
085e616c87 | ||
|
|
a7896d07f1 | ||
|
|
aff24dfbeb | ||
|
|
71cc9ab4c2 | ||
|
|
02cbe4591b | ||
|
|
0783797520 | ||
|
|
ab3ff69cf2 | ||
|
|
a0d5b8ab30 | ||
|
|
a1ee3ef218 | ||
|
|
171cd148a3 | ||
|
|
3f1f268583 | ||
|
|
3e890cc017 | ||
|
|
69290a6432 | ||
|
|
e2b175848b | ||
|
|
b7a75b5be3 | ||
|
|
6b705b3053 | ||
|
|
3636fd018a | ||
|
|
2506c9fa99 | ||
|
|
b3a14483bf | ||
|
|
013f323def | ||
|
|
b926700065 | ||
|
|
99a03a502e | ||
|
|
933fa498d0 | ||
|
|
9f848c9f54 | ||
|
|
912b862a05 | ||
|
|
714e5b571f | ||
|
|
8e999e3806 | ||
|
|
1d980145cb | ||
|
|
8259133ca0 | ||
|
|
38d88c6799 | ||
|
|
c38de96b94 | ||
|
|
2440d113ff | ||
|
|
ab8d7b82a1 | ||
|
|
9563707aaa | ||
|
|
193c1574a1 | ||
|
|
328bd049f6 | ||
|
|
4380de9fe9 | ||
|
|
98f527f42d | ||
|
|
3768696d92 | ||
|
|
d7d4f7d08b | ||
|
|
421a7b001c | ||
|
|
b1bdb3c57b | ||
|
|
7ec77a0677 | ||
|
|
55e3ed2a39 | ||
|
|
17d8c2b29d | ||
|
|
327bec2caf | ||
|
|
ea30a5ea5e | ||
|
|
0cf95f0673 | ||
|
|
a48e418dcb | ||
|
|
4cbcb687f4 | ||
|
|
0715e6defc | ||
|
|
bdd64cc9d3 | ||
|
|
f0fdef80c4 | ||
|
|
00754c8f9c | ||
|
|
1ae9434e8e | ||
|
|
8895bba448 | ||
|
|
48ca254d92 | ||
|
|
a0af02a396 | ||
|
|
0ce3a1188a | ||
|
|
1ea9ce11d4 | ||
|
|
4d4d12613b | ||
|
|
5490267a79 | ||
|
|
1434b32b5a | ||
|
|
159a3b2c85 | ||
|
|
ce87dcfbf0 | ||
|
|
d41d49d202 | ||
|
|
7a6c424f6e | ||
|
|
c3037adac9 | ||
|
|
964fd06e0f | ||
|
|
817fa605ee | ||
|
|
77de684ed9 | ||
|
|
83d550e511 | ||
|
|
1b3d3e7cd6 | ||
|
|
9e044e144b | ||
|
|
cb08fbc6c3 | ||
|
|
e59584a661 | ||
|
|
f7fb0bdc7a | ||
|
|
9faea740df | ||
|
|
2a8c551631 | ||
|
|
676630785b | ||
|
|
ec4b1d7215 | ||
|
|
e3c3a56718 | ||
|
|
a7b658c35b | ||
|
|
c1c8d1033d | ||
|
|
aa303bc205 | ||
|
|
7b6ccb515f | ||
|
|
4b174ce2c9 | ||
|
|
0ec4d0db4d | ||
|
|
d83ba30d8d | ||
|
|
f339587b27 | ||
|
|
b663abe091 | ||
|
|
9a11efc262 | ||
|
|
d760b26666 | ||
|
|
d413c9c02a | ||
|
|
addbfb9204 | ||
|
|
5687043328 | ||
|
|
72c87a7724 | ||
|
|
8507e28f89 | ||
|
|
7efcab43c9 | ||
|
|
e4a39d2ef6 | ||
|
|
afd3678f89 | ||
|
|
8182ecf262 | ||
|
|
a258bff701 | ||
|
|
e076d21f67 | ||
|
|
042433cdcc | ||
|
|
0284d1b5c8 | ||
|
|
710bc8071e | ||
|
|
a2b12bd579 | ||
|
|
e7b593cbee | ||
|
|
3aaf4a5377 | ||
|
|
8e741c1c0e | ||
|
|
47d46455bd | ||
|
|
20c1fdc678 | ||
|
|
638ce93591 | ||
|
|
c853991b1e | ||
|
|
6d410f6c90 | ||
|
|
a27575f284 | ||
|
|
13fbdace66 | ||
|
|
4380c3d821 | ||
|
|
383da87313 | ||
|
|
17117b591e | ||
|
|
aa70f6c5f0 | ||
|
|
67c343d9e9 | ||
|
|
34cdcc7d87 | ||
|
|
8e134172dd | ||
|
|
605ef578df | ||
|
|
9b578f0c9d | ||
|
|
0a15768380 | ||
|
|
eb1a2ee990 | ||
|
|
8713d39228 | ||
|
|
7e27911911 | ||
|
|
4b3da20c98 | ||
|
|
b8ac94ed70 | ||
|
|
5086e56dcb | ||
|
|
2671ffb55b | ||
|
|
7f09959b0a | ||
|
|
fb7dec0e95 | ||
|
|
ee19b0ed73 | ||
|
|
c081dfe3ce | ||
|
|
21a13f9eea | ||
|
|
a82bdeebe9 | ||
|
|
435e39001b | ||
|
|
dabae570b1 | ||
|
|
6bd3be2e45 | ||
|
|
6fc1a8c8da | ||
|
|
5c8cd33820 | ||
|
|
5f3296df77 | ||
|
|
cc974a646b | ||
|
|
15495fd883 | ||
|
|
ad4183412a | ||
|
|
bd3e857737 | ||
|
|
15f73666f1 | ||
|
|
fd2048dad1 | ||
|
|
e8f39157b6 | ||
|
|
67f127769b | ||
|
|
463502cbaf | ||
|
|
a1520f5525 | ||
|
|
acd6627d6d | ||
|
|
2353806daf | ||
|
|
7be2bfa744 | ||
|
|
e066431c94 | ||
|
|
2052938a9f | ||
|
|
b3ff911c38 | ||
|
|
9d42b977df | ||
|
|
970d880ceb | ||
|
|
4ca13e6015 | ||
|
|
b54b9d4338 | ||
|
|
10e23d1c62 | ||
|
|
aa7e0cef72 | ||
|
|
bfa8cadaa0 | ||
|
|
6d15c6cacb | ||
|
|
79a2afae9f | ||
|
|
48e07a6d10 | ||
|
|
eb72ddde3b | ||
|
|
dc16b12edb | ||
|
|
67355c6d23 | ||
|
|
405d746d38 | ||
|
|
e0d1293fac | ||
|
|
480c010b01 | ||
|
|
c032ea0203 | ||
|
|
0b31c2f73d | ||
|
|
129f9fc0c8 | ||
|
|
b5c227d51f | ||
|
|
dcae2b9eb8 | ||
|
|
bb9d57017e | ||
|
|
af9c683d52 | ||
|
|
4bc5a1a058 | ||
|
|
f8948ea021 | ||
|
|
bce9780dd3 | ||
|
|
38151f30cb | ||
|
|
5dda72d338 | ||
|
|
c07257f99f | ||
|
|
d08578fc9a | ||
|
|
4047d7962a | ||
|
|
cbdceb91ac | ||
|
|
d4178cf349 | ||
|
|
2f899144ed | ||
|
|
6255598baa | ||
|
|
772e0ca8a6 | ||
|
|
3b677d1fde | ||
|
|
bb7ca7507b | ||
|
|
4a34b77af5 | ||
|
|
8897885425 | ||
|
|
0dbdd196b6 | ||
|
|
94e55c092b | ||
|
|
4468f27c31 | ||
|
|
070a825999 | ||
|
|
946401c345 | ||
|
|
0c01fd23b4 | ||
|
|
ebe241647e | ||
|
|
01e04daaa1 | ||
|
|
92ccadeb3c | ||
|
|
fbfeb9a68a | ||
|
|
da745ff335 | ||
|
|
ce1c74f1cc | ||
|
|
28c9a14a2e | ||
|
|
32e0b4325f | ||
|
|
1656d8e231 | ||
|
|
c9e998e4b3 | ||
|
|
512e9decfb | ||
|
|
5ada5114df | ||
|
|
161b8c80d5 | ||
|
|
9cc4a81678 | ||
|
|
679d7657dc | ||
|
|
c3a935541d | ||
|
|
29cd6ec1ec | ||
|
|
b68f5c7f38 | ||
|
|
6c5dbfa08c | ||
|
|
2c14f88c90 | ||
|
|
e2e1e8e71d | ||
|
|
21df16d759 | ||
|
|
c8af499990 | ||
|
|
623928e371 | ||
|
|
09bc9a05e4 | ||
|
|
82628b8a78 | ||
|
|
21cf5a5469 | ||
|
|
8ef5163df8 | ||
|
|
542197cdb9 | ||
|
|
a302367b8f | ||
|
|
b5de84d19b | ||
|
|
02aaea28b9 | ||
|
|
83e922f051 | ||
|
|
a2496548d1 | ||
|
|
0cea36fa3d | ||
|
|
d6213594ca | ||
|
|
91169992b2 | ||
|
|
8046a87a99 | ||
|
|
5dcc848fc8 | ||
|
|
bd4ccae184 | ||
|
|
4ed0fa21e4 | ||
|
|
12b2567b95 | ||
|
|
35f244ad09 | ||
|
|
1166ecc2ac | ||
|
|
27539ae3bd | ||
|
|
ea6d652a10 | ||
|
|
11f66f43a0 | ||
|
|
479a56a010 | ||
|
|
b37f0de28a | ||
|
|
c9fad08466 | ||
|
|
cc70a211ad | ||
|
|
1ac8dff213 | ||
|
|
ff3f2a8878 | ||
|
|
5b9a290acd | ||
|
|
70569bd009 | ||
|
|
9bd6702a3f | ||
|
|
365269adaf | ||
|
|
3d439ab711 | ||
|
|
b2ea40bb54 | ||
|
|
5ea3c63d7e | ||
|
|
56f3f40c15 | ||
|
|
2d697fc92b | ||
|
|
39c3c19bbd | ||
|
|
694577fa85 | ||
|
|
17819062e2 | ||
|
|
6ad1912353 | ||
|
|
a491b16a89 | ||
|
|
0ecf5f0f03 | ||
|
|
1e798d26ed | ||
|
|
ab53bf0c47 | ||
|
|
338e13b04b | ||
|
|
1077b0b29d | ||
|
|
0cbf1a5ada | ||
|
|
04c2718508 | ||
|
|
ebda4c3d3f | ||
|
|
f602eae499 | ||
|
|
438def7957 | ||
|
|
ee8d4d1b05 | ||
|
|
3297052763 | ||
|
|
f8fe0ffb23 | ||
|
|
47210a8e6e | ||
|
|
29545d9037 | ||
|
|
48e35c11d9 | ||
|
|
dd1d37167d | ||
|
|
6a1a7b1591 | ||
|
|
51b03f0334 | ||
|
|
754a1fb03c | ||
|
|
90b23a9584 | ||
|
|
b69f16aa21 | ||
|
|
6e845b794d | ||
|
|
9681ce2b95 | ||
|
|
feb742c58e | ||
|
|
15cd5146d4 | ||
|
|
56f4bc6543 | ||
|
|
3ad5b6df1a | ||
|
|
1be8a8e691 | ||
|
|
da17c61464 | ||
|
|
f71bded943 | ||
|
|
fc058b9561 | ||
|
|
c0280624f3 | ||
|
|
1e286689ff | ||
|
|
14b9f71157 | ||
|
|
24344dfe4b | ||
|
|
15925efe43 | ||
|
|
4825c444eb | ||
|
|
c8f3539bc1 | ||
|
|
b2e4c5fc9e | ||
|
|
4d0d89fb70 | ||
|
|
fe7285d3d1 | ||
|
|
dff4ecd0e7 | ||
|
|
68d0e3cb5f | ||
|
|
65d0e08d39 | ||
|
|
06582776a5 | ||
|
|
ed4ddf443d | ||
|
|
9a75d6f7f7 | ||
|
|
3bf4f93854 | ||
|
|
39cf6bba00 | ||
|
|
8480a180e6 | ||
|
|
53ea44bfb9 | ||
|
|
a865ca139a | ||
|
|
479f736935 | ||
|
|
17555d71d9 | ||
|
|
341b5daa6e | ||
|
|
53b586c5b9 | ||
|
|
833ee027d0 | ||
|
|
126ce89bb4 | ||
|
|
8e15c234c6 | ||
|
|
16fccafb41 | ||
|
|
3f397ccded | ||
|
|
49609d073f | ||
|
|
a91d43823c | ||
|
|
8fca6f6dad | ||
|
|
75c953bd63 |
@@ -1,25 +0,0 @@
|
||||
version: 2.1
|
||||
jobs:
|
||||
test-arm:
|
||||
machine:
|
||||
image: default
|
||||
resource_class: arm.medium
|
||||
environment:
|
||||
# Change to pin rust version
|
||||
RUST_STABLE: stable
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install Rust
|
||||
command: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o rustup.sh
|
||||
chmod +x rustup.sh
|
||||
./rustup.sh -y --default-toolchain $RUST_STABLE
|
||||
source "$HOME"/.cargo/env
|
||||
# Only run Tokio tests
|
||||
- run: cargo test --all-features -p tokio
|
||||
|
||||
workflows:
|
||||
ci:
|
||||
jobs:
|
||||
- test-arm
|
||||
+18
-5
@@ -1,11 +1,13 @@
|
||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-0
|
||||
image_family: freebsd-14-3
|
||||
env:
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2024-05-05
|
||||
RUST_NIGHTLY: nightly-2025-01-25
|
||||
RUSTFLAGS: -D warnings
|
||||
# This excludes unstable features like io_uring, which require '--cfg tokio_unstable'.
|
||||
TOKIO_STABLE_FEATURES: full,test-util
|
||||
|
||||
# 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
|
||||
@@ -23,7 +25,16 @@ task:
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all --all-features
|
||||
- cargo test --workspace --features $TOKIO_STABLE_FEATURES
|
||||
# Free the disk space before the next build,
|
||||
# otherwise cirrus-ci complains about "No space left on device".
|
||||
- cargo clean
|
||||
# Enable all unstable features except `taskdump`, which is Linux-only.
|
||||
- |
|
||||
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable" \
|
||||
RUSTDOCFLAGS="$RUSTDOCFLAGS --cfg tokio_unstable" \
|
||||
cargo test \
|
||||
--features $TOKIO_STABLE_FEATURES,io-uring,tracing
|
||||
|
||||
task:
|
||||
name: FreeBSD docs
|
||||
@@ -40,7 +51,9 @@ task:
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo doc --lib --no-deps --all-features --document-private-items
|
||||
# We use `--features $TOKIO_STABLE_FEATURES,io-uring,tracing` instead of
|
||||
# `--all-features` to exclude `taskdump`, which is Linux-only.
|
||||
- cargo doc --lib --no-deps --features $TOKIO_STABLE_FEATURES,io-uring,tracing --document-private-items
|
||||
|
||||
task:
|
||||
name: FreeBSD 32-bit
|
||||
@@ -55,4 +68,4 @@ task:
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all --all-features --target i686-unknown-freebsd
|
||||
- cargo test --workspace --features $TOKIO_STABLE_FEATURES --target i686-unknown-freebsd
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Buildomat illumos CI
|
||||
|
||||
This directory contains CI configurations for the [illumos] operating system.
|
||||
Tokio's illumos CI jobs are run using [Buildomat], a CI system developed by
|
||||
Oxide Computer, which supports illumos. See [the Buildomat README] for more
|
||||
details.
|
||||
|
||||
## illumos-Specific CI Failures
|
||||
|
||||
If your pull request's CI build fails on illumos, and you aren't able to easily
|
||||
reproduce the failure on other operating systems, don't worry! The
|
||||
[tokio-rs/illumos] team is responsible for maintaining Tokio's illumos support,
|
||||
and can be called on to assist contributors with illumos-specific issues. Please
|
||||
feel free to tag @tokio-rs/illumos to ask for help resolving build failures on
|
||||
illumos.
|
||||
|
||||
[illumos]: https://www.illumos.org/
|
||||
[Buildomat]: https://github.com/oxidecomputer/buildomat
|
||||
[the Buildomat README]: https://github.com/oxidecomputer/buildomat
|
||||
[tokio-rs/illumos]: https://github.com/orgs/tokio-rs/teams/illumos
|
||||
@@ -0,0 +1,8 @@
|
||||
# Repository-level Buildomat configuration.
|
||||
# See: https://github.com/oxidecomputer/buildomat#per-repository-configuration
|
||||
|
||||
# Enable buildomat. This one should be self-explanatory.
|
||||
enable = true
|
||||
# Allow CI runs for PRs from users outside the `tokio-rs` organization. Our
|
||||
# buildomat jobs don't touch any secrets/keys, so this should be fine.
|
||||
org_only = false
|
||||
+6
-6
@@ -1,3 +1,6 @@
|
||||
R-loom-blocking:
|
||||
- tokio/src/runtime/blocking/*
|
||||
- tokio/src/runtime/blocking/**/*
|
||||
|
||||
R-loom-sync:
|
||||
- tokio/src/sync/*
|
||||
@@ -20,9 +23,6 @@ R-loom-multi-thread:
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-multi-thread-alt:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread_alt/*
|
||||
- tokio/src/runtime/scheduler/multi_thread_alt/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
R-loom-util:
|
||||
- tokio-util/src/*
|
||||
- tokio-util/src/**/*
|
||||
|
||||
@@ -13,18 +13,12 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
cargo-deny:
|
||||
permissions:
|
||||
checks: write # for rustsec/audit-check to create check
|
||||
contents: read # for actions/checkout to fetch code
|
||||
issues: write # for rustsec/audit-check to create issues
|
||||
checks: write
|
||||
contents: read
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Audit Check
|
||||
# https://github.com/rustsec/audit-check/issues/2
|
||||
uses: rustsec/audit-check@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
+414
-215
File diff suppressed because it is too large
Load Diff
+29
-22
@@ -23,13 +23,29 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
loom-blocking:
|
||||
name: loom tokio::runtime::spawn_blocking
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-blocking') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture loom_blocking
|
||||
working-directory: tokio
|
||||
|
||||
loom-sync:
|
||||
name: loom tokio::sync
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-sync') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -45,14 +61,14 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture runtime::time::tests
|
||||
run: cargo test --lib --release --features full -- --nocapture runtime::time
|
||||
working-directory: tokio
|
||||
|
||||
loom-current-thread:
|
||||
@@ -61,7 +77,7 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -84,39 +100,30 @@ jobs:
|
||||
- scope: loom_multi_thread::group_c
|
||||
- scope: loom_multi_thread::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
loom-multi-thread-alt:
|
||||
name: loom ALT multi-thread scheduler
|
||||
loom-util:
|
||||
name: loom tokio-util
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread-alt') || (github.base_ref == null))
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-util') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- scope: loom_multi_thread_alt::group_a
|
||||
- scope: loom_multi_thread_alt::group_b
|
||||
- scope: loom_multi_thread_alt::group_c
|
||||
- scope: loom_multi_thread_alt::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture
|
||||
working-directory: tokio-util
|
||||
|
||||
@@ -16,17 +16,8 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
|
||||
- name: Generate lockfile
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- name: Audit dependencies
|
||||
run: cargo audit
|
||||
- uses: actions/checkout@v5
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
@@ -13,7 +13,7 @@ env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_stable: 1.82
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
stress-test:
|
||||
- simple_echo_tcp
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
name: Uring Kernel Version Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
kernel_version:
|
||||
description: 'Version of the Linux kernel to build'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
KERNEL_VERSION: ${{ inputs.kernel_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential bison flex libssl-dev libelf-dev \
|
||||
qemu-system-x86 busybox-static cpio xz-utils wget
|
||||
|
||||
- name: Cache Linux source
|
||||
id: cache-kernel
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: linux-${{ env.KERNEL_VERSION }}
|
||||
key: kernel-${{ env.KERNEL_VERSION }}
|
||||
|
||||
- name: Download & build Linux kernel
|
||||
if: steps.cache-kernel.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
MAJOR=${KERNEL_VERSION%%.*}
|
||||
wget https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KERNEL_VERSION}.tar.xz
|
||||
tar xf linux-${KERNEL_VERSION}.tar.xz
|
||||
cd linux-${KERNEL_VERSION}
|
||||
make defconfig
|
||||
make -j$(nproc)
|
||||
|
||||
- name: Generate test binaries with io_uring enabled
|
||||
run: |
|
||||
# Build both integration (tokio/tests/) and unit (e.g., tokio/src/fs/file/tests.rs) tests with io_uring enabled
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
RUSTFLAGS="--cfg tokio_unstable" \
|
||||
cargo test -p tokio --features full,io-uring \
|
||||
--target x86_64-unknown-linux-musl --test 'fs*' --lib --no-run
|
||||
|
||||
- name: Prepare initramfs + tests binaries
|
||||
run: |
|
||||
set -e
|
||||
rm -rf initramfs
|
||||
mkdir -p initramfs/{bin,bin/tests,sbin,proc,sys,tmp}
|
||||
|
||||
# Copy test binaries into initramfs
|
||||
for bin in target/x86_64-unknown-linux-musl/debug/deps/{fs_*,tokio-*}; do
|
||||
if [ -f "$bin" ] && [ -x "$bin" ]; then
|
||||
cp "$bin" initramfs/bin/tests
|
||||
fi
|
||||
done
|
||||
|
||||
# Add BusyBox & symlinks
|
||||
cp /usr/bin/busybox initramfs/bin/
|
||||
for cmd in sh mount uname true sleep; do ln -sf busybox initramfs/bin/$cmd; done
|
||||
ln -sf ../bin/busybox initramfs/sbin/poweroff
|
||||
|
||||
# Generate init script
|
||||
cat > initramfs/init << 'EOF'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
mkdir -p /dev
|
||||
# create device nodes, as some tests require them
|
||||
mknod /dev/null c 1 3
|
||||
mknod /dev/zero c 1 5
|
||||
mknod /dev/tty c 5 0
|
||||
mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
mkdir -p /tmp && mount -t tmpfs -o mode=1777 tmpfs /tmp
|
||||
for f in /bin/tests/*; do RUST_BACKTRACE=1 "$f" ; done
|
||||
EOF
|
||||
chmod +x initramfs/init
|
||||
|
||||
# Pack into a CPIO archive
|
||||
(cd initramfs && find . -print0 \
|
||||
| cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz)
|
||||
|
||||
- name: Run tests in QEMU
|
||||
run: |
|
||||
qemu-system-x86_64 \
|
||||
-kernel linux-${{ env.KERNEL_VERSION }}/arch/x86/boot/bzImage \
|
||||
-initrd initramfs.cpio.gz \
|
||||
-append "console=ttyS0 rootfstype=ramfs panic=1" \
|
||||
-nographic -no-reboot -m 1024 -action panic=exit-failure 2>&1 | tee qemu-output.log
|
||||
|
||||
# qemu always exits with 0, so we check if the tests passed by using grep.
|
||||
if grep -q "test result: FAILED" qemu-output.log; then
|
||||
echo "tests failed (QEMU exited abnormally)"
|
||||
exit 1
|
||||
else
|
||||
echo "all tests passed"
|
||||
fi
|
||||
@@ -3,3 +3,4 @@ Cargo.lock
|
||||
|
||||
.cargo/config.toml
|
||||
.cargo/config
|
||||
|
||||
|
||||
+18
-698
@@ -1,7 +1,6 @@
|
||||
# Contributing to Tokio
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
you!
|
||||
Thanks for your help improving Tokio! 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
|
||||
@@ -9,15 +8,11 @@ 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.
|
||||
See the [contributing guidelines] to get started.
|
||||
|
||||
The [dev channel][dev] is available for any concerns not covered in this guide, please join
|
||||
us!
|
||||
[contributing guidelines]: docs/contributing/README.md
|
||||
|
||||
[dev]: https://discord.gg/tokio
|
||||
|
||||
## Conduct
|
||||
## Code of Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
|
||||
the _minimum_ behavior expected from all contributors. Instances of violations of the
|
||||
@@ -26,709 +21,34 @@ Code of Conduct can be reported by contacting the project team at
|
||||
|
||||
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
## Contributing in Issues
|
||||
## Need Help?
|
||||
|
||||
For any issue, there are fundamentally three ways an individual can contribute:
|
||||
Reach out to us on the [Discord server] for any concern not covered in this guide.
|
||||
|
||||
1. By opening the issue for discussion: For instance, if you believe that you
|
||||
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
|
||||
issue tracker][issue] 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.
|
||||
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues
|
||||
|
||||
**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 a discussion] 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.
|
||||
|
||||
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, you will be presented
|
||||
with a basic 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
|
||||
|
||||
### 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.
|
||||
|
||||
### Cargo Commands
|
||||
|
||||
Due to the extensive use of features in Tokio, you will often need to add extra
|
||||
arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
Ideally, you should use the same version of clippy as the one used in CI
|
||||
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
|
||||
might have new lints:
|
||||
|
||||
[ci.yml]: .github/workflows/ci.yml
|
||||
|
||||
<!--
|
||||
When updating this, also update:
|
||||
- .github/workflows/ci.yml
|
||||
- README.md
|
||||
- tokio/README.md
|
||||
- tokio/Cargo.toml
|
||||
- tokio-util/Cargo.toml
|
||||
- tokio-test/Cargo.toml
|
||||
- tokio-stream/Cargo.toml
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.77 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation, a simple `cargo doc` is not sufficient. To produce
|
||||
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
|
||||
docs, please use:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
|
||||
```
|
||||
|
||||
This turns on indicators to display the Cargo features required for
|
||||
conditionally compiled APIs in Tokio, and it enables documentation of unstable
|
||||
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
|
||||
*and* rustc.
|
||||
|
||||
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
|
||||
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
|
||||
as docs.rs itself does.
|
||||
|
||||
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
|
||||
|
||||
```
|
||||
cargo install --locked cargo-docs-rs
|
||||
cargo +nightly docs-rs [--open]
|
||||
```
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
|
||||
You can run loom tests with
|
||||
```
|
||||
cd tokio # tokio crate in workspace
|
||||
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
|
||||
run loom tests that test unstable features.
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" \
|
||||
cargo +nightly miri test --features full --lib
|
||||
```
|
||||
|
||||
### Performing spellcheck on tokio codebase
|
||||
|
||||
You can perform spell-check on tokio codebase. For details of how to use the spellcheck tool, feel free to visit
|
||||
https://github.com/drahnr/cargo-spellcheck
|
||||
```
|
||||
# First install the spell-check plugin
|
||||
cargo install --locked cargo-spellcheck
|
||||
|
||||
# Then run the cargo spell check command
|
||||
cargo spellcheck check
|
||||
```
|
||||
|
||||
if the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
|
||||
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
|
||||
the total number of words included in the file
|
||||
|
||||
### 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][integration-tests]
|
||||
and [documentation tests][documentation-tests].
|
||||
(Tokio avoids [unit tests][unit-tests] as much as possible).
|
||||
|
||||
Tokio uses [conditional compilation attributes][conditional-compilation]
|
||||
throughout the codebase, to modify rustc's behavior. Code marked with such
|
||||
attributes can be enabled using RUSTFLAGS and RUSTDOCFLAGS environment
|
||||
variables. One of the most prevalent flags passed in these variables is
|
||||
the `--cfg` option. To run tests in a particular file, check first what
|
||||
options #![cfg] declaration defines for that file.
|
||||
|
||||
For instance, to run a test marked with the 'tokio_unstable' cfg option,
|
||||
you must pass this flag to the compiler when running the test.
|
||||
```
|
||||
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
#### Fuzz tests
|
||||
|
||||
Some of our crates include a set of fuzz tests, this will be marked by a
|
||||
directory `fuzz`. It is a good idea to run fuzz tests after each change.
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install --locked cargo-fuzz`
|
||||
|
||||
To list the available fuzzing harnesses you can run;
|
||||
|
||||
```bash
|
||||
$ cd tokio
|
||||
$ cargo fuzz list
|
||||
fuzz_linked_list
|
||||
````
|
||||
|
||||
Running a fuzz test is as simple as;
|
||||
|
||||
`cargo fuzz run fuzz_linked_list`
|
||||
|
||||
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
|
||||
harness will run forever and will only exit if you `ctrl-c` or it finds
|
||||
a bug.
|
||||
|
||||
#### 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();
|
||||
/// # }
|
||||
```
|
||||
|
||||
### Benchmarks
|
||||
|
||||
You can run benchmarks locally for the changes you've made to the tokio codebase.
|
||||
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
|
||||
against the changes you have made, for example, you can run;
|
||||
|
||||
```bash
|
||||
cd benches
|
||||
|
||||
# Run all benchmarks.
|
||||
cargo bench
|
||||
|
||||
# Run all tests in the `benches/fs.rs` file
|
||||
cargo bench --bench fs
|
||||
|
||||
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
|
||||
cargo bench async_read_buf
|
||||
|
||||
# After running benches, you can check the statistics under `tokio/target/criterion/`
|
||||
```
|
||||
|
||||
You can also refer to Criterion docs for additional options and details.
|
||||
|
||||
### 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-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
|
||||
* start with an imperative verb
|
||||
* not have a period at the end
|
||||
* be prefixed with the name of the module being changed; usually this is the
|
||||
same as the M-* label on the PR
|
||||
|
||||
Examples:
|
||||
|
||||
* time: introduce `Timeout` and deprecate `Deadline`
|
||||
* codec: export `Encoder`, `Decoder`, `Framed*`
|
||||
* ci: fix the FreeBSD ci configuration
|
||||
|
||||
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
|
||||
module: 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](#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
|
||||
|
||||
## Keeping track of issues and PRs
|
||||
|
||||
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
|
||||
section explains the meaning of various labels, as well as our [GitHub
|
||||
project][project]. The section is primarily targeted at maintainers. Most
|
||||
contributors aren't able to set these labels.
|
||||
|
||||
### Area
|
||||
|
||||
The area label describes the crates relevant to this issue or PR.
|
||||
|
||||
- **A-tokio** This issue concerns the main Tokio crate.
|
||||
- **A-tokio-util** This issue concerns the `tokio-util` crate.
|
||||
- **A-tokio-tls** This issue concerns the `tokio-tls` crate. Only used for
|
||||
older issues, as the crate has been moved to another repository.
|
||||
- **A-tokio-test** The issue concerns the `tokio-test` crate.
|
||||
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
|
||||
be used for the procedural macros, and not `join!` or `select!`.
|
||||
- **A-ci** This issue concerns our GitHub Actions setup.
|
||||
|
||||
### Category
|
||||
|
||||
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
|
||||
- **C-enhancement** This is a PR that adds a new features.
|
||||
- **C-maintenance** This is an issue or PR about stuff such as documentation,
|
||||
GitHub Actions or code quality.
|
||||
- **C-feature-request** This is a feature request. Implementations of feature
|
||||
requests use `C-enhancement` instead.
|
||||
- **C-feature-accepted** If you submit a PR for this feature request, we wont
|
||||
close it with the reason "we don't want this". Issues with this label should
|
||||
also have the `C-feature-request` label.
|
||||
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
|
||||
world"
|
||||
- **C-proposal** A proposal of some kind, and a request for comments.
|
||||
- **C-question** A user question. Large overlap with GitHub discussions.
|
||||
- **C-request** A non-feature request, e.g. "please add deprecation notices to
|
||||
`-alpha.*` versions of crates"
|
||||
|
||||
### Calls for participation
|
||||
|
||||
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
|
||||
or `C-feature-accepted`.
|
||||
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
|
||||
can do after reading the tutorial on our website.
|
||||
- **E-medium** This is not `E-easy` or `E-hard`.
|
||||
- **E-hard** This either involves very tricky code, is something we don't know
|
||||
how to solve, or is difficult for some other reason.
|
||||
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
|
||||
example.
|
||||
|
||||
The "E-" prefix is the same as used in the Rust compiler repository. Some
|
||||
issues are missing a difficulty rating, but feel free to ask on our Discord
|
||||
server if you want to know how difficult an issue likely is.
|
||||
|
||||
### Module
|
||||
|
||||
The module label provides a more fine grained categorization than **Area**.
|
||||
|
||||
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
|
||||
- **M-codec** The `tokio_util::codec` module.
|
||||
- **M-compat** The `tokio_util::compat` module.
|
||||
- **M-coop** Things relevant to coop.
|
||||
- **M-fs** The `tokio::fs` module.
|
||||
- **M-io** The `tokio::io` module.
|
||||
- **M-macros** Issues about any kind of macro.
|
||||
- **M-net** The `tokio::net` module.
|
||||
- **M-process** The `tokio::process` module.
|
||||
- **M-runtime** The `tokio::runtime` module.
|
||||
- **M-signal** The `tokio::signal` module.
|
||||
- **M-sync** The `tokio::sync` module.
|
||||
- **M-task** The `tokio::task` module.
|
||||
- **M-time** The `tokio::time` module.
|
||||
- **M-tracing** Tracing support in Tokio.
|
||||
|
||||
### Topic
|
||||
|
||||
Some extra information.
|
||||
|
||||
- **T-docs** This is about documentation.
|
||||
- **T-performance** This is about performance.
|
||||
- **T-v0.1.x** This is about old Tokio.
|
||||
|
||||
Any label not listed here is not in active use.
|
||||
|
||||
[project]: https://github.com/orgs/tokio-rs/projects/1
|
||||
[Discord server]: https://discord.gg/tokio
|
||||
|
||||
## LTS guarantees
|
||||
|
||||
Tokio ≥1.0.0 comes with LTS guarantees:
|
||||
|
||||
* A minimum of 5 years of maintenance.
|
||||
* A minimum of 3 years before a hypothetical 2.0 release.
|
||||
In Tokio ≥1.0.0, each LTS release comes with the guarantee of at least one year of
|
||||
backported fixes.
|
||||
|
||||
The goal of these guarantees is to provide stability to the ecosystem.
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
|
||||
compiler release.
|
||||
* The MSRV will only be increased on 1.x releases.
|
||||
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
|
||||
compiler release.
|
||||
* The MSRV will only be increased on 1.x releases.
|
||||
|
||||
## Versioning Policy
|
||||
|
||||
With Tokio ≥1.0.0:
|
||||
|
||||
* Patch (1.\_.x) releases _should only_ contain bug fixes or documentation
|
||||
changes. Besides this, these releases should not substantially change
|
||||
runtime behavior.
|
||||
* Minor (1.x) releases may contain new functionality, MSRV increases (see
|
||||
above), minor dependency updates, deprecations, and larger internal
|
||||
implementation changes.
|
||||
* Patch (1.\_.x) releases _should only_ contain bug fixes or documentation
|
||||
changes. Besides this, these releases should not substantially change
|
||||
runtime behavior.
|
||||
* Minor (1.x) releases may contain new functionality, MSRV increases (see
|
||||
above), minor dependency updates, deprecations, and larger internal
|
||||
implementation changes.
|
||||
|
||||
This is as defined by [Semantic Versioning 2.0](https://semver.org/).
|
||||
|
||||
## 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 "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
|
||||
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
|
||||
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
|
||||
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
|
||||
+16
@@ -15,5 +15,21 @@ members = [
|
||||
"tests-integration",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
tokio = { path = "tokio" }
|
||||
|
||||
[workspace.metadata.spellcheck]
|
||||
config = "spellcheck.toml"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(fuzzing)',
|
||||
'cfg(loom)',
|
||||
'cfg(mio_unsupported_force_poll_poll)',
|
||||
'cfg(tokio_allow_from_blocking_fd)',
|
||||
'cfg(tokio_internal_mt_counters)',
|
||||
'cfg(tokio_no_parking_lot)',
|
||||
'cfg(tokio_no_tuning_tests)',
|
||||
'cfg(tokio_unstable)',
|
||||
'cfg(target_os, values("cygwin"))',
|
||||
] }
|
||||
|
||||
@@ -39,7 +39,7 @@ 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,
|
||||
IOCP, etc...).
|
||||
IOCP, etc.).
|
||||
* Asynchronous [TCP and UDP][net] sockets.
|
||||
|
||||
These components provide the runtime components necessary for building
|
||||
@@ -52,11 +52,11 @@ an asynchronous application.
|
||||
|
||||
A basic TCP echo server with Tokio.
|
||||
|
||||
Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
Make sure you enable the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.38.0", features = ["full"] }
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -78,7 +78,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(0) => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
@@ -103,7 +103,7 @@ More examples can be found [here][examples]. For a larger "real world" example,
|
||||
[examples]: https://github.com/tokio-rs/tokio/tree/master/examples
|
||||
[mini-redis]: https://github.com/tokio-rs/mini-redis/
|
||||
|
||||
To see a list of the available features flags that can be enabled, check our
|
||||
To see a list of the available feature flags that can be enabled, check our
|
||||
[docs][feature-flag-docs].
|
||||
|
||||
## Getting Help
|
||||
@@ -125,7 +125,7 @@ question. You can also ask your question on [the discussions page][discussions].
|
||||
you! We have a [contributing guide][guide] to help you get involved in the Tokio
|
||||
project.
|
||||
|
||||
[guide]: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
|
||||
[guide]: https://github.com/tokio-rs/tokio/blob/master/docs/contributing/README.md
|
||||
|
||||
## Related Projects
|
||||
|
||||
@@ -186,12 +186,14 @@ When updating this, also update:
|
||||
|
||||
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
|
||||
least** 6 months. When increasing the MSRV, the new Rust version must have been
|
||||
released at least six months ago. The current MSRV is 1.63.
|
||||
released at least six months ago. The current MSRV is 1.71.
|
||||
|
||||
Note that the MSRV is not increased automatically, and only as part of a minor
|
||||
release. The MSRV history for past minor releases can be found below:
|
||||
|
||||
* 1.30 to now - Rust 1.63
|
||||
* 1.48 to now - Rust 1.71
|
||||
* 1.39 to 1.47 - Rust 1.70
|
||||
* 1.30 to 1.38 - Rust 1.63
|
||||
* 1.27 to 1.29 - Rust 1.56
|
||||
* 1.17 to 1.26 - Rust 1.49
|
||||
* 1.15 to 1.16 - Rust 1.46
|
||||
@@ -204,8 +206,8 @@ works with the MSRV of that minor release.
|
||||
|
||||
## Release schedule
|
||||
|
||||
Tokio doesn't follow a fixed release schedule, but we typically make one to two
|
||||
new minor releases each month. We make patch releases for bugfixes as necessary.
|
||||
Tokio doesn't follow a fixed release schedule, but we typically make one minor
|
||||
release each month. We make patch releases for bugfixes as necessary.
|
||||
|
||||
## Bug patching policy
|
||||
|
||||
@@ -215,18 +217,18 @@ warrants a patch release with a fix for the bug, it will be backported and
|
||||
released as a new patch release for each LTS minor version. Our current LTS
|
||||
releases are:
|
||||
|
||||
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
|
||||
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
|
||||
* `1.43.x` - LTS release until March 2026. (MSRV 1.70)
|
||||
* `1.47.x` - LTS release until September 2026. (MSRV 1.70)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
use an LTS release.
|
||||
|
||||
To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.32.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.43.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.32", features = [...] }
|
||||
tokio = { version = "~1.43", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -236,6 +238,9 @@ tokio = { version = "~1.32", features = [...] }
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
* `1.25.x` - LTS release until March 2024.
|
||||
* `1.32.x` - LTS release until September 2024.
|
||||
* `1.36.x` - LTS release until March 2025.
|
||||
* `1.38.x` - LTS release until July 2025.
|
||||
|
||||
## License
|
||||
|
||||
@@ -246,5 +251,5 @@ This project is licensed under the [MIT license].
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Tokio by you shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
|
||||
+12
-4
@@ -3,6 +3,7 @@ name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[features]
|
||||
test-util = ["tokio/test-util"]
|
||||
@@ -10,13 +11,12 @@ test-util = ["tokio/test-util"]
|
||||
[dependencies]
|
||||
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
criterion = "0.5.1"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
num_cpus = "1.16.0"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
|
||||
tokio-stream = { path = "../tokio-stream" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.42"
|
||||
@@ -95,3 +95,11 @@ harness = false
|
||||
name = "time_timeout"
|
||||
path = "time_timeout.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "spawn_blocking"
|
||||
path = "spawn_blocking.rs"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ impl SlowHddWriter {
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
let service_res = self.as_mut().service_write(cx);
|
||||
|
||||
if service_res.is_pending() && self.blocking_rng.gen_bool(PROBABILITY_FLUSH_WAIT) {
|
||||
if service_res.is_pending() && self.blocking_rng.random_bool(PROBABILITY_FLUSH_WAIT) {
|
||||
return Poll::Pending;
|
||||
}
|
||||
let available = self.buffer_size - self.buffer_used;
|
||||
@@ -145,7 +145,7 @@ impl ChunkReader {
|
||||
fn new(chunk_size: usize, service_interval: Duration) -> Self {
|
||||
let mut service_intervals = interval(service_interval);
|
||||
service_intervals.set_missed_tick_behavior(MissedTickBehavior::Burst);
|
||||
let data: Vec<u8> = std::iter::repeat(0).take(chunk_size).collect();
|
||||
let data: Vec<u8> = std::iter::repeat_n(0, chunk_size).collect();
|
||||
Self {
|
||||
data,
|
||||
service_intervals,
|
||||
|
||||
+19
-14
@@ -10,10 +10,21 @@ async fn work() -> usize {
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
fn single_rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn multi_rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = single_rt();
|
||||
|
||||
c.bench_function("basic_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
@@ -26,9 +37,7 @@ fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = single_rt();
|
||||
|
||||
c.bench_function("basic_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
@@ -46,10 +55,8 @@ fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = multi_rt();
|
||||
|
||||
c.bench_function("threaded_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
@@ -61,10 +68,8 @@ fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = multi_rt();
|
||||
|
||||
c.bench_function("threaded_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//! Benchmark spawn_blocking at different concurrency levels on the multi-threaded scheduler.
|
||||
//!
|
||||
//! For each parallelism level N (1, 2, 4, 8, 16, 32, 64, capped at available parallelism):
|
||||
//! - Spawns N regular async tasks
|
||||
//! - Each task spawns M batches of B spawn_blocking tasks (no-ops)
|
||||
//! - Each batch is awaited to completion before starting the next
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use tokio::runtime::{self, Runtime};
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
/// Number of batches per task
|
||||
const NUM_BATCHES: usize = 100;
|
||||
/// Number of spawn_blocking calls per batch
|
||||
const BATCH_SIZE: usize = 16;
|
||||
|
||||
fn spawn_blocking_concurrency(c: &mut Criterion) {
|
||||
let max_parallelism = std::thread::available_parallelism()
|
||||
.map(|p| p.get())
|
||||
.unwrap_or(1);
|
||||
|
||||
let parallelism_levels: Vec<usize> = [1, 2, 4, 8, 16, 32, 64]
|
||||
.into_iter()
|
||||
.filter(|&n| n <= max_parallelism)
|
||||
.collect();
|
||||
|
||||
let mut group = c.benchmark_group("spawn_blocking");
|
||||
|
||||
for num_tasks in parallelism_levels {
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("concurrency", num_tasks),
|
||||
&num_tasks,
|
||||
|b, &num_tasks| {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
let mut tasks = JoinSet::new();
|
||||
|
||||
for _ in 0..num_tasks {
|
||||
tasks.spawn(async {
|
||||
for _ in 0..NUM_BATCHES {
|
||||
let mut batch = JoinSet::new();
|
||||
|
||||
for _ in 0..BATCH_SIZE {
|
||||
batch.spawn_blocking(|| black_box(0));
|
||||
}
|
||||
|
||||
batch.join_all().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tasks.join_all().await;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
criterion_group!(spawn_blocking_benches, spawn_blocking_concurrency);
|
||||
|
||||
criterion_main!(spawn_blocking_benches);
|
||||
@@ -17,7 +17,7 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
let _ = write!(&mut message, " {i}={}", rng.random::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
|
||||
@@ -37,7 +37,7 @@ fn create_medium<const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn send_data<T: Default, const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>, prefix: &str) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function(format!("{}_{}", prefix, SIZE), |b| {
|
||||
g.bench_function(format!("{prefix}_{SIZE}"), |b| {
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<T>(SIZE);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
let _ = write!(&mut message, " {i}={}", rng.random::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# https://embarkstudios.github.io/cargo-deny/cli/init.html
|
||||
|
||||
[graph]
|
||||
all-features = true
|
||||
|
||||
[licenses]
|
||||
allow = [
|
||||
"MIT",
|
||||
"Apache-2.0",
|
||||
]
|
||||
exceptions = [
|
||||
{ allow = ["Unicode-3.0"], crate = "unicode-ident" },
|
||||
]
|
||||
|
||||
[bans]
|
||||
multiple-versions = "allow"
|
||||
wildcards = "deny"
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
@@ -0,0 +1,46 @@
|
||||
# Contributing
|
||||
|
||||
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.
|
||||
|
||||
## Quick start
|
||||
|
||||
If you are unsure where to begin, use the following guides:
|
||||
|
||||
- Want to report or triage a bug? Start with [Contributing in Issues](contributing-in-issues.md).
|
||||
- Looking for something to work on? Filter issues by [`E-help-wanted`](https://github.com/tokio-rs/tokio/labels/E-help-wanted).
|
||||
- Planning to submit a PR? Read [Pull Requests](pull-requests.md) for the full workflow and required checks.
|
||||
- Want to understand what the labels on issues mean? See [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md).
|
||||
- Interested in code review? See [Reviewing Pull Requests](reviewing-pull-requests.md).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Contributing in Issues](contributing-in-issues.md)
|
||||
- [Asking for General Help](contributing-in-issues.md#asking-for-general-help)
|
||||
- [Submitting a Bug Report](contributing-in-issues.md#submitting-a-bug-report)
|
||||
- [Triaging a Bug Report](contributing-in-issues.md#triaging-a-bug-report)
|
||||
- [Resolving a Bug Report](contributing-in-issues.md#resolving-a-bug-report)
|
||||
- [Pull Requests](pull-requests.md)
|
||||
- [Cargo Commands](pull-requests.md#cargo-commands)
|
||||
- [Performing spellcheck on tokio codebase](pull-requests.md#performing-spellcheck-on-tokio-codebase)
|
||||
- [Tests](pull-requests.md#tests)
|
||||
- [Integration tests](pull-requests.md#integration-tests)
|
||||
- [Fuzz tests](pull-requests.md#fuzz-tests)
|
||||
- [Documentation tests](pull-requests.md#documentation-tests)
|
||||
- [Benchmarks](pull-requests.md#benchmarks)
|
||||
- [Commits](pull-requests.md#commits)
|
||||
- [Commit message guidelines](pull-requests.md#commit-message-guidelines)
|
||||
- [Opening the Pull Request](pull-requests.md#opening-the-pull-request)
|
||||
- [Discuss and update](pull-requests.md#discuss-and-update)
|
||||
- [Commit Squashing](pull-requests.md#commit-squashing)
|
||||
- [Reviewing Pull Requests](reviewing-pull-requests.md)
|
||||
- [Review a bit at a time](reviewing-pull-requests.md#review-a-bit-at-a-time)
|
||||
- [Be aware of the person behind the code](reviewing-pull-requests.md#be-aware-of-the-person-behind-the-code)
|
||||
- [Abandoned or Stalled Pull Requests](reviewing-pull-requests.md#abandoned-or-stalled-pull-requests)
|
||||
- [How to specify crates dependencies versions](how-to-specify-crates-dependencies-versions.md)
|
||||
- [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md)
|
||||
- [Area](keeping-track-of-issues-and-prs.md#area)
|
||||
- [Category](keeping-track-of-issues-and-prs.md#category)
|
||||
- [Calls for participation](keeping-track-of-issues-and-prs.md#calls-for-participation)
|
||||
- [Module](keeping-track-of-issues-and-prs.md#module)
|
||||
- [Topic](keeping-track-of-issues-and-prs.md#topic)
|
||||
@@ -0,0 +1,79 @@
|
||||
## 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 discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
|
||||
issue tracker][issue] 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.
|
||||
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues
|
||||
|
||||
**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 a discussion] 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.
|
||||
|
||||
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new/choose
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, you will be presented
|
||||
with a basic 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
|
||||
|
||||
### 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.
|
||||
@@ -0,0 +1,31 @@
|
||||
# How to specify crates dependencies versions
|
||||
|
||||
Each crate (e.g., `tokio-util`, `tokio-stream`, etc.) should specify dependencies
|
||||
according to these rules:
|
||||
|
||||
1. The listed version should be the oldest version that the crate works with
|
||||
(e.g., if `tokio-util` works with `tokio` version `1.44` but not `1.43`, then
|
||||
`tokio-util` should specify version `1.44` for its `tokio` dependency).
|
||||
We don't require users to use the latest version unnecessarily.
|
||||
2. When a crate starts using a newer feature in a dependency, the version
|
||||
should be bumped to the version that introduced it.
|
||||
3. If a crate depends on an unreleased feature in a dependency, it may use
|
||||
`path = ` dependency to specify this. Since path dependencies must be removed
|
||||
during the release of the crate, this ensures that it can't be released until
|
||||
the dependency has a new version.
|
||||
|
||||
Consider the following example from `tokio-stream`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.11"
|
||||
tokio = { path = "../tokio", features = ["sync"] }
|
||||
```
|
||||
|
||||
In this case, local development of `tokio-stream` uses the local version
|
||||
of `tokio` via the `path` dependency. This means that it's currently not
|
||||
possible to release `tokio-stream`. Once a new version of `tokio` is
|
||||
released, the path dependency will be removed from `tokio-stream`.
|
||||
As mentioned before, this version should only be bumped when adding a new
|
||||
feature in the crate that relies on a newer version.
|
||||
@@ -0,0 +1,89 @@
|
||||
## Keeping track of issues and PRs
|
||||
|
||||
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
|
||||
section explains the meaning of various labels, as well as our [GitHub
|
||||
project][project]. The section is primarily targeted at maintainers. Most
|
||||
contributors aren't able to set these labels.
|
||||
|
||||
### Area
|
||||
|
||||
The area label describes the crates relevant to this issue or PR.
|
||||
|
||||
- **A-ci** This issue concerns our GitHub Actions setup.
|
||||
- **A-tokio** This issue concerns the main Tokio crate.
|
||||
- **A-readme** This issue is related to documentation such as README.md.
|
||||
- **A-benches** This issue concerns the benchmarks.
|
||||
- **A-examples** This issue concerns the examples.
|
||||
- **A-tokio-test** The issue concerns the `tokio-test` crate.
|
||||
- **A-tokio-util** This issue concerns the `tokio-util` crate.
|
||||
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
|
||||
be used for the procedural macros, and not `join!` or `select!`.
|
||||
- **A-tokio-stream** This issue concerns the `tokio-stream` crate.
|
||||
|
||||
### Category
|
||||
|
||||
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
|
||||
- **C-enhancement** This is a PR that adds a new features.
|
||||
- **C-maintenance** This is an issue or PR about stuff such as documentation,
|
||||
GitHub Actions or code quality.
|
||||
- **C-feature-request** This is a feature request. Implementations of feature
|
||||
requests use `C-enhancement` instead.
|
||||
- **C-feature-accepted** If you submit a PR for this feature request, we won't
|
||||
close it with the reason "we don't want this". Issues with this label should
|
||||
also have the `C-feature-request` label.
|
||||
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
|
||||
world"
|
||||
- **C-proposal** A proposal of some kind, and a request for comments.
|
||||
- **C-question** A user question. Large overlap with GitHub discussions.
|
||||
- **C-request** A non-feature request, e.g. "please add deprecation notices to
|
||||
`-alpha.*` versions of crates"
|
||||
|
||||
### Calls for participation
|
||||
|
||||
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
|
||||
or `C-feature-accepted`.
|
||||
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
|
||||
can do after reading the tutorial on our website.
|
||||
- **E-medium** This is not `E-easy` or `E-hard`.
|
||||
- **E-hard** This either involves very tricky code, is something we don't know
|
||||
how to solve, or is challenging for some other reason.
|
||||
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
|
||||
example.
|
||||
|
||||
The "E-" prefix is the same as used in the Rust compiler repository. Some
|
||||
issues are missing a difficulty rating, but feel free to ask on our Discord
|
||||
server if you want to know how challenging an issue likely is.
|
||||
|
||||
### Module
|
||||
|
||||
The module label provides a more fine grained categorization than **Area**.
|
||||
|
||||
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
|
||||
- **M-codec** The `tokio_util::codec` module.
|
||||
- **M-compat** The `tokio_util::compat` module.
|
||||
- **M-coop** Things relevant to coop.
|
||||
- **M-fs** The `tokio::fs` module.
|
||||
- **M-io** The `tokio::io` module.
|
||||
- **M-macros** Issues about any kind of macro.
|
||||
- **M-metrics** Things relevant to `tokio::runtime::metrics`.
|
||||
- **M-net** The `tokio::net` module.
|
||||
- **M-process** The `tokio::process` module.
|
||||
- **M-runtime** The `tokio::runtime` module.
|
||||
- **M-signal** The `tokio::signal` module.
|
||||
- **M-sync** The `tokio::sync` module.
|
||||
- **M-task** The `tokio::task` module.
|
||||
- **M-time** The `tokio::time` module.
|
||||
- **M-tracing** Tracing support in Tokio.
|
||||
- **M-taskdump** Things relevant to taskdump.
|
||||
|
||||
### Topic
|
||||
|
||||
Some extra information.
|
||||
|
||||
- **T-docs** This is about documentation.
|
||||
- **T-performance** This is about performance.
|
||||
- **T-v0.1.x** This is about old Tokio.
|
||||
|
||||
Any label not listed here is not in active use.
|
||||
|
||||
[project]: https://github.com/orgs/tokio-rs/projects/1
|
||||
@@ -0,0 +1,346 @@
|
||||
## 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.
|
||||
|
||||
### Cargo Commands
|
||||
|
||||
Due to the extensive use of features in Tokio, you will often need to add extra
|
||||
arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
**NOTE**: there are some features that are not supported in every system, so you might
|
||||
need to specify which features you want to pass to cargo (e.g., `cargo check --features=full,io-uring`)
|
||||
|
||||
Ideally, you should use the same version of clippy as the one used in CI
|
||||
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
|
||||
might have new lints:
|
||||
|
||||
[ci.yml]: ../../.github/workflows/ci.yml
|
||||
|
||||
<!--
|
||||
When updating this, also update:
|
||||
- .github/workflows/ci.yml
|
||||
- README.md
|
||||
- tokio/README.md
|
||||
- tokio/Cargo.toml
|
||||
- tokio-util/Cargo.toml
|
||||
- tokio-test/Cargo.toml
|
||||
- tokio-stream/Cargo.toml
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.88 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation, a simple `cargo doc` is not sufficient. To produce
|
||||
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
|
||||
docs, please use:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
|
||||
```
|
||||
|
||||
This turns on indicators to display the Cargo features required for
|
||||
conditionally compiled APIs in Tokio, and it enables documentation of unstable
|
||||
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
|
||||
*and* rustc.
|
||||
|
||||
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
|
||||
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
|
||||
as docs.rs itself does.
|
||||
|
||||
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
|
||||
|
||||
```
|
||||
cargo install --locked cargo-docs-rs
|
||||
cargo +nightly docs-rs [--open]
|
||||
```
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
|
||||
You can run loom tests with
|
||||
```
|
||||
cd tokio # tokio crate in workspace
|
||||
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
|
||||
run loom tests that test unstable features.
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance" \
|
||||
cargo +nightly miri test --features full --lib --tests
|
||||
```
|
||||
|
||||
### Performing spellcheck on tokio codebase
|
||||
|
||||
You can perform a spell-check on the Tokio codebase. For details of how to use the spellcheck tool, feel free to visit
|
||||
https://github.com/drahnr/cargo-spellcheck
|
||||
```
|
||||
# First install the spell-check plugin
|
||||
cargo install --locked cargo-spellcheck
|
||||
|
||||
# Then run the cargo spell check command
|
||||
cargo spellcheck check
|
||||
```
|
||||
|
||||
If the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
|
||||
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
|
||||
the total number of words included in the file
|
||||
|
||||
### 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][integration-tests]
|
||||
and [documentation tests][documentation-tests].
|
||||
(Tokio avoids [unit tests][unit-tests] as much as possible).
|
||||
|
||||
Tokio uses [conditional compilation attributes][conditional-compilation]
|
||||
throughout the codebase, to modify rustc's behavior. Code marked with such
|
||||
attributes can be enabled using `RUSTFLAGS` and `RUSTDOCFLAGS` environment
|
||||
variables. One of the most prevalent flags passed in these variables is
|
||||
the `--cfg` option. To run tests in a particular file, check first what
|
||||
options #![cfg] declaration defines for that file.
|
||||
|
||||
For instance, to run a test marked with the 'tokio_unstable' cfg option,
|
||||
you must pass this flag to the compiler when running the test.
|
||||
```
|
||||
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
#### Fuzz tests
|
||||
|
||||
Some of our crates include a set of fuzz tests, this will be marked by a
|
||||
directory `fuzz`. It is a good idea to run fuzz tests after each change.
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install --locked cargo-fuzz`
|
||||
|
||||
To list the available fuzzing harnesses you can run;
|
||||
|
||||
```bash
|
||||
$ cd tokio
|
||||
$ cargo fuzz list
|
||||
fuzz_linked_list
|
||||
```
|
||||
|
||||
Running a fuzz test is as simple as;
|
||||
|
||||
`cargo fuzz run fuzz_linked_list`
|
||||
|
||||
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
|
||||
harness will run forever and will only exit if you `ctrl-c` or it finds
|
||||
a bug.
|
||||
|
||||
#### 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::time::timeout` provides a good example of a
|
||||
documentation test:
|
||||
|
||||
```
|
||||
/// Create a new `Timeout` set to expire in 10 milliseconds.
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::timeout;
|
||||
/// use tokio::sync::oneshot;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
/// # tx.send(()).unwrap();
|
||||
///
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// if let Err(_) = timeout(Duration::from_millis(10), rx).await {
|
||||
/// println!("did not receive value within 10 ms");
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
```
|
||||
|
||||
Lines that start with `/// #` are removed when the documentation is generated.
|
||||
|
||||
### Benchmarks
|
||||
|
||||
You can run benchmarks locally for the changes you've made to the tokio codebase.
|
||||
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
|
||||
against the changes you have made, for example, you can run;
|
||||
|
||||
```bash
|
||||
cd benches
|
||||
|
||||
# Run all benchmarks.
|
||||
cargo bench
|
||||
|
||||
# Run all tests in the `benches/fs.rs` file
|
||||
cargo bench --bench fs
|
||||
|
||||
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
|
||||
cargo bench async_read_buf
|
||||
|
||||
# After running benches, you can check the statistics under `tokio/target/criterion/`
|
||||
```
|
||||
|
||||
You can also refer to [Criterion] docs for additional options and details.
|
||||
|
||||
[Criterion]: https://docs.rs/criterion/latest/criterion/
|
||||
|
||||
### 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-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
|
||||
* start with an imperative verb
|
||||
* not have a period at the end
|
||||
* be prefixed with the name of the module being changed; usually this is the
|
||||
same as the M-* label on the PR
|
||||
|
||||
Examples:
|
||||
|
||||
* time: introduce `Timeout` and deprecate `Deadline`
|
||||
* codec: export `Encoder`, `Decoder`, `Framed*`
|
||||
* ci: fix the FreeBSD ci configuration
|
||||
|
||||
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
|
||||
module: 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](#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.
|
||||
|
||||
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
|
||||
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
|
||||
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
@@ -0,0 +1,80 @@
|
||||
## 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
|
||||
+19
-9
@@ -3,6 +3,7 @@ name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead, and delete the **path**.
|
||||
@@ -15,34 +16,40 @@ tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.1", default-features = false, features = ["fmt", "ansi", "env-filter", "tracing-log"] }
|
||||
bytes = "1.0.0"
|
||||
futures = { version = "0.3.0", features = ["thread-pool"]}
|
||||
http = "0.2"
|
||||
http = "1"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
httparse = "1.0"
|
||||
httpdate = "1.0"
|
||||
once_cell = "1.5.2"
|
||||
rand = "0.8.3"
|
||||
|
||||
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing", "taskdump"] }
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
version = "0.61"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
path = "chat.rs"
|
||||
|
||||
[[example]]
|
||||
name = "connect"
|
||||
path = "connect.rs"
|
||||
name = "connect-tcp"
|
||||
path = "connect-tcp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "connect-udp"
|
||||
path = "connect-udp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-tcp"
|
||||
path = "echo-tcp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-udp"
|
||||
path = "echo-udp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo"
|
||||
path = "echo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "hello_world.rs"
|
||||
@@ -94,3 +101,6 @@ path = "named-pipe-multi-client.rs"
|
||||
[[example]]
|
||||
name = "dump"
|
||||
path = "dump.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ cargo run --example $name
|
||||
```
|
||||
|
||||
A good starting point for the examples would be [`hello_world`](hello_world.rs)
|
||||
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
|
||||
and [`echo-tcp`](echo-tcp.rs). Additionally [the tokio website][tokioweb] contains
|
||||
additional guides for some of the examples.
|
||||
|
||||
For a larger "real world" example, see the [`mini-redis`][redis] repository.
|
||||
|
||||
+35
-22
@@ -39,6 +39,8 @@ use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
const DEFAULT_ADDR: &str = "127.0.0.1:6142";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter};
|
||||
@@ -70,14 +72,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:6142".to_string());
|
||||
.unwrap_or_else(|| DEFAULT_ADDR.to_string());
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
tracing::info!("server running on {}", addr);
|
||||
tracing::info!("server running on {addr}");
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound TcpStream.
|
||||
@@ -88,9 +90,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// Spawn our handler to be run asynchronously.
|
||||
tokio::spawn(async move {
|
||||
tracing::debug!("accepted connection");
|
||||
tracing::debug!("accepted connection from {addr}");
|
||||
if let Err(e) = process(state, stream, addr).await {
|
||||
tracing::info!("an error occurred; error = {:?}", e);
|
||||
tracing::warn!("Connection from {addr} failed: {e:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -138,12 +140,24 @@ impl Shared {
|
||||
|
||||
/// Send a `LineCodec` encoded message to every peer, except
|
||||
/// for the sender.
|
||||
///
|
||||
/// This function also cleans up disconnected peers automatically.
|
||||
async fn broadcast(&mut self, sender: SocketAddr, message: &str) {
|
||||
for peer in self.peers.iter_mut() {
|
||||
if *peer.0 != sender {
|
||||
let _ = peer.1.send(message.into());
|
||||
let mut failed_peers = Vec::new();
|
||||
let message = message.to_string(); // Clone once for all sends
|
||||
|
||||
for (addr, tx) in self.peers.iter() {
|
||||
if *addr != sender && tx.send(message.clone()).is_err() {
|
||||
// Receiver has been dropped, mark for removal
|
||||
failed_peers.push(*addr);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up disconnected peers
|
||||
for addr in failed_peers {
|
||||
self.peers.remove(&addr);
|
||||
tracing::debug!("Removed disconnected peer: {addr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,13 +192,10 @@ async fn process(
|
||||
lines.send("Please enter your username:").await?;
|
||||
|
||||
// Read the first line from the `LineCodec` stream to get the username.
|
||||
let username = match lines.next().await {
|
||||
Some(Ok(line)) => line,
|
||||
let Some(Ok(username)) = lines.next().await else {
|
||||
// We didn't get a line so we return early here.
|
||||
_ => {
|
||||
tracing::error!("Failed to get username from {}. Client disconnected.", addr);
|
||||
return Ok(());
|
||||
}
|
||||
tracing::error!("Failed to get username from {addr}. Client disconnected.");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Register our peer with state which internally sets up some channels.
|
||||
@@ -193,8 +204,8 @@ async fn process(
|
||||
// A client has connected, let's let everyone know.
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{} has joined the chat", username);
|
||||
tracing::info!("{}", msg);
|
||||
let msg = format!("{username} has joined the chat");
|
||||
tracing::info!("{msg}");
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
@@ -203,24 +214,26 @@ async fn process(
|
||||
tokio::select! {
|
||||
// A message was received from a peer. Send it to the current user.
|
||||
Some(msg) = peer.rx.recv() => {
|
||||
peer.lines.send(&msg).await?;
|
||||
if let Err(e) = peer.lines.send(&msg).await {
|
||||
tracing::error!("Failed to send message to {username}: {e:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = peer.lines.next() => match result {
|
||||
// A message was received from the current user, we should
|
||||
// broadcast this message to the other users.
|
||||
Some(Ok(msg)) => {
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{}: {}", username, msg);
|
||||
let msg = format!("{username}: {msg}");
|
||||
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
// An error occurred.
|
||||
Some(Err(e)) => {
|
||||
tracing::error!(
|
||||
"an error occurred while processing messages for {}; error = {:?}",
|
||||
username,
|
||||
e
|
||||
"an error occurred while processing messages for {username}; error = {e:?}"
|
||||
);
|
||||
break;
|
||||
}
|
||||
// The stream has been exhausted.
|
||||
None => break,
|
||||
@@ -234,8 +247,8 @@ async fn process(
|
||||
let mut state = state.lock().await;
|
||||
state.peers.remove(&addr);
|
||||
|
||||
let msg = format!("{} has left the chat", username);
|
||||
tracing::info!("{}", msg);
|
||||
let msg = format!("{username} has left the chat");
|
||||
tracing::info!("{msg}");
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//! An example of hooking up stdin/stdout to a TCP 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. Each line entered on stdin will be translated to a TCP
|
||||
//! packet which is then 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.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{stdin, stdout};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse what address we're going to connect to
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
|
||||
|
||||
connect(&addr, stdin, stdout).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; error={e}");
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
tokio::select! {
|
||||
r = sink.send_all(&mut stdin) => r?,
|
||||
r = stdout.send_all(&mut stream) => r?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//! An example of hooking up stdin/stdout to a 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. Each line entered on stdin will be translated to a UDP
|
||||
//! packet which is then 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.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{stdin, stdout};
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse what address we're going to connect to
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
|
||||
|
||||
connect(&addr, stdin, stdout).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
let bind_addr = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
};
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
|
||||
tokio::select! {
|
||||
r = send(stdin, &socket) => r?,
|
||||
r = recv(stdout, &socket) => r?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
writer: &UdpSocket,
|
||||
) -> Result<(), std::io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
writer.send(&buf[..]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
reader: &UdpSocket,
|
||||
) -> Result<(), std::io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(Bytes::copy_from_slice(&buf[..n])).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +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.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures::StreamExt;
|
||||
use tokio::io;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// 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()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(io::stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(io::stdout(), BytesCodec::new());
|
||||
|
||||
if tcp {
|
||||
tcp::connect(&addr, stdin, stdout).await?;
|
||||
} else {
|
||||
udp::connect(&addr, stdin, stdout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod tcp {
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::{error::Error, io, net::SocketAddr};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={}", e);
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
|
||||
(Err(e), _) | (_, Err(e)) => Err(e.into()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use bytes::Bytes;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
let bind_addr = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
};
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
|
||||
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
writer: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
writer.send(&buf[..]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
reader: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(Bytes::from(buf)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
@@ -82,7 +81,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
#[cfg(not(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
)))]
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
//!
|
||||
//! To see this server in action, you can run this in one terminal:
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//! cargo run --example echo-tcp
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 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
|
||||
//! Each line you type in to the `connect-tcp` terminal should be echo'd back to
|
||||
//! you! If you open up multiple terminals running the `connect-tcp` example you
|
||||
//! should be able to see them all make progress simultaneously.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
@@ -27,6 +27,9 @@ use tokio::net::TcpListener;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
||||
const DEFAULT_ADDR: &str = "127.0.0.1:8080";
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
@@ -34,17 +37,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
.unwrap_or_else(|| DEFAULT_ADDR.to_string());
|
||||
|
||||
// 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.
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
println!("Listening on: {addr}");
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
let (mut socket, addr) = listener.accept().await?;
|
||||
|
||||
// And this is where much of the magic of this server happens. We
|
||||
// crucially want all clients to make progress concurrently, rather than
|
||||
@@ -55,23 +58,27 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// which will allow all of our clients to be processed concurrently.
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = vec![0; 1024];
|
||||
let mut buf = vec![0; BUFFER_SIZE];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
let n = socket
|
||||
.read(&mut buf)
|
||||
.await
|
||||
.expect("failed to read data from socket");
|
||||
|
||||
if n == 0 {
|
||||
return;
|
||||
match socket.read(&mut buf).await {
|
||||
Ok(0) => {
|
||||
// Connection closed by peer
|
||||
return;
|
||||
}
|
||||
Ok(n) => {
|
||||
// Write the data back. If writing fails, log the error and exit.
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
eprintln!("Failed to write to socket {}: {}", addr, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read from socket {}: {}", addr, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
socket
|
||||
.write_all(&buf[0..n])
|
||||
.await
|
||||
.expect("failed to write data to socket");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect -- --udp 127.0.0.1:8080
|
||||
//! cargo run --example connect-udp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `nc` terminal should be echo'd back to you!
|
||||
//! Each line you type in to the `connect-udp` terminal should be echo'd back to you!
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
@@ -38,7 +38,7 @@ impl Server {
|
||||
if let Some((size, peer)) = to_send {
|
||||
let amt = socket.send_to(&buf[..size], &peer).await?;
|
||||
|
||||
println!("Echoed {}/{} bytes to {}", amt, size, peer);
|
||||
println!("Echoed {amt}/{size} bytes to {peer}");
|
||||
}
|
||||
|
||||
// If we're here then `to_send` is `None`, so we take a look for the
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be written to terminal!
|
||||
//! Each line you type in to the `connect-tcp` terminal should be written to terminal!
|
||||
//!
|
||||
//! Minimal js example:
|
||||
//!
|
||||
@@ -75,7 +75,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
println!("Listening on: {addr}");
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
@@ -96,8 +96,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// The stream will return None once the client disconnects.
|
||||
while let Some(message) = framed.next().await {
|
||||
match message {
|
||||
Ok(bytes) => println!("bytes: {:?}", bytes),
|
||||
Err(err) => println!("Socket closed with error: {:?}", err),
|
||||
Ok(bytes) => println!("bytes: {bytes:?}"),
|
||||
Err(err) => println!("Socket closed with error: {err:?}"),
|
||||
}
|
||||
}
|
||||
println!("Socket received FIN packet and closed connection");
|
||||
|
||||
+5
-5
@@ -11,11 +11,11 @@
|
||||
//!
|
||||
//! This in another terminal
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//! cargo run --example echo-tcp
|
||||
//!
|
||||
//! And finally this in another terminal
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8081
|
||||
//! cargo run --example connect-tcp 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.
|
||||
@@ -38,8 +38,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.nth(2)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
println!("Listening on: {listen_addr}");
|
||||
println!("Proxying to: {server_addr}");
|
||||
|
||||
let listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
@@ -50,7 +50,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
copy_bidirectional(&mut inbound, &mut outbound)
|
||||
.map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
println!("Failed to transfer; error={e}");
|
||||
}
|
||||
})
|
||||
.await
|
||||
|
||||
+12
-12
@@ -12,14 +12,14 @@
|
||||
//!
|
||||
//! and next in another windows run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! In the `connect` window you can type in commands where when you hit enter
|
||||
//! In the `connect-tcp` 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
|
||||
//! $ cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//! GET foo
|
||||
//! foo = bar
|
||||
//! GET FOOBAR
|
||||
@@ -90,7 +90,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
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`
|
||||
@@ -131,11 +131,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let response = response.serialize();
|
||||
|
||||
if let Err(e) = lines.send(response.as_str()).await {
|
||||
println!("error on sending response; error = {:?}", e);
|
||||
println!("error on sending response; error = {e:?}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error on decoding from socket; error = {:?}", e);
|
||||
println!("error on decoding from socket; error = {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// The connection will be closed at this point as `lines.next()` has returned `None`.
|
||||
});
|
||||
}
|
||||
Err(e) => println!("error accepting socket; error = {:?}", e),
|
||||
Err(e) => println!("error accepting socket; error = {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ fn handle_request(line: &str, db: &Arc<Database>) -> Response {
|
||||
value: value.clone(),
|
||||
},
|
||||
None => Response::Error {
|
||||
msg: format!("no key {}", key),
|
||||
msg: format!("no key {key}"),
|
||||
},
|
||||
},
|
||||
Request::Set { key, value } => {
|
||||
@@ -203,7 +203,7 @@ impl Request {
|
||||
value: value.to_string(),
|
||||
})
|
||||
}
|
||||
Some(cmd) => Err(format!("unknown command: {}", cmd)),
|
||||
Some(cmd) => Err(format!("unknown command: {cmd}")),
|
||||
None => Err("empty input".into()),
|
||||
}
|
||||
}
|
||||
@@ -212,13 +212,13 @@ impl Request {
|
||||
impl Response {
|
||||
fn serialize(&self) -> String {
|
||||
match *self {
|
||||
Response::Value { ref key, ref value } => format!("{} = {}", key, value),
|
||||
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),
|
||||
} => format!("set {key} = `{value}`, previous: {previous:?}"),
|
||||
Response::Error { ref msg } => format!("error: {msg}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-17
@@ -31,13 +31,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let server = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
println!("Listening on: {addr}");
|
||||
|
||||
loop {
|
||||
let (stream, _) = server.accept().await?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process(stream).await {
|
||||
println!("failed to process connection; error = {}", e);
|
||||
println!("failed to process connection; error = {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -82,9 +82,7 @@ async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
let response = response
|
||||
.body(body)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||
let response = response.body(body).map_err(io::Error::other)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -159,8 +157,8 @@ impl Decoder for Http {
|
||||
let mut parsed_headers = [httparse::EMPTY_HEADER; 16];
|
||||
let mut r = httparse::Request::new(&mut parsed_headers);
|
||||
let status = r.parse(src).map_err(|e| {
|
||||
let msg = format!("failed to parse http request: {:?}", e);
|
||||
io::Error::new(io::ErrorKind::Other, msg)
|
||||
let msg = format!("failed to parse http request: {e:?}");
|
||||
io::Error::other(msg)
|
||||
})?;
|
||||
|
||||
let amt = match status {
|
||||
@@ -180,8 +178,7 @@ impl Decoder for Http {
|
||||
headers[i] = Some((k, v));
|
||||
}
|
||||
|
||||
let method = http::Method::try_from(r.method.unwrap())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let method = http::Method::try_from(r.method.unwrap()).map_err(io::Error::other)?;
|
||||
|
||||
(
|
||||
method,
|
||||
@@ -191,10 +188,7 @@ impl Decoder for Http {
|
||||
)
|
||||
};
|
||||
if version != 1 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"only HTTP/1.1 accepted",
|
||||
));
|
||||
return Err(io::Error::other("only HTTP/1.1 accepted"));
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
@@ -209,13 +203,11 @@ impl Decoder for Http {
|
||||
None => break,
|
||||
};
|
||||
let value = HeaderValue::from_bytes(data.slice(v.0..v.1).as_ref())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "header decode error"))?;
|
||||
.map_err(|_| io::Error::other("header decode error"))?;
|
||||
ret = ret.header(&data[k.0..k.1], value);
|
||||
}
|
||||
|
||||
let req = ret
|
||||
.body(())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let req = ret.body(()).map_err(io::Error::other)?;
|
||||
Ok(Some(req))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// Run both futures simultaneously of `a` and `b` sending messages back and forth.
|
||||
match tokio::try_join!(a, b) {
|
||||
Err(e) => println!("an error occurred; error = {:?}", e),
|
||||
Err(e) => println!("an error occurred; error = {e:?}"),
|
||||
_ => println!("done!"),
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -8,9 +8,11 @@
|
||||
RUSTDOCFLAGS="""
|
||||
--cfg docsrs \
|
||||
--cfg tokio_unstable \
|
||||
--cfg tokio_taskdump \
|
||||
"""
|
||||
RUSTFLAGS="--cfg tokio_unstable --cfg tokio_taskdump --cfg docsrs"
|
||||
RUSTFLAGS="""
|
||||
--cfg docsrs \
|
||||
--cfg tokio_unstable
|
||||
"""
|
||||
|
||||
[[redirects]]
|
||||
from = "/"
|
||||
|
||||
+32
-4
@@ -1,4 +1,4 @@
|
||||
285
|
||||
312
|
||||
&
|
||||
+
|
||||
<
|
||||
@@ -12,12 +12,17 @@
|
||||
0xA
|
||||
0xD
|
||||
100ms
|
||||
100ns
|
||||
10ms
|
||||
10μs
|
||||
~12
|
||||
120s
|
||||
12.5%
|
||||
±1m
|
||||
±1ms
|
||||
1ms
|
||||
1s
|
||||
25%
|
||||
250ms
|
||||
2x
|
||||
~4
|
||||
@@ -25,11 +30,13 @@
|
||||
450ms
|
||||
50ms
|
||||
8MB
|
||||
ABI
|
||||
accessors
|
||||
adaptor
|
||||
adaptors
|
||||
Adaptors
|
||||
AIO
|
||||
ambiant
|
||||
ambient
|
||||
amongst
|
||||
api
|
||||
APIs
|
||||
@@ -57,21 +64,31 @@ codec
|
||||
codecs
|
||||
combinator
|
||||
combinators
|
||||
condvar
|
||||
config
|
||||
Config
|
||||
connectionless
|
||||
coroutines
|
||||
cpu
|
||||
cpus
|
||||
cqe
|
||||
CQE
|
||||
cqe's
|
||||
customizable
|
||||
Customizable
|
||||
Cygwin
|
||||
datagram
|
||||
Datagram
|
||||
datagrams
|
||||
deallocate
|
||||
deallocated
|
||||
Deallocates
|
||||
debuginfo
|
||||
decrement
|
||||
decrementing
|
||||
demangled
|
||||
dequeued
|
||||
dereferenced
|
||||
deregister
|
||||
deregistered
|
||||
deregistering
|
||||
@@ -99,6 +116,7 @@ errored
|
||||
EWMA
|
||||
expirations
|
||||
fcntl
|
||||
fd
|
||||
fd's
|
||||
FIFOs
|
||||
filename
|
||||
@@ -115,9 +133,11 @@ GID
|
||||
goroutines
|
||||
Growable
|
||||
gzip
|
||||
H2
|
||||
hashmaps
|
||||
HashMaps
|
||||
hashsets
|
||||
HdrHistogram
|
||||
ie
|
||||
Illumos
|
||||
impl
|
||||
@@ -125,6 +145,7 @@ implementers
|
||||
implementor
|
||||
implementors
|
||||
incrementing
|
||||
inlining
|
||||
interoperate
|
||||
invariants
|
||||
Invariants
|
||||
@@ -143,6 +164,7 @@ Lauck
|
||||
libc
|
||||
lifecycle
|
||||
lifo
|
||||
LLVM
|
||||
lookups
|
||||
macOS
|
||||
MacOS
|
||||
@@ -151,8 +173,10 @@ metadata
|
||||
mio
|
||||
Mio
|
||||
mio's
|
||||
miri
|
||||
misconfigured
|
||||
mock's
|
||||
monomorphization
|
||||
mpmc
|
||||
mpsc
|
||||
multi
|
||||
@@ -163,15 +187,17 @@ mut
|
||||
mutex
|
||||
Mutex
|
||||
Nagle
|
||||
namespace
|
||||
nonblocking
|
||||
nondecreasing
|
||||
noop
|
||||
ntasks
|
||||
NUMA
|
||||
ok
|
||||
oneshot
|
||||
opcode
|
||||
ORed
|
||||
os
|
||||
overweighing
|
||||
parker
|
||||
parsers
|
||||
peekable
|
||||
@@ -265,9 +291,11 @@ unparks
|
||||
Unparks
|
||||
unreceived
|
||||
unsafety
|
||||
unsets
|
||||
Unsets
|
||||
unsynchronized
|
||||
untrusted
|
||||
uring
|
||||
usecases
|
||||
Valgrind
|
||||
Varghese
|
||||
@@ -282,4 +310,4 @@ Wakers
|
||||
wakeup
|
||||
wakeups
|
||||
workstealing
|
||||
|
||||
ZST
|
||||
|
||||
@@ -3,12 +3,16 @@ name = "stress-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio/", features = ["full"] }
|
||||
tokio = { version = "1.0.0", path = "../tokio/", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
rand = "0.9"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"crt-objects-fallback": "false",
|
||||
"crt-static-respected": true,
|
||||
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
|
||||
"default-uwtable": true,
|
||||
"dynamic-linking": true,
|
||||
"env": "gnu",
|
||||
"has-rpath": true,
|
||||
@@ -12,10 +13,10 @@
|
||||
"llvm-target": "i686-unknown-linux-gnu",
|
||||
"max-atomic-width": 32,
|
||||
"metadata": {
|
||||
"description": null,
|
||||
"host_tools": null,
|
||||
"std": null,
|
||||
"tier": null
|
||||
"description": "32-bit Linux (kernel 3.2, glibc 2.17+)",
|
||||
"host_tools": true,
|
||||
"std": true,
|
||||
"tier": 1
|
||||
},
|
||||
"os": "linux",
|
||||
"position-independent-executables": true,
|
||||
@@ -28,6 +29,7 @@
|
||||
]
|
||||
},
|
||||
"relro-level": "full",
|
||||
"rustc-abi": "x86-sse2",
|
||||
"stack-probes": {
|
||||
"kind": "inline"
|
||||
},
|
||||
@@ -42,5 +44,5 @@
|
||||
"target-family": [
|
||||
"unix"
|
||||
],
|
||||
"target-pointer-width": "32"
|
||||
"target-pointer-width": 32
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "tests-build"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
@@ -10,7 +11,10 @@ full = ["tokio/full"]
|
||||
rt = ["tokio/rt", "tokio/macros"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", optional = true }
|
||||
tokio = { version = "1.0.0", path = "../tokio", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -6,5 +6,5 @@ To run all of the tests in this directory, run the following commands:
|
||||
cargo test --features full
|
||||
cargo test --features rt
|
||||
```
|
||||
If one of the tests fail, you can pass `TRYBUILD=overwrite` to the `cargo test`
|
||||
If any of the tests fail, you can pass `TRYBUILD=overwrite` to the `cargo test`
|
||||
command that failed to have it regenerate the test output.
|
||||
|
||||
@@ -41,6 +41,9 @@ async fn test_crate_not_path_int() {}
|
||||
#[tokio::test(crate = "456")]
|
||||
async fn test_crate_not_path_invalid() {}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", unhandled_panic = "shutdown_runtime")]
|
||||
async fn test_multi_thread_with_unhandled_panic() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[test]
|
||||
async fn test_has_second_test_attr() {}
|
||||
|
||||
@@ -1,115 +1,121 @@
|
||||
error: the `async` keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:6:1
|
||||
--> tests/fail/macros_invalid_input.rs:6:1
|
||||
|
|
||||
6 | fn main_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
|
||||
--> $DIR/macros_invalid_input.rs:8:15
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
|
||||
--> tests/fail/macros_invalid_input.rs:8:15
|
||||
|
|
||||
8 | #[tokio::main(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Must have specified ident
|
||||
--> $DIR/macros_invalid_input.rs:11:15
|
||||
--> tests/fail/macros_invalid_input.rs:11:15
|
||||
|
|
||||
11 | #[tokio::main(threadpool::bar)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: the `async` keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:15:1
|
||||
--> tests/fail/macros_invalid_input.rs:15:1
|
||||
|
|
||||
15 | fn test_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
|
||||
--> $DIR/macros_invalid_input.rs:17:15
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
|
||||
--> tests/fail/macros_invalid_input.rs:17:15
|
||||
|
|
||||
17 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
|
||||
--> $DIR/macros_invalid_input.rs:20:15
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`
|
||||
--> tests/fail/macros_invalid_input.rs:20:15
|
||||
|
|
||||
20 | #[tokio::test(foo = 123)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: Failed to parse value of `flavor` as string.
|
||||
--> $DIR/macros_invalid_input.rs:23:24
|
||||
--> tests/fail/macros_invalid_input.rs:23:24
|
||||
|
|
||||
23 | #[tokio::test(flavor = 123)]
|
||||
| ^^^
|
||||
|
||||
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
|
||||
--> $DIR/macros_invalid_input.rs:26:24
|
||||
error: No such runtime flavor `foo`. The runtime flavors are `current_thread`, `local`, and `multi_thread`.
|
||||
--> tests/fail/macros_invalid_input.rs:26:24
|
||||
|
|
||||
26 | #[tokio::test(flavor = "foo")]
|
||||
| ^^^^^
|
||||
|
||||
error: The `start_paused` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
|
||||
--> $DIR/macros_invalid_input.rs:29:55
|
||||
--> tests/fail/macros_invalid_input.rs:29:55
|
||||
|
|
||||
29 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
|
||||
| ^^^^^
|
||||
|
||||
error: Failed to parse value of `worker_threads` as integer.
|
||||
--> $DIR/macros_invalid_input.rs:32:57
|
||||
--> tests/fail/macros_invalid_input.rs:32:57
|
||||
|
|
||||
32 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
|
||||
| ^^^^^
|
||||
|
||||
error: The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[tokio::test(flavor = "multi_thread")]`
|
||||
--> $DIR/macros_invalid_input.rs:35:59
|
||||
--> tests/fail/macros_invalid_input.rs:35:59
|
||||
|
|
||||
35 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
|
||||
| ^
|
||||
|
||||
error: Failed to parse value of `crate` as path.
|
||||
--> $DIR/macros_invalid_input.rs:38:23
|
||||
--> tests/fail/macros_invalid_input.rs:38:23
|
||||
|
|
||||
38 | #[tokio::test(crate = 456)]
|
||||
| ^^^
|
||||
|
||||
error: Failed to parse value of `crate` as path: "456"
|
||||
--> $DIR/macros_invalid_input.rs:41:23
|
||||
--> tests/fail/macros_invalid_input.rs:41:23
|
||||
|
|
||||
41 | #[tokio::test(crate = "456")]
|
||||
| ^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> $DIR/macros_invalid_input.rs:45:1
|
||||
error: The `unhandled_panic` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
|
||||
--> tests/fail/macros_invalid_input.rs:44:58
|
||||
|
|
||||
45 | #[test]
|
||||
44 | #[tokio::test(flavor = "multi_thread", unhandled_panic = "shutdown_runtime")]
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> tests/fail/macros_invalid_input.rs:48:1
|
||||
|
|
||||
48 | #[test]
|
||||
| ^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> $DIR/macros_invalid_input.rs:49:1
|
||||
--> tests/fail/macros_invalid_input.rs:52:1
|
||||
|
|
||||
49 | #[::core::prelude::v1::test]
|
||||
52 | #[::core::prelude::v1::test]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> $DIR/macros_invalid_input.rs:53:1
|
||||
--> tests/fail/macros_invalid_input.rs:56:1
|
||||
|
|
||||
53 | #[core::prelude::rust_2015::test]
|
||||
56 | #[core::prelude::rust_2015::test]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> $DIR/macros_invalid_input.rs:57:1
|
||||
--> tests/fail/macros_invalid_input.rs:60:1
|
||||
|
|
||||
57 | #[::std::prelude::rust_2018::test]
|
||||
60 | #[::std::prelude::rust_2018::test]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> $DIR/macros_invalid_input.rs:61:1
|
||||
--> tests/fail/macros_invalid_input.rs:64:1
|
||||
|
|
||||
61 | #[std::prelude::rust_2021::test]
|
||||
64 | #[std::prelude::rust_2021::test]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> $DIR/macros_invalid_input.rs:64:1
|
||||
--> tests/fail/macros_invalid_input.rs:67:1
|
||||
|
|
||||
64 | #[tokio::test]
|
||||
67 | #[tokio::test]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `tokio::test` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// do not leak `RotatorSelect`
|
||||
let _ = tokio::join!(async {
|
||||
fn foo(_: impl RotatorSelect) {}
|
||||
});
|
||||
|
||||
// do not leak `std::task::Poll::Pending`
|
||||
let _ = tokio::join!(async { Pending });
|
||||
|
||||
// do not leak `std::task::Poll::Ready`
|
||||
let _ = tokio::join!(async { Ready(0) });
|
||||
|
||||
// do not leak `std::future::Future`
|
||||
let _ = tokio::join!(async {
|
||||
struct MyFuture;
|
||||
|
||||
impl Future for MyFuture {
|
||||
type Output = ();
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// do not leak `std::pin::Pin`
|
||||
let _ = tokio::join!(async {
|
||||
let mut x = 5;
|
||||
let _ = Pin::new(&mut x);
|
||||
});
|
||||
|
||||
// do not leak `std::future::poll_fn`
|
||||
let _ = tokio::join!(async {
|
||||
let _ = poll_fn(|_cx| todo!());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
error[E0405]: cannot find trait `RotatorSelect` in this scope
|
||||
--> tests/fail/macros_join.rs:7:24
|
||||
|
|
||||
7 | fn foo(_: impl RotatorSelect) {}
|
||||
| ^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `Pending` in this scope
|
||||
--> tests/fail/macros_join.rs:11:34
|
||||
|
|
||||
11 | let _ = tokio::join!(async { Pending });
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this unit variant
|
||||
|
|
||||
1 + use std::task::Poll::Pending;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
|
||||
--> tests/fail/macros_join.rs:14:34
|
||||
|
|
||||
14 | let _ = tokio::join!(async { Ready(0) });
|
||||
| ^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this tuple variant
|
||||
|
|
||||
1 + use std::task::Poll::Ready;
|
||||
|
|
||||
|
||||
error[E0405]: cannot find trait `Future` in this scope
|
||||
--> tests/fail/macros_join.rs:20:14
|
||||
|
|
||||
20 | impl Future for MyFuture {
|
||||
| ^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this trait
|
||||
|
|
||||
1 + use std::future::Future;
|
||||
|
|
||||
|
||||
error[E0433]: failed to resolve: use of undeclared type `Pin`
|
||||
--> tests/fail/macros_join.rs:35:17
|
||||
|
|
||||
35 | let _ = Pin::new(&mut x);
|
||||
| ^^^ use of undeclared type `Pin`
|
||||
|
|
||||
help: consider importing this struct
|
||||
|
|
||||
1 + use std::pin::Pin;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function `poll_fn` in this scope
|
||||
--> tests/fail/macros_join.rs:40:17
|
||||
|
|
||||
40 | let _ = poll_fn(|_cx| todo!());
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this function
|
||||
|
|
||||
1 + use std::future::poll_fn;
|
||||
|
|
||||
@@ -0,0 +1,42 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// do not leak `RotatorSelect`
|
||||
let _ = tokio::try_join!(async {
|
||||
fn foo(_: impl RotatorSelect) {}
|
||||
});
|
||||
|
||||
// do not leak `std::task::Poll::Pending`
|
||||
let _ = tokio::try_join!(async { Pending });
|
||||
|
||||
// do not leak `std::task::Poll::Ready`
|
||||
let _ = tokio::try_join!(async { Ready(0) });
|
||||
|
||||
// do not leak `std::future::Future`
|
||||
let _ = tokio::try_join!(async {
|
||||
struct MyFuture;
|
||||
|
||||
impl Future for MyFuture {
|
||||
type Output = ();
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// do not leak `std::pin::Pin`
|
||||
let _ = tokio::try_join!(async {
|
||||
let mut x = 5;
|
||||
let _ = Pin::new(&mut x);
|
||||
});
|
||||
|
||||
// do not leak `std::future::poll_fn`
|
||||
let _ = tokio::try_join!(async {
|
||||
let _ = poll_fn(|_cx| todo!());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
error[E0405]: cannot find trait `RotatorSelect` in this scope
|
||||
--> tests/fail/macros_try_join.rs:7:24
|
||||
|
|
||||
7 | fn foo(_: impl RotatorSelect) {}
|
||||
| ^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `Pending` in this scope
|
||||
--> tests/fail/macros_try_join.rs:11:38
|
||||
|
|
||||
11 | let _ = tokio::try_join!(async { Pending });
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this unit variant
|
||||
|
|
||||
1 + use std::task::Poll::Pending;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
|
||||
--> tests/fail/macros_try_join.rs:14:38
|
||||
|
|
||||
14 | let _ = tokio::try_join!(async { Ready(0) });
|
||||
| ^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this tuple variant
|
||||
|
|
||||
1 + use std::task::Poll::Ready;
|
||||
|
|
||||
|
||||
error[E0405]: cannot find trait `Future` in this scope
|
||||
--> tests/fail/macros_try_join.rs:20:14
|
||||
|
|
||||
20 | impl Future for MyFuture {
|
||||
| ^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this trait
|
||||
|
|
||||
1 + use std::future::Future;
|
||||
|
|
||||
|
||||
error[E0433]: failed to resolve: use of undeclared type `Pin`
|
||||
--> tests/fail/macros_try_join.rs:35:17
|
||||
|
|
||||
35 | let _ = Pin::new(&mut x);
|
||||
| ^^^ use of undeclared type `Pin`
|
||||
|
|
||||
help: consider importing this struct
|
||||
|
|
||||
1 + use std::pin::Pin;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function `poll_fn` in this scope
|
||||
--> tests/fail/macros_try_join.rs:40:17
|
||||
|
|
||||
40 | let _ = poll_fn(|_cx| todo!());
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this function
|
||||
|
|
||||
1 + use std::future::poll_fn;
|
||||
|
|
||||
@@ -23,6 +23,40 @@ async fn extra_semicolon() -> Result<(), ()> {
|
||||
Ok(());
|
||||
}
|
||||
|
||||
/// This test is a characterization test for the `?` operator.
|
||||
///
|
||||
/// See <https://github.com/tokio-rs/tokio/issues/6930#issuecomment-2572502517> for more details.
|
||||
///
|
||||
/// It should fail with a single error message about the return type of the function, but instead
|
||||
/// if fails with an extra error message due to the `?` operator being used within the async block
|
||||
/// rather than the original function.
|
||||
///
|
||||
/// ```text
|
||||
/// 28 | None?;
|
||||
/// | ^ cannot use the `?` operator in an async block that returns `()`
|
||||
/// ```
|
||||
#[tokio::main]
|
||||
async fn question_mark_operator_with_invalid_option() -> Option<()> {
|
||||
None?;
|
||||
}
|
||||
|
||||
/// This test is a characterization test for the `?` operator.
|
||||
///
|
||||
/// See <https://github.com/tokio-rs/tokio/issues/6930#issuecomment-2572502517> for more details.
|
||||
///
|
||||
/// It should fail with a single error message about the return type of the function, but instead
|
||||
/// if fails with an extra error message due to the `?` operator being used within the async block
|
||||
/// rather than the original function.
|
||||
///
|
||||
/// ```text
|
||||
/// 33 | Ok(())?;
|
||||
/// | ^ cannot use the `?` operator in an async block that returns `()`
|
||||
/// ```
|
||||
#[tokio::main]
|
||||
async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
|
||||
Ok(())?;
|
||||
}
|
||||
|
||||
// https://github.com/tokio-rs/tokio/issues/4635
|
||||
#[allow(redundant_semicolons)]
|
||||
#[rustfmt::skip]
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:3:1: 3:15}` to be a future that resolves to `()`, but it resolves to `Result<(), _>`
|
||||
--> tests/fail/macros_type_mismatch.rs:3:1
|
||||
|
|
||||
3 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:3:1: 3:15}` to `&dyn Future<Output = ()>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:5:5
|
||||
|
|
||||
@@ -15,6 +26,17 @@ help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panic
|
||||
5 | Ok(()).expect("REASON")
|
||||
| +++++++++++++++++
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:8:1: 8:15}` to be a future that resolves to `()`, but it resolves to `Result<(), _>`
|
||||
--> tests/fail/macros_type_mismatch.rs:8:1
|
||||
|
|
||||
8 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:8:1: 8:15}` to `&dyn Future<Output = ()>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:10:5
|
||||
|
|
||||
@@ -25,13 +47,24 @@ error[E0308]: mismatched types
|
||||
found enum `Result<(), _>`
|
||||
help: a return type might be missing here
|
||||
|
|
||||
9 | async fn missing_return_type() -> _ {
|
||||
9 | async fn missing_return_type() -> _ {
|
||||
| ++++
|
||||
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
||||
|
|
||||
10 | return Ok(());.expect("REASON")
|
||||
| +++++++++++++++++
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:13:1: 13:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
|
||||
--> tests/fail/macros_type_mismatch.rs:13:1
|
||||
|
|
||||
13 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:13:1: 13:15}` to `&dyn Future<Output = Result<(), ()>>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:23:5
|
||||
|
|
||||
@@ -49,11 +82,95 @@ help: try adding an expression at the end of the block
|
||||
24 + Ok(())
|
||||
|
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:32:5
|
||||
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
|
||||
--> tests/fail/macros_type_mismatch.rs:40:9
|
||||
|
|
||||
30 | async fn issue_4635() {
|
||||
38 | #[tokio::main]
|
||||
| -------------- this function should return `Result` or `Option` to accept `?`
|
||||
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
|
||||
40 | None?;
|
||||
| ^ cannot use the `?` operator in an async block that returns `()`
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to be a future that resolves to `Option<()>`, but it resolves to `()`
|
||||
--> tests/fail/macros_type_mismatch.rs:38:1
|
||||
|
|
||||
38 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `Option<()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Option<()>`
|
||||
found unit type `()`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to `&dyn Future<Output = Option<()>>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:40:5
|
||||
|
|
||||
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
|
||||
| ---------- expected `Option<()>` because of return type
|
||||
40 | None?;
|
||||
| ^^^^^^ expected `Option<()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Option<()>`
|
||||
found unit type `()`
|
||||
help: try adding an expression at the end of the block
|
||||
|
|
||||
40 ~ None?;;
|
||||
41 + None
|
||||
|
|
||||
40 ~ None?;;
|
||||
41 + Some(())
|
||||
|
|
||||
|
||||
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
|
||||
--> tests/fail/macros_type_mismatch.rs:57:11
|
||||
|
|
||||
55 | #[tokio::main]
|
||||
| -------------- this function should return `Result` or `Option` to accept `?`
|
||||
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
|
||||
57 | Ok(())?;
|
||||
| ^ cannot use the `?` operator in an async block that returns `()`
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
|
||||
--> tests/fail/macros_type_mismatch.rs:55:1
|
||||
|
|
||||
55 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to `&dyn Future<Output = Result<(), ()>>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:57:5
|
||||
|
|
||||
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
|
||||
| -------------- expected `Result<(), ()>` because of return type
|
||||
57 | Ok(())?;
|
||||
| ^^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
help: try adding an expression at the end of the block
|
||||
|
|
||||
57 ~ Ok(())?;;
|
||||
58 + Ok(())
|
||||
|
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:63:1: 63:15}` to be a future that resolves to `()`, but it resolves to `{integer}`
|
||||
--> tests/fail/macros_type_mismatch.rs:63:1
|
||||
|
|
||||
63 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found integer
|
||||
|
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:63:1: 63:15}` to `&dyn Future<Output = ()>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:66:5
|
||||
|
|
||||
64 | async fn issue_4635() {
|
||||
| - help: try adding a return type: `-> i32`
|
||||
31 | return 1;
|
||||
32 | ;
|
||||
65 | return 1;
|
||||
66 | ;
|
||||
| ^ expected `()`, found integer
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn compile_fail_full() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
@@ -11,12 +12,24 @@ fn compile_fail_full() {
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/macros_main_loop.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/impl_trait.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/use_builder_outer.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_dead_code.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_join.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_try_join.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_type_mismatch.rs");
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn never() -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn impl_trait() -> impl Iterator<Item = impl core::fmt::Debug> {
|
||||
[()].into_iter()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn impl_trait2() -> Result<(), impl core::fmt::Debug> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if impl_trait().count() == 10 {
|
||||
never();
|
||||
}
|
||||
let _ = impl_trait2();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#![deny(unused_qualifications)]
|
||||
|
||||
use tests_build::tokio;
|
||||
pub use tokio::runtime;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
if true {}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
@@ -55,8 +56,11 @@ rt = ["tokio/rt"]
|
||||
rt-multi-thread = ["rt", "tokio/rt-multi-thread"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio" }
|
||||
tokio-test = { path = "../tokio-test", optional = true }
|
||||
tokio = { version = "1.0.0", path = "../tokio" }
|
||||
tokio-test = { version = "0.4", path = "../tokio-test", optional = true }
|
||||
doc-comment = "0.3.1"
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
bytes = "1.0.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use futures::future::poll_fn;
|
||||
use std::future::poll_fn;
|
||||
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
|
||||
@@ -16,6 +16,13 @@ async fn spawning() -> usize {
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
#[tokio::main(flavor = "local")]
|
||||
async fn local_main() -> usize {
|
||||
let join = tokio::task::spawn_local(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
@@ -24,5 +31,8 @@ fn main_with_spawn() {
|
||||
#[test]
|
||||
fn shell() {
|
||||
assert_eq!(1, basic_main());
|
||||
assert_eq!(bool::default(), generic_fun::<bool>())
|
||||
assert_eq!(bool::default(), generic_fun::<bool>());
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
assert_eq!(1, local_main());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(all(feature = "full", not(target_os = "wasi")))]
|
||||
#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::join;
|
||||
@@ -10,6 +10,7 @@ use futures::future::{self, FutureExt};
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
use std::task::ready;
|
||||
|
||||
fn cat() -> Command {
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_test-cat"));
|
||||
@@ -24,7 +25,7 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
// Produce n lines on the child's stdout.
|
||||
let write = async {
|
||||
for i in 0..n {
|
||||
let bytes = format!("line {}\n", i).into_bytes();
|
||||
let bytes = format!("line {i}\n").into_bytes();
|
||||
stdin.write_all(&bytes).await.unwrap();
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
(false, 0) => panic!("broken pipe"),
|
||||
(true, n) if n != 0 => panic!("extraneous data"),
|
||||
_ => {
|
||||
let expected = format!("line {}", num_lines);
|
||||
let expected = format!("line {num_lines}");
|
||||
assert_eq!(expected, data);
|
||||
}
|
||||
};
|
||||
@@ -205,13 +206,13 @@ async fn vectored_writes() {
|
||||
let mut input = Bytes::from_static(b"hello\n").chain(Bytes::from_static(b"world!\n"));
|
||||
let mut writes_completed = 0;
|
||||
|
||||
futures::future::poll_fn(|cx| loop {
|
||||
std::future::poll_fn(|cx| loop {
|
||||
let mut slices = [IoSlice::new(&[]); 2];
|
||||
let vectored = input.chunks_vectored(&mut slices);
|
||||
if vectored == 0 {
|
||||
return std::task::Poll::Ready(std::io::Result::Ok(()));
|
||||
}
|
||||
let n = futures::ready!(Pin::new(&mut stdin).poll_write_vectored(cx, &slices))?;
|
||||
let n = ready!(Pin::new(&mut stdin).poll_write_vectored(cx, &slices))?;
|
||||
writes_completed += 1;
|
||||
input.advance(n);
|
||||
})
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
# 2.6.1 (Mar 2nd, 2026)
|
||||
|
||||
- macros: improve error message for return type mismatch in #[tokio::main] ([#7856])
|
||||
- macros: use call_site hygiene to avoid unused qualification ([#7866])
|
||||
|
||||
[#7856]: https://github.com/tokio-rs/tokio/pull/7856
|
||||
[#7866]: https://github.com/tokio-rs/tokio/pull/7866
|
||||
|
||||
# 2.6.0 (Oct 14th, 2025)
|
||||
|
||||
The MSRV is raised to 1.71.
|
||||
|
||||
- msrv: increase MSRV to 1.71 ([#7658])
|
||||
- macros: add `local` runtime flavor ([#7375], [#7597])
|
||||
- macros: suppress `clippy::unwrap_in_result` in `#[tokio::main]` ([#7651])
|
||||
|
||||
[#7375]: https://github.com/tokio-rs/tokio/pull/7375
|
||||
[#7597]: https://github.com/tokio-rs/tokio/pull/7597
|
||||
[#7651]: https://github.com/tokio-rs/tokio/pull/7651
|
||||
[#7658]: https://github.com/tokio-rs/tokio/pull/7658
|
||||
|
||||
# 2.5.0 (Jan 8th, 2025)
|
||||
|
||||
- macros: suppress `clippy::needless_return` in `#[tokio::main]` ([#6874])
|
||||
|
||||
[#6874]: https://github.com/tokio-rs/tokio/pull/6874
|
||||
|
||||
# 2.4.0 (July 22nd, 2024)
|
||||
|
||||
- msrv: increase MSRV to 1.70 ([#6645])
|
||||
- macros: allow `unhandled_panic` behavior for `#[tokio::main]` and `#[tokio::test]` ([#6593])
|
||||
|
||||
[#6593]: https://github.com/tokio-rs/tokio/pull/6593
|
||||
[#6645]: https://github.com/tokio-rs/tokio/pull/6645
|
||||
|
||||
# 2.3.0 (May 30th, 2024)
|
||||
|
||||
- macros: make `#[tokio::test]` append `#[test]` at the end of the attribute list ([#6497])
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "tokio-macros"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Remove path dependencies (if any)
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.x.y" git tag.
|
||||
version = "2.3.0"
|
||||
# - Create "tokio-macros-x.y.z" git tag.
|
||||
version = "2.6.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
rust-version = "1.71"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -27,7 +27,10 @@ quote = "1"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.0.0", features = ["full", "test-util"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+193
-32
@@ -10,6 +10,7 @@ type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
|
||||
enum RuntimeFlavor {
|
||||
CurrentThread,
|
||||
Threaded,
|
||||
Local,
|
||||
}
|
||||
|
||||
impl RuntimeFlavor {
|
||||
@@ -17,10 +18,36 @@ impl RuntimeFlavor {
|
||||
match s {
|
||||
"current_thread" => Ok(RuntimeFlavor::CurrentThread),
|
||||
"multi_thread" => Ok(RuntimeFlavor::Threaded),
|
||||
"local" => Ok(RuntimeFlavor::Local),
|
||||
"single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()),
|
||||
"basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()),
|
||||
"threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()),
|
||||
_ => Err(format!("No such runtime flavor `{}`. The runtime flavors are `current_thread` and `multi_thread`.", s)),
|
||||
_ => Err(format!("No such runtime flavor `{s}`. The runtime flavors are `current_thread`, `local`, and `multi_thread`.")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum UnhandledPanic {
|
||||
Ignore,
|
||||
ShutdownRuntime,
|
||||
}
|
||||
|
||||
impl UnhandledPanic {
|
||||
fn from_str(s: &str) -> Result<UnhandledPanic, String> {
|
||||
match s {
|
||||
"ignore" => Ok(UnhandledPanic::Ignore),
|
||||
"shutdown_runtime" => Ok(UnhandledPanic::ShutdownRuntime),
|
||||
_ => Err(format!("No such unhandled panic behavior `{s}`. The unhandled panic behaviors are `ignore` and `shutdown_runtime`.")),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_tokens(self, crate_path: &TokenStream) -> TokenStream {
|
||||
match self {
|
||||
UnhandledPanic::Ignore => quote! { #crate_path::runtime::UnhandledPanic::Ignore },
|
||||
UnhandledPanic::ShutdownRuntime => {
|
||||
quote! { #crate_path::runtime::UnhandledPanic::ShutdownRuntime }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +57,7 @@ struct FinalConfig {
|
||||
worker_threads: Option<usize>,
|
||||
start_paused: Option<bool>,
|
||||
crate_name: Option<Path>,
|
||||
unhandled_panic: Option<UnhandledPanic>,
|
||||
}
|
||||
|
||||
/// Config used in case of the attribute not being able to build a valid config
|
||||
@@ -38,6 +66,7 @@ const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
|
||||
worker_threads: None,
|
||||
start_paused: None,
|
||||
crate_name: None,
|
||||
unhandled_panic: None,
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
@@ -48,6 +77,7 @@ struct Configuration {
|
||||
start_paused: Option<(bool, Span)>,
|
||||
is_test: bool,
|
||||
crate_name: Option<Path>,
|
||||
unhandled_panic: Option<(UnhandledPanic, Span)>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
@@ -63,6 +93,7 @@ impl Configuration {
|
||||
start_paused: None,
|
||||
is_test,
|
||||
crate_name: None,
|
||||
unhandled_panic: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +148,25 @@ impl Configuration {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_unhandled_panic(
|
||||
&mut self,
|
||||
unhandled_panic: syn::Lit,
|
||||
span: Span,
|
||||
) -> Result<(), syn::Error> {
|
||||
if self.unhandled_panic.is_some() {
|
||||
return Err(syn::Error::new(
|
||||
span,
|
||||
"`unhandled_panic` set multiple times.",
|
||||
));
|
||||
}
|
||||
|
||||
let unhandled_panic = parse_string(unhandled_panic, span, "unhandled_panic")?;
|
||||
let unhandled_panic =
|
||||
UnhandledPanic::from_str(&unhandled_panic).map_err(|err| syn::Error::new(span, err))?;
|
||||
self.unhandled_panic = Some((unhandled_panic, span));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn macro_name(&self) -> &'static str {
|
||||
if self.is_test {
|
||||
"tokio::test"
|
||||
@@ -129,15 +179,16 @@ impl Configuration {
|
||||
use RuntimeFlavor as F;
|
||||
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
|
||||
let worker_threads = match (flavor, self.worker_threads) {
|
||||
(F::CurrentThread, Some((_, worker_threads_span))) => {
|
||||
(F::CurrentThread | F::Local, Some((_, worker_threads_span))) => {
|
||||
let msg = format!(
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(worker_threads_span, msg));
|
||||
}
|
||||
(F::CurrentThread, None) => None,
|
||||
(F::CurrentThread | F::Local, None) => None,
|
||||
(F::Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
worker_threads.map(|(val, _span)| val)
|
||||
}
|
||||
@@ -159,7 +210,19 @@ impl Configuration {
|
||||
);
|
||||
return Err(syn::Error::new(start_paused_span, msg));
|
||||
}
|
||||
(F::CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(F::CurrentThread | F::Local, Some((start_paused, _))) => Some(start_paused),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
let unhandled_panic = match (flavor, self.unhandled_panic) {
|
||||
(F::Threaded, Some((_, unhandled_panic_span))) => {
|
||||
let msg = format!(
|
||||
"The `unhandled_panic` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(unhandled_panic_span, msg));
|
||||
}
|
||||
(F::CurrentThread | F::Local, Some((unhandled_panic, _))) => Some(unhandled_panic),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
@@ -168,6 +231,7 @@ impl Configuration {
|
||||
flavor,
|
||||
worker_threads,
|
||||
start_paused,
|
||||
unhandled_panic,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -178,12 +242,12 @@ fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as integer: {}", field, e),
|
||||
format!("Failed to parse value of `{field}` as integer: {e}"),
|
||||
)),
|
||||
},
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as integer.", field),
|
||||
format!("Failed to parse value of `{field}` as integer."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -194,7 +258,7 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
|
||||
syn::Lit::Verbatim(s) => Ok(s.to_string()),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as string.", field),
|
||||
format!("Failed to parse value of `{field}` as string."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -214,7 +278,7 @@ fn parse_path(lit: syn::Lit, span: Span, field: &str) -> Result<Path, syn::Error
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as path.", field),
|
||||
format!("Failed to parse value of `{field}` as path."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -224,11 +288,40 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
|
||||
syn::Lit::Bool(b) => Ok(b.value),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as bool.", field),
|
||||
format!("Failed to parse value of `{field}` as bool."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_impl_trait(ty: &syn::Type) -> bool {
|
||||
match ty {
|
||||
syn::Type::ImplTrait(_) => true,
|
||||
syn::Type::Array(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Ptr(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Reference(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Slice(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Tuple(t) => t.elems.iter().any(contains_impl_trait),
|
||||
syn::Type::Paren(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Group(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Path(t) => match t.path.segments.last() {
|
||||
Some(segment) => match &segment.arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => args.args.iter().any(|arg| match arg {
|
||||
syn::GenericArgument::Type(t) => contains_impl_trait(t),
|
||||
syn::GenericArgument::AssocType(t) => contains_impl_trait(&t.ty),
|
||||
_ => false,
|
||||
}),
|
||||
syn::PathArguments::Parenthesized(args) => {
|
||||
args.inputs.iter().any(contains_impl_trait)
|
||||
|| matches!(&args.output, syn::ReturnType::Type(_, t) if contains_impl_trait(t))
|
||||
}
|
||||
syn::PathArguments::None => false,
|
||||
},
|
||||
None => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_config(
|
||||
input: &ItemFn,
|
||||
args: AttributeArgs,
|
||||
@@ -275,10 +368,13 @@ fn build_config(
|
||||
"crate" => {
|
||||
config.set_crate_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"unhandled_panic" => {
|
||||
config
|
||||
.set_unhandled_panic(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
name => {
|
||||
let msg = format!(
|
||||
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`",
|
||||
name,
|
||||
"Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
|
||||
);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
@@ -293,21 +389,19 @@ fn build_config(
|
||||
let msg = match name.as_str() {
|
||||
"threaded_scheduler" | "multi_thread" => {
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"multi_thread\")].",
|
||||
macro_name
|
||||
"Set the runtime flavor with #[{macro_name}(flavor = \"multi_thread\")]."
|
||||
)
|
||||
}
|
||||
"basic_scheduler" | "current_thread" | "single_threaded" => {
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"current_thread\")].",
|
||||
macro_name
|
||||
"Set the runtime flavor with #[{macro_name}(flavor = \"current_thread\")]."
|
||||
)
|
||||
}
|
||||
"flavor" | "worker_threads" | "start_paused" => {
|
||||
format!("The `{}` attribute requires an argument.", name)
|
||||
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
|
||||
format!("The `{name}` attribute requires an argument.")
|
||||
}
|
||||
name => {
|
||||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`", name)
|
||||
format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.")
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
@@ -343,22 +437,47 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
let crate_path = config
|
||||
.crate_name
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream());
|
||||
.unwrap_or_else(|| {
|
||||
Ident::new("tokio", Span::call_site().located_at(last_stmt_start_span))
|
||||
.into_token_stream()
|
||||
});
|
||||
|
||||
let use_builder = quote_spanned! {Span::call_site().located_at(last_stmt_start_span)=>
|
||||
use #crate_path::runtime::Builder;
|
||||
};
|
||||
|
||||
let mut rt = match config.flavor {
|
||||
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_path::runtime::Builder::new_current_thread()
|
||||
},
|
||||
RuntimeFlavor::CurrentThread | RuntimeFlavor::Local => {
|
||||
quote_spanned! {last_stmt_start_span=>
|
||||
Builder::new_current_thread()
|
||||
}
|
||||
}
|
||||
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_path::runtime::Builder::new_multi_thread()
|
||||
Builder::new_multi_thread()
|
||||
},
|
||||
};
|
||||
|
||||
let mut checks = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
let build = if let RuntimeFlavor::Local = config.flavor {
|
||||
checks.push(quote! { tokio_unstable });
|
||||
errors.push("The local runtime flavor is only available when `tokio_unstable` is set.");
|
||||
quote_spanned! {last_stmt_start_span=> build_local(Default::default())}
|
||||
} else {
|
||||
quote_spanned! {last_stmt_start_span=> build()}
|
||||
};
|
||||
|
||||
if let Some(v) = config.worker_threads {
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) };
|
||||
}
|
||||
if let Some(v) = config.start_paused {
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) };
|
||||
}
|
||||
if let Some(v) = config.unhandled_panic {
|
||||
let unhandled_panic = v.into_tokens(&crate_path);
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) };
|
||||
}
|
||||
|
||||
let generated_attrs = if is_test {
|
||||
quote! {
|
||||
@@ -368,16 +487,38 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let do_checks: TokenStream = checks
|
||||
.iter()
|
||||
.zip(&errors)
|
||||
.map(|(check, error)| {
|
||||
quote! {
|
||||
#[cfg(not(#check))]
|
||||
compile_error!(#error);
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let body_ident = quote! { body };
|
||||
// This explicit `return` is intentional. See tokio-rs/tokio#4636
|
||||
let last_block = quote_spanned! {last_stmt_end_span=>
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
#do_checks
|
||||
|
||||
#[cfg(all(#(#checks),*))]
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return, clippy::unwrap_in_result)]
|
||||
{
|
||||
#use_builder
|
||||
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.#build
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(#body_ident);
|
||||
}
|
||||
|
||||
#[cfg(not(all(#(#checks),*)))]
|
||||
{
|
||||
panic!("fell through checks")
|
||||
}
|
||||
};
|
||||
|
||||
let body = input.body();
|
||||
@@ -391,22 +532,42 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
//
|
||||
// We don't do this for the main function as it should only be used once so
|
||||
// there will be no benefit.
|
||||
let output_type = match &input.sig.output {
|
||||
// For functions with no return value syn doesn't print anything,
|
||||
// but that doesn't work as `Output` for our boxed `Future`, so
|
||||
// default to `()` (the same type as the function output).
|
||||
syn::ReturnType::Default => quote! { () },
|
||||
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
|
||||
};
|
||||
|
||||
let body = if is_test {
|
||||
let output_type = match &input.sig.output {
|
||||
// For functions with no return value syn doesn't print anything,
|
||||
// but that doesn't work as `Output` for our boxed `Future`, so
|
||||
// default to `()` (the same type as the function output).
|
||||
syn::ReturnType::Default => quote! { () },
|
||||
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
|
||||
};
|
||||
quote! {
|
||||
let body = async #body;
|
||||
#crate_path::pin!(body);
|
||||
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
|
||||
}
|
||||
} else {
|
||||
// force typecheck without runtime overhead
|
||||
let check_block = match &input.sig.output {
|
||||
syn::ReturnType::Type(_, t)
|
||||
if matches!(**t, syn::Type::Never(_)) || contains_impl_trait(t) =>
|
||||
{
|
||||
quote! {}
|
||||
}
|
||||
_ => quote! {
|
||||
if false {
|
||||
let _: &dyn ::core::future::Future<Output = #output_type> = &body;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
quote! {
|
||||
let body = async #body;
|
||||
// Compile-time assertion that the future's output matches the return type.
|
||||
let body = {
|
||||
#check_block
|
||||
body
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+160
-18
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -13,11 +12,6 @@
|
||||
|
||||
//! Macros for use with Tokio
|
||||
|
||||
// This `extern` is required for older `rustc` versions but newer `rustc`
|
||||
// versions warn about the unused `extern crate`.
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate proc_macro;
|
||||
|
||||
mod entry;
|
||||
mod select;
|
||||
|
||||
@@ -47,7 +41,12 @@ use proc_macro::TokenStream;
|
||||
/// Awaiting on other futures from the function provided here will not
|
||||
/// perform as fast as those spawned as workers.
|
||||
///
|
||||
/// # Multi-threaded runtime
|
||||
/// # Runtime flavors
|
||||
///
|
||||
/// The macro can be configured with a `flavor` parameter to select
|
||||
/// different runtime configurations.
|
||||
///
|
||||
/// ## Multi-threaded
|
||||
///
|
||||
/// To use the multi-threaded runtime, the macro can be configured using
|
||||
///
|
||||
@@ -62,23 +61,37 @@ use proc_macro::TokenStream;
|
||||
/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
|
||||
/// flag.
|
||||
///
|
||||
/// # Current thread runtime
|
||||
/// ## Current-thread
|
||||
///
|
||||
/// To use the single-threaded runtime known as the `current_thread` runtime,
|
||||
/// the macro can be configured using
|
||||
///
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// ## Function arguments:
|
||||
/// ## Local
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
/// [Unstable API][unstable] only.
|
||||
///
|
||||
/// ## Usage
|
||||
/// To use the [local runtime], the macro can be configured using
|
||||
///
|
||||
/// ### Using the multi-thread runtime
|
||||
/// ```rust
|
||||
/// # #[cfg(tokio_unstable)]
|
||||
/// #[tokio::main(flavor = "local")]
|
||||
/// # async fn main() {}
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// # Function arguments
|
||||
///
|
||||
/// Arguments are allowed for any functions, aside from `main` which is special.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ## Using the multi-threaded runtime
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
@@ -101,7 +114,7 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using current thread runtime
|
||||
/// ## Using the current-thread runtime
|
||||
///
|
||||
/// The basic scheduler is single-threaded.
|
||||
///
|
||||
@@ -126,7 +139,42 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Set number of worker threads
|
||||
/// ## Using the local runtime
|
||||
///
|
||||
/// Available in the [unstable API][unstable] only.
|
||||
///
|
||||
/// The [local runtime] is similar to the current-thread runtime but
|
||||
/// supports [`task::spawn_local`](../tokio/task/fn.spawn_local.html).
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(tokio_unstable)]
|
||||
/// #[tokio::main(flavor = "local")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(tokio_unstable)]
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .build_local(tokio::runtime::LocalOptions::default())
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// ## Set number of worker threads
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(worker_threads = 2)]
|
||||
@@ -150,7 +198,7 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Configure the runtime to start with time paused
|
||||
/// ## Configure the runtime to start with time paused
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
@@ -176,7 +224,7 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// Note that `start_paused` requires the `test-util` feature to be enabled.
|
||||
///
|
||||
/// ### Rename package
|
||||
/// ## Rename package
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio as tokio1;
|
||||
@@ -202,6 +250,53 @@ use proc_macro::TokenStream;
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Configure unhandled panic behavior
|
||||
///
|
||||
/// Available options are `shutdown_runtime` and `ignore`. For more details, see
|
||||
/// [`Builder::unhandled_panic`].
|
||||
///
|
||||
/// This option is only compatible with the `current_thread` runtime.
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[tokio::main(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
|
||||
/// async fn main() {
|
||||
/// let _ = tokio::spawn(async {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .unhandled_panic(tokio::runtime::UnhandledPanic::ShutdownRuntime)
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// let _ = tokio::spawn(async {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// })
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// **Note**: This option depends on Tokio's [unstable API][unstable]. See [the
|
||||
/// documentation on unstable features][unstable] for details on how to enable
|
||||
/// Tokio's unstable features.
|
||||
///
|
||||
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
|
||||
/// [unstable]: ../tokio/index.html#unstable-features
|
||||
/// [local runtime]: ../tokio/runtime/struct.LocalRuntime.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args.into(), item.into(), true).into()
|
||||
@@ -364,7 +459,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ### Set number of worker threads
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(flavor ="multi_thread", worker_threads = 2)]
|
||||
/// #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
@@ -423,6 +518,53 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Configure unhandled panic behavior
|
||||
///
|
||||
/// Available options are `shutdown_runtime` and `ignore`. For more details, see
|
||||
/// [`Builder::unhandled_panic`].
|
||||
///
|
||||
/// This option is only compatible with the `current_thread` runtime.
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[tokio::test(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
|
||||
/// async fn my_test() {
|
||||
/// let _ = tokio::spawn(async {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// }
|
||||
///
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::test]`
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[test]
|
||||
/// fn my_test() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// let _ = tokio::spawn(async {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// **Note**: This option depends on Tokio's [unstable API][unstable]. See [the
|
||||
/// documentation on unstable features][unstable] for details on how to enable
|
||||
/// Tokio's unstable features.
|
||||
///
|
||||
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
|
||||
/// [unstable]: ../tokio/index.html#unstable-features
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args.into(), item.into(), true).into()
|
||||
|
||||
@@ -11,7 +11,7 @@ pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
};
|
||||
|
||||
let variants = (0..branches)
|
||||
.map(|num| Ident::new(&format!("_{}", num), Span::call_site()))
|
||||
.map(|num| Ident::new(&format!("_{num}"), Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Use a bitfield to track which futures completed
|
||||
|
||||
@@ -1,3 +1,48 @@
|
||||
# 0.1.18 (January 4th, 2026)
|
||||
|
||||
### Added
|
||||
|
||||
- stream: add `ChunksTimeout::into_remainder` ([#7715])
|
||||
- stream: add examples to wrapper types ([#7024])
|
||||
- sync: implement `Stream::size_hint` for `ReceiverStream` and `UnboundedReceiverStream` ([#7492])
|
||||
|
||||
### Fixed
|
||||
|
||||
- stream: work around the rustc bug in `StreamExt::collect` ([#7754])
|
||||
|
||||
### Documented
|
||||
|
||||
- stream: improve the the docs of `TcpListenerStream` ([#7578])
|
||||
|
||||
[#7024]: https://github.com/tokio-rs/tokio/pull/7024
|
||||
[#7492]: https://github.com/tokio-rs/tokio/pull/7492
|
||||
[#7578]: https://github.com/tokio-rs/tokio/pull/7578
|
||||
[#7715]: https://github.com/tokio-rs/tokio/pull/7715
|
||||
[#7754]: https://github.com/tokio-rs/tokio/pull/7754
|
||||
|
||||
# 0.1.17 (December 6th, 2024)
|
||||
|
||||
- deps: fix dev-dependency on tokio-test ([#6931], [#7019])
|
||||
- stream: fix link on `Peekable` ([#6861])
|
||||
- sync: fix `Stream` link in broadcast docs ([#6873])
|
||||
|
||||
[#6861]: https://github.com/tokio-rs/tokio/pull/6861
|
||||
[#6873]: https://github.com/tokio-rs/tokio/pull/6873
|
||||
[#6931]: https://github.com/tokio-rs/tokio/pull/6931
|
||||
[#7019]: https://github.com/tokio-rs/tokio/pull/7019
|
||||
|
||||
# 0.1.16 (September 5th, 2024)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.70.
|
||||
|
||||
- stream: add `next_many` and `poll_next_many` to `StreamMap` ([#6409])
|
||||
- stream: make stream adapters public ([#6658])
|
||||
- readme: add readme for tokio-stream ([#6456])
|
||||
|
||||
[#6409]: https://github.com/tokio-rs/tokio/pull/6409
|
||||
[#6658]: https://github.com/tokio-rs/tokio/pull/6658
|
||||
[#6456]: https://github.com/tokio-rs/tokio/pull/6456
|
||||
|
||||
# 0.1.15 (March 14th, 2024)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.63.
|
||||
|
||||
+10
-7
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "tokio-stream"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Remove path dependencies (if any)
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.15"
|
||||
version = "0.1.18"
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
rust-version = "1.71"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -38,14 +38,14 @@ signal = ["tokio/signal"]
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.11"
|
||||
tokio = { version = "1.15.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
|
||||
tokio = { version = "1.38.0", features = ["sync"] }
|
||||
tokio-util = { version = "0.7.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
|
||||
tokio = { version = "1.38.0", features = ["full", "test-util"] }
|
||||
async-stream = "0.3"
|
||||
parking_lot = "0.12.0"
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
tokio-test = "0.4"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
@@ -56,3 +56,6 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
# This should allow `docsrs` to be read across projects, so that `tokio-stream`
|
||||
# can pick up stubbed types exported by `tokio`.
|
||||
rustc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -26,12 +26,12 @@ unsafe impl<T> Sync for Empty<T> {}
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut none = stream::empty::<i32>();
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let mut none = stream::empty::<i32>();
|
||||
///
|
||||
/// assert_eq!(None, none.next().await);
|
||||
/// }
|
||||
/// assert_eq!(None, none.next().await);
|
||||
/// # }
|
||||
/// ```
|
||||
pub const fn empty<T>() -> Empty<T> {
|
||||
Empty(PhantomData)
|
||||
|
||||
+21
-11
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
#![allow(
|
||||
clippy::cognitive_complexity,
|
||||
clippy::large_enum_variant,
|
||||
@@ -35,14 +34,14 @@
|
||||
//! ```rust
|
||||
//! use tokio_stream::{self as stream, StreamExt};
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let mut stream = stream::iter(vec![0, 1, 2]);
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! let mut stream = stream::iter(vec![0, 1, 2]);
|
||||
//!
|
||||
//! while let Some(value) = stream.next().await {
|
||||
//! println!("Got {}", value);
|
||||
//! }
|
||||
//! while let Some(value) = stream.next().await {
|
||||
//! println!("Got {}", value);
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! # Returning a Stream from a function
|
||||
@@ -74,15 +73,26 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod poll_fn;
|
||||
pub(crate) use poll_fn::poll_fn;
|
||||
|
||||
pub mod wrappers;
|
||||
|
||||
mod stream_ext;
|
||||
pub use stream_ext::{collect::FromStream, StreamExt};
|
||||
/// Adapters for [`Stream`]s created by methods in [`StreamExt`].
|
||||
pub mod adapters {
|
||||
pub use crate::stream_ext::{
|
||||
Chain, Filter, FilterMap, Fuse, Map, MapWhile, Merge, Peekable, Skip, SkipWhile, Take,
|
||||
TakeWhile, Then,
|
||||
};
|
||||
cfg_time! {
|
||||
pub use crate::stream_ext::{ChunksTimeout, Timeout, TimeoutRepeating};
|
||||
}
|
||||
}
|
||||
|
||||
cfg_time! {
|
||||
pub use stream_ext::timeout::{Elapsed, Timeout};
|
||||
#[deprecated = "Import those symbols from adapters instead"]
|
||||
#[doc(hidden)]
|
||||
pub use stream_ext::timeout::Timeout;
|
||||
pub use stream_ext::timeout::Elapsed;
|
||||
}
|
||||
|
||||
mod empty;
|
||||
|
||||
@@ -57,12 +57,3 @@ macro_rules! cfg_signal {
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ready {
|
||||
($e:expr $(,)?) => {
|
||||
match $e {
|
||||
std::task::Poll::Ready(t) => t,
|
||||
std::task::Poll::Pending => return std::task::Poll::Pending,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,16 +22,16 @@ impl<I> Unpin for Once<I> {}
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// // one is the loneliest number
|
||||
/// let mut one = stream::once(1);
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// // one is the loneliest number
|
||||
/// let mut one = stream::once(1);
|
||||
///
|
||||
/// assert_eq!(Some(1), one.next().await);
|
||||
/// assert_eq!(Some(1), one.next().await);
|
||||
///
|
||||
/// // just one, that's all we get
|
||||
/// assert_eq!(None, one.next().await);
|
||||
/// }
|
||||
/// // just one, that's all we get
|
||||
/// assert_eq!(None, one.next().await);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn once<T>(value: T) -> Once<T> {
|
||||
Once {
|
||||
|
||||
@@ -17,7 +17,7 @@ unsafe impl<T> Sync for Pending<T> {}
|
||||
///
|
||||
/// The returned stream is never ready. Attempting to call
|
||||
/// [`next()`](crate::StreamExt::next) will never complete. Use
|
||||
/// [`stream::empty()`](super::empty()) to obtain a stream that is is
|
||||
/// [`stream::empty()`](super::empty()) to obtain a stream that is
|
||||
/// immediately empty but returns no values.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub(crate) struct PollFn<F> {
|
||||
f: F,
|
||||
}
|
||||
|
||||
pub(crate) fn poll_fn<T, F>(f: F) -> PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
PollFn { f }
|
||||
}
|
||||
|
||||
impl<T, F> Future for PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
|
||||
// Safety: We never construct a `Pin<&mut F>` anywhere, so accessing `f`
|
||||
// mutably in an unpinned way is sound.
|
||||
//
|
||||
// This use of unsafe cannot be replaced with the pin-project macro
|
||||
// because:
|
||||
// * If we put `#[pin]` on the field, then it gives us a `Pin<&mut F>`,
|
||||
// which we can't use to call the closure.
|
||||
// * If we don't put `#[pin]` on the field, then it makes `PollFn` be
|
||||
// unconditionally `Unpin`, which we also don't want.
|
||||
let me = unsafe { Pin::into_inner_unchecked(self) };
|
||||
(me.f)(cx)
|
||||
}
|
||||
}
|
||||
@@ -17,20 +17,20 @@ pin_project! {
|
||||
/// ```
|
||||
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct StreamNotifyClose<S> {
|
||||
@@ -83,7 +83,7 @@ where
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if let Some(inner) = &self.inner {
|
||||
// We always return +1 because when there's stream there's atleast one more item.
|
||||
// We always return +1 because when there's a stream there's at least one more item.
|
||||
let (l, u) = inner.size_hint();
|
||||
(l.saturating_add(1), u.and_then(|u| u.checked_add(1)))
|
||||
} else {
|
||||
|
||||
@@ -8,66 +8,66 @@ mod any;
|
||||
use any::AnyFuture;
|
||||
|
||||
mod chain;
|
||||
use chain::Chain;
|
||||
pub use chain::Chain;
|
||||
|
||||
pub(crate) mod collect;
|
||||
use collect::{Collect, FromStream};
|
||||
|
||||
mod filter;
|
||||
use filter::Filter;
|
||||
pub use filter::Filter;
|
||||
|
||||
mod filter_map;
|
||||
use filter_map::FilterMap;
|
||||
pub use filter_map::FilterMap;
|
||||
|
||||
mod fold;
|
||||
use fold::FoldFuture;
|
||||
|
||||
mod fuse;
|
||||
use fuse::Fuse;
|
||||
pub use fuse::Fuse;
|
||||
|
||||
mod map;
|
||||
use map::Map;
|
||||
pub use map::Map;
|
||||
|
||||
mod map_while;
|
||||
use map_while::MapWhile;
|
||||
pub use map_while::MapWhile;
|
||||
|
||||
mod merge;
|
||||
use merge::Merge;
|
||||
pub use merge::Merge;
|
||||
|
||||
mod next;
|
||||
use next::Next;
|
||||
|
||||
mod skip;
|
||||
use skip::Skip;
|
||||
pub use skip::Skip;
|
||||
|
||||
mod skip_while;
|
||||
use skip_while::SkipWhile;
|
||||
pub use skip_while::SkipWhile;
|
||||
|
||||
mod take;
|
||||
use take::Take;
|
||||
pub use take::Take;
|
||||
|
||||
mod take_while;
|
||||
use take_while::TakeWhile;
|
||||
pub use take_while::TakeWhile;
|
||||
|
||||
mod then;
|
||||
use then::Then;
|
||||
pub use then::Then;
|
||||
|
||||
mod try_next;
|
||||
use try_next::TryNext;
|
||||
|
||||
mod peekable;
|
||||
use peekable::Peekable;
|
||||
pub use peekable::Peekable;
|
||||
|
||||
cfg_time! {
|
||||
pub(crate) mod timeout;
|
||||
pub(crate) mod timeout_repeating;
|
||||
use timeout::Timeout;
|
||||
use timeout_repeating::TimeoutRepeating;
|
||||
pub use timeout::Timeout;
|
||||
pub use timeout_repeating::TimeoutRepeating;
|
||||
use tokio::time::{Duration, Interval};
|
||||
mod throttle;
|
||||
use throttle::{throttle, Throttle};
|
||||
mod chunks_timeout;
|
||||
use chunks_timeout::ChunksTimeout;
|
||||
pub use chunks_timeout::ChunksTimeout;
|
||||
}
|
||||
|
||||
/// An extension trait for the [`Stream`] trait that provides a variety of
|
||||
@@ -129,7 +129,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -171,8 +171,9 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
///
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter(vec![Ok(1), Ok(2), Err("nope")]);
|
||||
@@ -203,7 +204,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -239,7 +240,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -283,7 +284,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -418,7 +419,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -454,7 +455,7 @@ pub trait StreamExt: Stream {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -514,7 +515,10 @@ pub trait StreamExt: Stream {
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # /*
|
||||
/// #[tokio::main]
|
||||
/// # */
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// let mut stream = Alternate { state: 0 };
|
||||
///
|
||||
@@ -551,7 +555,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -580,7 +584,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -606,7 +610,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -637,7 +641,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// let mut stream = stream::iter(vec![1,2,3,4,1]).skip_while(|x| *x < 3);
|
||||
@@ -680,7 +684,7 @@ pub trait StreamExt: Stream {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -695,7 +699,7 @@ pub trait StreamExt: Stream {
|
||||
/// Stopping at the first `false`:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -739,7 +743,7 @@ pub trait StreamExt: Stream {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -754,7 +758,7 @@ pub trait StreamExt: Stream {
|
||||
/// Stopping at the first `true`:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -787,21 +791,21 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let one = stream::iter(vec![1, 2, 3]);
|
||||
/// let two = stream::iter(vec![4, 5, 6]);
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let one = stream::iter(vec![1, 2, 3]);
|
||||
/// let two = stream::iter(vec![4, 5, 6]);
|
||||
///
|
||||
/// let mut stream = one.chain(two);
|
||||
/// let mut stream = one.chain(two);
|
||||
///
|
||||
/// assert_eq!(stream.next().await, Some(1));
|
||||
/// assert_eq!(stream.next().await, Some(2));
|
||||
/// assert_eq!(stream.next().await, Some(3));
|
||||
/// assert_eq!(stream.next().await, Some(4));
|
||||
/// assert_eq!(stream.next().await, Some(5));
|
||||
/// assert_eq!(stream.next().await, Some(6));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// }
|
||||
/// assert_eq!(stream.next().await, Some(1));
|
||||
/// assert_eq!(stream.next().await, Some(2));
|
||||
/// assert_eq!(stream.next().await, Some(3));
|
||||
/// assert_eq!(stream.next().await, Some(4));
|
||||
/// assert_eq!(stream.next().await, Some(5));
|
||||
/// assert_eq!(stream.next().await, Some(6));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// # }
|
||||
/// ```
|
||||
fn chain<U>(self, other: U) -> Chain<Self, U>
|
||||
where
|
||||
@@ -823,7 +827,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
/// Basic usage:
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, *};
|
||||
///
|
||||
@@ -874,16 +878,16 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let doubled: Vec<i32> =
|
||||
/// stream::iter(vec![1, 2, 3])
|
||||
/// .map(|x| x * 2)
|
||||
/// .collect()
|
||||
/// .await;
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let doubled: Vec<i32> =
|
||||
/// stream::iter(vec![1, 2, 3])
|
||||
/// .map(|x| x * 2)
|
||||
/// .collect()
|
||||
/// .await;
|
||||
///
|
||||
/// assert_eq!(vec![2, 4, 6], doubled);
|
||||
/// }
|
||||
/// assert_eq!(vec![2, 4, 6], doubled);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Collecting a stream of `Result` values
|
||||
@@ -891,28 +895,28 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// // A stream containing only `Ok` values will be collected
|
||||
/// let values: Result<Vec<i32>, &str> =
|
||||
/// stream::iter(vec![Ok(1), Ok(2), Ok(3)])
|
||||
/// .collect()
|
||||
/// .await;
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// // A stream containing only `Ok` values will be collected
|
||||
/// let values: Result<Vec<i32>, &str> =
|
||||
/// stream::iter(vec![Ok(1), Ok(2), Ok(3)])
|
||||
/// .collect()
|
||||
/// .await;
|
||||
///
|
||||
/// assert_eq!(Ok(vec![1, 2, 3]), values);
|
||||
/// assert_eq!(Ok(vec![1, 2, 3]), values);
|
||||
///
|
||||
/// // A stream containing `Err` values will return the first error.
|
||||
/// let results = vec![Ok(1), Err("no"), Ok(2), Ok(3), Err("nein")];
|
||||
/// // A stream containing `Err` values will return the first error.
|
||||
/// let results = vec![Ok(1), Err("no"), Ok(2), Ok(3), Err("nein")];
|
||||
///
|
||||
/// let values: Result<Vec<i32>, &str> =
|
||||
/// stream::iter(results)
|
||||
/// .collect()
|
||||
/// .await;
|
||||
/// let values: Result<Vec<i32>, &str> =
|
||||
/// stream::iter(results)
|
||||
/// .collect()
|
||||
/// .await;
|
||||
///
|
||||
/// assert_eq!(Err("no"), values);
|
||||
/// }
|
||||
/// assert_eq!(Err("no"), values);
|
||||
/// # }
|
||||
/// ```
|
||||
fn collect<T>(self) -> Collect<Self, T>
|
||||
fn collect<T>(self) -> Collect<Self, T, T::InternalCollection>
|
||||
where
|
||||
T: FromStream<Self::Item>,
|
||||
Self: Sized,
|
||||
@@ -945,7 +949,7 @@ pub trait StreamExt: Stream {
|
||||
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// use std::time::Duration;
|
||||
@@ -1031,7 +1035,7 @@ pub trait StreamExt: Stream {
|
||||
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// use std::time::Duration;
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::Stream;
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -42,7 +42,7 @@ where
|
||||
|
||||
// Take a maximum of 32 items from the stream before yielding.
|
||||
for _ in 0..32 {
|
||||
match futures_core::ready!(stream.as_mut().poll_next(cx)) {
|
||||
match ready!(stream.as_mut().poll_next(cx)) {
|
||||
Some(v) => {
|
||||
if !(me.f)(v) {
|
||||
return Poll::Ready(false);
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::Stream;
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -42,7 +42,7 @@ where
|
||||
|
||||
// Take a maximum of 32 items from the stream before yielding.
|
||||
for _ in 0..32 {
|
||||
match futures_core::ready!(stream.as_mut().poll_next(cx)) {
|
||||
match ready!(stream.as_mut().poll_next(cx)) {
|
||||
Some(v) => {
|
||||
if (me.f)(v) {
|
||||
return Poll::Ready(true);
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::stream_ext::Fuse;
|
||||
use crate::Stream;
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
|
||||
@@ -4,7 +4,7 @@ use tokio::time::{sleep, Sleep};
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -33,6 +33,12 @@ impl<S: Stream> ChunksTimeout<S> {
|
||||
cap: max_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the [`ChunksTimeout`] and then returns all buffered items.
|
||||
pub fn into_remainder(mut self: Pin<&mut Self>) -> Vec<S::Item> {
|
||||
let me = self.as_mut().project();
|
||||
std::mem::take(me.items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for ChunksTimeout<S> {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::marker::{PhantomData, PhantomPinned};
|
||||
use core::mem;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
// Do not export this struct until `FromStream` can be unsealed.
|
||||
@@ -12,14 +12,12 @@ pin_project! {
|
||||
/// Future returned by the [`collect`](super::StreamExt::collect) method.
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
#[derive(Debug)]
|
||||
pub struct Collect<T, U>
|
||||
where
|
||||
T: Stream,
|
||||
U: FromStream<T::Item>,
|
||||
pub struct Collect<T, U, C>
|
||||
{
|
||||
#[pin]
|
||||
stream: T,
|
||||
collection: U::InternalCollection,
|
||||
collection: C,
|
||||
_output: PhantomData<U>,
|
||||
// Make this future `!Unpin` for compatibility with async trait methods.
|
||||
#[pin]
|
||||
_pin: PhantomPinned,
|
||||
@@ -38,24 +36,25 @@ pin_project! {
|
||||
/// enhancements to the Rust language.
|
||||
pub trait FromStream<T>: sealed::FromStreamPriv<T> {}
|
||||
|
||||
impl<T, U> Collect<T, U>
|
||||
impl<T, U> Collect<T, U, U::InternalCollection>
|
||||
where
|
||||
T: Stream,
|
||||
U: FromStream<T::Item>,
|
||||
{
|
||||
pub(super) fn new(stream: T) -> Collect<T, U> {
|
||||
pub(super) fn new(stream: T) -> Collect<T, U, U::InternalCollection> {
|
||||
let (lower, upper) = stream.size_hint();
|
||||
let collection = U::initialize(sealed::Internal, lower, upper);
|
||||
|
||||
Collect {
|
||||
stream,
|
||||
collection,
|
||||
_output: PhantomData,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Future for Collect<T, U>
|
||||
impl<T, U> Future for Collect<T, U, U::InternalCollection>
|
||||
where
|
||||
T: Stream,
|
||||
U: FromStream<T::Item>,
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::Stream;
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::Stream;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::task::{ready, Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
/// Stream returned by [`fuse()`][super::StreamExt::fuse].
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::stream_ext::Fuse;
|
||||
use crate::StreamExt;
|
||||
|
||||
pin_project! {
|
||||
/// Stream returned by the [`chain`](super::StreamExt::peekable) method.
|
||||
/// Stream returned by the [`peekable`](super::StreamExt::peekable) method.
|
||||
pub struct Peekable<T: Stream> {
|
||||
peek: Option<T::Item>,
|
||||
#[pin]
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
|
||||
@@ -5,7 +5,7 @@ use tokio::time::{Duration, Instant, Sleep};
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{self, Poll};
|
||||
use std::task::{self, ready, Poll};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use tokio::time::{Instant, Sleep};
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Elapsed, Stream};
|
||||
use tokio::time::Interval;
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::{poll_fn, Stream};
|
||||
use crate::Stream;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::future::poll_fn;
|
||||
use std::hash::Hash;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::task::{ready, Context, Poll};
|
||||
|
||||
/// Combine many streams into one, indexing each source stream with a unique
|
||||
/// key.
|
||||
@@ -64,57 +65,57 @@ use std::task::{Context, Poll};
|
||||
/// use tokio::sync::mpsc;
|
||||
/// use std::pin::Pin;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx1, mut rx1) = mpsc::channel::<usize>(10);
|
||||
/// let (tx2, mut rx2) = mpsc::channel::<usize>(10);
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let (tx1, mut rx1) = mpsc::channel::<usize>(10);
|
||||
/// let (tx2, mut rx2) = mpsc::channel::<usize>(10);
|
||||
///
|
||||
/// // Convert the channels to a `Stream`.
|
||||
/// let rx1 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx1.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// let rx2 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx2.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx1.send(1).await.unwrap();
|
||||
///
|
||||
/// // This value will never be received. The send may or may not return
|
||||
/// // `Err` depending on if the remote end closed first or not.
|
||||
/// let _ = tx1.send(2).await;
|
||||
/// });
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx2.send(3).await.unwrap();
|
||||
/// let _ = tx2.send(4).await;
|
||||
/// });
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
/// // Insert both streams
|
||||
/// map.insert("one", rx1);
|
||||
/// map.insert("two", rx2);
|
||||
///
|
||||
/// // Read twice
|
||||
/// for _ in 0..2 {
|
||||
/// let (key, val) = map.next().await.unwrap();
|
||||
///
|
||||
/// if key == "one" {
|
||||
/// assert_eq!(val, 1);
|
||||
/// } else {
|
||||
/// assert_eq!(val, 3);
|
||||
/// }
|
||||
///
|
||||
/// // Remove the stream to prevent reading the next value
|
||||
/// map.remove(key);
|
||||
/// // Convert the channels to a `Stream`.
|
||||
/// let rx1 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx1.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// let rx2 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx2.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx1.send(1).await.unwrap();
|
||||
///
|
||||
/// // This value will never be received. The send may or may not return
|
||||
/// // `Err` depending on if the remote end closed first or not.
|
||||
/// let _ = tx1.send(2).await;
|
||||
/// });
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx2.send(3).await.unwrap();
|
||||
/// let _ = tx2.send(4).await;
|
||||
/// });
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
/// // Insert both streams
|
||||
/// map.insert("one", rx1);
|
||||
/// map.insert("two", rx2);
|
||||
///
|
||||
/// // Read twice
|
||||
/// for _ in 0..2 {
|
||||
/// let (key, val) = map.next().await.unwrap();
|
||||
///
|
||||
/// if key == "one" {
|
||||
/// assert_eq!(val, 1);
|
||||
/// } else {
|
||||
/// assert_eq!(val, 3);
|
||||
/// }
|
||||
///
|
||||
/// // Remove the stream to prevent reading the next value
|
||||
/// map.remove(key);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// This example models a read-only client to a chat system with channels. The
|
||||
@@ -184,20 +185,20 @@ use std::task::{Context, Poll};
|
||||
/// ```
|
||||
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -578,9 +579,11 @@ where
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. If `next_many` is used as the event in a
|
||||
/// [`tokio::select!`](tokio::select) statement and some other branch
|
||||
/// completes first, it is guaranteed that no items were received on any of
|
||||
/// the underlying streams.
|
||||
/// [`tokio::select!`] statement and some other branch completes first,
|
||||
/// it is guaranteed that no items were received on any of the underlying
|
||||
/// streams.
|
||||
///
|
||||
/// [`tokio::select!`]: https://docs.rs/tokio/latest/tokio/macro.select.html
|
||||
pub async fn next_many(&mut self, buffer: &mut Vec<(K, V::Item)>, limit: usize) -> usize {
|
||||
poll_fn(|cx| self.poll_next_many(cx, buffer, limit)).await
|
||||
}
|
||||
@@ -733,22 +736,15 @@ mod rand {
|
||||
#[cfg(not(loom))]
|
||||
pub(crate) mod rand {
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::hash::BuildHasher;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
pub(crate) fn seed() -> u64 {
|
||||
let rand_state = RandomState::new();
|
||||
|
||||
let mut hasher = rand_state.build_hasher();
|
||||
|
||||
// Hash some unique-ish data to generate some new state
|
||||
COUNTER.fetch_add(1, Relaxed).hash(&mut hasher);
|
||||
|
||||
// Get the seed
|
||||
hasher.finish()
|
||||
RandomState::new().hash_one(COUNTER.fetch_add(1, Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ cfg_sync! {
|
||||
}
|
||||
|
||||
cfg_signal! {
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(loom)))]
|
||||
mod signal_unix;
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(loom)))]
|
||||
pub use signal_unix::SignalStream;
|
||||
|
||||
#[cfg(any(windows, docsrs))]
|
||||
@@ -39,12 +39,14 @@ cfg_time! {
|
||||
}
|
||||
|
||||
cfg_net! {
|
||||
#[cfg(not(loom))]
|
||||
mod tcp_listener;
|
||||
#[cfg(not(loom))]
|
||||
pub use tcp_listener::TcpListenerStream;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(loom)))]
|
||||
mod unix_listener;
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(loom)))]
|
||||
pub use unix_listener::UnixListenerStream;
|
||||
}
|
||||
|
||||
@@ -57,6 +59,8 @@ cfg_io_util! {
|
||||
}
|
||||
|
||||
cfg_fs! {
|
||||
#[cfg(not(loom))]
|
||||
mod read_dir;
|
||||
#[cfg(not(loom))]
|
||||
pub use read_dir::ReadDirStream;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,35 @@ use futures_core::Stream;
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
|
||||
use std::fmt;
|
||||
use std::task::{Context, Poll};
|
||||
use std::task::{ready, Context, Poll};
|
||||
|
||||
/// A wrapper around [`tokio::sync::broadcast::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::broadcast;
|
||||
/// use tokio_stream::wrappers::BroadcastStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), tokio::sync::broadcast::error::SendError<u8>> {
|
||||
/// let (tx, rx) = broadcast::channel(16);
|
||||
/// tx.send(10)?;
|
||||
/// tx.send(20)?;
|
||||
/// # // prevent the doc test from hanging
|
||||
/// drop(tx);
|
||||
///
|
||||
/// let mut stream = BroadcastStream::new(rx);
|
||||
/// assert_eq!(stream.next().await, Some(Ok(10)));
|
||||
/// assert_eq!(stream.next().await, Some(Ok(20)));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::sync::broadcast::Receiver`]: struct@tokio::sync::broadcast::Receiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
/// [`Stream`]: trait@futures_core::Stream
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
|
||||
pub struct BroadcastStream<T> {
|
||||
inner: ReusableBoxFuture<'static, (Result<T, RecvError>, Receiver<T>)>,
|
||||
@@ -30,7 +53,7 @@ pub enum BroadcastStreamRecvError {
|
||||
impl fmt::Display for BroadcastStreamRecvError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BroadcastStreamRecvError::Lagged(amt) => write!(f, "channel lagged by {}", amt),
|
||||
BroadcastStreamRecvError::Lagged(amt) => write!(f, "channel lagged by {amt}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
use crate::Stream;
|
||||
use futures_core::stream::FusedStream;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::time::{Instant, Interval};
|
||||
|
||||
/// A wrapper around [`Interval`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::time::{Duration, Instant, interval};
|
||||
/// use tokio_stream::wrappers::IntervalStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let start = Instant::now();
|
||||
/// let interval = interval(Duration::from_millis(10));
|
||||
/// let mut stream = IntervalStream::new(interval);
|
||||
/// for _ in 0..3 {
|
||||
/// if let Some(instant) = stream.next().await {
|
||||
/// println!("elapsed: {:.1?}", instant.duration_since(start));
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Interval`]: struct@tokio::time::Interval
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
@@ -37,6 +58,12 @@ impl Stream for IntervalStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedStream for IntervalStream {
|
||||
fn is_terminated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Interval> for IntervalStream {
|
||||
fn as_ref(&self) -> &Interval {
|
||||
&self.inner
|
||||
|
||||
@@ -8,6 +8,24 @@ use tokio::io::{AsyncBufRead, Lines};
|
||||
pin_project! {
|
||||
/// A wrapper around [`tokio::io::Lines`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::io::AsyncBufReadExt;
|
||||
/// use tokio_stream::wrappers::LinesStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let input = b"Hello\nWorld\n";
|
||||
/// let mut stream = LinesStream::new(input.lines());
|
||||
/// while let Some(line) = stream.next().await {
|
||||
/// println!("{}", line?);
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::io::Lines`]: struct@tokio::io::Lines
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -5,6 +5,29 @@ use tokio::sync::mpsc::Receiver;
|
||||
|
||||
/// A wrapper around [`tokio::sync::mpsc::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::mpsc;
|
||||
/// use tokio_stream::wrappers::ReceiverStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), tokio::sync::mpsc::error::SendError<u8>> {
|
||||
/// let (tx, rx) = mpsc::channel(2);
|
||||
/// tx.send(10).await?;
|
||||
/// tx.send(20).await?;
|
||||
/// # // prevent the doc test from hanging
|
||||
/// drop(tx);
|
||||
///
|
||||
/// let mut stream = ReceiverStream::new(rx);
|
||||
/// assert_eq!(stream.next().await, Some(10));
|
||||
/// assert_eq!(stream.next().await, Some(20));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::sync::mpsc::Receiver`]: struct@tokio::sync::mpsc::Receiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
@@ -44,6 +67,25 @@ impl<T> Stream for ReceiverStream<T> {
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
|
||||
/// Returns the bounds of the stream based on the underlying receiver.
|
||||
///
|
||||
/// For open channels, it returns `(receiver.len(), None)`.
|
||||
///
|
||||
/// For closed channels, it returns `(receiver.len(), Some(used_capacity))`
|
||||
/// where `used_capacity` is calculated as `receiver.max_capacity() -
|
||||
/// receiver.capacity()`. This accounts for any [`Permit`] that is still
|
||||
/// able to send a message.
|
||||
///
|
||||
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if self.inner.is_closed() {
|
||||
let used_capacity = self.inner.max_capacity() - self.inner.capacity();
|
||||
(self.inner.len(), Some(used_capacity))
|
||||
} else {
|
||||
(self.inner.len(), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<Receiver<T>> for ReceiverStream<T> {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user