mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
539
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab1ce8706a | ||
|
|
481e78e912 | ||
|
|
63856b7bc1 | ||
|
|
a7d52c2fed | ||
|
|
f5f2b58b8d | ||
|
|
718dcc8dff | ||
|
|
10e141d211 | ||
|
|
6e42c26c80 | ||
|
|
82bef00db4 | ||
|
|
40633fc678 | ||
|
|
3dd5f7ae2e | ||
|
|
2c92cad9db | ||
|
|
197757d440 | ||
|
|
8b8005ebdd | ||
|
|
6cb106c353 | ||
|
|
dd23f08c3a | ||
|
|
5d29bdfb0c | ||
|
|
4c220af777 | ||
|
|
0a631f88e8 | ||
|
|
ee44dc98d8 | ||
|
|
51cffbb74f | ||
|
|
8832e936b1 | ||
|
|
dbda2045f1 | ||
|
|
7c54fdce3e | ||
|
|
38d1bcd9df | ||
|
|
52e6510215 | ||
|
|
e5e88551d2 | ||
|
|
efe3ab679a | ||
|
|
fb08591b43 | ||
|
|
6aca07bee7 | ||
|
|
5128601898 | ||
|
|
c445e467ce | ||
|
|
a58beb3aca | ||
|
|
4165601b1b | ||
|
|
63577cd8d3 | ||
|
|
02544540f1 | ||
|
|
d64c8e3ae0 | ||
|
|
f24b9824e6 | ||
|
|
267a231581 | ||
|
|
05feb2b0bb | ||
|
|
33d6d4f63c | ||
|
|
6166e9bcad | ||
|
|
e52d56e807 | ||
|
|
304d140361 | ||
|
|
91ad76c00c | ||
|
|
74a5a458ea | ||
|
|
0d382faa4e | ||
|
|
d8847cf891 | ||
|
|
918cf08a5f | ||
|
|
fc69666f8a | ||
|
|
bb4512eae0 | ||
|
|
9dbf1879ee | ||
|
|
1b1b9dc7e3 | ||
|
|
012c848401 | ||
|
|
6e990eb2c8 | ||
|
|
b573adc733 | ||
|
|
0c7d8d10fb | ||
|
|
ec1f52e1d3 | ||
|
|
1bfe778acb | ||
|
|
ce23db6bc7 | ||
|
|
48c55768fd | ||
|
|
657fd883d2 | ||
|
|
6b076a2743 | ||
|
|
910a1e2fcf | ||
|
|
6d25a00145 | ||
|
|
78bf8a9e5e | ||
|
|
b8af5aad16 | ||
|
|
2e62374e4a | ||
|
|
56c4365584 | ||
|
|
fb0d305a7a | ||
|
|
848482d2bb | ||
|
|
00af6eff77 | ||
|
|
b7290910f7 | ||
|
|
af6c87a045 | ||
|
|
6257712d68 | ||
|
|
c5d52c17ae | ||
|
|
2a54ad01d0 | ||
|
|
cb18b0a231 | ||
|
|
7ccd3e0c6d | ||
|
|
e63d0f10bf | ||
|
|
a2941e48be | ||
|
|
1c8d22c18b | ||
|
|
cbb3c155dd | ||
|
|
038c4d9999 | ||
|
|
7b24b22901 | ||
|
|
779b9c19d5 | ||
|
|
1204da7300 | ||
|
|
e75ca93d30 | ||
|
|
15712018da | ||
|
|
076d77c186 | ||
|
|
e2853c1b49 | ||
|
|
8f0103f6c5 | ||
|
|
fb4d43017d | ||
|
|
1e14ef0093 | ||
|
|
a8b6353535 | ||
|
|
c748f4965e | ||
|
|
a96dab1089 | ||
|
|
79a7e78c0d | ||
|
|
7c12e41d07 | ||
|
|
7a99f87df2 | ||
|
|
0b2c9b8bab | ||
|
|
98c8c38e96 | ||
|
|
080d52902f | ||
|
|
e87ff8a83a | ||
|
|
1605279abf | ||
|
|
25258d572a | ||
|
|
8ddb58bf6c | ||
|
|
4b032a25a4 | ||
|
|
9f9db7da63 | ||
|
|
d274ef3748 | ||
|
|
5e6d4c7999 | ||
|
|
9eb3f5b556 | ||
|
|
3a94eb0893 | ||
|
|
93bde0870f | ||
|
|
ddd7250e62 | ||
|
|
f64a1a3dbd | ||
|
|
c88f9bc930 | ||
|
|
29a6f468a6 | ||
|
|
8c076cb00d | ||
|
|
c84d0a14b1 | ||
|
|
a883fd4378 | ||
|
|
1014262d34 | ||
|
|
f6313f4382 | ||
|
|
70364b7079 | ||
|
|
dd9471d13a | ||
|
|
4e2ef63c4e | ||
|
|
dec390df1e | ||
|
|
89b73f39bf | ||
|
|
a26fc9c9f9 | ||
|
|
7fe88ce4ad | ||
|
|
f2d033e454 | ||
|
|
c999699f5e | ||
|
|
7430865d65 | ||
|
|
56239a9035 | ||
|
|
1b4106a1ce | ||
|
|
3abe877bf7 | ||
|
|
61b68a8abc | ||
|
|
52bc6b6f2d | ||
|
|
f478ff4a24 | ||
|
|
660eac71f0 | ||
|
|
1d785fd66f | ||
|
|
6a8f6f5a90 | ||
|
|
398dfda56d | ||
|
|
9bdc475539 | ||
|
|
b5a5ddb4cf | ||
|
|
74c6e6c683 | ||
|
|
f21d596099 | ||
|
|
66c62a4b74 | ||
|
|
a86c052218 | ||
|
|
c1778eda38 | ||
|
|
11b8807544 | ||
|
|
e789b61424 | ||
|
|
2cd4f4ab46 | ||
|
|
b6bbe5f487 | ||
|
|
57ba4a4b10 | ||
|
|
5e6c6bdafd | ||
|
|
77e3911806 | ||
|
|
b9868b23aa | ||
|
|
623483c81f | ||
|
|
5cef6eba7b | ||
|
|
db543639e1 | ||
|
|
9f9e596eec | ||
|
|
f6cb6e084b | ||
|
|
7aea597a8f | ||
|
|
9507f8b374 | ||
|
|
6037faeede | ||
|
|
8497f379b5 | ||
|
|
1b22cbfd33 | ||
|
|
effead29d1 | ||
|
|
3b16564ce0 | ||
|
|
fc1e03f91b | ||
|
|
ea5d448ee8 | ||
|
|
abc93f615e | ||
|
|
cf9a03c107 | ||
|
|
3b45e8614d | ||
|
|
b02c550c52 | ||
|
|
03912b9cf7 | ||
|
|
d4afbad6e5 | ||
|
|
b1ca0d8b12 | ||
|
|
16cdb109f4 | ||
|
|
88445e762c | ||
|
|
3c403d6ee8 | ||
|
|
d63d659078 | ||
|
|
b31f1a4662 | ||
|
|
663e56e983 | ||
|
|
1df874ead4 | ||
|
|
614fe357fc | ||
|
|
68b02db154 | ||
|
|
822af18cf5 | ||
|
|
92d33b7181 | ||
|
|
1cb7bf11b3 | ||
|
|
768ede65c1 | ||
|
|
54a394696f | ||
|
|
35dd635630 | ||
|
|
a7bb054414 | ||
|
|
0c8e8248f8 | ||
|
|
2dfe4e8885 | ||
|
|
b489acb46c | ||
|
|
d46c844bb9 | ||
|
|
4cd4b02389 | ||
|
|
17cc283f58 | ||
|
|
0a93ed7e7a | ||
|
|
cef98e25e7 | ||
|
|
e7bd754231 | ||
|
|
d459a93453 | ||
|
|
f177aad6e4 | ||
|
|
4ea632005d | ||
|
|
bfc43795f9 | ||
|
|
89329cd07f | ||
|
|
e34978233b | ||
|
|
8eb94a33c0 | ||
|
|
2b7b1a0494 | ||
|
|
002f4a28c8 | ||
|
|
ff2f286c12 | ||
|
|
bd4ce68864 | ||
|
|
abd92fb27f | ||
|
|
9931901d5c | ||
|
|
a377240bbf | ||
|
|
52da177dea | ||
|
|
ee1c940709 | ||
|
|
815d89a407 | ||
|
|
54aaf3d0e3 | ||
|
|
5a3abe56ee | ||
|
|
d44b1ca9c8 | ||
|
|
e23c6f3935 | ||
|
|
0a50cb3baa | ||
|
|
2298679af4 | ||
|
|
cadcd5da5e | ||
|
|
ca9f7ee9f4 | ||
|
|
c89406965f | ||
|
|
cf486361d0 | ||
|
|
d7b7c61317 | ||
|
|
12f81ffa61 | ||
|
|
3ea5cc5a82 | ||
|
|
fa31cd9990 | ||
|
|
46f974d8cf | ||
|
|
018d0450c7 | ||
|
|
ee09e04c31 | ||
|
|
d07027f5bc | ||
|
|
2e0372be6f | ||
|
|
eca24068f7 | ||
|
|
795754a846 | ||
|
|
0f17d69303 | ||
|
|
2e7f996f17 | ||
|
|
901f6d26c6 | ||
|
|
a8fda87058 | ||
|
|
d7abdbb315 | ||
|
|
24aac0add3 | ||
|
|
b921fe45ac | ||
|
|
0dc1b71e6e | ||
|
|
d19f2f2d39 | ||
|
|
e106c4d32b | ||
|
|
28d6f4d509 | ||
|
|
d1da6c20d8 | ||
|
|
e629ad7c9a | ||
|
|
36fdccc3bc | ||
|
|
01bb1ecf4d | ||
|
|
36d2233579 | ||
|
|
74fb9e387a | ||
|
|
8b44077ebc | ||
|
|
d6dbefcdc0 | ||
|
|
d96bbf0465 | ||
|
|
09b2653e71 | ||
|
|
061325ba7e | ||
|
|
d7d5d05333 | ||
|
|
1dcfe1cc9b | ||
|
|
5653b4583c | ||
|
|
abf5d28f2c | ||
|
|
a7945b469d | ||
|
|
80ec80165b | ||
|
|
88b1eb54fb | ||
|
|
1f50c57185 | ||
|
|
a18b3645f3 | ||
|
|
fe2dcb9453 | ||
|
|
c90757f07a | ||
|
|
f3f8e4f17f | ||
|
|
42bec96189 | ||
|
|
4f6a95badc | ||
|
|
3d33610ed2 | ||
|
|
38a9c6c1a5 | ||
|
|
06f1a601bb | ||
|
|
40782efb76 | ||
|
|
c390a62387 | ||
|
|
f9dbfa8251 | ||
|
|
31c7e82919 | ||
|
|
8d8db27442 | ||
|
|
dfe252d1fa | ||
|
|
21b233fa9c | ||
|
|
72993044e6 | ||
|
|
1a997ffbd6 | ||
|
|
a8fe333cc4 | ||
|
|
c6552c5680 | ||
|
|
048049f888 | ||
|
|
98d484e29c | ||
|
|
ef0224246b | ||
|
|
4a4f80ca70 | ||
|
|
9af2f5ee59 | ||
|
|
b75dba6904 | ||
|
|
353e5cabb8 | ||
|
|
8d58dc85b5 | ||
|
|
519afd4458 | ||
|
|
682e93df93 | ||
|
|
b9ae7e6659 | ||
|
|
d9e0f66113 | ||
|
|
6b3727d580 | ||
|
|
e14ca72e68 | ||
|
|
42db755ac1 | ||
|
|
81b50e946f | ||
|
|
39766220f4 | ||
|
|
ae69d11d1f | ||
|
|
c693ccd210 | ||
|
|
36039d0bb9 | ||
|
|
22cff80048 | ||
|
|
07da5e73ee | ||
|
|
c4ed16d1b4 | ||
|
|
3ce5a2681c | ||
|
|
644cb8207d | ||
|
|
a1316cd792 | ||
|
|
86ffabe2af | ||
|
|
00bf5ee8a8 | ||
|
|
87510100ce | ||
|
|
2be71ad746 | ||
|
|
d1b789f33a | ||
|
|
22862739dd | ||
|
|
993a60b7c7 | ||
|
|
5d25ec46d5 | ||
|
|
766f22fae3 | ||
|
|
f5686f6bc0 | ||
|
|
224acd2500 | ||
|
|
2fcc6c2cb0 | ||
|
|
28ec4a6161 | ||
|
|
939b5bb42f | ||
|
|
718d6ce8ca | ||
|
|
e316428210 | ||
|
|
fc83e01949 | ||
|
|
3a5f7b7f2f | ||
|
|
6da81471f9 | ||
|
|
299bd6aee3 | ||
|
|
e14307393a | ||
|
|
45e37dbfa2 | ||
|
|
304b5152a7 | ||
|
|
f15d14ee91 | ||
|
|
6a2cd9a652 | ||
|
|
2682c505e8 | ||
|
|
808d52563e | ||
|
|
bf31759bff | ||
|
|
d65826236b | ||
|
|
1cbbcc9ad5 | ||
|
|
a668020150 | ||
|
|
01f0193971 | ||
|
|
71bd49e146 | ||
|
|
582d512907 | ||
|
|
b7812c85ca | ||
|
|
9e3fb1673a | ||
|
|
53cba023da | ||
|
|
236d026667 | ||
|
|
909439c9f5 | ||
|
|
9884fe3394 | ||
|
|
a1002a2203 | ||
|
|
fc9518b627 | ||
|
|
f4643608ad | ||
|
|
687aa2bae5 | ||
|
|
b2f5dbea47 | ||
|
|
23fdd32b01 | ||
|
|
5b46395a1e | ||
|
|
32da1aa9da | ||
|
|
df6348fb4a | ||
|
|
74a29be607 | ||
|
|
26791a62bc | ||
|
|
467adec4e1 | ||
|
|
d8ee205530 | ||
|
|
ca8e176ce9 | ||
|
|
a051ed726f | ||
|
|
203a079743 | ||
|
|
b1f40f4356 | ||
|
|
a9d5eb2fc7 | ||
|
|
c2210dfe37 | ||
|
|
df99428c17 | ||
|
|
d1a8ec6495 | ||
|
|
fe1843c0e0 | ||
|
|
15b362d2dd | ||
|
|
620880f4ca | ||
|
|
29c6de0e3e | ||
|
|
3886a3eaf4 | ||
|
|
3b5ef4eb98 | ||
|
|
cbbf81b922 | ||
|
|
1ab80ba580 | ||
|
|
7483509746 | ||
|
|
121f839b4b | ||
|
|
b749013a54 | ||
|
|
2f24451434 | ||
|
|
5c2e275659 | ||
|
|
6c19748f90 | ||
|
|
32d68fe579 | ||
|
|
4bcd08b938 | ||
|
|
cb67f28fe3 | ||
|
|
58c457190b | ||
|
|
ec66a92b01 | ||
|
|
1ca17bef67 | ||
|
|
80568dfc6d | ||
|
|
b248be2879 | ||
|
|
d32ba2cf9d | ||
|
|
a03e042024 | ||
|
|
00082c668f | ||
|
|
607816491d | ||
|
|
1e437595a3 | ||
|
|
bf5eed8fa0 | ||
|
|
37d1d09d4a | ||
|
|
6929decf2c | ||
|
|
f8097437dd | ||
|
|
964535eab0 | ||
|
|
23a1ccf24f | ||
|
|
ca9dd726b1 | ||
|
|
ae0d49d59c | ||
|
|
992a168122 | ||
|
|
9b486357ed | ||
|
|
fdc28bc883 | ||
|
|
5dbd8ca8d4 | ||
|
|
ff4ab49bcf | ||
|
|
6bdcb813c6 | ||
|
|
b821e436c5 | ||
|
|
f4e08aec66 | ||
|
|
96fab053ab | ||
|
|
9b87daad7e | ||
|
|
e8354279fb | ||
|
|
78fc94099b | ||
|
|
2df45234ef | ||
|
|
aedcec666d | ||
|
|
909de08845 | ||
|
|
3db330dad2 | ||
|
|
feef48fb58 | ||
|
|
47b6e194fb | ||
|
|
a79b824faa | ||
|
|
3fbf314985 | ||
|
|
7096a80075 | ||
|
|
d69e5bebf1 | ||
|
|
0b92f80a65 | ||
|
|
cba5c1009e | ||
|
|
cdd6eeaf70 | ||
|
|
2effa7ff8a | ||
|
|
ebeb78ed40 | ||
|
|
b5709baa91 | ||
|
|
9e02759779 | ||
|
|
198e2d8e79 | ||
|
|
57e08e7147 | ||
|
|
98e642d234 | ||
|
|
3f379abda4 | ||
|
|
588408c060 | ||
|
|
ac1ae2cfbc | ||
|
|
56ffea09e5 | ||
|
|
b891714bdb | ||
|
|
0fddb765d4 | ||
|
|
29e3584c4d | ||
|
|
0d68bef7f7 | ||
|
|
dea1cd4995 | ||
|
|
e4cbc70279 | ||
|
|
97e981e797 | ||
|
|
3a4f18b93b | ||
|
|
1c823093cb | ||
|
|
0b1f640308 | ||
|
|
d7b3b33c9c | ||
|
|
71b29b9409 | ||
|
|
3966acf966 | ||
|
|
dfdb550cd4 | ||
|
|
99aa8d12b7 | ||
|
|
2ad347465e | ||
|
|
291fce8de3 | ||
|
|
0267516214 | ||
|
|
bbfd34f6a3 | ||
|
|
116fa7c614 | ||
|
|
1fd7f82468 | ||
|
|
c2612b446f | ||
|
|
50795e652e | ||
|
|
a6a95bb4a6 | ||
|
|
ce5d2a466f | ||
|
|
e5467ca767 | ||
|
|
431ec68f93 | ||
|
|
f207e1afe4 | ||
|
|
d3cae06b5e | ||
|
|
01ebb0aa29 | ||
|
|
adc774bc4f | ||
|
|
5a2bc850af | ||
|
|
ba93b28033 | ||
|
|
505cc0901d | ||
|
|
a3411a412c | ||
|
|
218f2629ff | ||
|
|
a66884a2fb | ||
|
|
b023522a37 | ||
|
|
733931d85f | ||
|
|
df28ac092f | ||
|
|
d720770b07 | ||
|
|
c9d444e8e0 | ||
|
|
e005b6c899 | ||
|
|
f5c1ff7599 | ||
|
|
b67b8c1398 | ||
|
|
de81985762 | ||
|
|
4b1c4801b1 | ||
|
|
b3c7c9846b | ||
|
|
d416b1d1d6 | ||
|
|
6e7a630243 | ||
|
|
16427f98c1 | ||
|
|
50b8ee99b7 | ||
|
|
bdbb0ece10 | ||
|
|
53e127205b | ||
|
|
cd0ff7fbfe | ||
|
|
d48c4370ce | ||
|
|
9d9488db67 | ||
|
|
53cf021b81 | ||
|
|
aea09478e1 | ||
|
|
2099d0bd87 | ||
|
|
255c1f95b7 | ||
|
|
ff6fbc327d | ||
|
|
199878e287 | ||
|
|
5ab6aaf3cd | ||
|
|
1d75b57ad0 | ||
|
|
b2ada60e70 | ||
|
|
0dc62da21b | ||
|
|
28ce4eeab2 | ||
|
|
ffff9e65fd | ||
|
|
0906351daf | ||
|
|
f3e340a35b | ||
|
|
94a7eaed51 | ||
|
|
62593be261 | ||
|
|
1578575db8 | ||
|
|
8c482a5d62 | ||
|
|
5288e1e144 | ||
|
|
56be5286ee | ||
|
|
21900bd42b | ||
|
|
228d4fce99 | ||
|
|
cbdba8bd32 | ||
|
|
6f048ca954 | ||
|
|
922fc91408 | ||
|
|
5cf22e48c5 | ||
|
|
14fca343d5 | ||
|
|
8e20cfb9ef | ||
|
|
d7b074eca3 | ||
|
|
3b6c74a40a | ||
|
|
de686b5355 | ||
|
|
6d3f92dddc |
@@ -1,2 +0,0 @@
|
||||
# [build]
|
||||
# rustflags = ["--cfg", "tokio_unstable"]
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
resource_class: arm.medium
|
||||
environment:
|
||||
# Change to pin rust version
|
||||
RUST_STABLE: 1.62.1
|
||||
RUST_STABLE: stable
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-1
|
||||
env:
|
||||
RUST_STABLE: 1.62.1
|
||||
RUST_NIGHTLY: nightly-2022-03-21
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2022-10-25
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
msrv = "1.49"
|
||||
msrv = "1.56"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://github.com/tokio-rs/tokio/discussions
|
||||
about: Questions about Tokio should be posted as a GitHub discussion.
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Please use the discussions tab for questions
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please post your question as a discussion here:
|
||||
https://github.com/tokio-rs/tokio/discussions
|
||||
|
||||
|
||||
You may also be able to find help here:
|
||||
https://discord.gg/tokio
|
||||
https://users.rust-lang.org/
|
||||
+25
-5
@@ -1,8 +1,28 @@
|
||||
|
||||
R-loom:
|
||||
R-loom-sync:
|
||||
- tokio/src/sync/*
|
||||
- tokio/src/sync/**/*
|
||||
- tokio-util/src/sync/*
|
||||
- tokio-util/src/sync/**/*
|
||||
- tokio/src/runtime/*
|
||||
- tokio/src/runtime/**/*
|
||||
|
||||
R-loom-time-driver:
|
||||
- tokio/src/runtime/time/*
|
||||
- tokio/src/runtime/time/**/*
|
||||
|
||||
R-loom-current-thread:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/current_thread/*
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-multi-thread:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/*
|
||||
- tokio/src/runtime/scheduler/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/**
|
||||
|
||||
@@ -9,14 +9,22 @@ on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Audit Check
|
||||
uses: actions-rs/audit-check@v1
|
||||
# https://github.com/rustsec/audit-check/issues/2
|
||||
uses: rustsec/audit-check@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+568
-138
@@ -6,13 +6,17 @@ on:
|
||||
|
||||
name: CI
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: 1.62.1
|
||||
rust_nightly: nightly-2022-04-18
|
||||
rust_clippy: 1.56.0
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2023-05-18
|
||||
rust_clippy: 1.65.0
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
# - tokio/README.md
|
||||
@@ -21,37 +25,67 @@ env:
|
||||
# - tokio-util/Cargo.toml
|
||||
# - tokio-test/Cargo.toml
|
||||
# - tokio-stream/Cargo.toml
|
||||
rust_min: 1.49.0
|
||||
rust_min: 1.63.0
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Depends on all action sthat are required for a "successful" CI run.
|
||||
# Depends on all actions that are required for a "successful" CI run.
|
||||
tests-pass:
|
||||
name: all systems go
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
- test-unstable
|
||||
- test-tokio-full
|
||||
- test-workspace-all-features
|
||||
- test-integration-tests-per-feature
|
||||
- test-parking_lot
|
||||
- valgrind
|
||||
- test-unstable
|
||||
- miri
|
||||
- cross
|
||||
- asan
|
||||
- cross-check
|
||||
- cross-test-with-parking_lot
|
||||
- cross-test-without-parking_lot
|
||||
- no-atomic-u64-test
|
||||
- no-atomic-u64-check
|
||||
- features
|
||||
- minrust
|
||||
- minimal-versions
|
||||
- fmt
|
||||
- clippy
|
||||
- docs
|
||||
- valgrind
|
||||
- loom-compile
|
||||
- check-readme
|
||||
- test-hyper
|
||||
- x86_64-fortanix-unknown-sgx
|
||||
- check-redox
|
||||
- wasm32-unknown-unknown
|
||||
- wasm32-wasi
|
||||
- check-external-types
|
||||
- check-fuzzing
|
||||
- check-unstable-mt-counters
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
test:
|
||||
# Basic actions that must pass before we kick off more expensive tests.
|
||||
basics:
|
||||
name: basic checks
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- clippy
|
||||
- fmt
|
||||
- docs
|
||||
- minrust
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
test-tokio-full:
|
||||
needs: basics
|
||||
name: test tokio full
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -63,25 +97,77 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack --version 0.5.26
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Run `tokio` with `full` features. This excludes testing utilities which
|
||||
# can alter the runtime behavior of Tokio.
|
||||
- name: test tokio full
|
||||
run: cargo test --features full
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --features full
|
||||
cargo test --doc --features full
|
||||
working-directory: tokio
|
||||
|
||||
test-workspace-all-features:
|
||||
needs: basics
|
||||
name: test all crates in the workspace with all features
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Test **all** crates in the workspace with all features.
|
||||
- name: test all --all-features
|
||||
run: cargo test --workspace --all-features
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --workspace --all-features
|
||||
cargo test --doc --workspace --all-features
|
||||
|
||||
test-integration-tests-per-feature:
|
||||
needs: basics
|
||||
name: Run integration tests for each feature
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Run integration tests for each feature
|
||||
- name: test tests-integration --each-feature
|
||||
@@ -93,9 +179,9 @@ jobs:
|
||||
run: cargo hack test --each-feature
|
||||
working-directory: tests-build
|
||||
|
||||
# Build benchmarks. Run of benchmarks is done by bench.yml workflow.
|
||||
- name: build benches
|
||||
run: cargo build --benches
|
||||
# Check benchmarks. Run of benchmarks is done by bench.yml workflow.
|
||||
- name: Check benches
|
||||
run: cargo check --benches
|
||||
working-directory: benches
|
||||
# bench.yml workflow runs benchmarks only on linux.
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
@@ -109,38 +195,38 @@ jobs:
|
||||
# This relies on the potentially affected Tokio type being listed in
|
||||
# `tokio/tokio/tests/async_send_sync.rs`.
|
||||
name: compile tests with parking lot send guards
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Enable parking_lot send_guard feature
|
||||
# Inserts the line "plsend = ["parking_lot/send_guard"]" right after [features]
|
||||
run: sed -i '/\[features\]/a plsend = ["parking_lot/send_guard"]' tokio/Cargo.toml
|
||||
- name: Compile tests with all features enabled
|
||||
run: cargo build --workspace --all-features --tests
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Check tests with all features enabled
|
||||
run: cargo check --workspace --all-features --tests
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install Valgrind
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y valgrind
|
||||
uses: taiki-e/install-action@valgrind
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Compile tests
|
||||
- name: cargo build test-mem
|
||||
run: cargo build --features rt-net --bin test-mem
|
||||
@@ -148,7 +234,7 @@ jobs:
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind test-mem
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/debug/test-mem
|
||||
|
||||
# Compile tests
|
||||
- name: cargo build test-process-signal
|
||||
@@ -157,28 +243,37 @@ jobs:
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind test-process-signal
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/test-process-signal
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/debug/test-process-signal
|
||||
|
||||
test-unstable:
|
||||
name: test tokio full --unstable
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
include:
|
||||
- os: windows-latest
|
||||
- os: ubuntu-latest
|
||||
- os: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" cfg flag.
|
||||
- name: test tokio full --cfg unstable
|
||||
run: cargo test --all-features
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
@@ -186,29 +281,96 @@ jobs:
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable
|
||||
|
||||
test-unstable-taskdump:
|
||||
name: test tokio full --unstable --taskdump
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" and "taskdump" cfg flags.
|
||||
- name: test tokio full --cfg unstable --cfg taskdump
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_taskdump
|
||||
|
||||
check-unstable-mt-counters:
|
||||
name: check tokio full --internal-mt-counters
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" and "taskdump" cfg flags.
|
||||
- name: check tokio full --cfg unstable --cfg internal-mt-counters
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_internal_mt_counters -Dwarnings
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_internal_mt_counters
|
||||
|
||||
miri:
|
||||
name: miri
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: nightly-2022-07-10
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: miri
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: miri
|
||||
# Many of tests in tokio/tests and doctests use #[tokio::test] or
|
||||
# #[tokio::main] that calls epoll_create1 that Miri does not support.
|
||||
run: cargo miri test --features full --lib --no-fail-fast
|
||||
run: |
|
||||
cargo miri test --features full --lib --no-fail-fast
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-tag-raw-pointers
|
||||
PROPTEST_CASES: 10
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
|
||||
asan:
|
||||
name: asan
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -216,25 +378,37 @@ jobs:
|
||||
# Required to resolve symbols in sanitizer output
|
||||
run: sudo apt-get install -y llvm
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: asan
|
||||
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1
|
||||
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1 --nocapture
|
||||
env:
|
||||
RUSTFLAGS: -Z sanitizer=address
|
||||
RUSTFLAGS: -Z sanitizer=address --cfg tokio_no_tuning_tests
|
||||
# Ignore `trybuild` errors as they are irrelevant and flaky on nightly
|
||||
TRYBUILD: overwrite
|
||||
|
||||
cross:
|
||||
name: cross
|
||||
semver:
|
||||
name: semver
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check semver
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: ${{ env.rust_stable }}
|
||||
release-type: minor
|
||||
|
||||
cross-check:
|
||||
name: cross-check
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- i686-unknown-linux-gnu
|
||||
- powerpc-unknown-linux-gnu
|
||||
- powerpc64-unknown-linux-gnu
|
||||
- mips-unknown-linux-gnu
|
||||
@@ -243,45 +417,181 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: check
|
||||
args: --workspace --all-features --target ${{ matrix.target }}
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: check
|
||||
args: --workspace --all-features --target ${{ matrix.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo check --workspace --all-features --target ${{ matrix.target }}
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
features:
|
||||
name: features
|
||||
cross-test-with-parking_lot:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: armv5te-unknown-linux-gnueabi
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Tests run with all features (including parking_lot)
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run -p tokio --all-features --target ${{ matrix.target }}
|
||||
cargo test --doc -p tokio --all-features --target ${{ matrix.target }}
|
||||
env:
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
|
||||
|
||||
cross-test-without-parking_lot:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: armv5te-unknown-linux-gnueabi
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Remove `parking_lot` from `full` feature
|
||||
run: sed -i '0,/parking_lot/{/parking_lot/d;}' tokio/Cargo.toml
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# The `tokio_no_parking_lot` cfg is here to ensure the `sed` above does not silently break.
|
||||
- name: Tests run with all features (without parking_lot)
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run -p tokio --features full,test-util --target ${{ matrix.target }}
|
||||
cargo test --doc -p tokio --features full,test-util --target ${{ matrix.target }}
|
||||
env:
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_parking_lot --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
|
||||
|
||||
# See https://github.com/tokio-rs/tokio/issues/5187
|
||||
no-atomic-u64-test:
|
||||
name: Test tokio --all-features on i686-unknown-linux-gnu without AtomicU64
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: rust-src
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: i686-unknown-linux-gnu
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test tokio --all-features
|
||||
run: |
|
||||
cargo nextest run -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
cargo test --doc -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
env:
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_tuning_tests
|
||||
|
||||
no-atomic-u64-check:
|
||||
name: Check tokio --feature-powerset --depth 2 on i686-unknown-linux-gnu without AtomicU64
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: rust-src
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# https://github.com/tokio-rs/tokio/pull/5356
|
||||
# https://github.com/tokio-rs/tokio/issues/5373
|
||||
- name: Check
|
||||
run: cargo hack check -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --feature-powerset --depth 2 --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
|
||||
features:
|
||||
name: features ${{ matrix.name }}
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { name: "", rustflags: "" }
|
||||
# Try with unstable feature flags
|
||||
- { name: "--unstable", rustflags: "--cfg tokio_unstable -Dwarnings" }
|
||||
# Try with unstable and taskdump feature flags
|
||||
- { name: "--unstable --taskdump", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump" }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack --version 0.5.26
|
||||
- name: check --each-feature
|
||||
run: cargo hack check --all --each-feature -Z avoid-dev-deps
|
||||
# Try with unstable feature flags
|
||||
- name: check --each-feature --unstable
|
||||
run: cargo hack check --all --each-feature -Z avoid-dev-deps
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: check --feature-powerset ${{ matrix.name }}
|
||||
run: cargo hack check --all --feature-powerset --depth 2 --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
RUSTFLAGS: ${{ matrix.rustflags }}
|
||||
|
||||
minrust:
|
||||
name: minrust
|
||||
@@ -289,28 +599,29 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_min }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_min }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: "test --all-features"
|
||||
run: cargo check --all-features
|
||||
working-directory: tokio
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "check --workspace --all-features"
|
||||
run: cargo check --workspace --all-features
|
||||
env:
|
||||
RUSTFLAGS: "" # remove -Dwarnings
|
||||
|
||||
minimal-versions:
|
||||
name: minimal-versions
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack --version 0.5.26
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "check --all-features -Z minimal-versions"
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
@@ -321,7 +632,7 @@ jobs:
|
||||
cargo hack check --all-features --ignore-private
|
||||
- name: "check --all-features --unstable -Z minimal-versions"
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
@@ -336,49 +647,63 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
components: rustfmt
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Check fmt
|
||||
- name: "rustfmt --check"
|
||||
# Workaround for rust-lang/cargo#7732
|
||||
run: |
|
||||
if ! rustfmt --check --edition 2018 $(git ls-files '*.rs'); then
|
||||
printf "Please run \`rustfmt --edition 2018 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
if ! rustfmt --check --edition 2021 $(git ls-files '*.rs'); then
|
||||
printf "Please run \`rustfmt --edition 2021 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_clippy }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_clippy }}
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run clippy
|
||||
- name: "clippy --all"
|
||||
run: cargo clippy --all --tests --all-features --no-deps
|
||||
|
||||
docs:
|
||||
name: docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "doc --lib --all-features"
|
||||
run: cargo doc --lib --no-deps --all-features --document-private-items
|
||||
run: |
|
||||
cargo doc --lib --no-deps --all-features --document-private-items
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
|
||||
loom-compile:
|
||||
name: build loom tests
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: build --cfg loom
|
||||
run: cargo test --no-run --lib --features full
|
||||
working-directory: tokio
|
||||
@@ -399,6 +724,7 @@ jobs:
|
||||
|
||||
test-hyper:
|
||||
name: Test hyper
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -409,19 +735,23 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Test hyper
|
||||
|
||||
- name: Clone hyper
|
||||
run: git clone https://github.com/hyperium/hyper.git
|
||||
|
||||
- name: Checkout the latest release because HEAD maybe contains breakage.
|
||||
run: |
|
||||
set -x
|
||||
git clone https://github.com/hyperium/hyper.git
|
||||
cd hyper
|
||||
# checkout the latest release because HEAD maybe contains breakage.
|
||||
tag=$(git describe --abbrev=0 --tags)
|
||||
git checkout "${tag}"
|
||||
working-directory: hyper
|
||||
|
||||
- name: Patch hyper to use tokio from this repository
|
||||
run: |
|
||||
set -x
|
||||
echo '[workspace]' >>Cargo.toml
|
||||
echo '[patch.crates-io]' >>Cargo.toml
|
||||
echo 'tokio = { path = "../tokio" }' >>Cargo.toml
|
||||
@@ -429,57 +759,157 @@ jobs:
|
||||
echo 'tokio-stream = { path = "../tokio-stream" }' >>Cargo.toml
|
||||
echo 'tokio-test = { path = "../tokio-test" }' >>Cargo.toml
|
||||
git diff
|
||||
cargo test --features full
|
||||
working-directory: hyper
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# The cargo workspaces and target directory configuration.
|
||||
# These entries are separated by newlines and have the form
|
||||
# `$workspace -> $target`. The `$target` part is treated as a directory
|
||||
# relative to the `$workspace` and defaults to "target" if not explicitly given.
|
||||
# default: ". -> target"
|
||||
workspaces: "./hyper"
|
||||
|
||||
- name: Test hyper
|
||||
run: cargo test --features full
|
||||
working-directory: hyper
|
||||
|
||||
x86_64-fortanix-unknown-sgx:
|
||||
name: build tokio for x86_64-fortanix-unknown-sgx
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
target: x86_64-fortanix-unknown-sgx
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# NOTE: Currently the only test we can run is to build tokio with rt and sync features.
|
||||
- name: build tokio
|
||||
run: cargo build --target x86_64-fortanix-unknown-sgx --features rt,sync
|
||||
working-directory: tokio
|
||||
|
||||
check-redox:
|
||||
name: build tokio for redox-os
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
target: x86_64-unknown-redox
|
||||
- name: check tokio on redox
|
||||
run: cargo check --target x86_64-unknown-redox --all-features
|
||||
working-directory: tokio
|
||||
|
||||
wasm32-unknown-unknown:
|
||||
name: test tokio for wasm32-unknown-unknown
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
uses: taiki-e/install-action@wasm-pack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test tokio
|
||||
run: wasm-pack test --node -- --features "macros sync"
|
||||
working-directory: tokio
|
||||
|
||||
wasm32-wasi:
|
||||
name: wasm32-wasi
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
targets: wasm32-wasi
|
||||
|
||||
# Install dependencies
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
- name: Install cargo-hack, wasmtime, and cargo-wasi
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack,wasmtime,cargo-wasi
|
||||
|
||||
- name: Install wasm32-wasi target
|
||||
run: rustup target add wasm32-wasi
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: WASI test tokio full
|
||||
run: cargo test -p tokio --target wasm32-wasi --features full
|
||||
env:
|
||||
CARGO_TARGET_WASM32_WASI_RUNNER: "wasmtime run --"
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
- name: Install wasmtime
|
||||
run: cargo install wasmtime-cli
|
||||
- name: WASI test tokio-util full
|
||||
run: cargo test -p tokio-util --target wasm32-wasi --features full
|
||||
env:
|
||||
CARGO_TARGET_WASM32_WASI_RUNNER: "wasmtime run --"
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
- name: Install cargo-wasi
|
||||
run: cargo install cargo-wasi
|
||||
|
||||
# TODO: Expand this when full WASI support lands.
|
||||
# Currently, this is a bare bones regression test
|
||||
# for features that work today with wasi.
|
||||
- name: WASI test tokio-stream
|
||||
run: cargo test -p tokio-stream --target wasm32-wasi --features time,net,io-util,sync
|
||||
env:
|
||||
CARGO_TARGET_WASM32_WASI_RUNNER: "wasmtime run --"
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
- name: test tests-integration --features wasi-rt
|
||||
# TODO: this should become: `cargo hack wasi test --each-feature`
|
||||
run: cargo wasi test --test rt_yield --features wasi-rt
|
||||
working-directory: tests-integration
|
||||
env:
|
||||
RUSTFLAGS: ""
|
||||
|
||||
check-external-types:
|
||||
name: check-external-types (${{ matrix.os }})
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
rust:
|
||||
# `check-external-types` requires a specific Rust nightly version. See
|
||||
# the README for details: https://github.com/awslabs/cargo-check-external-types
|
||||
- nightly-2023-05-31
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v1
|
||||
with:
|
||||
tool: [email protected]
|
||||
- name: check-external-types
|
||||
run: cargo check-external-types --all-features --config external-types.toml
|
||||
working-directory: tokio
|
||||
|
||||
check-fuzzing:
|
||||
name: check-fuzzing
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-fuzz
|
||||
run: cargo install cargo-fuzz
|
||||
- name: Check /tokio/
|
||||
run: cargo fuzz check --all-features
|
||||
working-directory: tokio
|
||||
- name: Check /tokio-stream/
|
||||
run: cargo fuzz check --all-features
|
||||
working-directory: tokio-stream
|
||||
|
||||
@@ -4,9 +4,20 @@ on:
|
||||
|
||||
# See .github/labeler.yml file
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
permissions:
|
||||
contents: read # for actions/labeler to determine modified files
|
||||
pull-requests: write # for actions/labeler to add labels to PRs
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'tokio-rs'
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
with:
|
||||
|
||||
+97
-20
@@ -7,39 +7,116 @@ on:
|
||||
|
||||
name: Loom
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUSTFLAGS: -Dwarnings --cfg loom --cfg tokio_unstable -C debug_assertions
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
LOOM_MAX_BRANCHES: 10000
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: 1.62.1
|
||||
rust_stable: stable
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
loom:
|
||||
name: loom
|
||||
loom-sync:
|
||||
name: loom tokio::sync
|
||||
# base_ref is null when it's not a pull request
|
||||
if: contains(github.event.pull_request.labels.*.name, 'R-loom') || (github.base_ref == null)
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-sync') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
scope:
|
||||
- --skip loom_pool
|
||||
- loom_pool::group_a
|
||||
- loom_pool::group_b
|
||||
- loom_pool::group_c
|
||||
- loom_pool::group_d
|
||||
- time::driver
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture sync::tests
|
||||
working-directory: tokio
|
||||
|
||||
loom-time-driver:
|
||||
name: loom time driver
|
||||
# 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-time-driver') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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
|
||||
working-directory: tokio
|
||||
|
||||
loom-current-thread:
|
||||
name: loom current-thread scheduler
|
||||
# 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-current-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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_current_thread
|
||||
working-directory: tokio
|
||||
|
||||
loom-multi-thread:
|
||||
name: loom multi-thread scheduler
|
||||
# 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') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- scope: loom_multi_thread::group_a
|
||||
- scope: loom_multi_thread::group_b
|
||||
- scope: loom_multi_thread::group_c
|
||||
- scope: loom_multi_thread::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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 -- --nocapture $SCOPE
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
loom-multi-thread-alt:
|
||||
name: loom ALT multi-thread scheduler
|
||||
# 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))
|
||||
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@v3
|
||||
- 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:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
@@ -8,6 +8,13 @@ on:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,17 +23,10 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install cargo-audit
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
|
||||
- name: Generate lockfile
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: generate-lockfile
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- name: Audit dependencies
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: audit
|
||||
run: cargo audit
|
||||
|
||||
@@ -5,14 +5,21 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: 1.62.1
|
||||
rust_stable: stable
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
stess-test:
|
||||
stress-test:
|
||||
name: Stress Test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -22,20 +29,17 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install Valgrind
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y valgrind
|
||||
uses: taiki-e/install-action@valgrind
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Compiles each of the stress test examples.
|
||||
- name: Compile stress test examples
|
||||
run: cargo build -p stress-test --release --example ${{ matrix.stress-test }}
|
||||
|
||||
# Runs each of the examples using Valgrind. Detects leaks and displays them.
|
||||
- name: Run valgrind
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/release/examples/${{ matrix.stress-test }}
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/release/examples/${{ matrix.stress-test }}
|
||||
|
||||
@@ -2,3 +2,4 @@ target
|
||||
Cargo.lock
|
||||
|
||||
.cargo/config.toml
|
||||
.cargo/config
|
||||
|
||||
+57
-17
@@ -131,8 +131,11 @@ cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
Clippy must be run using the MSRV, so Tokio can avoid having to `#[allow]` new
|
||||
lints whose fixes would be incompatible with the current MSRV:
|
||||
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:
|
||||
@@ -146,7 +149,7 @@ When updating this, also update:
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.49.0 clippy --all-features
|
||||
cargo +1.65.0 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation normally, the markers that list the features
|
||||
@@ -154,7 +157,7 @@ required for various parts of Tokio are missing. To build the documentation
|
||||
correctly, use this command:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
||||
RUSTDOCFLAGS="--cfg docsrs" RUSTFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
||||
```
|
||||
|
||||
To build documentation including Tokio's unstable features, it is necessary to
|
||||
@@ -162,24 +165,18 @@ pass `--cfg tokio_unstable` to both RustDoc *and* rustc. To build the
|
||||
documentation for unstable features, use this command:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg tokio_unstable" cargo +nightly doc --all-features
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features
|
||||
```
|
||||
|
||||
There is currently a [bug in cargo] that means documentation cannot be built
|
||||
from the root of the workspace. If you `cd` into the `tokio` subdirectory the
|
||||
command shown above will work.
|
||||
|
||||
[bug in cargo]: https://github.com/rust-lang/cargo/issues/9274
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2018 $(git ls-files '*.rs')
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
|
||||
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.
|
||||
@@ -193,7 +190,7 @@ LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" PROPTEST_CASES=10 \
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" \
|
||||
cargo +nightly miri test --features full --lib
|
||||
```
|
||||
|
||||
@@ -203,8 +200,22 @@ If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: integration tests and documentation tests
|
||||
(Tokio avoids unit tests as much as possible).
|
||||
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
|
||||
|
||||
@@ -215,6 +226,31 @@ 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 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
|
||||
@@ -562,7 +598,7 @@ Tokio ≥1.0.0 comes with LTS guarantees:
|
||||
|
||||
The goal of these guarantees is to provide stability to the ecosystem.
|
||||
|
||||
## Mininum Supported Rust Version (MSRV)
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
|
||||
compiler release.
|
||||
@@ -639,3 +675,7 @@ When releasing a new version of a crate, follow these steps:
|
||||
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
|
||||
|
||||
Generated
-32
@@ -1,32 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df"
|
||||
dependencies = [
|
||||
"difference",
|
||||
"float-cmp",
|
||||
"normalize-line-endings",
|
||||
"predicates-core",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
|
||||
resolver = "2"
|
||||
members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
[build.env]
|
||||
passthrough = [
|
||||
"RUSTFLAGS",
|
||||
"RUST_BACKTRACE",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
Copyright (c) 2023 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.20.5", features = ["full"] }
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -161,6 +161,16 @@ several other libraries, including:
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
|
||||
## Changelog
|
||||
|
||||
The Tokio repository contains multiple crates. Each crate has its own changelog.
|
||||
|
||||
* `tokio` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio/CHANGELOG.md)
|
||||
* `tokio-util` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-util/CHANGELOG.md)
|
||||
* `tokio-stream` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-stream/CHANGELOG.md)
|
||||
* `tokio-macros` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-macros/CHANGELOG.md)
|
||||
* `tokio-test` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-test/CHANGELOG.md)
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
<!--
|
||||
@@ -177,7 +187,21 @@ 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.49.0.
|
||||
released at least six months ago. The current MSRV is 1.63.
|
||||
|
||||
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.27 to 1.29 - Rust 1.56
|
||||
* 1.17 to 1.26 - Rust 1.49
|
||||
* 1.15 to 1.16 - Rust 1.46
|
||||
* 1.0 to 1.14 - Rust 1.45
|
||||
|
||||
Note that although we try to avoid the situation where a dependency transitively
|
||||
increases the MSRV of Tokio, we do not guarantee that this does not happen.
|
||||
However, every minor release will have some set of versions of dependencies that
|
||||
works with the MSRV of that minor release.
|
||||
|
||||
## Release schedule
|
||||
|
||||
@@ -192,20 +216,26 @@ 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.14.x` - LTS release until June 2022.
|
||||
* `1.18.x` - LTS release until January 2023
|
||||
* `1.20.x` - LTS release until September 2023. (MSRV 1.49)
|
||||
* `1.25.x` - LTS release until March 2024. (MSRV 1.49)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least half a
|
||||
year. If you wish to use a fixed minor release in your project, we recommend
|
||||
that you use an LTS release.
|
||||
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.14.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.18.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.14", features = [...] }
|
||||
tokio = { version = "~1.18", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
|
||||
* `1.8.x` - LTS release until February 2022.
|
||||
* `1.14.x` - LTS release until June 2022.
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
## Report a security issue
|
||||
|
||||
The Tokio project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [[email protected]](mailto:[email protected]). Security issues should not be reported via the public Github Issue tracker.
|
||||
The Tokio project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [[email protected]](mailto:[email protected]). Security issues should not be reported via the public GitHub Issue tracker.
|
||||
|
||||
## Vulnerability coordination
|
||||
|
||||
Remediation of security vulnerabilities is prioritized by the project team. The project team coordinates remediation with third-party project stakeholders via [Github Security Advisories](https://help.github.com/en/github/managing-security-vulnerabilities/about-github-security-advisories). Third-party stakeholders may include the reporter of the issue, affected direct or indirect users of Tokio, and maintainers of upstream dependencies if applicable.
|
||||
Remediation of security vulnerabilities is prioritized by the project team. The project team coordinates remediation with third-party project stakeholders via [GitHub Security Advisories](https://help.github.com/en/github/managing-security-vulnerabilities/about-github-security-advisories). Third-party stakeholders may include the reporter of the issue, affected direct or indirect users of Tokio, and maintainers of upstream dependencies if applicable.
|
||||
|
||||
Downstream project maintainers and Tokio users can request participation in coordination of applicable security issues by sending your contact email address, Github username(s) and any other salient information to [[email protected]](mailto:[email protected]). Participation in security issue coordination processes is at the discretion of the Tokio team.
|
||||
Downstream project maintainers and Tokio users can request participation in coordination of applicable security issues by sending your contact email address, GitHub username(s) and any other salient information to [[email protected]](mailto:[email protected]). Participation in security issue coordination processes is at the discretion of the Tokio team.
|
||||
|
||||
## Security advisories
|
||||
|
||||
The project team is committed to transparency in the security issue disclosure process. The Tokio team announces security issues via [project Github Release notes](https://github.com/tokio-rs/tokio/releases) and the [RustSec advisory database](https://github.com/RustSec/advisory-db) (i.e. `cargo-audit`).
|
||||
The project team is committed to transparency in the security issue disclosure process. The Tokio team announces security issues via [project GitHub Release notes](https://github.com/tokio-rs/tokio/releases) and the [RustSec advisory database](https://github.com/RustSec/advisory-db) (i.e. `cargo-audit`).
|
||||
|
||||
+35
-1
@@ -2,11 +2,16 @@
|
||||
name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
test-util = ["tokio/test-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
|
||||
@@ -25,11 +30,30 @@ name = "sync_mpsc"
|
||||
path = "sync_mpsc.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_mpsc_oneshot"
|
||||
path = "sync_mpsc_oneshot.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_watch"
|
||||
path = "sync_watch.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_current_thread"
|
||||
path = "rt_current_thread.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_multi_threaded"
|
||||
path = "rt_multi_threaded.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_notify"
|
||||
path = "sync_notify.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_rwlock"
|
||||
@@ -50,3 +74,13 @@ harness = false
|
||||
name = "fs"
|
||||
path = "fs.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "copy"
|
||||
path = "copy.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "time_now"
|
||||
path = "time_now.rs"
|
||||
harness = false
|
||||
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
use tokio::io::{copy, repeat, AsyncRead, AsyncReadExt, AsyncWrite};
|
||||
use tokio::time::{interval, Interval, MissedTickBehavior};
|
||||
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
|
||||
const KILO: usize = 1024;
|
||||
|
||||
// Tunable parameters if you want to change this benchmark. If reader and writer
|
||||
// are matched in kilobytes per second, then this only exposes buffering to the
|
||||
// benchmark.
|
||||
const RNG_SEED: u64 = 0;
|
||||
// How much data to copy in a single benchmark run
|
||||
const SOURCE_SIZE: u64 = 256 * KILO as u64;
|
||||
// Read side provides CHUNK_SIZE every READ_SERVICE_PERIOD. If it's not called
|
||||
// frequently, it'll burst to catch up (representing OS buffers draining)
|
||||
const CHUNK_SIZE: usize = 2 * KILO;
|
||||
const READ_SERVICE_PERIOD: Duration = Duration::from_millis(1);
|
||||
// Write side buffers up to WRITE_BUFFER, and flushes to disk every
|
||||
// WRITE_SERVICE_PERIOD.
|
||||
const WRITE_BUFFER: usize = 40 * KILO;
|
||||
const WRITE_SERVICE_PERIOD: Duration = Duration::from_millis(20);
|
||||
// How likely you are to have to wait for previously written data to be flushed
|
||||
// because another writer claimed the buffer space
|
||||
const PROBABILITY_FLUSH_WAIT: f64 = 0.1;
|
||||
|
||||
/// A slow writer that aims to simulate HDD behaviour under heavy load.
|
||||
///
|
||||
/// There is a limited buffer, which is fully drained on the next write after
|
||||
/// a time limit is reached. Flush waits for the time limit to be reached
|
||||
/// and then drains the buffer.
|
||||
///
|
||||
/// At random, the HDD will stall writers while it flushes out all buffers. If
|
||||
/// this happens to you, you will be unable to write until the next time the
|
||||
/// buffer is drained.
|
||||
struct SlowHddWriter {
|
||||
service_intervals: Interval,
|
||||
blocking_rng: ChaCha20Rng,
|
||||
buffer_size: usize,
|
||||
buffer_used: usize,
|
||||
}
|
||||
|
||||
impl SlowHddWriter {
|
||||
fn new(service_interval: Duration, buffer_size: usize) -> Self {
|
||||
let blocking_rng = ChaCha20Rng::seed_from_u64(RNG_SEED);
|
||||
let mut service_intervals = interval(service_interval);
|
||||
service_intervals.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||
Self {
|
||||
service_intervals,
|
||||
blocking_rng,
|
||||
buffer_size,
|
||||
buffer_used: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn service_write(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
// If we hit a service interval, the buffer can be cleared
|
||||
let res = self.service_intervals.poll_tick(cx).map(|_| Ok(()));
|
||||
if let Poll::Ready(_) = res {
|
||||
self.buffer_used = 0;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn write_bytes(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
writeable: usize,
|
||||
) -> 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) {
|
||||
return Poll::Pending;
|
||||
}
|
||||
let available = self.buffer_size - self.buffer_used;
|
||||
|
||||
if available == 0 {
|
||||
assert!(service_res.is_pending());
|
||||
Poll::Pending
|
||||
} else {
|
||||
let written = available.min(writeable);
|
||||
self.buffer_used += written;
|
||||
Poll::Ready(Ok(written))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpin for SlowHddWriter {}
|
||||
|
||||
impl AsyncWrite for SlowHddWriter {
|
||||
fn poll_write(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
self.write_bytes(cx, buf.len())
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
self.service_write(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
self.service_write(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
let writeable = bufs.into_iter().fold(0, |acc, buf| acc + buf.len());
|
||||
self.write_bytes(cx, writeable)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A reader that limits the maximum chunk it'll give you back
|
||||
///
|
||||
/// Simulates something reading from a slow link - you get one chunk per call,
|
||||
/// and you are offered chunks on a schedule
|
||||
struct ChunkReader {
|
||||
data: Vec<u8>,
|
||||
service_intervals: Interval,
|
||||
}
|
||||
|
||||
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();
|
||||
Self {
|
||||
data,
|
||||
service_intervals,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for ChunkReader {
|
||||
fn poll_read(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
if self.service_intervals.poll_tick(cx).is_pending() {
|
||||
return Poll::Pending;
|
||||
}
|
||||
buf.put_slice(&self.data[..buf.remaining().min(self.data.len())]);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn copy_mem_to_mem(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
}
|
||||
|
||||
fn copy_mem_to_slow_hdd(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
}
|
||||
|
||||
fn copy_chunk_to_mem(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
}
|
||||
|
||||
fn copy_chunk_to_slow_hdd(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
copy_bench,
|
||||
copy_mem_to_mem,
|
||||
copy_mem_to_slow_hdd,
|
||||
copy_chunk_to_mem,
|
||||
copy_chunk_to_slow_hdd,
|
||||
);
|
||||
benchmark_main!(copy_bench);
|
||||
@@ -0,0 +1,83 @@
|
||||
//! Benchmark implementation details of the threaded scheduler. These benches are
|
||||
//! intended to be used as a form of regression testing and not as a general
|
||||
//! purpose benchmark demonstrating real-world performance.
|
||||
|
||||
use tokio::runtime::{self, Runtime};
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(tokio::spawn(async move {}));
|
||||
}
|
||||
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_busy(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
rt.spawn(async {
|
||||
fn iter() {
|
||||
tokio::spawn(async { iter() });
|
||||
}
|
||||
|
||||
iter()
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_current_thread().build().unwrap()
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
@@ -6,13 +6,16 @@ use tokio::runtime::{self, Runtime};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn spawn_many(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const NUM_WORKERS: usize = 4;
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const STALL_DUR: Duration = Duration::from_micros(10);
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
@@ -38,6 +41,99 @@ fn spawn_many(b: &mut Bencher) {
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and yield. Yielding is a
|
||||
// lower notification priority than spawning / regular notification.
|
||||
fn spawn_many_remote_busy1(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
let flag = Arc::new(AtomicBool::new(true));
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(2 * NUM_WORKERS) {
|
||||
let flag = flag.clone();
|
||||
rt.spawn(async move {
|
||||
while flag.load(Relaxed) {
|
||||
tokio::task::yield_now().await;
|
||||
stall();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and spawn new high-CPU
|
||||
// tasks. Spawning goes via a higher notification priority than yielding.
|
||||
fn spawn_many_remote_busy2(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
let flag = Arc::new(AtomicBool::new(true));
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(NUM_WORKERS) {
|
||||
let flag = flag.clone();
|
||||
fn iter(flag: Arc<AtomicBool>) {
|
||||
tokio::spawn(async {
|
||||
if flag.load(Relaxed) {
|
||||
stall();
|
||||
iter(flag);
|
||||
}
|
||||
});
|
||||
}
|
||||
rt.spawn(async {
|
||||
iter(flag);
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
fn yield_many(b: &mut Bencher) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS: usize = 200;
|
||||
@@ -140,12 +236,28 @@ fn chained_spawn(b: &mut Bencher) {
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread()
|
||||
.worker_threads(4)
|
||||
.worker_threads(NUM_WORKERS)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
benchmark_group!(scheduler, spawn_many, ping_pong, yield_many, chained_spawn,);
|
||||
fn stall() {
|
||||
let now = Instant::now();
|
||||
while now.elapsed() < STALL_DUR {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy1,
|
||||
spawn_many_remote_busy2,
|
||||
ping_pong,
|
||||
yield_many,
|
||||
chained_spawn,
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
sync::{mpsc, oneshot},
|
||||
};
|
||||
|
||||
fn request_reply_current_thread(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(b, rt);
|
||||
}
|
||||
|
||||
fn request_reply_multi_threaded(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(b, rt);
|
||||
}
|
||||
|
||||
fn request_reply(b: &mut Bencher, rt: Runtime) {
|
||||
let tx = rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<oneshot::Sender<()>>(10);
|
||||
tokio::spawn(async move {
|
||||
while let Some(reply) = rx.recv().await {
|
||||
reply.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
tx
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
let task_tx = tx.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..1_000 {
|
||||
let (o_tx, o_rx) = oneshot::channel();
|
||||
task_tx.send(o_tx).await.unwrap();
|
||||
let _ = o_rx.await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
sync_mpsc_oneshot_group,
|
||||
request_reply_current_thread,
|
||||
request_reply_multi_threaded,
|
||||
);
|
||||
|
||||
benchmark_main!(sync_mpsc_oneshot_group);
|
||||
@@ -0,0 +1,90 @@
|
||||
use bencher::Bencher;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Notify;
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn notify_waiters<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
for _ in 0..N_WAITERS {
|
||||
rt.spawn({
|
||||
let notify = notify.clone();
|
||||
let counter = counter.clone();
|
||||
async move {
|
||||
loop {
|
||||
notify.notified().await;
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_waiters();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn notify_one<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
for _ in 0..N_WAITERS {
|
||||
rt.spawn({
|
||||
let notify = notify.clone();
|
||||
let counter = counter.clone();
|
||||
async move {
|
||||
loop {
|
||||
notify.notified().await;
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_one();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
notify_waiters_simple,
|
||||
notify_waiters::<10>,
|
||||
notify_waiters::<50>,
|
||||
notify_waiters::<100>,
|
||||
notify_waiters::<200>,
|
||||
notify_waiters::<500>
|
||||
);
|
||||
|
||||
bencher::benchmark_group!(
|
||||
notify_one_simple,
|
||||
notify_one::<10>,
|
||||
notify_one::<50>,
|
||||
notify_one::<100>,
|
||||
notify_one::<200>,
|
||||
notify_one::<500>
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(notify_waiters_simple, notify_one_simple);
|
||||
@@ -14,7 +14,7 @@ fn read_uncontended(b: &mut Bencher) {
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
let _read = black_box(read);
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -28,7 +28,7 @@ fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
|
||||
async fn task(lock: Arc<RwLock<()>>) {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
let _read = black_box(read);
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
@@ -55,7 +55,7 @@ fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
|
||||
async fn task(lock: Arc<RwLock<()>>) {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
let _read = black_box(read);
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
@@ -82,7 +82,7 @@ fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
|
||||
async fn task(lock: Arc<RwLock<()>>) {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
let _read = black_box(read);
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
@@ -110,7 +110,7 @@ fn read_concurrent_contended(b: &mut Bencher) {
|
||||
|
||||
async fn task(lock: Arc<RwLock<()>>) {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
let _read = black_box(read);
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use rand::prelude::*;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{watch, Notify};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
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>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.map(|&c| c as u32)
|
||||
.fold(0, u32::wrapping_add)
|
||||
}
|
||||
|
||||
fn contention_resubscribe(b: &mut Bencher) {
|
||||
const NTASK: u64 = 1000;
|
||||
|
||||
let rt = rt();
|
||||
let (snd, rcv) = watch::channel(0i32);
|
||||
let wg = Arc::new((AtomicU64::new(0), Notify::new()));
|
||||
for n in 0..NTASK {
|
||||
let mut rcv = rcv.clone();
|
||||
let wg = wg.clone();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n);
|
||||
rt.spawn(async move {
|
||||
while rcv.changed().await.is_ok() {
|
||||
let _ = *rcv.borrow(); // contend on rwlock
|
||||
let r = do_work(&mut rng);
|
||||
let _ = black_box(r);
|
||||
if wg.0.fetch_sub(1, Ordering::Release) == 1 {
|
||||
wg.1.notify_one();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..100 {
|
||||
assert_eq!(wg.0.fetch_add(NTASK, Ordering::Relaxed), 0);
|
||||
let _ = snd.send(black_box(42));
|
||||
while wg.0.load(Ordering::Acquire) > 0 {
|
||||
wg.1.notified().await;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(contention, contention_resubscribe);
|
||||
|
||||
bencher::benchmark_main!(contention);
|
||||
@@ -0,0 +1,25 @@
|
||||
//! Benchmark spawning a task onto the basic and threaded Tokio executors.
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bencher;
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
|
||||
fn time_now_current_thread(bench: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
bench.iter(|| {
|
||||
rt.block_on(async {
|
||||
black_box(tokio::time::Instant::now());
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(time_now, time_now_current_thread,);
|
||||
|
||||
bencher::benchmark_main!(time_now);
|
||||
+12
-3
@@ -2,7 +2,7 @@
|
||||
name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead, and delete the **path**.
|
||||
@@ -21,10 +21,11 @@ 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(windows)'.dev-dependencies.winapi]
|
||||
version = "0.3.8"
|
||||
[target.'cfg(windows)'.dev-dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
@@ -70,6 +71,10 @@ path = "udp-codec.rs"
|
||||
name = "tinyhttp"
|
||||
path = "tinyhttp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "custom-executor"
|
||||
path = "custom-executor.rs"
|
||||
|
||||
[[example]]
|
||||
name = "custom-executor-tokio-context"
|
||||
path = "custom-executor-tokio-context.rs"
|
||||
@@ -85,3 +90,7 @@ path = "named-pipe-ready.rs"
|
||||
[[example]]
|
||||
name = "named-pipe-multi-client"
|
||||
path = "named-pipe-multi-client.rs"
|
||||
|
||||
[[example]]
|
||||
name = "dump"
|
||||
path = "dump.rs"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
//! This example demonstrates tokio's experimental task dumping functionality.
|
||||
//! This application deadlocks. Input CTRL+C to display traces of each task, or
|
||||
//! input CTRL+C twice within 1 second to quit.
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Barrier;
|
||||
|
||||
#[inline(never)]
|
||||
async fn a(barrier: Arc<Barrier>) {
|
||||
b(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn b(barrier: Arc<Barrier>) {
|
||||
c(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn c(barrier: Arc<Barrier>) {
|
||||
barrier.wait().await;
|
||||
}
|
||||
|
||||
// Prints a task dump upon receipt of CTRL+C, or returns if CTRL+C is
|
||||
// inputted twice within a second.
|
||||
async fn dump_or_quit() {
|
||||
use tokio::time::{timeout, Duration, Instant};
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
let mut last_signal: Option<Instant> = None;
|
||||
// wait for CTRL+C
|
||||
while let Ok(_) = tokio::signal::ctrl_c().await {
|
||||
// exit if a CTRL+C is inputted twice within 1 second
|
||||
if let Some(time_since_last_signal) = last_signal.map(|i| i.elapsed()) {
|
||||
if time_since_last_signal < Duration::from_secs(1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
last_signal = Some(Instant::now());
|
||||
|
||||
// capture a dump, and print each trace
|
||||
println!("{:-<80}", "");
|
||||
if let Ok(dump) = timeout(Duration::from_secs(2), handle.dump()).await {
|
||||
for (i, task) in dump.tasks().iter().enumerate() {
|
||||
let trace = task.trace();
|
||||
println!("TASK {i}:");
|
||||
println!("{trace}\n");
|
||||
}
|
||||
} else {
|
||||
println!("Task dumping timed out. Use a native debugger (like gdb) to debug the deadlock.");
|
||||
}
|
||||
println!("{:-<80}", "");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
}
|
||||
}
|
||||
|
||||
println!("This program has a deadlock.");
|
||||
println!("Input CTRL+C to print a task dump.");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
|
||||
// oops! this barrier waits for one more task than will ever come.
|
||||
let barrier = Arc::new(Barrier::new(3));
|
||||
|
||||
let task_1 = tokio::spawn(a(barrier.clone()));
|
||||
let task_2 = tokio::spawn(a(barrier));
|
||||
|
||||
tokio::select!(
|
||||
_ = dump_or_quit() => {},
|
||||
_ = task_1 => {},
|
||||
_ = task_2 => {},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
)))]
|
||||
fn main() {
|
||||
println!("task dumps are not available")
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
//! Hello world server.
|
||||
//!
|
||||
//! A simple client that opens a TCP stream, writes "hello world\n", and closes
|
||||
//! the connection.
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//! To start a server that this client can talk to on port 6142, you can use this command:
|
||||
//!
|
||||
//! ncat -l 6142
|
||||
//!
|
||||
@@ -26,7 +24,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect("127.0.0.1:6142").await?;
|
||||
println!("created stream");
|
||||
|
||||
let result = stream.write(b"hello world\n").await;
|
||||
let result = stream.write_all(b"hello world\n").await;
|
||||
println!("wrote to stream; success={:?}", result.is_ok());
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -6,7 +6,7 @@ async fn windows_main() -> io::Result<()> {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
|
||||
use tokio::time;
|
||||
use winapi::shared::winerror;
|
||||
use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;
|
||||
|
||||
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client";
|
||||
const N: usize = 10;
|
||||
@@ -59,7 +59,7 @@ async fn windows_main() -> io::Result<()> {
|
||||
let mut client = loop {
|
||||
match ClientOptions::new().open(PIPE_NAME) {
|
||||
Ok(client) => break client,
|
||||
Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (),
|
||||
Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
|
||||
+12
-30
@@ -22,8 +22,7 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::copy_bidirectional;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use futures::FutureExt;
|
||||
@@ -44,36 +43,19 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
}
|
||||
});
|
||||
while let Ok((mut inbound, _)) = listener.accept().await {
|
||||
let mut outbound = TcpStream::connect(server_addr.clone()).await?;
|
||||
|
||||
tokio::spawn(transfer);
|
||||
tokio::spawn(async move {
|
||||
copy_bidirectional(&mut inbound, &mut outbound)
|
||||
.map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
}
|
||||
})
|
||||
.await
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<dyn Error>> {
|
||||
let mut outbound = TcpStream::connect(proxy_addr).await?;
|
||||
|
||||
let (mut ri, mut wi) = inbound.split();
|
||||
let (mut ro, mut wo) = outbound.split();
|
||||
|
||||
let client_to_server = async {
|
||||
io::copy(&mut ri, &mut wo).await?;
|
||||
wo.shutdown().await
|
||||
};
|
||||
|
||||
let server_to_client = async {
|
||||
io::copy(&mut ro, &mut wi).await?;
|
||||
wi.shutdown().await
|
||||
};
|
||||
|
||||
tokio::try_join!(client_to_server, server_to_client)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -180,8 +180,11 @@ 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))?;
|
||||
|
||||
(
|
||||
toslice(r.method.unwrap().as_bytes()),
|
||||
method,
|
||||
toslice(r.path.unwrap().as_bytes()),
|
||||
r.version.unwrap(),
|
||||
amt,
|
||||
@@ -195,7 +198,7 @@ impl Decoder for Http {
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
ret = ret.method(&data[method.0..method.1]);
|
||||
ret = ret.method(method);
|
||||
let s = data.slice(path.0..path.1);
|
||||
let s = unsafe { String::from_utf8_unchecked(Vec::from(s.as_ref())) };
|
||||
ret = ret.uri(s);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "stress-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"arch": "x86",
|
||||
"cpu": "pentium4",
|
||||
"crt-static-respected": true,
|
||||
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128",
|
||||
"dynamic-linking": true,
|
||||
"env": "gnu",
|
||||
"has-rpath": true,
|
||||
"has-thread-local": true,
|
||||
"llvm-target": "i686-unknown-linux-gnu",
|
||||
"max-atomic-width": 32,
|
||||
"os": "linux",
|
||||
"position-independent-executables": true,
|
||||
"pre-link-args": {
|
||||
"gcc": [
|
||||
"-m32"
|
||||
]
|
||||
},
|
||||
"relro-level": "full",
|
||||
"stack-probes": {
|
||||
"kind": "inline-or-call",
|
||||
"min-llvm-version-for-inline": [
|
||||
16,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"supported-sanitizers": [
|
||||
"address"
|
||||
],
|
||||
"supported-split-debuginfo": [
|
||||
"packed",
|
||||
"unpacked",
|
||||
"off"
|
||||
],
|
||||
"target-family": [
|
||||
"unix"
|
||||
],
|
||||
"target-pointer-width": "32"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tests-build"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
|
||||
@@ -1,2 +1,10 @@
|
||||
Tests the various combination of feature flags. This is broken out to a separate
|
||||
crate to work around limitations with cargo features.
|
||||
|
||||
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`
|
||||
command that failed to have it regenerate the test output.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
error: The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled.
|
||||
--> tests/fail/macros_core_no_default.rs:3:1
|
||||
--> $DIR/macros_core_no_default.rs:3:1
|
||||
|
|
||||
3 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
error: function is never used: `f`
|
||||
error: function `f` is never used
|
||||
--> $DIR/macros_dead_code.rs:6:10
|
||||
|
|
||||
6 | async fn f() {}
|
||||
|
||||
@@ -36,13 +36,10 @@ async fn test_worker_threads_not_int() {}
|
||||
async fn test_worker_threads_and_current_thread() {}
|
||||
|
||||
#[tokio::test(crate = 456)]
|
||||
async fn test_crate_not_ident_int() {}
|
||||
async fn test_crate_not_path_int() {}
|
||||
|
||||
#[tokio::test(crate = "456")]
|
||||
async fn test_crate_not_ident_invalid() {}
|
||||
|
||||
#[tokio::test(crate = "abc::edf")]
|
||||
async fn test_crate_not_ident_path() {}
|
||||
async fn test_crate_not_path_invalid() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[test]
|
||||
|
||||
@@ -64,34 +64,28 @@ error: The `worker_threads` option requires the `multi_thread` runtime flavor. U
|
||||
35 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
|
||||
| ^
|
||||
|
||||
error: Failed to parse value of `crate` as ident.
|
||||
error: Failed to parse value of `crate` as path.
|
||||
--> $DIR/macros_invalid_input.rs:38:23
|
||||
|
|
||||
38 | #[tokio::test(crate = 456)]
|
||||
| ^^^
|
||||
|
||||
error: Failed to parse value of `crate` as ident: "456"
|
||||
error: Failed to parse value of `crate` as path: "456"
|
||||
--> $DIR/macros_invalid_input.rs:41:23
|
||||
|
|
||||
41 | #[tokio::test(crate = "456")]
|
||||
| ^^^^^
|
||||
|
||||
error: Failed to parse value of `crate` as ident: "abc::edf"
|
||||
--> $DIR/macros_invalid_input.rs:44:23
|
||||
|
|
||||
44 | #[tokio::test(crate = "abc::edf")]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied
|
||||
--> $DIR/macros_invalid_input.rs:48:1
|
||||
--> $DIR/macros_invalid_input.rs:45:1
|
||||
|
|
||||
48 | #[test]
|
||||
45 | #[test]
|
||||
| ^^^^^^^
|
||||
|
||||
error: duplicated attribute
|
||||
--> $DIR/macros_invalid_input.rs:48:1
|
||||
--> $DIR/macros_invalid_input.rs:45:1
|
||||
|
|
||||
48 | #[test]
|
||||
45 | #[test]
|
||||
| ^^^^^^^
|
||||
|
|
||||
note: the lint level is defined here
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:5:5
|
||||
--> tests/fail/macros_type_mismatch.rs:5:5
|
||||
|
|
||||
4 | async fn missing_semicolon_or_return_type() {
|
||||
| - help: a return type might be missing here: `-> _`
|
||||
5 | Ok(())
|
||||
| ^^^^^^ expected `()`, found enum `Result`
|
||||
| ^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:10:5
|
||||
--> tests/fail/macros_type_mismatch.rs:10:5
|
||||
|
|
||||
9 | async fn missing_return_type() {
|
||||
| - help: a return type might be missing here: `-> _`
|
||||
10 | return Ok(());
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found enum `Result`
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:23:5
|
||||
--> tests/fail/macros_type_mismatch.rs:23:5
|
||||
|
|
||||
14 | async fn extra_semicolon() -> Result<(), ()> {
|
||||
| -------------- expected `Result<(), ()>` because of return type
|
||||
...
|
||||
23 | Ok(());
|
||||
| ^^^^^^^ expected enum `Result`, found `()`
|
||||
| ^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
@@ -38,7 +38,7 @@ help: try adding an expression at the end of the block
|
||||
|
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:32:5
|
||||
--> tests/fail/macros_type_mismatch.rs:32:5
|
||||
|
|
||||
30 | async fn issue_4635() {
|
||||
| - help: try adding a return type: `-> i32`
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "test-cat"
|
||||
required-features = ["rt-process-io-util"]
|
||||
|
||||
[[bin]]
|
||||
name = "test-mem"
|
||||
@@ -31,7 +30,6 @@ name = "rt_yield"
|
||||
required-features = ["rt", "macros", "sync"]
|
||||
|
||||
[features]
|
||||
rt-process-io-util = ["tokio/rt", "tokio/macros", "tokio/process", "tokio/io-util", "tokio/io-std"]
|
||||
# For mem check
|
||||
rt-net = ["tokio/rt", "tokio/rt-multi-thread", "tokio/net"]
|
||||
# For test-process-signal
|
||||
@@ -60,3 +58,4 @@ tokio = { path = "../tokio" }
|
||||
tokio-test = { path = "../tokio-test", optional = true }
|
||||
doc-comment = "0.3.1"
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
bytes = "1.0.0"
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
//! A cat-like utility that can be used as a subprocess to test I/O
|
||||
//! stream communication.
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let mut stdin = tokio::io::stdin();
|
||||
let mut stdout = tokio::io::stdout();
|
||||
|
||||
tokio::io::copy(&mut stdin, &mut stdout).await.unwrap();
|
||||
|
||||
stdout.flush().await.unwrap();
|
||||
fn main() {
|
||||
let stdin = io::stdin();
|
||||
let mut stdout = io::stdout();
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
line.clear();
|
||||
stdin.read_line(&mut line).unwrap();
|
||||
if line.is_empty() {
|
||||
break;
|
||||
}
|
||||
stdout.write_all(line.as_bytes()).unwrap();
|
||||
}
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#![cfg(all(feature = "macros", feature = "rt-multi-thread"))]
|
||||
#![cfg(all(
|
||||
feature = "macros",
|
||||
feature = "rt-multi-thread",
|
||||
not(target_os = "wasi")
|
||||
))]
|
||||
|
||||
#[tokio::main]
|
||||
async fn basic_main() -> usize {
|
||||
|
||||
@@ -4,6 +4,7 @@ use futures::channel::oneshot;
|
||||
use futures::executor::block_on;
|
||||
use std::thread;
|
||||
|
||||
#[cfg_attr(target_os = "wasi", ignore = "WASI: std::thread::spawn not supported")]
|
||||
#[test]
|
||||
fn join_with_select() {
|
||||
block_on(async {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
#![cfg(all(feature = "full", not(target_os = "wasi")))]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::join;
|
||||
@@ -7,23 +7,12 @@ use tokio::process::{Child, Command};
|
||||
use tokio_test::assert_ok;
|
||||
|
||||
use futures::future::{self, FutureExt};
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
|
||||
// so, we need to change this back as a test, but for now this doesn't work because of:
|
||||
// https://github.com/rust-lang/rust/pull/95469
|
||||
//
|
||||
// undo when this is closed: https://github.com/tokio-rs/tokio/issues/4802
|
||||
|
||||
// fn cat() -> Command {
|
||||
// let mut cmd = Command::new(std::env!("CARGO_BIN_EXE_test-cat"));
|
||||
// cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
// cmd
|
||||
// }
|
||||
|
||||
fn cat() -> Command {
|
||||
let mut cmd = Command::new("cat");
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_test-cat"));
|
||||
cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
cmd
|
||||
}
|
||||
@@ -200,3 +189,54 @@ async fn pipe_from_one_command_to_another() {
|
||||
assert!(second_status.expect("second status").success());
|
||||
assert!(third_status.expect("third status").success());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn vectored_writes() {
|
||||
use bytes::{Buf, Bytes};
|
||||
use std::{io::IoSlice, pin::Pin};
|
||||
use tokio::io::AsyncWrite;
|
||||
|
||||
let mut cat = cat().spawn().unwrap();
|
||||
let mut stdin = cat.stdin.take().unwrap();
|
||||
let are_writes_vectored = stdin.is_write_vectored();
|
||||
let mut stdout = cat.stdout.take().unwrap();
|
||||
|
||||
let write = async {
|
||||
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 {
|
||||
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))?;
|
||||
writes_completed += 1;
|
||||
input.advance(n);
|
||||
})
|
||||
.await?;
|
||||
|
||||
drop(stdin);
|
||||
|
||||
std::io::Result::Ok(writes_completed)
|
||||
};
|
||||
|
||||
let read = async {
|
||||
let mut buffer = Vec::with_capacity(6 + 7);
|
||||
stdout.read_to_end(&mut buffer).await?;
|
||||
std::io::Result::Ok(buffer)
|
||||
};
|
||||
|
||||
let (write, read, status) = future::join3(write, read, cat.wait()).await;
|
||||
|
||||
assert!(status.unwrap().success());
|
||||
|
||||
let writes_completed = write.unwrap();
|
||||
// on unix our small payload should always fit in whatever default sized pipe with a single
|
||||
// syscall. if multiple are used, then the forwarding does not work, or we are on a platform
|
||||
// for which the `std` does not support vectored writes.
|
||||
assert_eq!(writes_completed == 1, are_writes_vectored);
|
||||
|
||||
assert_eq!(&read.unwrap(), b"hello\nworld!\n");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,44 @@
|
||||
# 2.1.0 (April 25th, 2023)
|
||||
|
||||
- macros: fix typo in `#[tokio::test]` docs ([#5636])
|
||||
- macros: make entrypoints more efficient ([#5621])
|
||||
|
||||
[#5621]: https://github.com/tokio-rs/tokio/pull/5621
|
||||
[#5636]: https://github.com/tokio-rs/tokio/pull/5636
|
||||
|
||||
# 2.0.0 (March 24th, 2023)
|
||||
|
||||
This major release updates the dependency on the syn crate to 2.0.0, and
|
||||
increases the MSRV to 1.56.
|
||||
|
||||
As part of this release, we are adopting a policy of depending on a specific minor
|
||||
release of tokio-macros. This prevents Tokio from being able to pull in many different
|
||||
versions of tokio-macros.
|
||||
|
||||
- macros: update `syn` ([#5572])
|
||||
- macros: accept path as crate rename ([#5557])
|
||||
|
||||
[#5572]: https://github.com/tokio-rs/tokio/pull/5572
|
||||
[#5557]: https://github.com/tokio-rs/tokio/pull/5557
|
||||
|
||||
# 1.8.2 (November 30th, 2022)
|
||||
|
||||
- fix a regression introduced in 1.8.1 ([#5244])
|
||||
|
||||
[#5244]: https://github.com/tokio-rs/tokio/pull/5244
|
||||
|
||||
# 1.8.1 (November 29th, 2022)
|
||||
|
||||
(yanked)
|
||||
|
||||
- macros: Pin Futures in `#[tokio::test]` to stack ([#5205])
|
||||
- macros: Reduce usage of last statement spans in proc-macros ([#5092])
|
||||
- macros: Improve the documentation for `#[tokio::test]` ([#4761])
|
||||
|
||||
[#5205]: https://github.com/tokio-rs/tokio/pull/5205
|
||||
[#5092]: https://github.com/tokio-rs/tokio/pull/5092
|
||||
[#4761]: https://github.com/tokio-rs/tokio/pull/4761
|
||||
|
||||
# 1.8.0 (June 4th, 2022)
|
||||
|
||||
- macros: always emit return statement ([#4636])
|
||||
|
||||
@@ -3,10 +3,10 @@ name = "tokio-macros"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.0.x" git tag.
|
||||
version = "1.8.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
# - Create "tokio-macros-1.x.y" git tag.
|
||||
version = "2.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -24,7 +24,7 @@ proc-macro = true
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.7"
|
||||
quote = "1"
|
||||
syn = { version = "1.0.56", features = ["full"] }
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
Copyright (c) 2023 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
+206
-76
@@ -1,10 +1,10 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use proc_macro2::{Span, TokenStream, TokenTree};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::parse::Parser;
|
||||
use syn::parse::{Parse, ParseStream, Parser};
|
||||
use syn::{braced, Attribute, Ident, Path, Signature, Visibility};
|
||||
|
||||
// syn::AttributeArgs does not implement syn::Parse
|
||||
type AttributeArgs = syn::punctuated::Punctuated<syn::NestedMeta, syn::Token![,]>;
|
||||
type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum RuntimeFlavor {
|
||||
@@ -29,7 +29,7 @@ struct FinalConfig {
|
||||
flavor: RuntimeFlavor,
|
||||
worker_threads: Option<usize>,
|
||||
start_paused: Option<bool>,
|
||||
crate_name: Option<String>,
|
||||
crate_name: Option<Path>,
|
||||
}
|
||||
|
||||
/// Config used in case of the attribute not being able to build a valid config
|
||||
@@ -47,7 +47,7 @@ struct Configuration {
|
||||
worker_threads: Option<(usize, Span)>,
|
||||
start_paused: Option<(bool, Span)>,
|
||||
is_test: bool,
|
||||
crate_name: Option<String>,
|
||||
crate_name: Option<Path>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
@@ -112,8 +112,8 @@ impl Configuration {
|
||||
if self.crate_name.is_some() {
|
||||
return Err(syn::Error::new(span, "`crate` set multiple times."));
|
||||
}
|
||||
let name_ident = parse_ident(name, span, "crate")?;
|
||||
self.crate_name = Some(name_ident.to_string());
|
||||
let name_path = parse_path(name, span, "crate")?;
|
||||
self.crate_name = Some(name_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -199,23 +199,22 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ident(lit: syn::Lit, span: Span, field: &str) -> Result<Ident, syn::Error> {
|
||||
fn parse_path(lit: syn::Lit, span: Span, field: &str) -> Result<Path, syn::Error> {
|
||||
match lit {
|
||||
syn::Lit::Str(s) => {
|
||||
let err = syn::Error::new(
|
||||
span,
|
||||
format!(
|
||||
"Failed to parse value of `{}` as ident: \"{}\"",
|
||||
"Failed to parse value of `{}` as path: \"{}\"",
|
||||
field,
|
||||
s.value()
|
||||
),
|
||||
);
|
||||
let path = s.parse::<syn::Path>().map_err(|_| err.clone())?;
|
||||
path.get_ident().cloned().ok_or(err)
|
||||
s.parse::<syn::Path>().map_err(|_| err.clone())
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as ident.", field),
|
||||
format!("Failed to parse value of `{}` as path.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -231,7 +230,7 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
|
||||
}
|
||||
|
||||
fn build_config(
|
||||
input: syn::ItemFn,
|
||||
input: &ItemFn,
|
||||
args: AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_multi_thread: bool,
|
||||
@@ -246,7 +245,7 @@ fn build_config(
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
|
||||
syn::Meta::NameValue(namevalue) => {
|
||||
let ident = namevalue
|
||||
.path
|
||||
.get_ident()
|
||||
@@ -255,34 +254,26 @@ fn build_config(
|
||||
})?
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
let lit = match &namevalue.value {
|
||||
syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit,
|
||||
expr => return Err(syn::Error::new_spanned(expr, "Must be a literal")),
|
||||
};
|
||||
match ident.as_str() {
|
||||
"worker_threads" => {
|
||||
config.set_worker_threads(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
config.set_worker_threads(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"flavor" => {
|
||||
config.set_flavor(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
config.set_flavor(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"start_paused" => {
|
||||
config.set_start_paused(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
config.set_start_paused(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"core_threads" => {
|
||||
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
"crate" => {
|
||||
config.set_crate_name(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
config.set_crate_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
name => {
|
||||
let msg = format!(
|
||||
@@ -293,7 +284,7 @@ fn build_config(
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
||||
syn::Meta::Path(path) => {
|
||||
let name = path
|
||||
.get_ident()
|
||||
.ok_or_else(|| syn::Error::new_spanned(&path, "Must have specified ident"))?
|
||||
@@ -333,18 +324,13 @@ fn build_config(
|
||||
config.build()
|
||||
}
|
||||
|
||||
fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> TokenStream {
|
||||
fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenStream {
|
||||
input.sig.asyncness = None;
|
||||
|
||||
// If type mismatch occurs, the current rustc points to the last statement.
|
||||
let (last_stmt_start_span, last_stmt_end_span) = {
|
||||
let mut last_stmt = input
|
||||
.block
|
||||
.stmts
|
||||
.last()
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_default()
|
||||
.into_iter();
|
||||
let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter();
|
||||
|
||||
// `Span` on stable Rust has a limitation that only points to the first
|
||||
// token, not the whole tokens. We can work around this limitation by
|
||||
// using the first/last span of the tokens like
|
||||
@@ -354,23 +340,24 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
|
||||
(start, end)
|
||||
};
|
||||
|
||||
let crate_name = config.crate_name.as_deref().unwrap_or("tokio");
|
||||
|
||||
let crate_ident = Ident::new(crate_name, last_stmt_start_span);
|
||||
let crate_path = config
|
||||
.crate_name
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream());
|
||||
|
||||
let mut rt = match config.flavor {
|
||||
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_ident::runtime::Builder::new_current_thread()
|
||||
#crate_path::runtime::Builder::new_current_thread()
|
||||
},
|
||||
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_ident::runtime::Builder::new_multi_thread()
|
||||
#crate_path::runtime::Builder::new_multi_thread()
|
||||
},
|
||||
};
|
||||
if let Some(v) = config.worker_threads {
|
||||
rt = quote! { #rt.worker_threads(#v) };
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) };
|
||||
}
|
||||
if let Some(v) = config.start_paused {
|
||||
rt = quote! { #rt.start_paused(#v) };
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) };
|
||||
}
|
||||
|
||||
let header = if is_test {
|
||||
@@ -381,34 +368,53 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let body = &input.block;
|
||||
let brace_token = input.block.brace_token;
|
||||
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
|
||||
let body_ident = quote! { body };
|
||||
let last_block = quote_spanned! {last_stmt_end_span=>
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
{
|
||||
let body = async #body;
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
{
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(body);
|
||||
}
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(#body_ident);
|
||||
}
|
||||
})
|
||||
.expect("Parsing failure");
|
||||
input.block.brace_token = brace_token;
|
||||
|
||||
let result = quote! {
|
||||
#header
|
||||
#input
|
||||
};
|
||||
|
||||
result.into()
|
||||
let body = input.body();
|
||||
|
||||
// For test functions pin the body to the stack and use `Pin<&mut dyn
|
||||
// Future>` to reduce the amount of `Runtime::block_on` (and related
|
||||
// functions) copies we generate during compilation due to the generic
|
||||
// parameter `F` (the future to block on). This could have an impact on
|
||||
// performance, but because it's only for testing it's unlikely to be very
|
||||
// large.
|
||||
//
|
||||
// We don't do this for the main function as it should only be used once so
|
||||
// there will be no benefit.
|
||||
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: ::std::pin::Pin<&mut dyn ::std::future::Future<Output = #output_type>> = body;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let body = async #body;
|
||||
}
|
||||
};
|
||||
|
||||
input.into_tokens(header, body, last_block)
|
||||
}
|
||||
|
||||
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||
tokens.extend(TokenStream::from(error.into_compile_error()));
|
||||
tokens.extend(error.into_compile_error());
|
||||
tokens
|
||||
}
|
||||
|
||||
@@ -417,7 +423,7 @@ pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
let input: ItemFn = match syn::parse2(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
@@ -427,8 +433,8 @@ pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
Err(syn::Error::new_spanned(&input.sig.ident, msg))
|
||||
} else {
|
||||
AttributeArgs::parse_terminated
|
||||
.parse(args)
|
||||
.and_then(|args| build_config(input.clone(), args, false, rt_multi_thread))
|
||||
.parse2(args)
|
||||
.and_then(|args| build_config(&input, args, false, rt_multi_thread))
|
||||
};
|
||||
|
||||
match config {
|
||||
@@ -441,17 +447,17 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
let input: ItemFn = match syn::parse2(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
let config = if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) {
|
||||
let config = if let Some(attr) = input.attrs().find(|attr| attr.meta.path().is_ident("test")) {
|
||||
let msg = "second test attribute is supplied";
|
||||
Err(syn::Error::new_spanned(&attr, msg))
|
||||
Err(syn::Error::new_spanned(attr, msg))
|
||||
} else {
|
||||
AttributeArgs::parse_terminated
|
||||
.parse(args)
|
||||
.and_then(|args| build_config(input.clone(), args, true, rt_multi_thread))
|
||||
.parse2(args)
|
||||
.and_then(|args| build_config(&input, args, true, rt_multi_thread))
|
||||
};
|
||||
|
||||
match config {
|
||||
@@ -459,3 +465,127 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e),
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemFn {
|
||||
outer_attrs: Vec<Attribute>,
|
||||
vis: Visibility,
|
||||
sig: Signature,
|
||||
brace_token: syn::token::Brace,
|
||||
inner_attrs: Vec<Attribute>,
|
||||
stmts: Vec<proc_macro2::TokenStream>,
|
||||
}
|
||||
|
||||
impl ItemFn {
|
||||
/// Access all attributes of the function item.
|
||||
fn attrs(&self) -> impl Iterator<Item = &Attribute> {
|
||||
self.outer_attrs.iter().chain(self.inner_attrs.iter())
|
||||
}
|
||||
|
||||
/// Get the body of the function item in a manner so that it can be
|
||||
/// conveniently used with the `quote!` macro.
|
||||
fn body(&self) -> Body<'_> {
|
||||
Body {
|
||||
brace_token: self.brace_token,
|
||||
stmts: &self.stmts,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert our local function item into a token stream.
|
||||
fn into_tokens(
|
||||
self,
|
||||
header: proc_macro2::TokenStream,
|
||||
body: proc_macro2::TokenStream,
|
||||
last_block: proc_macro2::TokenStream,
|
||||
) -> TokenStream {
|
||||
let mut tokens = proc_macro2::TokenStream::new();
|
||||
header.to_tokens(&mut tokens);
|
||||
|
||||
// Outer attributes are simply streamed as-is.
|
||||
for attr in self.outer_attrs {
|
||||
attr.to_tokens(&mut tokens);
|
||||
}
|
||||
|
||||
// Inner attributes require extra care, since they're not supported on
|
||||
// blocks (which is what we're expanded into) we instead lift them
|
||||
// outside of the function. This matches the behaviour of `syn`.
|
||||
for mut attr in self.inner_attrs {
|
||||
attr.style = syn::AttrStyle::Outer;
|
||||
attr.to_tokens(&mut tokens);
|
||||
}
|
||||
|
||||
self.vis.to_tokens(&mut tokens);
|
||||
self.sig.to_tokens(&mut tokens);
|
||||
|
||||
self.brace_token.surround(&mut tokens, |tokens| {
|
||||
body.to_tokens(tokens);
|
||||
last_block.to_tokens(tokens);
|
||||
});
|
||||
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ItemFn {
|
||||
#[inline]
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
// This parse implementation has been largely lifted from `syn`, with
|
||||
// the exception of:
|
||||
// * We don't have access to the plumbing necessary to parse inner
|
||||
// attributes in-place.
|
||||
// * We do our own statements parsing to avoid recursively parsing
|
||||
// entire statements and only look for the parts we're interested in.
|
||||
|
||||
let outer_attrs = input.call(Attribute::parse_outer)?;
|
||||
let vis: Visibility = input.parse()?;
|
||||
let sig: Signature = input.parse()?;
|
||||
|
||||
let content;
|
||||
let brace_token = braced!(content in input);
|
||||
let inner_attrs = Attribute::parse_inner(&content)?;
|
||||
|
||||
let mut buf = proc_macro2::TokenStream::new();
|
||||
let mut stmts = Vec::new();
|
||||
|
||||
while !content.is_empty() {
|
||||
if let Some(semi) = content.parse::<Option<syn::Token![;]>>()? {
|
||||
semi.to_tokens(&mut buf);
|
||||
stmts.push(buf);
|
||||
buf = proc_macro2::TokenStream::new();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse a single token tree and extend our current buffer with it.
|
||||
// This avoids parsing the entire content of the sub-tree.
|
||||
buf.extend([content.parse::<TokenTree>()?]);
|
||||
}
|
||||
|
||||
if !buf.is_empty() {
|
||||
stmts.push(buf);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
outer_attrs,
|
||||
vis,
|
||||
sig,
|
||||
brace_token,
|
||||
inner_attrs,
|
||||
stmts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Body<'a> {
|
||||
brace_token: syn::token::Brace,
|
||||
// Statements, with terminating `;`.
|
||||
stmts: &'a [TokenStream],
|
||||
}
|
||||
|
||||
impl ToTokens for Body<'_> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
self.brace_token.surround(tokens, |tokens| {
|
||||
for stmt in self.stmts {
|
||||
stmt.to_tokens(tokens);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+13
-7
@@ -39,6 +39,13 @@ use proc_macro::TokenStream;
|
||||
/// function is called often, it is preferable to create the runtime using the
|
||||
/// runtime builder so the runtime can be reused across calls.
|
||||
///
|
||||
/// # Non-worker async function
|
||||
///
|
||||
/// Note that the async function marked with this macro does not run as a
|
||||
/// worker. The expectation is that other tasks are spawned by the function here.
|
||||
/// Awaiting on other futures from the function provided here will not
|
||||
/// perform as fast as those spawned as workers.
|
||||
///
|
||||
/// # Multi-threaded runtime
|
||||
///
|
||||
/// To use the multi-threaded runtime, the macro can be configured using
|
||||
@@ -197,12 +204,12 @@ use proc_macro::TokenStream;
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, true)
|
||||
entry::main(args.into(), item.into(), true).into()
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime. This macro helps set up a `Runtime`
|
||||
/// without requiring the user to use [Runtime](../tokio/runtime/struct.Runtime.html) or
|
||||
/// [Builder](../tokio/runtime/struct.builder.html) directly.
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html) directly.
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
@@ -262,7 +269,7 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, false)
|
||||
entry::main(args.into(), item.into(), false).into()
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment.
|
||||
@@ -288,8 +295,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
///
|
||||
/// The `worker_threads` option configures the number of worker threads, and
|
||||
/// defaults to the number of cpus on the system. This is the default
|
||||
/// flavor.
|
||||
/// defaults to the number of cpus on the system.
|
||||
///
|
||||
/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
|
||||
/// flag.
|
||||
@@ -420,7 +426,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, true)
|
||||
entry::test(args.into(), item.into(), true).into()
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
@@ -435,7 +441,7 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
entry::test(args.into(), item.into(), false).into()
|
||||
}
|
||||
|
||||
/// Always fails with the error message below.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
use syn::{parse::Parser, Ident};
|
||||
|
||||
pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
// passed in is: `(_ _ _)` with one `_` per branch
|
||||
@@ -46,7 +46,7 @@ pub(crate) fn clean_pattern_macro(input: TokenStream) -> TokenStream {
|
||||
// If this isn't a pattern, we return the token stream as-is. The select!
|
||||
// macro is using it in a location requiring a pattern, so an error will be
|
||||
// emitted there.
|
||||
let mut input: syn::Pat = match syn::parse(input.clone()) {
|
||||
let mut input: syn::Pat = match syn::Pat::parse_single.parse(input.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(_) => return input,
|
||||
};
|
||||
@@ -58,7 +58,6 @@ pub(crate) fn clean_pattern_macro(input: TokenStream) -> TokenStream {
|
||||
// Removes any occurrences of ref or mut in the provided pattern.
|
||||
fn clean_pattern(pat: &mut syn::Pat) {
|
||||
match pat {
|
||||
syn::Pat::Box(_box) => {}
|
||||
syn::Pat::Lit(_literal) => {}
|
||||
syn::Pat::Macro(_macro) => {}
|
||||
syn::Pat::Path(_path) => {}
|
||||
@@ -94,16 +93,16 @@ fn clean_pattern(pat: &mut syn::Pat) {
|
||||
}
|
||||
}
|
||||
syn::Pat::TupleStruct(tuple) => {
|
||||
for elem in tuple.pat.elems.iter_mut() {
|
||||
for elem in tuple.elems.iter_mut() {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::Reference(reference) => {
|
||||
reference.mutability = None;
|
||||
clean_pattern(&mut *reference.pat);
|
||||
clean_pattern(&mut reference.pat);
|
||||
}
|
||||
syn::Pat::Type(type_pat) => {
|
||||
clean_pattern(&mut *type_pat.pat);
|
||||
clean_pattern(&mut type_pat.pat);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,46 @@
|
||||
# 0.1.14 (April 26th, 2023)
|
||||
|
||||
This bugfix release bumps the minimum version of Tokio to 1.15, which is
|
||||
necessary for `timeout_repeating` to compile. ([#5657])
|
||||
|
||||
[#5657]: https://github.com/tokio-rs/tokio/pull/5657
|
||||
|
||||
# 0.1.13 (April 25th, 2023)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.56.
|
||||
|
||||
- stream: add "full" feature flag ([#5639])
|
||||
- stream: add `StreamExt::timeout_repeating` ([#5577])
|
||||
- stream: add `StreamNotifyClose` ([#4851])
|
||||
|
||||
[#4851]: https://github.com/tokio-rs/tokio/pull/4851
|
||||
[#5577]: https://github.com/tokio-rs/tokio/pull/5577
|
||||
[#5639]: https://github.com/tokio-rs/tokio/pull/5639
|
||||
|
||||
# 0.1.12 (January 20, 2023)
|
||||
|
||||
- time: remove `Unpin` bound on `Throttle` methods ([#5105])
|
||||
- time: document that `throttle` operates on ms granularity ([#5101])
|
||||
- sync: add `WatchStream::from_changes` ([#5432])
|
||||
|
||||
[#5105]: https://github.com/tokio-rs/tokio/pull/5105
|
||||
[#5101]: https://github.com/tokio-rs/tokio/pull/5101
|
||||
[#5432]: https://github.com/tokio-rs/tokio/pull/5432
|
||||
|
||||
# 0.1.11 (October 11, 2022)
|
||||
|
||||
- time: allow `StreamExt::chunks_timeout` outside of a runtime ([#5036])
|
||||
|
||||
[#5036]: https://github.com/tokio-rs/tokio/pull/5036
|
||||
|
||||
# 0.1.10 (Sept 18, 2022)
|
||||
|
||||
- time: add `StreamExt::chunks_timeout` ([#4695])
|
||||
- stream: add track_caller to public APIs ([#4786])
|
||||
|
||||
[#4695]: https://github.com/tokio-rs/tokio/pull/4695
|
||||
[#4786]: https://github.com/tokio-rs/tokio/pull/4786
|
||||
|
||||
# 0.1.9 (June 4, 2022)
|
||||
|
||||
- deps: upgrade `tokio-util` dependency to `0.7.x` ([#3762])
|
||||
|
||||
+15
-7
@@ -4,9 +4,9 @@ name = "tokio-stream"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.9"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
version = "0.1.14"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -18,6 +18,16 @@ categories = ["asynchronous"]
|
||||
|
||||
[features]
|
||||
default = ["time"]
|
||||
|
||||
full = [
|
||||
"time",
|
||||
"net",
|
||||
"io-util",
|
||||
"fs",
|
||||
"sync",
|
||||
"signal"
|
||||
]
|
||||
|
||||
time = ["tokio/time"]
|
||||
net = ["tokio/net"]
|
||||
io-util = ["tokio/io-util"]
|
||||
@@ -27,8 +37,8 @@ signal = ["tokio/signal"]
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.8.0", path = "../tokio", features = ["sync"] }
|
||||
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 }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -38,8 +48,6 @@ parking_lot = "0.12.0"
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
proptest = "1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
Copyright (c) 2023 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "tokio-stream-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
tokio-test = { path = "../../tokio-test" }
|
||||
|
||||
[dependencies.tokio-stream]
|
||||
path = ".."
|
||||
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_stream_map"
|
||||
path = "fuzz_targets/fuzz_stream_map.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,72 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::pin::Pin;
|
||||
|
||||
use tokio_stream::{self as stream, Stream, StreamMap};
|
||||
use tokio_test::{assert_pending, assert_ready, task};
|
||||
|
||||
macro_rules! assert_ready_none {
|
||||
($($t:tt)*) => {
|
||||
match assert_ready!($($t)*) {
|
||||
None => {}
|
||||
Some(v) => panic!("expected `None`, got `Some({:?})`", v),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
|
||||
Box::pin(s)
|
||||
}
|
||||
|
||||
fuzz_target!(|data: [bool; 64]| {
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct DidPoll<T> {
|
||||
did_poll: bool,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: Stream + Unpin> Stream for DidPoll<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T::Item>> {
|
||||
self.did_poll = true;
|
||||
Pin::new(&mut self.inner).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// Try the test with each possible length.
|
||||
for len in 0..data.len() {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let mut expect = 0;
|
||||
|
||||
for (i, is_empty) in data[..len].iter().copied().enumerate() {
|
||||
let inner = if is_empty {
|
||||
pin_box(stream::empty::<()>())
|
||||
} else {
|
||||
expect += 1;
|
||||
pin_box(stream::pending::<()>())
|
||||
};
|
||||
|
||||
let stream = DidPoll {
|
||||
did_poll: false,
|
||||
inner,
|
||||
};
|
||||
|
||||
map.insert(i, stream);
|
||||
}
|
||||
|
||||
if expect == 0 {
|
||||
assert_ready_none!(map.poll_next());
|
||||
} else {
|
||||
assert_pending!(map.poll_next());
|
||||
|
||||
assert_eq!(expect, map.values().count());
|
||||
|
||||
for stream in map.values() {
|
||||
assert!(stream.did_poll);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -63,12 +63,12 @@
|
||||
//! [`tokio-util`] provides the [`StreamReader`] and [`ReaderStream`]
|
||||
//! types when the io feature is enabled.
|
||||
//!
|
||||
//! [`tokio-util`]: https://docs.rs/tokio-util/0.4/tokio_util/codec/index.html
|
||||
//! [`tokio::io`]: https://docs.rs/tokio/1.0/tokio/io/index.html
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/1.0/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/1.0/tokio/io/trait.AsyncWrite.html
|
||||
//! [`ReaderStream`]: https://docs.rs/tokio-util/0.4/tokio_util/io/struct.ReaderStream.html
|
||||
//! [`StreamReader`]: https://docs.rs/tokio-util/0.4/tokio_util/io/struct.StreamReader.html
|
||||
//! [`tokio-util`]: https://docs.rs/tokio-util/latest/tokio_util/codec/index.html
|
||||
//! [`tokio::io`]: https://docs.rs/tokio/latest/tokio/io/index.html
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html
|
||||
//! [`ReaderStream`]: https://docs.rs/tokio-util/latest/tokio_util/io/struct.ReaderStream.html
|
||||
//! [`StreamReader`]: https://docs.rs/tokio-util/latest/tokio_util/io/struct.StreamReader.html
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
@@ -96,5 +96,8 @@ pub use pending::{pending, Pending};
|
||||
mod stream_map;
|
||||
pub use stream_map::StreamMap;
|
||||
|
||||
mod stream_close;
|
||||
pub use stream_close::StreamNotifyClose;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use futures_core::Stream;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use crate::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
/// A `Stream` that wraps the values in an `Option`.
|
||||
///
|
||||
/// Whenever the wrapped stream yields an item, this stream yields that item
|
||||
/// wrapped in `Some`. When the inner stream ends, then this stream first
|
||||
/// yields a `None` item, and then this stream will also end.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Using `StreamNotifyClose` to handle closed streams with `StreamMap`.
|
||||
///
|
||||
/// ```
|
||||
/// 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"),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct StreamNotifyClose<S> {
|
||||
#[pin]
|
||||
inner: Option<S>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StreamNotifyClose<S> {
|
||||
/// Create a new `StreamNotifyClose`.
|
||||
pub fn new(stream: S) -> Self {
|
||||
Self {
|
||||
inner: Some(stream),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get back the inner `Stream`.
|
||||
///
|
||||
/// Returns `None` if the stream has reached its end.
|
||||
pub fn into_inner(self) -> Option<S> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Stream for StreamNotifyClose<S>
|
||||
where
|
||||
S: Stream,
|
||||
{
|
||||
type Item = Option<S::Item>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// We can't invoke poll_next after it ended, so we unset the inner stream as a marker.
|
||||
match self
|
||||
.as_mut()
|
||||
.project()
|
||||
.inner
|
||||
.as_pin_mut()
|
||||
.map(|stream| S::poll_next(stream, cx))
|
||||
{
|
||||
Some(Poll::Ready(Some(item))) => Poll::Ready(Some(Some(item))),
|
||||
Some(Poll::Ready(None)) => {
|
||||
self.project().inner.set(None);
|
||||
Poll::Ready(Some(None))
|
||||
}
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[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.
|
||||
let (l, u) = inner.size_hint();
|
||||
(l.saturating_add(1), u.and_then(|u| u.checked_add(1)))
|
||||
} else {
|
||||
(0, Some(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,10 @@ use try_next::TryNext;
|
||||
|
||||
cfg_time! {
|
||||
pub(crate) mod timeout;
|
||||
pub(crate) mod timeout_repeating;
|
||||
use timeout::Timeout;
|
||||
use tokio::time::Duration;
|
||||
use timeout_repeating::TimeoutRepeating;
|
||||
use tokio::time::{Duration, Interval};
|
||||
mod throttle;
|
||||
use throttle::{throttle, Throttle};
|
||||
mod chunks_timeout;
|
||||
@@ -924,7 +926,9 @@ pub trait StreamExt: Stream {
|
||||
/// If the wrapped stream yields a value before the deadline is reached, the
|
||||
/// value is returned. Otherwise, an error is returned. The caller may decide
|
||||
/// to continue consuming the stream and will eventually get the next source
|
||||
/// stream value once it becomes available.
|
||||
/// stream value once it becomes available. See
|
||||
/// [`timeout_repeating`](StreamExt::timeout_repeating) for an alternative
|
||||
/// where the timeouts will repeat.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
@@ -971,7 +975,26 @@ pub trait StreamExt: Stream {
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(all(feature = "time"))]
|
||||
///
|
||||
/// Once a timeout error is received, no further events will be received
|
||||
/// unless the wrapped stream yields a value (timeouts do not repeat).
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::IntervalStream};
|
||||
/// use std::time::Duration;
|
||||
/// let interval_stream = IntervalStream::new(tokio::time::interval(Duration::from_millis(100)));
|
||||
/// let timeout_stream = interval_stream.timeout(Duration::from_millis(10));
|
||||
/// tokio::pin!(timeout_stream);
|
||||
///
|
||||
/// // Only one timeout will be received between values in the source stream.
|
||||
/// assert!(timeout_stream.try_next().await.is_ok());
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected one timeout");
|
||||
/// assert!(timeout_stream.try_next().await.is_ok(), "expected no more timeouts");
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn timeout(self, duration: Duration) -> Timeout<Self>
|
||||
where
|
||||
@@ -980,8 +1003,99 @@ pub trait StreamExt: Stream {
|
||||
Timeout::new(self, duration)
|
||||
}
|
||||
|
||||
/// Applies a per-item timeout to the passed stream.
|
||||
///
|
||||
/// `timeout_repeating()` takes an [`Interval`](tokio::time::Interval) that
|
||||
/// controls the time each element of the stream has to complete before
|
||||
/// timing out.
|
||||
///
|
||||
/// If the wrapped stream yields a value before the deadline is reached, the
|
||||
/// value is returned. Otherwise, an error is returned. The caller may decide
|
||||
/// to continue consuming the stream and will eventually get the next source
|
||||
/// stream value once it becomes available. Unlike `timeout()`, if no value
|
||||
/// becomes available before the deadline is reached, additional errors are
|
||||
/// returned at the specified interval. See [`timeout`](StreamExt::timeout)
|
||||
/// for an alternative where the timeouts do not repeat.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This function consumes the stream passed into it and returns a
|
||||
/// wrapped version of it.
|
||||
///
|
||||
/// Polling the returned stream will continue to poll the inner stream even
|
||||
/// if one or more items time out.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// use std::time::Duration;
|
||||
/// # let int_stream = stream::iter(1..=3);
|
||||
///
|
||||
/// let int_stream = int_stream.timeout_repeating(tokio::time::interval(Duration::from_secs(1)));
|
||||
/// tokio::pin!(int_stream);
|
||||
///
|
||||
/// // When no items time out, we get the 3 elements in succession:
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(2)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(3)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
///
|
||||
/// // If the second item times out, we get an error and continue polling the stream:
|
||||
/// # let mut int_stream = stream::iter(vec![Ok(1), Err(()), Ok(2), Ok(3)]);
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert!(int_stream.try_next().await.is_err());
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(2)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(3)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
///
|
||||
/// // If we want to stop consuming the source stream the first time an
|
||||
/// // element times out, we can use the `take_while` operator:
|
||||
/// # let int_stream = stream::iter(vec![Ok(1), Err(()), Ok(2), Ok(3)]);
|
||||
/// let mut int_stream = int_stream.take_while(Result::is_ok);
|
||||
///
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Timeout errors will be continuously produced at the specified interval
|
||||
/// until the wrapped stream yields a value.
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::IntervalStream};
|
||||
/// use std::time::Duration;
|
||||
/// let interval_stream = IntervalStream::new(tokio::time::interval(Duration::from_millis(23)));
|
||||
/// let timeout_stream = interval_stream.timeout_repeating(tokio::time::interval(Duration::from_millis(9)));
|
||||
/// tokio::pin!(timeout_stream);
|
||||
///
|
||||
/// // Multiple timeouts will be received between values in the source stream.
|
||||
/// assert!(timeout_stream.try_next().await.is_ok());
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected one timeout");
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected a second timeout");
|
||||
/// // Will eventually receive another value from the source stream...
|
||||
/// assert!(timeout_stream.try_next().await.is_ok(), "expected non-timeout");
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn timeout_repeating(self, interval: Interval) -> TimeoutRepeating<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
TimeoutRepeating::new(self, interval)
|
||||
}
|
||||
|
||||
/// Slows down a stream by enforcing a delay between items.
|
||||
///
|
||||
/// The underlying timer behind this utility has a granularity of one millisecond.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Create a throttled stream.
|
||||
@@ -999,7 +1113,7 @@ pub trait StreamExt: Stream {
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(all(feature = "time"))]
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn throttle(self, duration: Duration) -> Throttle<Self>
|
||||
where
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::stream_ext::Fuse;
|
||||
use crate::Stream;
|
||||
use tokio::time::{sleep, Instant, Sleep};
|
||||
use tokio::time::{sleep, Sleep};
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
@@ -16,7 +16,7 @@ pin_project! {
|
||||
#[pin]
|
||||
stream: Fuse<S>,
|
||||
#[pin]
|
||||
deadline: Sleep,
|
||||
deadline: Option<Sleep>,
|
||||
duration: Duration,
|
||||
items: Vec<S::Item>,
|
||||
cap: usize, // https://github.com/rust-lang/futures-rs/issues/1475
|
||||
@@ -27,7 +27,7 @@ impl<S: Stream> ChunksTimeout<S> {
|
||||
pub(super) fn new(stream: S, max_size: usize, duration: Duration) -> Self {
|
||||
ChunksTimeout {
|
||||
stream: Fuse::new(stream),
|
||||
deadline: sleep(duration),
|
||||
deadline: None,
|
||||
duration,
|
||||
items: Vec::with_capacity(max_size),
|
||||
cap: max_size,
|
||||
@@ -45,7 +45,7 @@ impl<S: Stream> Stream for ChunksTimeout<S> {
|
||||
Poll::Pending => break,
|
||||
Poll::Ready(Some(item)) => {
|
||||
if me.items.is_empty() {
|
||||
me.deadline.as_mut().reset(Instant::now() + *me.duration);
|
||||
me.deadline.set(Some(sleep(*me.duration)));
|
||||
me.items.reserve_exact(*me.cap);
|
||||
}
|
||||
me.items.push(item);
|
||||
@@ -67,7 +67,9 @@ impl<S: Stream> Stream for ChunksTimeout<S> {
|
||||
}
|
||||
|
||||
if !me.items.is_empty() {
|
||||
ready!(me.deadline.poll(cx));
|
||||
if let Some(deadline) = me.deadline.as_pin_mut() {
|
||||
ready!(deadline.poll(cx));
|
||||
}
|
||||
return Poll::Ready(Some(std::mem::take(me.items)));
|
||||
}
|
||||
|
||||
|
||||
@@ -195,11 +195,7 @@ where
|
||||
} else {
|
||||
let res = mem::replace(collection, Ok(U::initialize(sealed::Internal, 0, Some(0))));
|
||||
|
||||
if let Err(err) = res {
|
||||
Err(err)
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
Err(res.map(drop).unwrap_err())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,11 @@ where
|
||||
|
||||
let (lower, upper) = self.stream.size_hint();
|
||||
|
||||
let lower = cmp::min(lower, self.remaining as usize);
|
||||
let lower = cmp::min(lower, self.remaining);
|
||||
|
||||
let upper = match upper {
|
||||
Some(x) if x < self.remaining as usize => Some(x),
|
||||
_ => Some(self.remaining as usize),
|
||||
Some(x) if x < self.remaining => Some(x),
|
||||
_ => Some(self.remaining),
|
||||
};
|
||||
|
||||
(lower, upper)
|
||||
|
||||
@@ -72,7 +72,7 @@ where
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let future_len = if self.future.is_some() { 1 } else { 0 };
|
||||
let future_len = usize::from(self.future.is_some());
|
||||
let (lower, upper) = self.stream.size_hint();
|
||||
|
||||
let lower = lower.saturating_add(future_len);
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::Stream;
|
||||
use tokio::time::{Duration, Instant, Sleep};
|
||||
|
||||
use std::future::Future;
|
||||
use std::marker::Unpin;
|
||||
use std::pin::Pin;
|
||||
use std::task::{self, Poll};
|
||||
|
||||
@@ -41,8 +40,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: are these safe if `T: !Unpin`?
|
||||
impl<T: Unpin> Throttle<T> {
|
||||
impl<T> Throttle<T> {
|
||||
/// Acquires a reference to the underlying stream that this combinator is
|
||||
/// pulling from.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
|
||||
@@ -23,8 +23,8 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by `Timeout`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// Error returned by `Timeout` and `TimeoutRepeating`.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Elapsed(());
|
||||
|
||||
impl<S: Stream> Timeout<S> {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use crate::stream_ext::Fuse;
|
||||
use crate::{Elapsed, Stream};
|
||||
use tokio::time::Interval;
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Stream returned by the [`timeout_repeating`](super::StreamExt::timeout_repeating) method.
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
pub struct TimeoutRepeating<S> {
|
||||
#[pin]
|
||||
stream: Fuse<S>,
|
||||
#[pin]
|
||||
interval: Interval,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> TimeoutRepeating<S> {
|
||||
pub(super) fn new(stream: S, interval: Interval) -> Self {
|
||||
TimeoutRepeating {
|
||||
stream: Fuse::new(stream),
|
||||
interval,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for TimeoutRepeating<S> {
|
||||
type Item = Result<S::Item, Elapsed>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut me = self.project();
|
||||
|
||||
match me.stream.poll_next(cx) {
|
||||
Poll::Ready(v) => {
|
||||
if v.is_some() {
|
||||
me.interval.reset();
|
||||
}
|
||||
return Poll::Ready(v.map(Ok));
|
||||
}
|
||||
Poll::Pending => {}
|
||||
};
|
||||
|
||||
ready!(me.interval.poll_tick(cx));
|
||||
Poll::Ready(Some(Err(Elapsed::new())))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (lower, _) = self.stream.size_hint();
|
||||
|
||||
// The timeout stream may insert an error an infinite number of times.
|
||||
(lower, None)
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,18 @@ use std::task::{Context, Poll};
|
||||
/// to be merged, it may be advisable to use tasks sending values on a shared
|
||||
/// [`mpsc`] channel.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// `StreamMap` removes finished streams automatically, without alerting the user.
|
||||
/// In some scenarios, the caller would want to know on closed streams.
|
||||
/// To do this, use [`StreamNotifyClose`] as a wrapper to your stream.
|
||||
/// It will return None when the stream is closed.
|
||||
///
|
||||
/// [`StreamExt::merge`]: crate::StreamExt::merge
|
||||
/// [`mpsc`]: https://docs.rs/tokio/1.0/tokio/sync/mpsc/index.html
|
||||
/// [`pin!`]: https://docs.rs/tokio/1.0/tokio/macro.pin.html
|
||||
/// [`Box::pin`]: std::boxed::Box::pin
|
||||
/// [`StreamNotifyClose`]: crate::StreamNotifyClose
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -170,6 +178,28 @@ use std::task::{Context, Poll};
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Using `StreamNotifyClose` to handle closed streams with `StreamMap`.
|
||||
///
|
||||
/// ```
|
||||
/// 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"),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StreamMap<K, V> {
|
||||
/// Streams stored in the map
|
||||
@@ -568,7 +598,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> std::iter::FromIterator<(K, V)> for StreamMap<K, V>
|
||||
impl<K, V> FromIterator<(K, V)> for StreamMap<K, V>
|
||||
where
|
||||
K: Hash + Eq,
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct BroadcastStream<T> {
|
||||
}
|
||||
|
||||
/// An error returned from the inner stream of a [`BroadcastStream`].
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BroadcastStreamRecvError {
|
||||
/// The receiver lagged too far behind. Attempting to receive again will
|
||||
/// return the oldest message still retained by the channel.
|
||||
|
||||
@@ -10,8 +10,9 @@ use tokio::sync::watch::error::RecvError;
|
||||
|
||||
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// This stream will always start by yielding the current value when the WatchStream is polled,
|
||||
/// regardless of whether it was the initial value or sent afterwards.
|
||||
/// This stream will start by yielding the current value when the WatchStream is polled,
|
||||
/// regardless of whether it was the initial value or sent afterwards,
|
||||
/// unless you use [`WatchStream<T>::from_changes`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -40,6 +41,28 @@ use tokio::sync::watch::error::RecvError;
|
||||
/// let (tx, rx) = watch::channel("hello");
|
||||
/// let mut rx = WatchStream::new(rx);
|
||||
///
|
||||
/// // existing rx output with "hello" is ignored here
|
||||
///
|
||||
/// tx.send("goodbye").unwrap();
|
||||
/// assert_eq!(rx.next().await, Some("goodbye"));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Example with [`WatchStream<T>::from_changes`]:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use futures::future::FutureExt;
|
||||
/// use tokio::sync::watch;
|
||||
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
|
||||
///
|
||||
/// let (tx, rx) = watch::channel("hello");
|
||||
/// let mut rx = WatchStream::from_changes(rx);
|
||||
///
|
||||
/// // no output from rx is available at this point - let's check this:
|
||||
/// assert!(rx.next().now_or_never().is_none());
|
||||
///
|
||||
/// tx.send("goodbye").unwrap();
|
||||
/// assert_eq!(rx.next().await, Some("goodbye"));
|
||||
/// # }
|
||||
@@ -66,6 +89,13 @@ impl<T: 'static + Clone + Send + Sync> WatchStream<T> {
|
||||
inner: ReusableBoxFuture::new(async move { (Ok(()), rx) }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `WatchStream` that waits for the value to be changed.
|
||||
pub fn from_changes(rx: Receiver<T>) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(make_future(rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static + Send + Sync> Stream for WatchStream<T> {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use tokio_stream::{StreamExt, StreamNotifyClose};
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_usage() {
|
||||
let mut stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
|
||||
assert_eq!(stream.next().await, Some(Some(0)));
|
||||
assert_eq!(stream.next().await, Some(Some(1)));
|
||||
assert_eq!(stream.next().await, Some(None));
|
||||
assert_eq!(stream.next().await, None);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "time")]
|
||||
#![cfg(all(feature = "time", not(target_os = "wasi")))] // Wasi does not support panic recovery
|
||||
|
||||
use parking_lot::{const_mutex, Mutex};
|
||||
use std::error::Error;
|
||||
|
||||
@@ -325,62 +325,6 @@ fn one_ready_many_none() {
|
||||
}
|
||||
}
|
||||
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn fuzz_pending_complete_mix(kinds: Vec<bool>) {
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct DidPoll<T> {
|
||||
did_poll: bool,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: Stream + Unpin> Stream for DidPoll<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
|
||||
-> Poll<Option<T::Item>>
|
||||
{
|
||||
self.did_poll = true;
|
||||
Pin::new(&mut self.inner).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let mut expect = 0;
|
||||
|
||||
for (i, &is_empty) in kinds.iter().enumerate() {
|
||||
let inner = if is_empty {
|
||||
pin_box(stream::empty::<()>())
|
||||
} else {
|
||||
expect += 1;
|
||||
pin_box(stream::pending::<()>())
|
||||
};
|
||||
|
||||
let stream = DidPoll {
|
||||
did_poll: false,
|
||||
inner,
|
||||
};
|
||||
|
||||
map.insert(i, stream);
|
||||
}
|
||||
|
||||
if expect == 0 {
|
||||
assert_ready_none!(map.poll_next());
|
||||
} else {
|
||||
assert_pending!(map.poll_next());
|
||||
|
||||
assert_eq!(expect, map.values().count());
|
||||
|
||||
for stream in map.values() {
|
||||
assert!(stream.did_poll);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
|
||||
Box::pin(s)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
use tokio::sync::watch;
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_test::assert_pending;
|
||||
use tokio_test::task::spawn;
|
||||
|
||||
#[tokio::test]
|
||||
async fn message_not_twice() {
|
||||
async fn watch_stream_message_not_twice() {
|
||||
let (tx, rx) = watch::channel("hello");
|
||||
|
||||
let mut counter = 0;
|
||||
@@ -27,3 +29,29 @@ async fn message_not_twice() {
|
||||
drop(tx);
|
||||
task.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watch_stream_from_rx() {
|
||||
let (tx, rx) = watch::channel("hello");
|
||||
|
||||
let mut stream = WatchStream::from(rx);
|
||||
|
||||
assert_eq!(stream.next().await.unwrap(), "hello");
|
||||
|
||||
tx.send("bye").unwrap();
|
||||
|
||||
assert_eq!(stream.next().await.unwrap(), "bye");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watch_stream_from_changes() {
|
||||
let (tx, rx) = watch::channel("hello");
|
||||
|
||||
let mut stream = WatchStream::from_changes(rx);
|
||||
|
||||
assert_pending!(spawn(&mut stream).poll_next());
|
||||
|
||||
tx.send("bye").unwrap();
|
||||
|
||||
assert_eq!(stream.next().await.unwrap(), "bye");
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ name = "tokio-test"
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.2"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -19,7 +19,7 @@ categories = ["asynchronous", "testing"]
|
||||
[dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio-stream = { version = "0.1.1", path = "../tokio-stream" }
|
||||
async-stream = "0.3"
|
||||
async-stream = "0.3.3"
|
||||
|
||||
bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
Copyright (c) 2023 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -310,6 +310,8 @@ impl Inner {
|
||||
|
||||
if now < until {
|
||||
break;
|
||||
} else {
|
||||
self.waiting = None;
|
||||
}
|
||||
} else {
|
||||
self.waiting = Some(Instant::now() + *dur);
|
||||
@@ -407,6 +409,20 @@ impl AsyncWrite for Mock {
|
||||
// If a sleep is set, it has already fired
|
||||
self.inner.sleep = None;
|
||||
|
||||
if self.inner.actions.is_empty() {
|
||||
match self.inner.poll_action(cx) {
|
||||
Poll::Pending => {
|
||||
// do not propagate pending
|
||||
}
|
||||
Poll::Ready(Some(action)) => {
|
||||
self.inner.actions.push_back(action);
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
panic!("unexpected write");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.inner.write(buf) {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if let Some(rem) = self.inner.remaining_wait() {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
//! Tokio and Futures based testing utilities
|
||||
|
||||
pub mod io;
|
||||
pub mod stream_mock;
|
||||
|
||||
mod macros;
|
||||
pub mod task;
|
||||
|
||||
@@ -260,7 +260,7 @@ macro_rules! assert_err {
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts that an exact duration has elapsed since since the start instant ±1ms.
|
||||
/// Asserts that an exact duration has elapsed since the start instant ±1ms.
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::{self, Instant};
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#![cfg(not(loom))]
|
||||
|
||||
//! A mock stream implementing [`Stream`].
|
||||
//!
|
||||
//! # Overview
|
||||
//! This crate provides a `StreamMock` that can be used to test code that interacts with streams.
|
||||
//! It allows you to mock the behavior of a stream and control the items it yields and the waiting
|
||||
//! intervals between items.
|
||||
//!
|
||||
//! # Usage
|
||||
//! To use the `StreamMock`, you need to create a builder using[`StreamMockBuilder`]. The builder
|
||||
//! allows you to enqueue actions such as returning items or waiting for a certain duration.
|
||||
//!
|
||||
//! # Example
|
||||
//! ```rust
|
||||
//!
|
||||
//! use futures_util::StreamExt;
|
||||
//! use std::time::Duration;
|
||||
//! use tokio_test::stream_mock::StreamMockBuilder;
|
||||
//!
|
||||
//! async fn test_stream_mock_wait() {
|
||||
//! let mut stream_mock = StreamMockBuilder::new()
|
||||
//! .next(1)
|
||||
//! .wait(Duration::from_millis(300))
|
||||
//! .next(2)
|
||||
//! .build();
|
||||
//!
|
||||
//! assert_eq!(stream_mock.next().await, Some(1));
|
||||
//! let start = std::time::Instant::now();
|
||||
//! assert_eq!(stream_mock.next().await, Some(2));
|
||||
//! let elapsed = start.elapsed();
|
||||
//! assert!(elapsed >= Duration::from_millis(300));
|
||||
//! assert_eq!(stream_mock.next().await, None);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures_core::{ready, Stream};
|
||||
use std::future::Future;
|
||||
use tokio::time::{sleep_until, Instant, Sleep};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Action<T: Unpin> {
|
||||
Next(T),
|
||||
Wait(Duration),
|
||||
}
|
||||
|
||||
/// A builder for [`StreamMock`]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StreamMockBuilder<T: Unpin> {
|
||||
actions: VecDeque<Action<T>>,
|
||||
}
|
||||
|
||||
impl<T: Unpin> StreamMockBuilder<T> {
|
||||
/// Create a new empty [`StreamMockBuilder`]
|
||||
pub fn new() -> Self {
|
||||
StreamMockBuilder::default()
|
||||
}
|
||||
|
||||
/// Queue an item to be returned by the stream
|
||||
pub fn next(mut self, value: T) -> Self {
|
||||
self.actions.push_back(Action::Next(value));
|
||||
self
|
||||
}
|
||||
|
||||
// Queue an item to be consumed by the sink,
|
||||
// commented out until Sink is implemented.
|
||||
//
|
||||
// pub fn consume(mut self, value: T) -> Self {
|
||||
// self.actions.push_back(Action::Consume(value));
|
||||
// self
|
||||
// }
|
||||
|
||||
/// Queue the stream to wait for a duration
|
||||
pub fn wait(mut self, duration: Duration) -> Self {
|
||||
self.actions.push_back(Action::Wait(duration));
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the [`StreamMock`]
|
||||
pub fn build(self) -> StreamMock<T> {
|
||||
StreamMock {
|
||||
actions: self.actions,
|
||||
sleep: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> Default for StreamMockBuilder<T> {
|
||||
fn default() -> Self {
|
||||
StreamMockBuilder {
|
||||
actions: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mock stream implementing [`Stream`]
|
||||
///
|
||||
/// See [`StreamMockBuilder`] for more information.
|
||||
#[derive(Debug)]
|
||||
pub struct StreamMock<T: Unpin> {
|
||||
actions: VecDeque<Action<T>>,
|
||||
sleep: Option<Pin<Box<Sleep>>>,
|
||||
}
|
||||
|
||||
impl<T: Unpin> StreamMock<T> {
|
||||
fn next_action(&mut self) -> Option<Action<T>> {
|
||||
self.actions.pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> Stream for StreamMock<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
// Try polling the sleep future first
|
||||
if let Some(ref mut sleep) = self.sleep {
|
||||
ready!(Pin::new(sleep).poll(cx));
|
||||
// Since we're ready, discard the sleep future
|
||||
self.sleep.take();
|
||||
}
|
||||
|
||||
match self.next_action() {
|
||||
Some(action) => match action {
|
||||
Action::Next(item) => Poll::Ready(Some(item)),
|
||||
Action::Wait(duration) => {
|
||||
// Set up a sleep future and schedule this future to be polled again for it.
|
||||
self.sleep = Some(Box::pin(sleep_until(Instant::now() + duration)));
|
||||
cx.waker().wake_by_ref();
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
},
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> Drop for StreamMock<T> {
|
||||
fn drop(&mut self) {
|
||||
// Avoid double panicking to make debugging easier.
|
||||
if std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
|
||||
let undropped_count = self
|
||||
.actions
|
||||
.iter()
|
||||
.filter(|action| match action {
|
||||
Action::Next(_) => true,
|
||||
Action::Wait(_) => false,
|
||||
})
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
undropped_count == 0,
|
||||
"StreamMock was dropped before all actions were consumed, {} actions were not consumed",
|
||||
undropped_count
|
||||
);
|
||||
}
|
||||
}
|
||||
+37
-8
@@ -1,4 +1,29 @@
|
||||
//! Futures task based helpers
|
||||
//! Futures task based helpers to easily test futures and manually written futures.
|
||||
//!
|
||||
//! The [`Spawn`] type is used as a mock task harness that allows you to poll futures
|
||||
//! without needing to setup pinning or context. Any future can be polled but if the
|
||||
//! future requires the tokio async context you will need to ensure that you poll the
|
||||
//! [`Spawn`] within a tokio context, this means that as long as you are inside the
|
||||
//! runtime it will work and you can poll it via [`Spawn`].
|
||||
//!
|
||||
//! [`Spawn`] also supports [`Stream`] to call `poll_next` without pinning
|
||||
//! or context.
|
||||
//!
|
||||
//! In addition to circumventing the need for pinning and context, [`Spawn`] also tracks
|
||||
//! the amount of times the future/task was woken. This can be useful to track if some
|
||||
//! leaf future notified the root task correctly.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio_test::task;
|
||||
//!
|
||||
//! let fut = async {};
|
||||
//!
|
||||
//! let mut task = task::spawn(fut);
|
||||
//!
|
||||
//! assert!(task.poll().is_ready(), "Task was not ready!");
|
||||
//! ```
|
||||
|
||||
#![allow(clippy::mutex_atomic)]
|
||||
|
||||
@@ -11,7 +36,11 @@ use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use tokio_stream::Stream;
|
||||
|
||||
/// TODO: dox
|
||||
/// Spawn a future into a [`Spawn`] which wraps the future in a mocked executor.
|
||||
///
|
||||
/// This can be used to spawn a [`Future`] or a [`Stream`].
|
||||
///
|
||||
/// For more information, check the module docs.
|
||||
pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
Spawn {
|
||||
task: MockTask::new(),
|
||||
@@ -19,16 +48,14 @@ pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Future spawned on a mock task
|
||||
/// Future spawned on a mock task that can be used to poll the future or stream
|
||||
/// without needing pinning or context types.
|
||||
#[derive(Debug)]
|
||||
pub struct Spawn<T> {
|
||||
task: MockTask,
|
||||
future: Pin<Box<T>>,
|
||||
}
|
||||
|
||||
/// Mock task
|
||||
///
|
||||
/// A mock task is able to intercept and track wake notifications.
|
||||
#[derive(Debug, Clone)]
|
||||
struct MockTask {
|
||||
waker: Arc<ThreadWaker>,
|
||||
@@ -91,7 +118,8 @@ impl<T: Unpin> ops::DerefMut for Spawn<T> {
|
||||
}
|
||||
|
||||
impl<T: Future> Spawn<T> {
|
||||
/// Polls a future
|
||||
/// If `T` is a [`Future`] then poll it. This will handle pinning and the context
|
||||
/// type for the future.
|
||||
pub fn poll(&mut self) -> Poll<T::Output> {
|
||||
let fut = self.future.as_mut();
|
||||
self.task.enter(|cx| fut.poll(cx))
|
||||
@@ -99,7 +127,8 @@ impl<T: Future> Spawn<T> {
|
||||
}
|
||||
|
||||
impl<T: Stream> Spawn<T> {
|
||||
/// Polls a stream
|
||||
/// If `T` is a [`Stream`] then poll_next it. This will handle pinning and the context
|
||||
/// type for the stream.
|
||||
pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
|
||||
let stream = self.future.as_mut();
|
||||
self.task.enter(|cx| stream.poll_next(cx))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::io;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::time::{Duration, Instant};
|
||||
use tokio_test::io::Builder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -50,6 +51,29 @@ async fn write() {
|
||||
mock.write_all(b"world!").await.expect("write 2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_with_handle() {
|
||||
let (mut mock, mut handle) = Builder::new().build_with_handle();
|
||||
handle.write(b"hello ");
|
||||
handle.write(b"world!");
|
||||
|
||||
mock.write_all(b"hello ").await.expect("write 1");
|
||||
mock.write_all(b"world!").await.expect("write 2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_with_handle() {
|
||||
let (mut mock, mut handle) = Builder::new().build_with_handle();
|
||||
handle.read(b"hello ");
|
||||
handle.read(b"world!");
|
||||
|
||||
let mut buf = vec![0; 6];
|
||||
mock.read_exact(&mut buf).await.expect("read 1");
|
||||
assert_eq!(&buf[..], b"hello ");
|
||||
mock.read_exact(&mut buf).await.expect("read 2");
|
||||
assert_eq!(&buf[..], b"world!");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_error() {
|
||||
let error = io::Error::new(io::ErrorKind::Other, "cruel");
|
||||
@@ -84,3 +108,65 @@ async fn mock_panics_write_data_left() {
|
||||
use tokio_test::io::Builder;
|
||||
Builder::new().write(b"write").build();
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn wait() {
|
||||
const FIRST_WAIT: Duration = Duration::from_secs(1);
|
||||
|
||||
let mut mock = Builder::new()
|
||||
.wait(FIRST_WAIT)
|
||||
.read(b"hello ")
|
||||
.read(b"world!")
|
||||
.build();
|
||||
|
||||
let mut buf = [0; 256];
|
||||
|
||||
let start = Instant::now(); // record the time the read call takes
|
||||
//
|
||||
let n = mock.read(&mut buf).await.expect("read 1");
|
||||
assert_eq!(&buf[..n], b"hello ");
|
||||
println!("time elapsed after first read {:?}", start.elapsed());
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 2");
|
||||
assert_eq!(&buf[..n], b"world!");
|
||||
println!("time elapsed after second read {:?}", start.elapsed());
|
||||
|
||||
// make sure the .wait() instruction worked
|
||||
assert!(
|
||||
start.elapsed() >= FIRST_WAIT,
|
||||
"consuming the whole mock only took {}ms",
|
||||
start.elapsed().as_millis()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn multiple_wait() {
|
||||
const FIRST_WAIT: Duration = Duration::from_secs(1);
|
||||
const SECOND_WAIT: Duration = Duration::from_secs(1);
|
||||
|
||||
let mut mock = Builder::new()
|
||||
.wait(FIRST_WAIT)
|
||||
.read(b"hello ")
|
||||
.wait(SECOND_WAIT)
|
||||
.read(b"world!")
|
||||
.build();
|
||||
|
||||
let mut buf = [0; 256];
|
||||
|
||||
let start = Instant::now(); // record the time it takes to consume the mock
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 1");
|
||||
assert_eq!(&buf[..n], b"hello ");
|
||||
println!("time elapsed after first read {:?}", start.elapsed());
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 2");
|
||||
assert_eq!(&buf[..n], b"world!");
|
||||
println!("time elapsed after second read {:?}", start.elapsed());
|
||||
|
||||
// make sure the .wait() instruction worked
|
||||
assert!(
|
||||
start.elapsed() >= FIRST_WAIT + SECOND_WAIT,
|
||||
"consuming the whole mock only took {}ms",
|
||||
start.elapsed().as_millis()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
use futures_util::StreamExt;
|
||||
use std::time::Duration;
|
||||
use tokio_test::stream_mock::StreamMockBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_mock_empty() {
|
||||
let mut stream_mock = StreamMockBuilder::<u32>::new().build();
|
||||
|
||||
assert_eq!(stream_mock.next().await, None);
|
||||
assert_eq!(stream_mock.next().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_mock_items() {
|
||||
let mut stream_mock = StreamMockBuilder::new().next(1).next(2).build();
|
||||
|
||||
assert_eq!(stream_mock.next().await, Some(1));
|
||||
assert_eq!(stream_mock.next().await, Some(2));
|
||||
assert_eq!(stream_mock.next().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_mock_wait() {
|
||||
let mut stream_mock = StreamMockBuilder::new()
|
||||
.next(1)
|
||||
.wait(Duration::from_millis(300))
|
||||
.next(2)
|
||||
.build();
|
||||
|
||||
assert_eq!(stream_mock.next().await, Some(1));
|
||||
let start = std::time::Instant::now();
|
||||
assert_eq!(stream_mock.next().await, Some(2));
|
||||
let elapsed = start.elapsed();
|
||||
assert!(elapsed >= Duration::from_millis(300));
|
||||
assert_eq!(stream_mock.next().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "StreamMock was dropped before all actions were consumed")]
|
||||
async fn test_stream_mock_drop_without_consuming_all() {
|
||||
let stream_mock = StreamMockBuilder::new().next(1).next(2).build();
|
||||
drop(stream_mock);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "test panic was not masked")]
|
||||
async fn test_stream_mock_drop_during_panic_doesnt_mask_panic() {
|
||||
let _stream_mock = StreamMockBuilder::new().next(1).next(2).build();
|
||||
panic!("test panic was not masked");
|
||||
}
|
||||
@@ -1,3 +1,101 @@
|
||||
# 0.7.8 (April 25th, 2023)
|
||||
|
||||
This release bumps the MSRV of tokio-util to 1.56.
|
||||
|
||||
### Added
|
||||
|
||||
- time: add `DelayQueue::peek` ([#5569])
|
||||
|
||||
### Changed
|
||||
|
||||
This release contains one performance improvement:
|
||||
|
||||
- sync: try to lock the parent first in `CancellationToken` ([#5561])
|
||||
|
||||
### Fixed
|
||||
|
||||
- time: fix panic in `DelayQueue` ([#5630])
|
||||
|
||||
### Documented
|
||||
|
||||
- sync: improve `CancellationToken` doc on child tokens ([#5632])
|
||||
|
||||
[#5561]: https://github.com/tokio-rs/tokio/pull/5561
|
||||
[#5569]: https://github.com/tokio-rs/tokio/pull/5569
|
||||
[#5630]: https://github.com/tokio-rs/tokio/pull/5630
|
||||
[#5632]: https://github.com/tokio-rs/tokio/pull/5632
|
||||
|
||||
# 0.7.7 (February 12, 2023)
|
||||
|
||||
This release reverts the removal of the `Encoder` bound on the `FramedParts`
|
||||
constructor from [#5280] since it turned out to be a breaking change. ([#5450])
|
||||
|
||||
[#5450]: https://github.com/tokio-rs/tokio/pull/5450
|
||||
|
||||
# 0.7.6 (February 10, 2023)
|
||||
|
||||
This release fixes a compilation failure in 0.7.5 when it is used together with
|
||||
Tokio version 1.21 and unstable features are enabled. ([#5445])
|
||||
|
||||
[#5445]: https://github.com/tokio-rs/tokio/pull/5445
|
||||
|
||||
# 0.7.5 (February 9, 2023)
|
||||
|
||||
This release fixes an accidental breaking change where `UnwindSafe` was
|
||||
accidentally removed from `CancellationToken`.
|
||||
|
||||
### Added
|
||||
- codec: add `Framed::backpressure_boundary` ([#5124])
|
||||
- io: add `InspectReader` and `InspectWriter` ([#5033])
|
||||
- io: add `tokio_util::io::{CopyToBytes, SinkWriter}` ([#5070], [#5436])
|
||||
- io: impl `std::io::BufRead` on `SyncIoBridge` ([#5265])
|
||||
- sync: add `PollSemaphore::poll_acquire_many` ([#5137])
|
||||
- sync: add owned future for `CancellationToken` ([#5153])
|
||||
- time: add `DelayQueue::try_remove` ([#5052])
|
||||
|
||||
### Fixed
|
||||
- codec: fix `LengthDelimitedCodec` buffer over-reservation ([#4997])
|
||||
- sync: impl `UnwindSafe` on `CancellationToken` ([#5438])
|
||||
- util: remove `Encoder` bound on `FramedParts` constructor ([#5280])
|
||||
|
||||
### Documented
|
||||
- io: add lines example for `StreamReader` ([#5145])
|
||||
|
||||
[#4997]: https://github.com/tokio-rs/tokio/pull/4997
|
||||
[#5033]: https://github.com/tokio-rs/tokio/pull/5033
|
||||
[#5052]: https://github.com/tokio-rs/tokio/pull/5052
|
||||
[#5070]: https://github.com/tokio-rs/tokio/pull/5070
|
||||
[#5124]: https://github.com/tokio-rs/tokio/pull/5124
|
||||
[#5137]: https://github.com/tokio-rs/tokio/pull/5137
|
||||
[#5145]: https://github.com/tokio-rs/tokio/pull/5145
|
||||
[#5153]: https://github.com/tokio-rs/tokio/pull/5153
|
||||
[#5265]: https://github.com/tokio-rs/tokio/pull/5265
|
||||
[#5280]: https://github.com/tokio-rs/tokio/pull/5280
|
||||
[#5436]: https://github.com/tokio-rs/tokio/pull/5436
|
||||
[#5438]: https://github.com/tokio-rs/tokio/pull/5438
|
||||
|
||||
# 0.7.4 (September 8, 2022)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `SyncIoBridge::shutdown()` ([#4938])
|
||||
- task: improve `LocalPoolHandle` ([#4680])
|
||||
|
||||
### Fixed
|
||||
|
||||
- util: add `track_caller` to public APIs ([#4785])
|
||||
|
||||
### Unstable
|
||||
|
||||
- task: fix compilation errors in `JoinMap` with Tokio v1.21.0 ([#4755])
|
||||
- task: remove the unstable, deprecated `JoinMap::join_one` ([#4920])
|
||||
|
||||
[#4680]: https://github.com/tokio-rs/tokio/pull/4680
|
||||
[#4755]: https://github.com/tokio-rs/tokio/pull/4755
|
||||
[#4785]: https://github.com/tokio-rs/tokio/pull/4785
|
||||
[#4920]: https://github.com/tokio-rs/tokio/pull/4920
|
||||
[#4938]: https://github.com/tokio-rs/tokio/pull/4938
|
||||
|
||||
# 0.7.3 (June 4, 2022)
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -4,9 +4,9 @@ name = "tokio-util"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.7.x" git tag.
|
||||
version = "0.7.3"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
version = "0.7.8"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -34,13 +34,13 @@ rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
|
||||
__docs_rs = ["futures-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.19.0", path = "../tokio", features = ["sync"] }
|
||||
tokio = { version = "1.28.0", path = "../tokio", features = ["sync"] }
|
||||
bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
futures-io = { version = "0.3.0", optional = true }
|
||||
futures-util = { version = "0.3.0", optional = true }
|
||||
pin-project-lite = "0.2.0"
|
||||
pin-project-lite = "0.2.11"
|
||||
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
|
||||
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
@@ -56,6 +56,7 @@ async-stream = "0.3.0"
|
||||
futures = "0.3.0"
|
||||
futures-test = "0.3.5"
|
||||
parking_lot = "0.12.0"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
Copyright (c) 2023 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -253,6 +253,16 @@ impl<T, U> Framed<T, U> {
|
||||
&mut self.inner.state.write.buffer
|
||||
}
|
||||
|
||||
/// Returns backpressure boundary
|
||||
pub fn backpressure_boundary(&self) -> usize {
|
||||
self.inner.state.write.backpressure_boundary
|
||||
}
|
||||
|
||||
/// Updates backpressure boundary
|
||||
pub fn set_backpressure_boundary(&mut self, boundary: usize) {
|
||||
self.inner.state.write.backpressure_boundary = boundary;
|
||||
}
|
||||
|
||||
/// Consumes the `Framed`, returning its underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
|
||||
@@ -25,7 +25,6 @@ pin_project! {
|
||||
}
|
||||
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ReadFrame {
|
||||
@@ -37,6 +36,7 @@ pub(crate) struct ReadFrame {
|
||||
|
||||
pub(crate) struct WriteFrame {
|
||||
pub(crate) buffer: BytesMut,
|
||||
pub(crate) backpressure_boundary: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -60,6 +60,7 @@ impl Default for WriteFrame {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
backpressure_boundary: INITIAL_CAPACITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +88,10 @@ impl From<BytesMut> for WriteFrame {
|
||||
buffer.reserve(INITIAL_CAPACITY - size);
|
||||
}
|
||||
|
||||
Self { buffer }
|
||||
Self {
|
||||
buffer,
|
||||
backpressure_boundary: INITIAL_CAPACITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +260,7 @@ where
|
||||
type Error = U::Error;
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.state.borrow().buffer.len() >= BACKPRESSURE_BOUNDARY {
|
||||
if self.state.borrow().buffer.len() >= self.state.borrow().backpressure_boundary {
|
||||
self.as_mut().poll_flush(cx)
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
@@ -277,7 +281,7 @@ where
|
||||
let mut pinned = self.project();
|
||||
|
||||
while !pinned.state.borrow_mut().buffer.is_empty() {
|
||||
let WriteFrame { buffer } = pinned.state.borrow_mut();
|
||||
let WriteFrame { buffer, .. } = pinned.state.borrow_mut();
|
||||
trace!(remaining = buffer.len(), "writing;");
|
||||
|
||||
let n = ready!(poll_write_buf(pinned.inner.as_mut(), cx, buffer))?;
|
||||
|
||||
@@ -123,6 +123,16 @@ impl<T, E> FramedWrite<T, E> {
|
||||
pub fn write_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.inner.state.buffer
|
||||
}
|
||||
|
||||
/// Returns backpressure boundary
|
||||
pub fn backpressure_boundary(&self) -> usize {
|
||||
self.inner.state.backpressure_boundary
|
||||
}
|
||||
|
||||
/// Updates backpressure boundary
|
||||
pub fn set_backpressure_boundary(&mut self, boundary: usize) {
|
||||
self.inner.state.backpressure_boundary = boundary;
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
|
||||
@@ -522,15 +522,11 @@ impl LengthDelimitedCodec {
|
||||
}
|
||||
};
|
||||
|
||||
let num_skip = self.builder.get_num_skip();
|
||||
|
||||
if num_skip > 0 {
|
||||
src.advance(num_skip);
|
||||
}
|
||||
src.advance(self.builder.get_num_skip());
|
||||
|
||||
// Ensure that the buffer has enough space to read the incoming
|
||||
// payload
|
||||
src.reserve(n);
|
||||
src.reserve(n.saturating_sub(src.len()));
|
||||
|
||||
Ok(Some(n))
|
||||
}
|
||||
@@ -568,7 +564,7 @@ impl Decoder for LengthDelimitedCodec {
|
||||
self.state = DecodeState::Head;
|
||||
|
||||
// Make sure the buffer has enough space to read the next head
|
||||
src.reserve(self.builder.num_head_bytes());
|
||||
src.reserve(self.builder.num_head_bytes().saturating_sub(src.len()));
|
||||
|
||||
Ok(Some(data))
|
||||
}
|
||||
|
||||
@@ -227,12 +227,14 @@ impl<T: tokio::io::AsyncSeek> futures_io::AsyncSeek for Compat<T> {
|
||||
pos: io::SeekFrom,
|
||||
) -> Poll<io::Result<u64>> {
|
||||
if self.seek_pos != Some(pos) {
|
||||
// Ensure previous seeks have finished before starting a new one
|
||||
ready!(self.as_mut().project().inner.poll_complete(cx))?;
|
||||
self.as_mut().project().inner.start_seek(pos)?;
|
||||
*self.as_mut().project().seek_pos = Some(pos);
|
||||
}
|
||||
let res = ready!(self.as_mut().project().inner.poll_complete(cx));
|
||||
*self.as_mut().project().seek_pos = None;
|
||||
Poll::Ready(res.map(|p| p as u64))
|
||||
Poll::Ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +257,7 @@ impl<T: futures_io::AsyncSeek> tokio::io::AsyncSeek for Compat<T> {
|
||||
};
|
||||
let res = ready!(self.as_mut().project().inner.poll_seek(cx, pos));
|
||||
*self.as_mut().project().seek_pos = None;
|
||||
Poll::Ready(res.map(|p| p as u64))
|
||||
Poll::Ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
use bytes::Bytes;
|
||||
use futures_sink::Sink;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
/// A helper that wraps a [`Sink`]`<`[`Bytes`]`>` and converts it into a
|
||||
/// [`Sink`]`<&'a [u8]>` by copying each byte slice into an owned [`Bytes`].
|
||||
///
|
||||
/// See the documentation for [`SinkWriter`] for an example.
|
||||
///
|
||||
/// [`Bytes`]: bytes::Bytes
|
||||
/// [`SinkWriter`]: crate::io::SinkWriter
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
#[derive(Debug)]
|
||||
pub struct CopyToBytes<S> {
|
||||
#[pin]
|
||||
inner: S,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> CopyToBytes<S> {
|
||||
/// Creates a new [`CopyToBytes`].
|
||||
pub fn new(inner: S) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// Gets a reference to the underlying sink.
|
||||
pub fn get_ref(&self) -> &S {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the underlying sink.
|
||||
pub fn get_mut(&mut self) -> &mut S {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
/// Consumes this [`CopyToBytes`], returning the underlying sink.
|
||||
pub fn into_inner(self) -> S {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S> Sink<&'a [u8]> for CopyToBytes<S>
|
||||
where
|
||||
S: Sink<Bytes>,
|
||||
{
|
||||
type Error = S::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: &'a [u8]) -> Result<(), Self::Error> {
|
||||
self.project()
|
||||
.inner
|
||||
.start_send(Bytes::copy_from_slice(item))
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_close(cx)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io::{IoSlice, Result};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
pin_project! {
|
||||
/// An adapter that lets you inspect the data that's being read.
|
||||
///
|
||||
/// This is useful for things like hashing data as it's read in.
|
||||
pub struct InspectReader<R, F> {
|
||||
#[pin]
|
||||
reader: R,
|
||||
f: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, F> InspectReader<R, F> {
|
||||
/// Create a new InspectReader, wrapping `reader` and calling `f` for the
|
||||
/// new data supplied by each read call.
|
||||
///
|
||||
/// The closure will only be called with an empty slice if the inner reader
|
||||
/// returns without reading data into the buffer. This happens at EOF, or if
|
||||
/// `poll_read` is called with a zero-size buffer.
|
||||
pub fn new(reader: R, f: F) -> InspectReader<R, F>
|
||||
where
|
||||
R: AsyncRead,
|
||||
F: FnMut(&[u8]),
|
||||
{
|
||||
InspectReader { reader, f }
|
||||
}
|
||||
|
||||
/// Consumes the `InspectReader`, returning the wrapped reader
|
||||
pub fn into_inner(self) -> R {
|
||||
self.reader
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncRead, F: FnMut(&[u8])> AsyncRead for InspectReader<R, F> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<Result<()>> {
|
||||
let me = self.project();
|
||||
let filled_length = buf.filled().len();
|
||||
ready!(me.reader.poll_read(cx, buf))?;
|
||||
(me.f)(&buf.filled()[filled_length..]);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncWrite, F> AsyncWrite for InspectReader<R, F> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::result::Result<usize, std::io::Error>> {
|
||||
self.project().reader.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), std::io::Error>> {
|
||||
self.project().reader.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), std::io::Error>> {
|
||||
self.project().reader.poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<Result<usize>> {
|
||||
self.project().reader.poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.reader.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// An adapter that lets you inspect the data that's being written.
|
||||
///
|
||||
/// This is useful for things like hashing data as it's written out.
|
||||
pub struct InspectWriter<W, F> {
|
||||
#[pin]
|
||||
writer: W,
|
||||
f: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<W, F> InspectWriter<W, F> {
|
||||
/// Create a new InspectWriter, wrapping `write` and calling `f` for the
|
||||
/// data successfully written by each write call.
|
||||
///
|
||||
/// The closure `f` will never be called with an empty slice. A vectored
|
||||
/// write can result in multiple calls to `f` - at most one call to `f` per
|
||||
/// buffer supplied to `poll_write_vectored`.
|
||||
pub fn new(writer: W, f: F) -> InspectWriter<W, F>
|
||||
where
|
||||
W: AsyncWrite,
|
||||
F: FnMut(&[u8]),
|
||||
{
|
||||
InspectWriter { writer, f }
|
||||
}
|
||||
|
||||
/// Consumes the `InspectWriter`, returning the wrapped writer
|
||||
pub fn into_inner(self) -> W {
|
||||
self.writer
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: AsyncWrite, F: FnMut(&[u8])> AsyncWrite for InspectWriter<W, F> {
|
||||
fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
|
||||
let me = self.project();
|
||||
let res = me.writer.poll_write(cx, buf);
|
||||
if let Poll::Ready(Ok(count)) = res {
|
||||
if count != 0 {
|
||||
(me.f)(&buf[..count]);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
|
||||
let me = self.project();
|
||||
me.writer.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
|
||||
let me = self.project();
|
||||
me.writer.poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<Result<usize>> {
|
||||
let me = self.project();
|
||||
let res = me.writer.poll_write_vectored(cx, bufs);
|
||||
if let Poll::Ready(Ok(mut count)) = res {
|
||||
for buf in bufs {
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
let size = count.min(buf.len());
|
||||
if size != 0 {
|
||||
(me.f)(&buf[..size]);
|
||||
count -= size;
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.writer.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: AsyncRead, F> AsyncRead for InspectWriter<W, F> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
self.project().writer.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,22 @@
|
||||
//! [`Body`]: https://docs.rs/hyper/0.13/hyper/struct.Body.html
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
|
||||
mod copy_to_bytes;
|
||||
mod inspect;
|
||||
mod read_buf;
|
||||
mod reader_stream;
|
||||
mod sink_writer;
|
||||
mod stream_reader;
|
||||
|
||||
cfg_io_util! {
|
||||
mod sync_bridge;
|
||||
pub use self::sync_bridge::SyncIoBridge;
|
||||
}
|
||||
|
||||
pub use self::copy_to_bytes::CopyToBytes;
|
||||
pub use self::inspect::{InspectReader, InspectWriter};
|
||||
pub use self::read_buf::read_buf;
|
||||
pub use self::reader_stream::ReaderStream;
|
||||
pub use self::sink_writer::SinkWriter;
|
||||
pub use self::stream_reader::StreamReader;
|
||||
pub use crate::util::{poll_read_buf, poll_write_buf};
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncWrite;
|
||||
|
||||
pin_project! {
|
||||
/// Convert a [`Sink`] of byte chunks into an [`AsyncWrite`].
|
||||
///
|
||||
/// Whenever you write to this [`SinkWriter`], the supplied bytes are
|
||||
/// forwarded to the inner [`Sink`]. When `shutdown` is called on this
|
||||
/// [`SinkWriter`], the inner sink is closed.
|
||||
///
|
||||
/// This adapter takes a `Sink<&[u8]>` and provides an [`AsyncWrite`] impl
|
||||
/// for it. Because of the lifetime, this trait is relatively rarely
|
||||
/// implemented. The main ways to get a `Sink<&[u8]>` that you can use with
|
||||
/// this type are:
|
||||
///
|
||||
/// * With the codec module by implementing the [`Encoder`]`<&[u8]>` trait.
|
||||
/// * By wrapping a `Sink<Bytes>` in a [`CopyToBytes`].
|
||||
/// * Manually implementing `Sink<&[u8]>` directly.
|
||||
///
|
||||
/// The opposite conversion of implementing `Sink<_>` for an [`AsyncWrite`]
|
||||
/// is done using the [`codec`] module.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::Bytes;
|
||||
/// use futures_util::SinkExt;
|
||||
/// use std::io::{Error, ErrorKind};
|
||||
/// use tokio::io::AsyncWriteExt;
|
||||
/// use tokio_util::io::{SinkWriter, CopyToBytes};
|
||||
/// use tokio_util::sync::PollSender;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), Error> {
|
||||
/// // We use an mpsc channel as an example of a `Sink<Bytes>`.
|
||||
/// let (tx, mut rx) = tokio::sync::mpsc::channel::<Bytes>(1);
|
||||
/// let sink = PollSender::new(tx).sink_map_err(|_| Error::from(ErrorKind::BrokenPipe));
|
||||
///
|
||||
/// // Wrap it in `CopyToBytes` to get a `Sink<&[u8]>`.
|
||||
/// let mut writer = SinkWriter::new(CopyToBytes::new(sink));
|
||||
///
|
||||
/// // Write data to our interface...
|
||||
/// let data: [u8; 4] = [1, 2, 3, 4];
|
||||
/// let _ = writer.write(&data).await?;
|
||||
///
|
||||
/// // ... and receive it.
|
||||
/// assert_eq!(data.as_slice(), &*rx.recv().await.unwrap());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
/// [`CopyToBytes`]: crate::io::CopyToBytes
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`codec`]: crate::codec
|
||||
#[derive(Debug)]
|
||||
pub struct SinkWriter<S> {
|
||||
#[pin]
|
||||
inner: S,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> SinkWriter<S> {
|
||||
/// Creates a new [`SinkWriter`].
|
||||
pub fn new(sink: S) -> Self {
|
||||
Self { inner: sink }
|
||||
}
|
||||
|
||||
/// Gets a reference to the underlying sink.
|
||||
pub fn get_ref(&self) -> &S {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the underlying sink.
|
||||
pub fn get_mut(&mut self) -> &mut S {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
/// Consumes this [`SinkWriter`], returning the underlying sink.
|
||||
pub fn into_inner(self) -> S {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
impl<S, E> AsyncWrite for SinkWriter<S>
|
||||
where
|
||||
for<'a> S: Sink<&'a [u8], Error = E>,
|
||||
E: Into<io::Error>,
|
||||
{
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
let mut this = self.project();
|
||||
|
||||
ready!(this.inner.as_mut().poll_ready(cx).map_err(Into::into))?;
|
||||
match this.inner.as_mut().start_send(buf) {
|
||||
Ok(()) => Poll::Ready(Ok(buf.len())),
|
||||
Err(e) => Poll::Ready(Err(e.into())),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
self.project().inner.poll_flush(cx).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
self.project().inner.poll_close(cx).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user