mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
470
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a664e6c2e5 | ||
|
|
32312ae0d6 | ||
|
|
37ced33efd | ||
|
|
923e72345c | ||
|
|
f619fc0587 | ||
|
|
82fe082ef1 | ||
|
|
1fe1b0e727 | ||
|
|
c6af672353 | ||
|
|
2a05f364b7 | ||
|
|
c6d58ce7e7 | ||
|
|
de360cfc7e | ||
|
|
7d3b0ad192 | ||
|
|
0121120b6d | ||
|
|
bdcea6b2cd | ||
|
|
ee0dc90926 | ||
|
|
78594a7497 | ||
|
|
d87569164f | ||
|
|
e1aebb031c | ||
|
|
fd63094ee0 | ||
|
|
067f229371 | ||
|
|
8c600d0fd2 | ||
|
|
11bfc1345b | ||
|
|
f085b6211b | ||
|
|
30d25ccb8b | ||
|
|
9fccf5339d | ||
|
|
ebf61b45b5 | ||
|
|
02ff0833e0 | ||
|
|
e56ff72fe7 | ||
|
|
4abe9d732e | ||
|
|
f82bcf3f45 | ||
|
|
7db9bc41f1 | ||
|
|
64834ec701 | ||
|
|
967f5715a7 | ||
|
|
e77885a494 | ||
|
|
0926ab195a | ||
|
|
50c23d300a | ||
|
|
1cd6e65d76 | ||
|
|
5ee26af3db | ||
|
|
9271e3ed05 | ||
|
|
cd1823f43e | ||
|
|
a97cf12ed9 | ||
|
|
bde3f20b0f | ||
|
|
670a907c55 | ||
|
|
5030b3005e | ||
|
|
26dee92b53 | ||
|
|
d3565a2923 | ||
|
|
8f81e0814b | ||
|
|
47cc31590f | ||
|
|
dc0f728162 | ||
|
|
89713ad1ec | ||
|
|
6c03e03898 | ||
|
|
16bc4e2e28 | ||
|
|
1afb391350 | ||
|
|
b010b5ddaf | ||
|
|
905c146aed | ||
|
|
56aaa43e91 | ||
|
|
57ff47ab58 | ||
|
|
812de3e134 | ||
|
|
ba82e73c7b | ||
|
|
2e85f9ddf8 | ||
|
|
a7e1cd8ff8 | ||
|
|
5f7be0ac42 | ||
|
|
36d12d2686 | ||
|
|
f943312865 | ||
|
|
5db10f538b | ||
|
|
bbdba7101d | ||
|
|
7cfce54386 | ||
|
|
81370e6202 | ||
|
|
203af02126 | ||
|
|
de230926dc | ||
|
|
432ec3f2b2 | ||
|
|
1604bc3351 | ||
|
|
4c7d8b2a14 | ||
|
|
d7db722bb4 | ||
|
|
fccc28f1d0 | ||
|
|
927df0e9d9 | ||
|
|
98df02d7a4 | ||
|
|
3ea11e2a5f | ||
|
|
c79121391d | ||
|
|
ad8c59add6 | ||
|
|
654d38b132 | ||
|
|
857ba80933 | ||
|
|
95b9342da7 | ||
|
|
0af06b7bab | ||
|
|
01a7f1dfab | ||
|
|
eeb55c733b | ||
|
|
1fc450aefb | ||
|
|
324218f9bb | ||
|
|
aa65d0d0b8 | ||
|
|
bf18ed452d | ||
|
|
43134f1e57 | ||
|
|
b4c3246d33 | ||
|
|
7947fa4bd7 | ||
|
|
9f132172db | ||
|
|
6752f50154 | ||
|
|
6c72168a93 | ||
|
|
ee4de81806 | ||
|
|
a8435c0fc8 | ||
|
|
467d614267 | ||
|
|
66c786540e | ||
|
|
dd1196d369 | ||
|
|
2db0070eb8 | ||
|
|
26de3187e7 | ||
|
|
d3c7f56c10 | ||
|
|
ee04abe797 | ||
|
|
aa1e7e2b9d | ||
|
|
961757f548 | ||
|
|
e67cd87f71 | ||
|
|
a502d1b5c8 | ||
|
|
0273e45ead | ||
|
|
e3ee4e58dc | ||
|
|
8c980ea75a | ||
|
|
e35fd6d6b7 | ||
|
|
03fe44c103 | ||
|
|
d18e5dfbb0 | ||
|
|
f21f2693f0 | ||
|
|
d81e8f0acb | ||
|
|
25e7f2641e | ||
|
|
e1a91ef114 | ||
|
|
1b11840f53 | ||
|
|
09f92b5aed | ||
|
|
e65040f061 | ||
|
|
c23735d43b | ||
|
|
d83921bc51 | ||
|
|
96f64f4ee2 | ||
|
|
00d10c22f8 | ||
|
|
a25d95a8c4 | ||
|
|
9e7e1ef7ad | ||
|
|
5bcd7c1d09 | ||
|
|
2486d49778 | ||
|
|
9dacb1c53e | ||
|
|
8167f87137 | ||
|
|
159f70bc55 | ||
|
|
d1e4db1018 | ||
|
|
530af3f331 | ||
|
|
d89e998922 | ||
|
|
306ed1c30b | ||
|
|
7e952697e8 | ||
|
|
c0943f99f0 | ||
|
|
187a2146a7 | ||
|
|
f61374f931 | ||
|
|
0d6c7af3e4 | ||
|
|
9dc9b53ae1 | ||
|
|
c3b31ba2ab | ||
|
|
b68ea4156a | ||
|
|
8f6d0864c3 | ||
|
|
63abec0525 | ||
|
|
8fd44d9bdc | ||
|
|
450fa2d09d | ||
|
|
8cfa309126 | ||
|
|
bf185b61ff | ||
|
|
27d1383581 | ||
|
|
11f9d4db73 | ||
|
|
1280cf81de | ||
|
|
67682ac2e8 | ||
|
|
b88c02c55e | ||
|
|
240cc44da8 | ||
|
|
7ed6da6733 | ||
|
|
7a2135f426 | ||
|
|
0913cad381 | ||
|
|
ff9681b3c6 | ||
|
|
f1cb007a28 | ||
|
|
2d4853ea16 | ||
|
|
d65165f7b5 | ||
|
|
71a1a3da7a | ||
|
|
df73fa2188 | ||
|
|
09ad5367b8 | ||
|
|
934f68d91c | ||
|
|
41d1877689 | ||
|
|
60b083b630 | ||
|
|
9cc02cc88d | ||
|
|
d2799d791b | ||
|
|
4d4870f291 | ||
|
|
fdb150901a | ||
|
|
426a562780 | ||
|
|
e3b89bbefa | ||
|
|
4f577b84e9 | ||
|
|
f320197693 | ||
|
|
ea6b144cd1 | ||
|
|
264e703296 | ||
|
|
dfb0f00838 | ||
|
|
4a91f197b0 | ||
|
|
601c383ab6 | ||
|
|
484cb52d8d | ||
|
|
16f20c34ed | ||
|
|
46674789ab | ||
|
|
8f4ebfd2f7 | ||
|
|
bd3940c14f | ||
|
|
c27bed36ac | ||
|
|
6d3feb581f | ||
|
|
a708ad19cb | ||
|
|
4bc2a15d28 | ||
|
|
33566434bb | ||
|
|
7388f2d2ea | ||
|
|
0a3e386269 | ||
|
|
d666068be7 | ||
|
|
1b17a7e241 | ||
|
|
5b91709edf | ||
|
|
08f40652aa | ||
|
|
6403b5370e | ||
|
|
064181f386 | ||
|
|
009a2567d0 | ||
|
|
231a3a69f9 | ||
|
|
0fa7755e97 | ||
|
|
d3fe35593d | ||
|
|
0ec0a85461 | ||
|
|
97d06ae1a6 | ||
|
|
b5054e1dff | ||
|
|
398eef8120 | ||
|
|
c8116ecd7b | ||
|
|
5471a5835e | ||
|
|
be99e7aa04 | ||
|
|
3bf2e53f0b | ||
|
|
ab3996a6dd | ||
|
|
c03a37fa0b | ||
|
|
73d733a341 | ||
|
|
749322d351 | ||
|
|
963b631754 | ||
|
|
9a1b076c00 | ||
|
|
c434ed7865 | ||
|
|
4714ca168d | ||
|
|
5e3ad02fb1 | ||
|
|
12412afea4 | ||
|
|
cae083a26f | ||
|
|
fd7a8d7c65 | ||
|
|
d709df2571 | ||
|
|
665f08b5ad | ||
|
|
d4641ba9fc | ||
|
|
2bf80f0ac6 | ||
|
|
62ecff895a | ||
|
|
d84a9e9af3 | ||
|
|
0671c205cc | ||
|
|
1ece2f1fa7 | ||
|
|
12319f26d0 | ||
|
|
454fd8c347 | ||
|
|
4421022c25 | ||
|
|
5a709e391b | ||
|
|
5efb1c3b16 | ||
|
|
f490029b8f | ||
|
|
d25778f67d | ||
|
|
b8318fa172 | ||
|
|
acfdb87e2b | ||
|
|
d060401f6c | ||
|
|
5dacc2e2a8 | ||
|
|
444d3f5c49 | ||
|
|
d23a838732 | ||
|
|
2137f7d953 | ||
|
|
51e9dc0943 | ||
|
|
3762a6a990 | ||
|
|
07f6cc7e1d | ||
|
|
308e3e6871 | ||
|
|
5a1879c2d0 | ||
|
|
de6ef21a81 | ||
|
|
90551d234f | ||
|
|
bd4c3dddca | ||
|
|
49b331855e | ||
|
|
da292dfb66 | ||
|
|
b9feac8d68 | ||
|
|
4fee6e3500 | ||
|
|
b1e69e560c | ||
|
|
556820ff84 | ||
|
|
fd1659a052 | ||
|
|
53e8acac64 | ||
|
|
9e5527d1d5 | ||
|
|
25a24de0e6 | ||
|
|
c1fa25f300 | ||
|
|
e7e02fcf0f | ||
|
|
f7a7f62959 | ||
|
|
d1f1499f63 | ||
|
|
ad6f618952 | ||
|
|
0f9ae13c31 | ||
|
|
9255d96b1b | ||
|
|
ffcc9f7c95 | ||
|
|
1a4cf319b5 | ||
|
|
3698a6f153 | ||
|
|
5b4cbbc39e | ||
|
|
c1f0c76fa0 | ||
|
|
d0953e833d | ||
|
|
b157f5da76 | ||
|
|
35470bfc6e | ||
|
|
95edd8515e | ||
|
|
a0f7f5c94a | ||
|
|
02486978d1 | ||
|
|
8ccf2fb92e | ||
|
|
bce76c515f | ||
|
|
b48586f560 | ||
|
|
eb99e476e6 | ||
|
|
b9b532485b | ||
|
|
1b98d5ad85 | ||
|
|
6d1ae62868 | ||
|
|
3b5a15dfdf | ||
|
|
2af3e4430a | ||
|
|
67869be3d7 | ||
|
|
c6b16cc861 | ||
|
|
7c197c7784 | ||
|
|
5f3f5b0be4 | ||
|
|
32a1acc85f | ||
|
|
7f455b2d93 | ||
|
|
86de2e306b | ||
|
|
6dc4f85f0b | ||
|
|
637fc1d103 | ||
|
|
7a0ca807be | ||
|
|
d8e8037de5 | ||
|
|
de978c47e0 | ||
|
|
024bd60933 | ||
|
|
7127e257a7 | ||
|
|
94b6df699b | ||
|
|
510b9ea9dc | ||
|
|
0fc23971d9 | ||
|
|
f07233f742 | ||
|
|
8efd04e382 | ||
|
|
044eaa1a41 | ||
|
|
03bb6e29b1 | ||
|
|
ac4c95972e | ||
|
|
14e739c306 | ||
|
|
9f59c6952e | ||
|
|
86400a1920 | ||
|
|
c9b4e4c110 | ||
|
|
3eb515e1a7 | ||
|
|
1ed2a1436f | ||
|
|
c6aceed643 | ||
|
|
4590828fb4 | ||
|
|
a99a351802 | ||
|
|
d1e06f831e | ||
|
|
37ca2f049c | ||
|
|
c8371d45bc | ||
|
|
adc3e19ba7 | ||
|
|
925c614c89 | ||
|
|
dd74c7c1bf | ||
|
|
23263231fd | ||
|
|
86528741f9 | ||
|
|
131afd3c53 | ||
|
|
9ed6f70b81 | ||
|
|
46f7d87962 | ||
|
|
987675e843 | ||
|
|
11d7c0486a | ||
|
|
3e84a198e4 | ||
|
|
7497561fed | ||
|
|
ef5b6af7f6 | ||
|
|
0922aa2a0b | ||
|
|
2403b91d75 | ||
|
|
f1d3b065b6 | ||
|
|
cf6b50a3fd | ||
|
|
416e36b0df | ||
|
|
9741c90f9f | ||
|
|
4e3f17bce3 | ||
|
|
f020b5c4b5 | ||
|
|
86cbf81e15 | ||
|
|
be8ee45b3f | ||
|
|
d9b19166cd | ||
|
|
db8edc620f | ||
|
|
e47565b086 | ||
|
|
4730984d66 | ||
|
|
1979615cbf | ||
|
|
1bc50825f3 | ||
|
|
f669a609cf | ||
|
|
ad2e19ffe1 | ||
|
|
5f04d14d81 | ||
|
|
01ea8f22ea | ||
|
|
9f423053fb | ||
|
|
0e5c5d64f5 | ||
|
|
1b27e17ff8 | ||
|
|
8fc62c06c7 | ||
|
|
4b96af6040 | ||
|
|
ce41896f8d | ||
|
|
c8ab78a84f | ||
|
|
3911cb8523 | ||
|
|
d545aa2601 | ||
|
|
911ab21d70 | ||
|
|
9e94fa7e15 | ||
|
|
0d234c3cf9 | ||
|
|
3754e059b6 | ||
|
|
6d868d96ce | ||
|
|
0a3fe46086 | ||
|
|
154d7d5fe6 | ||
|
|
611b793356 | ||
|
|
888ee60e41 | ||
|
|
7dd4d8a30e | ||
|
|
085e616c87 | ||
|
|
a7896d07f1 | ||
|
|
aff24dfbeb | ||
|
|
71cc9ab4c2 | ||
|
|
02cbe4591b | ||
|
|
0783797520 | ||
|
|
ab3ff69cf2 | ||
|
|
a0d5b8ab30 | ||
|
|
a1ee3ef218 | ||
|
|
171cd148a3 | ||
|
|
3f1f268583 | ||
|
|
3e890cc017 | ||
|
|
69290a6432 | ||
|
|
e2b175848b | ||
|
|
b7a75b5be3 | ||
|
|
6b705b3053 | ||
|
|
3636fd018a | ||
|
|
2506c9fa99 | ||
|
|
b3a14483bf | ||
|
|
013f323def | ||
|
|
b926700065 | ||
|
|
99a03a502e | ||
|
|
933fa498d0 | ||
|
|
9f848c9f54 | ||
|
|
912b862a05 | ||
|
|
714e5b571f | ||
|
|
8e999e3806 | ||
|
|
1d980145cb | ||
|
|
8259133ca0 | ||
|
|
38d88c6799 | ||
|
|
c38de96b94 | ||
|
|
2440d113ff | ||
|
|
ab8d7b82a1 | ||
|
|
9563707aaa | ||
|
|
193c1574a1 | ||
|
|
328bd049f6 | ||
|
|
4380de9fe9 | ||
|
|
98f527f42d | ||
|
|
3768696d92 | ||
|
|
d7d4f7d08b | ||
|
|
421a7b001c | ||
|
|
b1bdb3c57b | ||
|
|
7ec77a0677 | ||
|
|
55e3ed2a39 | ||
|
|
17d8c2b29d | ||
|
|
327bec2caf | ||
|
|
ea30a5ea5e | ||
|
|
0cf95f0673 | ||
|
|
a48e418dcb | ||
|
|
4cbcb687f4 | ||
|
|
0715e6defc | ||
|
|
bdd64cc9d3 | ||
|
|
f0fdef80c4 | ||
|
|
00754c8f9c | ||
|
|
1ae9434e8e | ||
|
|
8895bba448 | ||
|
|
48ca254d92 | ||
|
|
a0af02a396 | ||
|
|
0ce3a1188a | ||
|
|
1ea9ce11d4 | ||
|
|
4d4d12613b | ||
|
|
5490267a79 | ||
|
|
1434b32b5a | ||
|
|
159a3b2c85 | ||
|
|
ce87dcfbf0 | ||
|
|
d41d49d202 | ||
|
|
7a6c424f6e | ||
|
|
c3037adac9 | ||
|
|
964fd06e0f | ||
|
|
817fa605ee | ||
|
|
77de684ed9 | ||
|
|
83d550e511 | ||
|
|
1b3d3e7cd6 | ||
|
|
9e044e144b | ||
|
|
cb08fbc6c3 | ||
|
|
e59584a661 | ||
|
|
f7fb0bdc7a | ||
|
|
9faea740df | ||
|
|
2a8c551631 | ||
|
|
676630785b | ||
|
|
0ec4d0db4d | ||
|
|
d83ba30d8d | ||
|
|
f339587b27 | ||
|
|
b663abe091 | ||
|
|
9a11efc262 | ||
|
|
d760b26666 | ||
|
|
5687043328 | ||
|
|
72c87a7724 | ||
|
|
8507e28f89 | ||
|
|
7efcab43c9 | ||
|
|
e4a39d2ef6 | ||
|
|
afd3678f89 |
-58
@@ -1,58 +0,0 @@
|
||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-2
|
||||
env:
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2025-01-25
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 64-bit
|
||||
setup_script:
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all --all-features
|
||||
|
||||
task:
|
||||
name: FreeBSD docs
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_NIGHTLY
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo doc --lib --no-deps --all-features --document-private-items
|
||||
|
||||
task:
|
||||
name: FreeBSD 32-bit
|
||||
setup_script:
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
|
||||
- . $HOME/.cargo/env
|
||||
- rustup target add i686-unknown-freebsd
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all --all-features --target i686-unknown-freebsd
|
||||
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
+31
-19
@@ -1,28 +1,40 @@
|
||||
R-loom-blocking:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio/src/runtime/blocking/*
|
||||
- tokio/src/runtime/blocking/**/*
|
||||
|
||||
R-loom-sync:
|
||||
- tokio/src/sync/*
|
||||
- tokio/src/sync/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio/src/sync/*
|
||||
- tokio/src/sync/**/*
|
||||
|
||||
R-loom-time-driver:
|
||||
- tokio/src/runtime/time/*
|
||||
- tokio/src/runtime/time/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 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/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 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/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 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/**
|
||||
R-loom-util:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio-util/src/*
|
||||
- tokio-util/src/**/*
|
||||
|
||||
@@ -20,5 +20,5 @@ jobs:
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
+372
-176
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'tokio-rs'
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
|
||||
+28
-21
@@ -23,13 +23,29 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
loom-blocking:
|
||||
name: loom tokio::runtime::spawn_blocking
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-blocking') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture loom_blocking
|
||||
working-directory: tokio
|
||||
|
||||
loom-sync:
|
||||
name: loom tokio::sync
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-sync') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -45,14 +61,14 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture runtime::time::tests
|
||||
run: cargo test --lib --release --features full -- --nocapture runtime::time
|
||||
working-directory: tokio
|
||||
|
||||
loom-current-thread:
|
||||
@@ -61,7 +77,7 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -84,7 +100,7 @@ jobs:
|
||||
- scope: loom_multi_thread::group_c
|
||||
- scope: loom_multi_thread::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -96,27 +112,18 @@ jobs:
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
loom-multi-thread-alt:
|
||||
name: loom ALT multi-thread scheduler
|
||||
loom-util:
|
||||
name: loom tokio-util
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread-alt') || (github.base_ref == null))
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-util') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- scope: loom_multi_thread_alt::group_a
|
||||
- scope: loom_multi_thread_alt::group_b
|
||||
- scope: loom_multi_thread_alt::group_c
|
||||
- scope: loom_multi_thread_alt::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- 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
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture
|
||||
working-directory: tokio-util
|
||||
|
||||
@@ -19,5 +19,5 @@ jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
stress-test:
|
||||
- simple_echo_tcp
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
name: Uring Kernel Version Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
kernel_version:
|
||||
description: 'Version of the Linux kernel to build'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
KERNEL_VERSION: ${{ inputs.kernel_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential bison flex libssl-dev libelf-dev \
|
||||
qemu-system-x86 busybox-static cpio xz-utils wget
|
||||
|
||||
- name: Cache Linux source
|
||||
id: cache-kernel
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: linux-${{ env.KERNEL_VERSION }}
|
||||
key: kernel-${{ env.KERNEL_VERSION }}
|
||||
|
||||
- name: Download & build Linux kernel
|
||||
if: steps.cache-kernel.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
MAJOR=${KERNEL_VERSION%%.*}
|
||||
wget https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KERNEL_VERSION}.tar.xz
|
||||
tar xf linux-${KERNEL_VERSION}.tar.xz
|
||||
cd linux-${KERNEL_VERSION}
|
||||
make defconfig
|
||||
make -j$(nproc)
|
||||
|
||||
# We are running tests also on a Kernel version that does not support io_uring
|
||||
# to check if the fallback mechanism works
|
||||
- name: Generate test binaries with io_uring enabled
|
||||
run: |
|
||||
# Build both integration (tokio/tests/) and unit (e.g., tokio/src/fs/file/tests.rs) tests with io_uring enabled
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
RUSTFLAGS="--cfg tokio_unstable" \
|
||||
cargo test -p tokio --features full,io-uring \
|
||||
--target x86_64-unknown-linux-musl --test 'fs*' --lib --no-run
|
||||
|
||||
- name: Prepare initramfs + tests binaries
|
||||
run: |
|
||||
set -e
|
||||
rm -rf initramfs
|
||||
mkdir -p initramfs/{bin,bin/tests,sbin,proc,sys,tmp}
|
||||
|
||||
# Copy test binaries into initramfs
|
||||
for bin in target/x86_64-unknown-linux-musl/debug/deps/{fs_*,tokio-*}; do
|
||||
if [ -f "$bin" ] && [ -x "$bin" ]; then
|
||||
cp "$bin" initramfs/bin/tests
|
||||
fi
|
||||
done
|
||||
|
||||
# Add BusyBox & symlinks
|
||||
cp /usr/bin/busybox initramfs/bin/
|
||||
for cmd in sh mount uname true sleep; do ln -sf busybox initramfs/bin/$cmd; done
|
||||
ln -sf ../bin/busybox initramfs/sbin/poweroff
|
||||
|
||||
# Generate init script
|
||||
cat > initramfs/init << 'EOF'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
mkdir -p /dev
|
||||
# create device nodes, as some tests require them
|
||||
mknod /dev/null c 1 3
|
||||
mknod /dev/zero c 1 5
|
||||
mknod /dev/tty c 5 0
|
||||
mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
mkdir -p /tmp && mount -t tmpfs -o mode=1777 tmpfs /tmp
|
||||
for f in /bin/tests/*; do RUST_BACKTRACE=1 "$f" ; done
|
||||
EOF
|
||||
chmod +x initramfs/init
|
||||
|
||||
# Pack into a CPIO archive
|
||||
(cd initramfs && find . -print0 \
|
||||
| cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz)
|
||||
|
||||
- name: Run tests in QEMU
|
||||
run: |
|
||||
qemu-system-x86_64 \
|
||||
-kernel linux-${{ env.KERNEL_VERSION }}/arch/x86/boot/bzImage \
|
||||
-initrd initramfs.cpio.gz \
|
||||
-append "console=ttyS0 rootfstype=ramfs panic=1" \
|
||||
-nographic -no-reboot -m 1024 -action panic=exit-failure 2>&1 | tee qemu-output.log
|
||||
|
||||
# qemu always exits with 0, so we check if the tests passed by using grep.
|
||||
if grep -q "test result: FAILED" qemu-output.log; then
|
||||
echo "tests failed (QEMU exited abnormally)"
|
||||
exit 1
|
||||
else
|
||||
echo "all tests passed"
|
||||
fi
|
||||
@@ -3,3 +3,4 @@ Cargo.lock
|
||||
|
||||
.cargo/config.toml
|
||||
.cargo/config
|
||||
|
||||
|
||||
+18
-698
@@ -1,7 +1,6 @@
|
||||
# Contributing to Tokio
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
you!
|
||||
Thanks for your help improving Tokio! We are so happy to have you!
|
||||
|
||||
There are opportunities to contribute to Tokio at any level. It doesn't matter if
|
||||
you are just getting started with Rust or are the most weathered expert, we can
|
||||
@@ -9,15 +8,11 @@ use your help.
|
||||
|
||||
**No contribution is too small and all contributions are valued.**
|
||||
|
||||
This guide will help you get started. **Do not let this guide intimidate you**.
|
||||
It should be considered a map to help you navigate the process.
|
||||
See the [contributing guidelines] to get started.
|
||||
|
||||
The [dev channel][dev] is available for any concerns not covered in this guide, please join
|
||||
us!
|
||||
[contributing guidelines]: docs/contributing/README.md
|
||||
|
||||
[dev]: https://discord.gg/tokio
|
||||
|
||||
## Conduct
|
||||
## Code of Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
|
||||
the _minimum_ behavior expected from all contributors. Instances of violations of the
|
||||
@@ -26,709 +21,34 @@ Code of Conduct can be reported by contacting the project team at
|
||||
|
||||
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
## Contributing in Issues
|
||||
## Need Help?
|
||||
|
||||
For any issue, there are fundamentally three ways an individual can contribute:
|
||||
Reach out to us on the [Discord server] for any concern not covered in this guide.
|
||||
|
||||
1. By opening the issue for discussion: For instance, if you believe that you
|
||||
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
|
||||
issue tracker][issue] is the way to report it.
|
||||
|
||||
2. By helping to triage the issue: This can be done by providing
|
||||
supporting details (a test case that demonstrates a bug), providing
|
||||
suggestions on how to address the issue, or ensuring that the issue is tagged
|
||||
correctly.
|
||||
|
||||
3. By helping to resolve the issue: Typically this is done either in the form of
|
||||
demonstrating that the issue reported is not a problem after all, or more
|
||||
often, by opening a Pull Request that changes some bit of something in
|
||||
Tokio in a concrete and reviewable manner.
|
||||
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues
|
||||
|
||||
**Anybody can participate in any stage of contribution**. We urge you to
|
||||
participate in the discussion around bugs and participate in reviewing PRs.
|
||||
|
||||
### Asking for General Help
|
||||
|
||||
If you have reviewed existing documentation and still have questions or are
|
||||
having problems, you can [open a discussion] asking for help.
|
||||
|
||||
In exchange for receiving help, we ask that you contribute back a documentation
|
||||
PR that helps others avoid the problems that you encountered.
|
||||
|
||||
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, you will be presented
|
||||
with a basic template that should be filled in. If you believe that you have
|
||||
uncovered a bug, please fill out this form, following the template to the best
|
||||
of your ability. Do not worry if you cannot answer every detail, just fill in
|
||||
what you can.
|
||||
|
||||
The two most important pieces of information we need in order to properly
|
||||
evaluate the report is a description of the behavior you are seeing and a simple
|
||||
test case we can use to recreate the problem on our own. If we cannot recreate
|
||||
the issue, it becomes impossible for us to fix.
|
||||
|
||||
In order to rule out the possibility of bugs introduced by userland code, test
|
||||
cases should be limited, as much as possible, to using only Tokio APIs.
|
||||
|
||||
See [How to create a Minimal, Complete, and Verifiable example][mcve].
|
||||
|
||||
[mcve]: https://stackoverflow.com/help/mcve
|
||||
|
||||
### Triaging a Bug Report
|
||||
|
||||
Once an issue has been opened, it is not uncommon for there to be discussion
|
||||
around it. Some contributors may have differing opinions about the issue,
|
||||
including whether the behavior being seen is a bug or a feature. This discussion
|
||||
is part of the process and should be kept focused, helpful, and professional.
|
||||
|
||||
Short, clipped responses—that provide neither additional context nor supporting
|
||||
detail—are not helpful or professional. To many, such responses are simply
|
||||
annoying and unfriendly.
|
||||
|
||||
Contributors are encouraged to help one another make forward progress as much as
|
||||
possible, empowering one another to solve issues collaboratively. If you choose
|
||||
to comment on an issue that you feel either is not a problem that needs to be
|
||||
fixed, or if you encounter information in an issue that you feel is incorrect,
|
||||
explain why you feel that way with additional supporting context, and be willing
|
||||
to be convinced that you may be wrong. By doing so, we can often reach the
|
||||
correct outcome much faster.
|
||||
|
||||
### Resolving a Bug Report
|
||||
|
||||
In the majority of cases, issues are resolved by opening a Pull Request. The
|
||||
process for opening and reviewing a Pull Request is similar to that of opening
|
||||
and triaging issues, but carries with it a necessary review and approval
|
||||
workflow that ensures that the proposed changes meet the minimal quality and
|
||||
functional guidelines of the Tokio project.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull Requests are the way concrete changes are made to the code, documentation,
|
||||
and dependencies in the Tokio repository.
|
||||
|
||||
Even tiny pull requests (e.g., one character pull request fixing a typo in API
|
||||
documentation) are greatly appreciated. Before making a large change, it is
|
||||
usually a good idea to first open an issue describing the change to solicit
|
||||
feedback and guidance. This will increase the likelihood of the PR getting
|
||||
merged.
|
||||
|
||||
### Cargo Commands
|
||||
|
||||
Due to the extensive use of features in Tokio, you will often need to add extra
|
||||
arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
Ideally, you should use the same version of clippy as the one used in CI
|
||||
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
|
||||
might have new lints:
|
||||
|
||||
[ci.yml]: .github/workflows/ci.yml
|
||||
|
||||
<!--
|
||||
When updating this, also update:
|
||||
- .github/workflows/ci.yml
|
||||
- README.md
|
||||
- tokio/README.md
|
||||
- tokio/Cargo.toml
|
||||
- tokio-util/Cargo.toml
|
||||
- tokio-test/Cargo.toml
|
||||
- tokio-stream/Cargo.toml
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.77 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation, a simple `cargo doc` is not sufficient. To produce
|
||||
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
|
||||
docs, please use:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
|
||||
```
|
||||
|
||||
This turns on indicators to display the Cargo features required for
|
||||
conditionally compiled APIs in Tokio, and it enables documentation of unstable
|
||||
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
|
||||
*and* rustc.
|
||||
|
||||
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
|
||||
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
|
||||
as docs.rs itself does.
|
||||
|
||||
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
|
||||
|
||||
```
|
||||
cargo install --locked cargo-docs-rs
|
||||
cargo +nightly docs-rs [--open]
|
||||
```
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
|
||||
You can run loom tests with
|
||||
```
|
||||
cd tokio # tokio crate in workspace
|
||||
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
|
||||
run loom tests that test unstable features.
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields" \
|
||||
cargo +nightly miri test --features full --lib --tests
|
||||
```
|
||||
|
||||
### Performing spellcheck on tokio codebase
|
||||
|
||||
You can perform spell-check on tokio codebase. For details of how to use the spellcheck tool, feel free to visit
|
||||
https://github.com/drahnr/cargo-spellcheck
|
||||
```
|
||||
# First install the spell-check plugin
|
||||
cargo install --locked cargo-spellcheck
|
||||
|
||||
# Then run the cargo spell check command
|
||||
cargo spellcheck check
|
||||
```
|
||||
|
||||
if the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
|
||||
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
|
||||
the total number of words included in the file
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: [integration tests][integration-tests]
|
||||
and [documentation tests][documentation-tests].
|
||||
(Tokio avoids [unit tests][unit-tests] as much as possible).
|
||||
|
||||
Tokio uses [conditional compilation attributes][conditional-compilation]
|
||||
throughout the codebase, to modify rustc's behavior. Code marked with such
|
||||
attributes can be enabled using RUSTFLAGS and RUSTDOCFLAGS environment
|
||||
variables. One of the most prevalent flags passed in these variables is
|
||||
the `--cfg` option. To run tests in a particular file, check first what
|
||||
options #![cfg] declaration defines for that file.
|
||||
|
||||
For instance, to run a test marked with the 'tokio_unstable' cfg option,
|
||||
you must pass this flag to the compiler when running the test.
|
||||
```
|
||||
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
|
||||
```
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Integration tests go in the same crate as the code they are testing. Each sub
|
||||
crate should have a `dev-dependency` on `tokio` itself. This makes all Tokio
|
||||
utilities available to use in tests, no matter the crate being tested.
|
||||
|
||||
The best strategy for writing a new integration test is to look at existing
|
||||
integration tests in the crate and follow the style.
|
||||
|
||||
#### Fuzz tests
|
||||
|
||||
Some of our crates include a set of fuzz tests, this will be marked by a
|
||||
directory `fuzz`. It is a good idea to run fuzz tests after each change.
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install --locked cargo-fuzz`
|
||||
|
||||
To list the available fuzzing harnesses you can run;
|
||||
|
||||
```bash
|
||||
$ cd tokio
|
||||
$ cargo fuzz list
|
||||
fuzz_linked_list
|
||||
```
|
||||
|
||||
Running a fuzz test is as simple as;
|
||||
|
||||
`cargo fuzz run fuzz_linked_list`
|
||||
|
||||
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
|
||||
harness will run forever and will only exit if you `ctrl-c` or it finds
|
||||
a bug.
|
||||
|
||||
#### Documentation tests
|
||||
|
||||
Ideally, every API has at least one [documentation test] that demonstrates how to
|
||||
use the API. Documentation tests are run with `cargo test --doc`. This ensures
|
||||
that the example is correct and provides additional test coverage.
|
||||
|
||||
The trick to documentation tests is striking a balance between being succinct
|
||||
for a reader to understand and actually testing the API.
|
||||
|
||||
Same as with integration tests, when writing a documentation test, the full
|
||||
`tokio` crate is available. This is especially useful for getting access to the
|
||||
runtime to run the example.
|
||||
|
||||
The documentation tests will be visible from both the crate specific
|
||||
documentation **and** the `tokio` facade documentation via the re-export. The
|
||||
example should be written from the point of view of a user that is using the
|
||||
`tokio` crate. As such, the example should use the API via the facade and not by
|
||||
directly referencing the crate.
|
||||
|
||||
The type level example for `tokio_timer::Timeout` provides a good example of a
|
||||
documentation test:
|
||||
|
||||
```
|
||||
/// // import the `timeout` function, usually this is done
|
||||
/// // with `use tokio::prelude::*`
|
||||
/// use tokio::prelude::FutureExt;
|
||||
/// use futures::Stream;
|
||||
/// use futures::sync::mpsc;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let (tx, rx) = mpsc::unbounded();
|
||||
/// # tx.unbounded_send(()).unwrap();
|
||||
/// # drop(tx);
|
||||
///
|
||||
/// let process = rx.for_each(|item| {
|
||||
/// // do something with `item`
|
||||
/// # drop(item);
|
||||
/// # Ok(())
|
||||
/// });
|
||||
///
|
||||
/// # tokio::runtime::current_thread::block_on_all(
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// process.timeout(Duration::from_millis(10))
|
||||
/// # ).unwrap();
|
||||
/// # }
|
||||
```
|
||||
|
||||
Given that this is a *type* level documentation test and the primary way users
|
||||
of `tokio` will create an instance of `Timeout` is by using
|
||||
`FutureExt::timeout`, this is how the documentation test is structured.
|
||||
|
||||
Lines that start with `/// #` are removed when the documentation is generated.
|
||||
They are only there to get the test to run. The `block_on_all` function is the
|
||||
easiest way to execute a future from a test.
|
||||
|
||||
If this were a documentation test for the `Timeout::new` function, then the
|
||||
example would explicitly use `Timeout::new`. For example:
|
||||
|
||||
```
|
||||
/// use tokio::timer::Timeout;
|
||||
/// use futures::Future;
|
||||
/// use futures::sync::oneshot;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
/// # tx.send(()).unwrap();
|
||||
///
|
||||
/// # tokio::runtime::current_thread::block_on_all(
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// Timeout::new(rx, Duration::from_millis(10))
|
||||
/// # ).unwrap();
|
||||
/// # }
|
||||
```
|
||||
|
||||
### Benchmarks
|
||||
|
||||
You can run benchmarks locally for the changes you've made to the tokio codebase.
|
||||
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
|
||||
against the changes you have made, for example, you can run;
|
||||
|
||||
```bash
|
||||
cd benches
|
||||
|
||||
# Run all benchmarks.
|
||||
cargo bench
|
||||
|
||||
# Run all tests in the `benches/fs.rs` file
|
||||
cargo bench --bench fs
|
||||
|
||||
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
|
||||
cargo bench async_read_buf
|
||||
|
||||
# After running benches, you can check the statistics under `tokio/target/criterion/`
|
||||
```
|
||||
|
||||
You can also refer to Criterion docs for additional options and details.
|
||||
|
||||
### Commits
|
||||
|
||||
It is a recommended best practice to keep your changes as logically grouped as
|
||||
possible within individual commits. There is no limit to the number of commits
|
||||
any single Pull Request may have, and many contributors find it easier to review
|
||||
changes that are split across multiple commits.
|
||||
|
||||
That said, if you have a number of commits that are "checkpoints" and don't
|
||||
represent a single logical change, please squash those together.
|
||||
|
||||
Note that multiple commits often get squashed when they are landed (see the
|
||||
notes about [commit squashing](#commit-squashing)).
|
||||
|
||||
#### Commit message guidelines
|
||||
|
||||
A good commit message should describe what changed and why.
|
||||
|
||||
1. The first line should:
|
||||
|
||||
* contain a short description of the change (preferably 50 characters or less,
|
||||
and no more than 72 characters)
|
||||
* be entirely in lowercase with the exception of proper nouns, acronyms, and
|
||||
the words that refer to code, like function/variable names
|
||||
* start with an imperative verb
|
||||
* not have a period at the end
|
||||
* be prefixed with the name of the module being changed; usually this is the
|
||||
same as the M-* label on the PR
|
||||
|
||||
Examples:
|
||||
|
||||
* time: introduce `Timeout` and deprecate `Deadline`
|
||||
* codec: export `Encoder`, `Decoder`, `Framed*`
|
||||
* ci: fix the FreeBSD ci configuration
|
||||
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns (except for long URLs).
|
||||
4. If your patch fixes an open issue, you can add a reference to it at the end
|
||||
of the log. Use the `Fixes: #` prefix and the issue number. For other
|
||||
references use `Refs: #`. `Refs` may include multiple issues, separated by a
|
||||
comma.
|
||||
|
||||
Examples:
|
||||
|
||||
- `Fixes: #1337`
|
||||
- `Refs: #1234`
|
||||
|
||||
Sample complete commit message:
|
||||
|
||||
```txt
|
||||
module: explain the commit in one line
|
||||
|
||||
Body of commit message is a few lines of text, explaining things
|
||||
in more detail, possibly giving some background about the issue
|
||||
being fixed, etc.
|
||||
|
||||
The body of the commit message can be several paragraphs, and
|
||||
please do proper word-wrap and keep columns shorter than about
|
||||
72 characters or so. That way, `git log` will show things
|
||||
nicely even when it is indented.
|
||||
|
||||
Fixes: #1337
|
||||
Refs: #453, #154
|
||||
```
|
||||
|
||||
### Opening the Pull Request
|
||||
|
||||
From within GitHub, opening a new Pull Request will present you with a
|
||||
[template] that should be filled out. Please try to do your best at filling out
|
||||
the details, but feel free to skip parts if you're not sure what to put.
|
||||
|
||||
[template]: .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Discuss and update
|
||||
|
||||
You will probably get feedback or requests for changes to your Pull Request.
|
||||
This is a big part of the submission process so don't be discouraged! Some
|
||||
contributors may sign off on the Pull Request right away, others may have
|
||||
more detailed comments or feedback. This is a necessary part of the process
|
||||
in order to evaluate whether the changes are correct and necessary.
|
||||
|
||||
**Any community member can review a PR and you might get conflicting feedback**.
|
||||
Keep an eye out for comments from code owners to provide guidance on conflicting
|
||||
feedback.
|
||||
|
||||
**Once the PR is open, do not rebase the commits**. See [Commit Squashing](#commit-squashing) for
|
||||
more details.
|
||||
|
||||
### Commit Squashing
|
||||
|
||||
In most cases, **do not squash commits that you add to your Pull Request during
|
||||
the review process**. When the commits in your Pull Request land, they may be
|
||||
squashed into one commit per logical change. Metadata will be added to the
|
||||
commit message (including links to the Pull Request, links to relevant issues,
|
||||
and the names of the reviewers). The commit history of your Pull Request,
|
||||
however, will stay intact on the Pull Request page.
|
||||
|
||||
## Reviewing Pull Requests
|
||||
|
||||
**Any Tokio community member is welcome to review any pull request**.
|
||||
|
||||
All Tokio contributors who choose to review and provide feedback on Pull
|
||||
Requests have a responsibility to both the project and the individual making the
|
||||
contribution. Reviews and feedback must be helpful, insightful, and geared
|
||||
towards improving the contribution as opposed to simply blocking it. If there
|
||||
are reasons why you feel the PR should not land, explain what those are. Do not
|
||||
expect to be able to block a Pull Request from advancing simply because you say
|
||||
"No" without giving an explanation. Be open to having your mind changed. Be open
|
||||
to working with the contributor to make the Pull Request better.
|
||||
|
||||
Reviews that are dismissive or disrespectful of the contributor or any other
|
||||
reviewers are strictly counter to the Code of Conduct.
|
||||
|
||||
When reviewing a Pull Request, the primary goals are for the codebase to improve
|
||||
and for the person submitting the request to succeed. **Even if a Pull Request
|
||||
does not land, the submitters should come away from the experience feeling like
|
||||
their effort was not wasted or unappreciated**. Every Pull Request from a new
|
||||
contributor is an opportunity to grow the community.
|
||||
|
||||
### Review a bit at a time.
|
||||
|
||||
Do not overwhelm new contributors.
|
||||
|
||||
It is tempting to micro-optimize and make everything about relative performance,
|
||||
perfect grammar, or exact style matches. Do not succumb to that temptation.
|
||||
|
||||
Focus first on the most significant aspects of the change:
|
||||
|
||||
1. Does this change make sense for Tokio?
|
||||
2. Does this change make Tokio better, even if only incrementally?
|
||||
3. Are there clear bugs or larger scale issues that need attending to?
|
||||
4. Is the commit message readable and correct? If it contains a breaking change
|
||||
is it clear enough?
|
||||
|
||||
Note that only **incremental** improvement is needed to land a PR. This means
|
||||
that the PR does not need to be perfect, only better than the status quo. Follow
|
||||
up PRs may be opened to continue iterating.
|
||||
|
||||
When changes are necessary, *request* them, do not *demand* them, and **do not
|
||||
assume that the submitter already knows how to add a test or run a benchmark**.
|
||||
|
||||
Specific performance optimization techniques, coding styles and conventions
|
||||
change over time. The first impression you give to a new contributor never does.
|
||||
|
||||
Nits (requests for small changes that are not essential) are fine, but try to
|
||||
avoid stalling the Pull Request. Most nits can typically be fixed by the Tokio
|
||||
Collaborator landing the Pull Request but they can also be an opportunity for
|
||||
the contributor to learn a bit more about the project.
|
||||
|
||||
It is always good to clearly indicate nits when you comment: e.g.
|
||||
`Nit: change foo() to bar(). But this is not blocking.`
|
||||
|
||||
If your comments were addressed but were not folded automatically after new
|
||||
commits or if they proved to be mistaken, please, [hide them][hiding-a-comment]
|
||||
with the appropriate reason to keep the conversation flow concise and relevant.
|
||||
|
||||
### Be aware of the person behind the code
|
||||
|
||||
Be aware that *how* you communicate requests and reviews in your feedback can
|
||||
have a significant impact on the success of the Pull Request. Yes, we may land
|
||||
a particular change that makes Tokio better, but the individual might just not
|
||||
want to have anything to do with Tokio ever again. The goal is not just having
|
||||
good code.
|
||||
|
||||
### Abandoned or Stalled Pull Requests
|
||||
|
||||
If a Pull Request appears to be abandoned or stalled, it is polite to first
|
||||
check with the contributor to see if they intend to continue the work before
|
||||
checking if they would mind if you took it over (especially if it just has nits
|
||||
left). When doing so, it is courteous to give the original contributor credit
|
||||
for the work they started (either by preserving their name and email address in
|
||||
the commit log, or by using an `Author: ` meta-data tag in the commit.
|
||||
|
||||
_Adapted from the [Node.js contributing guide][node]_.
|
||||
|
||||
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
|
||||
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
|
||||
[documentation test]: https://doc.rust-lang.org/rustdoc/documentation-tests.html
|
||||
|
||||
## Keeping track of issues and PRs
|
||||
|
||||
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
|
||||
section explains the meaning of various labels, as well as our [GitHub
|
||||
project][project]. The section is primarily targeted at maintainers. Most
|
||||
contributors aren't able to set these labels.
|
||||
|
||||
### Area
|
||||
|
||||
The area label describes the crates relevant to this issue or PR.
|
||||
|
||||
- **A-tokio** This issue concerns the main Tokio crate.
|
||||
- **A-tokio-util** This issue concerns the `tokio-util` crate.
|
||||
- **A-tokio-tls** This issue concerns the `tokio-tls` crate. Only used for
|
||||
older issues, as the crate has been moved to another repository.
|
||||
- **A-tokio-test** The issue concerns the `tokio-test` crate.
|
||||
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
|
||||
be used for the procedural macros, and not `join!` or `select!`.
|
||||
- **A-ci** This issue concerns our GitHub Actions setup.
|
||||
|
||||
### Category
|
||||
|
||||
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
|
||||
- **C-enhancement** This is a PR that adds a new features.
|
||||
- **C-maintenance** This is an issue or PR about stuff such as documentation,
|
||||
GitHub Actions or code quality.
|
||||
- **C-feature-request** This is a feature request. Implementations of feature
|
||||
requests use `C-enhancement` instead.
|
||||
- **C-feature-accepted** If you submit a PR for this feature request, we wont
|
||||
close it with the reason "we don't want this". Issues with this label should
|
||||
also have the `C-feature-request` label.
|
||||
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
|
||||
world"
|
||||
- **C-proposal** A proposal of some kind, and a request for comments.
|
||||
- **C-question** A user question. Large overlap with GitHub discussions.
|
||||
- **C-request** A non-feature request, e.g. "please add deprecation notices to
|
||||
`-alpha.*` versions of crates"
|
||||
|
||||
### Calls for participation
|
||||
|
||||
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
|
||||
or `C-feature-accepted`.
|
||||
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
|
||||
can do after reading the tutorial on our website.
|
||||
- **E-medium** This is not `E-easy` or `E-hard`.
|
||||
- **E-hard** This either involves very tricky code, is something we don't know
|
||||
how to solve, or is difficult for some other reason.
|
||||
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
|
||||
example.
|
||||
|
||||
The "E-" prefix is the same as used in the Rust compiler repository. Some
|
||||
issues are missing a difficulty rating, but feel free to ask on our Discord
|
||||
server if you want to know how difficult an issue likely is.
|
||||
|
||||
### Module
|
||||
|
||||
The module label provides a more fine grained categorization than **Area**.
|
||||
|
||||
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
|
||||
- **M-codec** The `tokio_util::codec` module.
|
||||
- **M-compat** The `tokio_util::compat` module.
|
||||
- **M-coop** Things relevant to coop.
|
||||
- **M-fs** The `tokio::fs` module.
|
||||
- **M-io** The `tokio::io` module.
|
||||
- **M-macros** Issues about any kind of macro.
|
||||
- **M-net** The `tokio::net` module.
|
||||
- **M-process** The `tokio::process` module.
|
||||
- **M-runtime** The `tokio::runtime` module.
|
||||
- **M-signal** The `tokio::signal` module.
|
||||
- **M-sync** The `tokio::sync` module.
|
||||
- **M-task** The `tokio::task` module.
|
||||
- **M-time** The `tokio::time` module.
|
||||
- **M-tracing** Tracing support in Tokio.
|
||||
|
||||
### Topic
|
||||
|
||||
Some extra information.
|
||||
|
||||
- **T-docs** This is about documentation.
|
||||
- **T-performance** This is about performance.
|
||||
- **T-v0.1.x** This is about old Tokio.
|
||||
|
||||
Any label not listed here is not in active use.
|
||||
|
||||
[project]: https://github.com/orgs/tokio-rs/projects/1
|
||||
[Discord server]: https://discord.gg/tokio
|
||||
|
||||
## LTS guarantees
|
||||
|
||||
Tokio ≥1.0.0 comes with LTS guarantees:
|
||||
|
||||
* A minimum of 5 years of maintenance.
|
||||
* A minimum of 3 years before a hypothetical 2.0 release.
|
||||
In Tokio ≥1.0.0, each LTS release comes with the guarantee of at least one year of
|
||||
backported fixes.
|
||||
|
||||
The goal of these guarantees is to provide stability to the ecosystem.
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
|
||||
compiler release.
|
||||
* The MSRV will only be increased on 1.x releases.
|
||||
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
|
||||
compiler release.
|
||||
* The MSRV will only be increased on 1.x releases.
|
||||
|
||||
## Versioning Policy
|
||||
|
||||
With Tokio ≥1.0.0:
|
||||
|
||||
* Patch (1.\_.x) releases _should only_ contain bug fixes or documentation
|
||||
changes. Besides this, these releases should not substantially change
|
||||
runtime behavior.
|
||||
* Minor (1.x) releases may contain new functionality, MSRV increases (see
|
||||
above), minor dependency updates, deprecations, and larger internal
|
||||
implementation changes.
|
||||
* Patch (1.\_.x) releases _should only_ contain bug fixes or documentation
|
||||
changes. Besides this, these releases should not substantially change
|
||||
runtime behavior.
|
||||
* Minor (1.x) releases may contain new functionality, MSRV increases (see
|
||||
above), minor dependency updates, deprecations, and larger internal
|
||||
implementation changes.
|
||||
|
||||
This is as defined by [Semantic Versioning 2.0](https://semver.org/).
|
||||
|
||||
## Releasing
|
||||
|
||||
Since the Tokio project consists of a number of crates, many of which depend on
|
||||
each other, releasing new versions to crates.io can involve some complexities.
|
||||
When releasing a new version of a crate, follow these steps:
|
||||
|
||||
1. **Ensure that the release crate has no path dependencies.** When the HEAD
|
||||
version of a Tokio crate requires unreleased changes in another Tokio crate,
|
||||
the crates.io dependency on the second crate will be replaced with a path
|
||||
dependency. Crates with path dependencies cannot be published, so before
|
||||
publishing the dependent crate, any path dependencies must also be published.
|
||||
This should be done through a form of depth-first tree traversal:
|
||||
|
||||
1. Starting with the first path dependency in the crate to be released,
|
||||
inspect the `Cargo.toml` for the dependency. If the dependency has any
|
||||
path dependencies of its own, repeat this step with the first such
|
||||
dependency.
|
||||
2. Begin the release process for the path dependency.
|
||||
3. Once the path dependency has been published to crates.io, update the
|
||||
dependent crate to depend on the crates.io version.
|
||||
4. When all path dependencies have been published, the dependent crate may
|
||||
be published.
|
||||
|
||||
To verify that a crate is ready to publish, run:
|
||||
|
||||
```bash
|
||||
bin/publish --dry-run <CRATE NAME> <CRATE VERSION>
|
||||
```
|
||||
|
||||
2. **Update Cargo metadata.** After releasing any path dependencies, update the
|
||||
`version` field in `Cargo.toml` to the new version, and the `documentation`
|
||||
field to the docs.rs URL of the new version.
|
||||
3. **Update other documentation links.** Update the "Documentation" link in the
|
||||
crate's `README.md` to point to the docs.rs URL of the new version.
|
||||
4. **Update the changelog for the crate.** Each crate in the Tokio repository
|
||||
has its own `CHANGELOG.md` in that crate's subdirectory. Any changes to that
|
||||
crate since the last release should be added to the changelog. Change
|
||||
descriptions may be taken from the Git history, but should be edited to
|
||||
ensure a consistent format, based on [Keep A Changelog][keep-a-changelog].
|
||||
Other entries in that crate's changelog may also be used for reference.
|
||||
5. **Perform a final audit for breaking changes.** Compare the HEAD version of
|
||||
crate with the Git tag for the most recent release version. If there are any
|
||||
breaking API changes, determine if those changes can be made without breaking
|
||||
existing APIs. If so, resolve those issues. Otherwise, if it is necessary to
|
||||
make a breaking release, update the version numbers to reflect this.
|
||||
6. **Open a pull request with your changes.** Once that pull request has been
|
||||
approved by a maintainer and the pull request has been merged, continue to
|
||||
the next step.
|
||||
7. **Release the crate.** Run the following command:
|
||||
|
||||
```bash
|
||||
bin/publish <NAME OF CRATE> <VERSION>
|
||||
```
|
||||
|
||||
Your editor and prompt you to edit a message for the tag. Copy the changelog
|
||||
entry for that release version into your editor and close the window.
|
||||
|
||||
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog/blob/master/CHANGELOG.md
|
||||
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
|
||||
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
|
||||
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
|
||||
+8
-1
@@ -15,6 +15,13 @@ members = [
|
||||
"tests-integration",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
tokio = { path = "tokio" }
|
||||
tokio-macros = { path = "tokio-macros" }
|
||||
tokio-stream = { path = "tokio-stream" }
|
||||
tokio-test = { path = "tokio-test" }
|
||||
tokio-util = { path = "tokio-util" }
|
||||
|
||||
[workspace.metadata.spellcheck]
|
||||
config = "spellcheck.toml"
|
||||
|
||||
@@ -27,6 +34,6 @@ unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(tokio_internal_mt_counters)',
|
||||
'cfg(tokio_no_parking_lot)',
|
||||
'cfg(tokio_no_tuning_tests)',
|
||||
'cfg(tokio_taskdump)',
|
||||
'cfg(tokio_unstable)',
|
||||
'cfg(target_os, values("cygwin"))',
|
||||
] }
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
*[TokioConf 2026 program and tickets are now available!](https://tokioconf.com)*
|
||||
|
||||
---
|
||||
|
||||
# Tokio
|
||||
|
||||
A runtime for writing reliable, asynchronous, and slim applications with
|
||||
@@ -39,7 +43,7 @@ level, it provides a few major components:
|
||||
|
||||
* A multithreaded, work-stealing based task [scheduler].
|
||||
* A reactor backed by the operating system's event queue (epoll, kqueue,
|
||||
IOCP, etc...).
|
||||
IOCP, etc.).
|
||||
* Asynchronous [TCP and UDP][net] sockets.
|
||||
|
||||
These components provide the runtime components necessary for building
|
||||
@@ -52,11 +56,11 @@ an asynchronous application.
|
||||
|
||||
A basic TCP echo server with Tokio.
|
||||
|
||||
Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
Make sure you enable the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
tokio = { version = "1.52.3", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -103,7 +107,7 @@ More examples can be found [here][examples]. For a larger "real world" example,
|
||||
[examples]: https://github.com/tokio-rs/tokio/tree/master/examples
|
||||
[mini-redis]: https://github.com/tokio-rs/mini-redis/
|
||||
|
||||
To see a list of the available features flags that can be enabled, check our
|
||||
To see a list of the available feature flags that can be enabled, check our
|
||||
[docs][feature-flag-docs].
|
||||
|
||||
## Getting Help
|
||||
@@ -125,7 +129,7 @@ question. You can also ask your question on [the discussions page][discussions].
|
||||
you! We have a [contributing guide][guide] to help you get involved in the Tokio
|
||||
project.
|
||||
|
||||
[guide]: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
|
||||
[guide]: https://github.com/tokio-rs/tokio/blob/master/docs/contributing/README.md
|
||||
|
||||
## Related Projects
|
||||
|
||||
@@ -186,12 +190,13 @@ 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.70.
|
||||
released at least six months ago. The current MSRV is 1.71.
|
||||
|
||||
Note that the MSRV is not increased automatically, and only as part of a minor
|
||||
release. The MSRV history for past minor releases can be found below:
|
||||
|
||||
* 1.39 to now - Rust 1.70
|
||||
* 1.48 to now - Rust 1.71
|
||||
* 1.39 to 1.47 - Rust 1.70
|
||||
* 1.30 to 1.38 - Rust 1.63
|
||||
* 1.27 to 1.29 - Rust 1.56
|
||||
* 1.17 to 1.26 - Rust 1.49
|
||||
@@ -216,18 +221,18 @@ warrants a patch release with a fix for the bug, it will be backported and
|
||||
released as a new patch release for each LTS minor version. Our current LTS
|
||||
releases are:
|
||||
|
||||
* `1.38.x` - LTS release until July 2025. (MSRV 1.63)
|
||||
* `1.43.x` - LTS release until March 2026. (MSRV 1.70)
|
||||
* `1.47.x` - LTS release until September 2026. (MSRV 1.70)
|
||||
* `1.51.x` - LTS release until March 2027. (MSRV 1.71)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
use an LTS release.
|
||||
|
||||
To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.32.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.47.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.38", features = [...] }
|
||||
tokio = { version = "~1.47", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -239,6 +244,8 @@ tokio = { version = "~1.38", features = [...] }
|
||||
* `1.25.x` - LTS release until March 2024.
|
||||
* `1.32.x` - LTS release until September 2024.
|
||||
* `1.36.x` - LTS release until March 2025.
|
||||
* `1.38.x` - LTS release until July 2025.
|
||||
* `1.43.x` - LTS release until March 2026.
|
||||
|
||||
## License
|
||||
|
||||
@@ -249,5 +256,5 @@ This project is licensed under the [MIT license].
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
for inclusion in Tokio by you shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
|
||||
+12
-2
@@ -11,8 +11,8 @@ test-util = ["tokio/test-util"]
|
||||
[dependencies]
|
||||
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
criterion = "0.5.1"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
|
||||
@@ -96,5 +96,15 @@ name = "time_timeout"
|
||||
path = "time_timeout.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "spawn_blocking"
|
||||
path = "spawn_blocking.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "remote_spawn"
|
||||
path = "remote_spawn.rs"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ impl SlowHddWriter {
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
let service_res = self.as_mut().service_write(cx);
|
||||
|
||||
if service_res.is_pending() && self.blocking_rng.gen_bool(PROBABILITY_FLUSH_WAIT) {
|
||||
if service_res.is_pending() && self.blocking_rng.random_bool(PROBABILITY_FLUSH_WAIT) {
|
||||
return Poll::Pending;
|
||||
}
|
||||
let available = self.buffer_size - self.buffer_used;
|
||||
@@ -145,7 +145,7 @@ impl ChunkReader {
|
||||
fn new(chunk_size: usize, service_interval: Duration) -> Self {
|
||||
let mut service_intervals = interval(service_interval);
|
||||
service_intervals.set_missed_tick_behavior(MissedTickBehavior::Burst);
|
||||
let data: Vec<u8> = std::iter::repeat(0).take(chunk_size).collect();
|
||||
let data: Vec<u8> = std::iter::repeat_n(0, chunk_size).collect();
|
||||
Self {
|
||||
data,
|
||||
service_intervals,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
//! Benchmark remote task spawning (push_remote_task) at different concurrency
|
||||
//! levels on the multi-threaded scheduler.
|
||||
//!
|
||||
//! This measures contention on the scheduler's inject queue mutex when multiple
|
||||
//! external (non-worker) threads spawn tasks into the tokio runtime simultaneously.
|
||||
//! Every rt.spawn() from an external thread unconditionally goes through
|
||||
//! push_remote_task, making this a direct measurement of inject queue contention.
|
||||
//!
|
||||
//! For each parallelism level N (1, 2, 4, 8, 16, 32, 64, capped at available parallelism):
|
||||
//! - Spawns N std::threads (external to the runtime)
|
||||
//! - Each thread spawns TOTAL_TASKS / N tasks into the runtime via rt.spawn()
|
||||
//! - All threads are synchronized with a barrier to maximize contention
|
||||
//! - Tasks are trivial no-ops to isolate the push overhead
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use std::sync::Barrier;
|
||||
use tokio::runtime::{self, Runtime};
|
||||
|
||||
/// Total number of tasks spawned across all threads per iteration.
|
||||
/// Must be divisible by the largest parallelism level (64).
|
||||
const TOTAL_TASKS: usize = 12_800;
|
||||
const _: () = assert!(
|
||||
TOTAL_TASKS.is_multiple_of(64),
|
||||
"TOTAL_TASKS must be divisible by 64"
|
||||
);
|
||||
|
||||
fn remote_spawn_contention(c: &mut Criterion) {
|
||||
let parallelism_levels = parallelism_levels();
|
||||
let mut group = c.benchmark_group("remote_spawn");
|
||||
|
||||
for num_threads in ¶llelism_levels {
|
||||
let num_threads = *num_threads;
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("threads", num_threads),
|
||||
&num_threads,
|
||||
|b, &num_threads| {
|
||||
let rt = rt();
|
||||
let tasks_per_thread = TOTAL_TASKS / num_threads;
|
||||
let barrier = Barrier::new(num_threads);
|
||||
|
||||
b.iter_custom(|iters| {
|
||||
let mut total_duration = std::time::Duration::ZERO;
|
||||
for _ in 0..iters {
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let all_handles = std::thread::scope(|s| {
|
||||
let handles: Vec<_> = (0..num_threads)
|
||||
.map(|_| {
|
||||
let barrier = &barrier;
|
||||
let rt = &rt;
|
||||
s.spawn(move || {
|
||||
let mut join_handles = Vec::with_capacity(tasks_per_thread);
|
||||
barrier.wait();
|
||||
|
||||
for _ in 0..tasks_per_thread {
|
||||
join_handles.push(rt.spawn(async {}));
|
||||
}
|
||||
join_handles
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
handles
|
||||
.into_iter()
|
||||
.flat_map(|h| h.join().unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
total_duration += start.elapsed();
|
||||
|
||||
rt.block_on(async {
|
||||
for h in all_handles {
|
||||
h.await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
total_duration
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn parallelism_levels() -> Vec<usize> {
|
||||
let max_parallelism = std::thread::available_parallelism()
|
||||
.map(|p| p.get())
|
||||
.unwrap_or(1);
|
||||
|
||||
[1, 2, 4, 8, 16, 32, 64]
|
||||
.into_iter()
|
||||
.filter(|&n| n <= max_parallelism)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread().build().unwrap()
|
||||
}
|
||||
|
||||
criterion_group!(remote_spawn_benches, remote_spawn_contention);
|
||||
|
||||
criterion_main!(remote_spawn_benches);
|
||||
+19
-14
@@ -10,10 +10,21 @@ async fn work() -> usize {
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
fn single_rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn multi_rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = single_rt();
|
||||
|
||||
c.bench_function("basic_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
@@ -26,9 +37,7 @@ fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = single_rt();
|
||||
|
||||
c.bench_function("basic_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
@@ -46,10 +55,8 @@ fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = multi_rt();
|
||||
|
||||
c.bench_function("threaded_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
@@ -61,10 +68,8 @@ fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = multi_rt();
|
||||
|
||||
c.bench_function("threaded_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//! Benchmark spawn_blocking at different concurrency levels on the multi-threaded scheduler.
|
||||
//!
|
||||
//! For each parallelism level N (1, 2, 4, 8, 16, 32, 64, capped at available parallelism):
|
||||
//! - Spawns N regular async tasks
|
||||
//! - Each task spawns M batches of B spawn_blocking tasks (no-ops)
|
||||
//! - Each batch is awaited to completion before starting the next
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use tokio::runtime::{self, Runtime};
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
/// Number of batches per task
|
||||
const NUM_BATCHES: usize = 100;
|
||||
/// Number of spawn_blocking calls per batch
|
||||
const BATCH_SIZE: usize = 16;
|
||||
|
||||
fn spawn_blocking_concurrency(c: &mut Criterion) {
|
||||
let max_parallelism = std::thread::available_parallelism()
|
||||
.map(|p| p.get())
|
||||
.unwrap_or(1);
|
||||
|
||||
let parallelism_levels: Vec<usize> = [1, 2, 4, 8, 16, 32, 64]
|
||||
.into_iter()
|
||||
.filter(|&n| n <= max_parallelism)
|
||||
.collect();
|
||||
|
||||
let mut group = c.benchmark_group("spawn_blocking");
|
||||
|
||||
for num_tasks in parallelism_levels {
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("concurrency", num_tasks),
|
||||
&num_tasks,
|
||||
|b, &num_tasks| {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
let mut tasks = JoinSet::new();
|
||||
|
||||
for _ in 0..num_tasks {
|
||||
tasks.spawn(async {
|
||||
for _ in 0..NUM_BATCHES {
|
||||
let mut batch = JoinSet::new();
|
||||
|
||||
for _ in 0..BATCH_SIZE {
|
||||
batch.spawn_blocking(|| black_box(0));
|
||||
}
|
||||
|
||||
batch.join_all().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tasks.join_all().await;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
criterion_group!(spawn_blocking_benches, spawn_blocking_concurrency);
|
||||
|
||||
criterion_main!(spawn_blocking_benches);
|
||||
@@ -17,7 +17,7 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
let _ = write!(&mut message, " {i}={}", rng.random::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
|
||||
@@ -17,7 +17,7 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
let _ = write!(&mut message, " {i}={}", rng.random::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
|
||||
@@ -9,7 +9,7 @@ allow = [
|
||||
"Apache-2.0",
|
||||
]
|
||||
exceptions = [
|
||||
{ allow = ["Unicode-3.0", "Unicode-DFS-2016"], crate = "unicode-ident" },
|
||||
{ allow = ["Unicode-3.0"], crate = "unicode-ident" },
|
||||
]
|
||||
|
||||
[bans]
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Contributing
|
||||
|
||||
This guide will help you get started. **Do not let this guide intimidate you**.
|
||||
It should be considered a map to help you navigate the process.
|
||||
|
||||
## Quick start
|
||||
|
||||
If you are unsure where to begin, use the following guides:
|
||||
|
||||
- Want to report or triage a bug? Start with [Contributing in Issues](contributing-in-issues.md).
|
||||
- Looking for something to work on? Check the open [`issues`](https://github.com/tokio-rs/tokio/issues)
|
||||
(some might be in progress or still under discussion, so leave a comment before starting the work).
|
||||
- Planning to submit a PR? Read [Pull Requests](pull-requests.md) for the full workflow and required checks.
|
||||
- Want to understand what the labels on issues mean? See [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md).
|
||||
- Interested in code review? See [Reviewing Pull Requests](reviewing-pull-requests.md).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Contributing in Issues](contributing-in-issues.md)
|
||||
- [Asking for General Help](contributing-in-issues.md#asking-for-general-help)
|
||||
- [Submitting a Bug Report](contributing-in-issues.md#submitting-a-bug-report)
|
||||
- [Triaging a Bug Report](contributing-in-issues.md#triaging-a-bug-report)
|
||||
- [Resolving a Bug Report](contributing-in-issues.md#resolving-a-bug-report)
|
||||
- [Pull Requests](pull-requests.md)
|
||||
- [Cargo Commands](pull-requests.md#cargo-commands)
|
||||
- [Performing spellcheck on Tokio codebase](pull-requests.md#performing-spellcheck-on-tokio-codebase)
|
||||
- [Tests](pull-requests.md#tests)
|
||||
- [Integration tests](pull-requests.md#integration-tests)
|
||||
- [Fuzz tests](pull-requests.md#fuzz-tests)
|
||||
- [Documentation tests](pull-requests.md#documentation-tests)
|
||||
- [Benchmarks](pull-requests.md#benchmarks)
|
||||
- [Commits](pull-requests.md#commits)
|
||||
- [Commit message guidelines](pull-requests.md#commit-message-guidelines)
|
||||
- [Opening the Pull Request](pull-requests.md#opening-the-pull-request)
|
||||
- [Discuss and update](pull-requests.md#discuss-and-update)
|
||||
- [Commit Squashing](pull-requests.md#commit-squashing)
|
||||
- [Reviewing Pull Requests](reviewing-pull-requests.md)
|
||||
- [Review a bit at a time](reviewing-pull-requests.md#review-a-bit-at-a-time)
|
||||
- [Be aware of the person behind the code](reviewing-pull-requests.md#be-aware-of-the-person-behind-the-code)
|
||||
- [Abandoned or Stalled Pull Requests](reviewing-pull-requests.md#abandoned-or-stalled-pull-requests)
|
||||
- [How to specify crates dependencies versions](how-to-specify-crates-dependencies-versions.md)
|
||||
- [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md)
|
||||
- [Area](keeping-track-of-issues-and-prs.md#area)
|
||||
- [Category](keeping-track-of-issues-and-prs.md#category)
|
||||
- [Calls for participation](keeping-track-of-issues-and-prs.md#calls-for-participation)
|
||||
- [Module](keeping-track-of-issues-and-prs.md#module)
|
||||
- [Topic](keeping-track-of-issues-and-prs.md#topic)
|
||||
@@ -0,0 +1,79 @@
|
||||
## Contributing in Issues
|
||||
|
||||
For any issue, there are fundamentally three ways an individual can contribute:
|
||||
|
||||
1. By opening the issue for discussion: For instance, if you believe that you
|
||||
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
|
||||
issue tracker][issue] is the way to report it.
|
||||
|
||||
2. By helping to triage the issue: This can be done by providing
|
||||
supporting details (a test case that demonstrates a bug), providing
|
||||
suggestions on how to address the issue, or ensuring that the issue is tagged
|
||||
correctly.
|
||||
|
||||
3. By helping to resolve the issue: Typically this is done either in the form of
|
||||
demonstrating that the issue reported is not a problem after all, or more
|
||||
often, by opening a Pull Request that changes some bit of something in
|
||||
Tokio in a concrete and reviewable manner.
|
||||
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues
|
||||
|
||||
**Anybody can participate in any stage of contribution**. We urge you to
|
||||
participate in the discussion around bugs and participate in reviewing PRs.
|
||||
|
||||
### Asking for General Help
|
||||
|
||||
If you have reviewed existing documentation and still have questions or are
|
||||
having problems, you can [open a discussion] asking for help.
|
||||
|
||||
In exchange for receiving help, we ask that you contribute back a documentation
|
||||
PR that helps others avoid the problems that you encountered.
|
||||
|
||||
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new/choose
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, you will be presented
|
||||
with a basic template that should be filled in. If you believe that you have
|
||||
uncovered a bug, please fill out this form, following the template to the best
|
||||
of your ability. Do not worry if you cannot answer every detail, just fill in
|
||||
what you can.
|
||||
|
||||
The two most important pieces of information we need in order to properly
|
||||
evaluate the report is a description of the behavior you are seeing and a simple
|
||||
test case we can use to recreate the problem on our own. If we cannot recreate
|
||||
the issue, it becomes impossible for us to fix.
|
||||
|
||||
In order to rule out the possibility of bugs introduced by userland code, test
|
||||
cases should be limited, as much as possible, to using only Tokio APIs.
|
||||
|
||||
See [How to create a Minimal, Complete, and Verifiable example][mcve].
|
||||
|
||||
[mcve]: https://stackoverflow.com/help/mcve
|
||||
|
||||
### Triaging a Bug Report
|
||||
|
||||
Once an issue has been opened, it is not uncommon for there to be discussion
|
||||
around it. Some contributors may have differing opinions about the issue,
|
||||
including whether the behavior being seen is a bug or a feature. This discussion
|
||||
is part of the process and should be kept focused, helpful, and professional.
|
||||
|
||||
Short, clipped responses—that provide neither additional context nor supporting
|
||||
detail—are not helpful or professional. To many, such responses are simply
|
||||
annoying and unfriendly.
|
||||
|
||||
Contributors are encouraged to help one another make forward progress as much as
|
||||
possible, empowering one another to solve issues collaboratively. If you choose
|
||||
to comment on an issue that you feel either is not a problem that needs to be
|
||||
fixed, or if you encounter information in an issue that you feel is incorrect,
|
||||
explain why you feel that way with additional supporting context, and be willing
|
||||
to be convinced that you may be wrong. By doing so, we can often reach the
|
||||
correct outcome much faster.
|
||||
|
||||
### Resolving a Bug Report
|
||||
|
||||
In the majority of cases, issues are resolved by opening a Pull Request. The
|
||||
process for opening and reviewing a Pull Request is similar to that of opening
|
||||
and triaging issues, but carries with it a necessary review and approval
|
||||
workflow that ensures that the proposed changes meet the minimal quality and
|
||||
functional guidelines of the Tokio project.
|
||||
@@ -0,0 +1,32 @@
|
||||
# How to specify crates dependencies versions
|
||||
|
||||
Each crate (e.g., `tokio-util`, `tokio-stream`, etc.) should specify dependencies
|
||||
according to the following rules:
|
||||
|
||||
1. The listed version should be the oldest version that the crate works with
|
||||
(e.g., if `tokio-util` works with `tokio` version `1.44` but not `1.43`, then
|
||||
`tokio-util` should specify version `1.44` for its `tokio` dependency).
|
||||
We don't require users to use the latest version unnecessarily.
|
||||
2. When a crate starts using a newer feature in a dependency, the version
|
||||
should be bumped to the version that introduced it.
|
||||
3. If a crate depends on an unreleased feature in a dependency, it may use
|
||||
`path =` dependency to specify this. Since path dependencies must be removed
|
||||
during the release of the crate, this ensures that it can't be released until
|
||||
the dependency has a new version.
|
||||
|
||||
Consider the following example from `tokio-stream`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.11"
|
||||
tokio = { version = "1.38.0", path = "../tokio", features = ["sync"] }
|
||||
```
|
||||
|
||||
In this case, local development of `tokio-stream` uses the local version
|
||||
of `tokio` via the `path` dependency. This means that it's currently not
|
||||
possible to release `tokio-stream`, and `tokio` should be released first.
|
||||
Once a new version of `tokio` is released (in this example the `1.38.0`),
|
||||
the path dependency should be removed.
|
||||
As mentioned before, this version should only be bumped when adding a new
|
||||
feature in the crate that relies on a newer version.
|
||||
@@ -0,0 +1,91 @@
|
||||
## Keeping track of issues and PRs
|
||||
|
||||
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
|
||||
section explains the meaning of various labels, as well as our [GitHub
|
||||
project][project]. The section is primarily targeted at maintainers. Most
|
||||
contributors aren't able to set these labels.
|
||||
|
||||
### Area
|
||||
|
||||
The area label describes the crates relevant to this issue or PR.
|
||||
|
||||
- **A-ci** This issue concerns our GitHub Actions setup.
|
||||
- **A-tokio** This issue concerns the main Tokio crate.
|
||||
- **A-readme** This issue is related to documentation such as README.md.
|
||||
- **A-benches** This issue concerns the benchmarks.
|
||||
- **A-examples** This issue concerns the examples.
|
||||
- **A-tokio-test** The issue concerns the `tokio-test` crate.
|
||||
- **A-tokio-util** This issue concerns the `tokio-util` crate.
|
||||
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
|
||||
be used for the procedural macros, and not `join!` or `select!`.
|
||||
- **A-tokio-stream** This issue concerns the `tokio-stream` crate.
|
||||
|
||||
### Category
|
||||
|
||||
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
|
||||
- **C-enhancement** This is a PR that adds a new features.
|
||||
- **C-maintenance** This is an issue or PR about stuff such as documentation,
|
||||
GitHub Actions or code quality.
|
||||
- **C-feature-request** This is a feature request. Implementations of feature
|
||||
requests use `C-enhancement` instead.
|
||||
- **C-feature-accepted** If you submit a PR for this feature request, we won't
|
||||
close it with the reason "we don't want this". Issues with this label should
|
||||
also have the `C-feature-request` label.
|
||||
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
|
||||
world"
|
||||
- **C-proposal** A proposal of some kind, and a request for comments.
|
||||
- **C-question** A user question. Large overlap with GitHub discussions.
|
||||
- **C-request** A non-feature request, e.g. "please add deprecation notices to
|
||||
`-alpha.*` versions of crates"
|
||||
|
||||
### Calls for participation
|
||||
|
||||
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
|
||||
or `C-feature-accepted`.
|
||||
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
|
||||
can do after reading the tutorial on our website.
|
||||
- **E-medium** This is not `E-easy` or `E-hard`.
|
||||
- **E-hard** This either involves very tricky code, is something we don't know
|
||||
how to solve, or is challenging for some other reason.
|
||||
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
|
||||
example.
|
||||
|
||||
The "E-" prefix is the same as used in the Rust compiler repository. Some
|
||||
issues are missing a difficulty rating, but feel free to ask on our Discord
|
||||
server if you want to know how challenging an issue likely is.
|
||||
|
||||
### Module
|
||||
|
||||
The module label provides a more fine grained categorization than **Area**.
|
||||
|
||||
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
|
||||
- **M-codec** The `tokio_util::codec` module.
|
||||
- **M-compat** The `tokio_util::compat` module.
|
||||
- **M-coop** Things relevant to coop.
|
||||
- **M-fs** The `tokio::fs` module.
|
||||
- **M-io** The `tokio::io` module.
|
||||
- **M-macros** Issues about any kind of macro.
|
||||
- **M-metrics** Things relevant to `tokio::runtime::metrics`.
|
||||
- **M-net** The `tokio::net` module.
|
||||
- **M-process** The `tokio::process` module.
|
||||
- **M-runtime** The `tokio::runtime` module.
|
||||
- **M-signal** The `tokio::signal` module.
|
||||
- **M-sync** The `tokio::sync` module.
|
||||
- **M-task** The `tokio::task` module.
|
||||
- **M-time** The `tokio::time` module.
|
||||
- **M-tracing** Tracing support in Tokio.
|
||||
- **M-taskdump** Things relevant to taskdump.
|
||||
|
||||
### Topic
|
||||
|
||||
Some extra information.
|
||||
|
||||
- **T-docs** This is about documentation.
|
||||
- **T-io-uring** This is about io-uring (Linux).
|
||||
- **T-performance** This is about performance.
|
||||
- **T-v0.1.x** This is about old Tokio.
|
||||
- **T-wasm** This is about Web Assembly.
|
||||
|
||||
Any label not listed here is not in active use.
|
||||
|
||||
[project]: https://github.com/orgs/tokio-rs/projects/1
|
||||
@@ -0,0 +1,350 @@
|
||||
## Pull Requests
|
||||
|
||||
Pull Requests are the way concrete changes are made to the code, documentation,
|
||||
and dependencies in the Tokio repository.
|
||||
|
||||
Even tiny pull requests (e.g., one-character pull request fixing a typo in API
|
||||
documentation) are greatly appreciated.
|
||||
|
||||
> [!NOTE]
|
||||
> Before making a large change, it is usually a good idea to first open an
|
||||
> issue describing the change to solicit feedback and guidance.
|
||||
> This will increase the likelihood of the PR getting merged.
|
||||
|
||||
### Cargo Commands
|
||||
|
||||
Due to the extensive use of features in Tokio, you will often need to add extra
|
||||
arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> There are some features that are not available in some systems (e.g., `io-uring`
|
||||
> which is Linux only). In that case you cannot use the `--all-features` and you
|
||||
> should specify only the features that are supported (e.g., `--features=full`).
|
||||
|
||||
Ideally, you should use the same version of clippy as the one used in CI
|
||||
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
|
||||
might have new lints:
|
||||
|
||||
[ci.yml]: ../../.github/workflows/ci.yml
|
||||
|
||||
<!--
|
||||
When updating this, also update:
|
||||
- .github/workflows/ci.yml
|
||||
- README.md
|
||||
- tokio/README.md
|
||||
- tokio/Cargo.toml
|
||||
- tokio-util/Cargo.toml
|
||||
- tokio-test/Cargo.toml
|
||||
- tokio-stream/Cargo.toml
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.88 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation, a simple `cargo doc` is not sufficient. To produce
|
||||
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
|
||||
docs, please use:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
|
||||
```
|
||||
|
||||
This turns on indicators to display the Cargo features required for
|
||||
conditionally compiled APIs in Tokio, and it enables documentation of unstable
|
||||
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
|
||||
*and* rustc.
|
||||
|
||||
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
|
||||
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
|
||||
as docs.rs itself does.
|
||||
|
||||
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
|
||||
|
||||
```
|
||||
cargo install --locked cargo-docs-rs
|
||||
cargo +nightly docs-rs [--open]
|
||||
```
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
|
||||
You can run loom tests with
|
||||
```
|
||||
cd tokio # tokio crate in workspace
|
||||
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
|
||||
run loom tests that test unstable features.
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance" \
|
||||
cargo +nightly miri test --features full --lib --tests
|
||||
```
|
||||
|
||||
### Performing spellcheck on Tokio codebase
|
||||
|
||||
You can perform a spell-check on the Tokio codebase. For details of how to use the spellcheck tool, feel free to visit
|
||||
https://github.com/drahnr/cargo-spellcheck
|
||||
```
|
||||
# First install the spell-check plugin
|
||||
cargo install --locked cargo-spellcheck
|
||||
|
||||
# Then run the cargo spell check command
|
||||
cargo spellcheck check
|
||||
```
|
||||
|
||||
If the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
|
||||
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
|
||||
the total number of words included in the file
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: [integration tests][integration-tests]
|
||||
and [documentation tests][documentation-tests].
|
||||
(Tokio avoids [unit tests][unit-tests] as much as possible).
|
||||
|
||||
Tokio uses [conditional compilation attributes][conditional-compilation]
|
||||
throughout the codebase, to modify rustc's behavior. Code marked with such
|
||||
attributes can be enabled using `RUSTFLAGS` and `RUSTDOCFLAGS` environment
|
||||
variables. One of the most prevalent flags passed in these variables is
|
||||
the `--cfg` option. To run tests in a particular file, check first what
|
||||
options #![cfg] declaration defines for that file.
|
||||
|
||||
For instance, to run a test marked with the 'tokio_unstable' cfg option,
|
||||
you must pass this flag to the compiler when running the test.
|
||||
```
|
||||
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
|
||||
```
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Integration tests go in the same crate as the code they are testing. Each sub
|
||||
crate should have a `dev-dependency` on `tokio` itself. This makes all Tokio
|
||||
utilities available to use in tests, no matter the crate being tested.
|
||||
|
||||
The best strategy for writing a new integration test is to look at existing
|
||||
integration tests in the crate and follow the style.
|
||||
|
||||
#### Fuzz tests
|
||||
|
||||
Some of our crates include a set of fuzz tests, this will be marked by a
|
||||
directory `fuzz`. It is a good idea to run fuzz tests after each change.
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install --locked cargo-fuzz`
|
||||
|
||||
To list the available fuzzing harnesses you can run;
|
||||
|
||||
```bash
|
||||
$ cd tokio
|
||||
$ cargo fuzz list
|
||||
fuzz_linked_list
|
||||
```
|
||||
|
||||
Running a fuzz test is as simple as;
|
||||
|
||||
`cargo fuzz run fuzz_linked_list`
|
||||
|
||||
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
|
||||
harness will run forever and will only exit if you `ctrl-c` or it finds
|
||||
a bug.
|
||||
|
||||
#### Documentation tests
|
||||
|
||||
Ideally, every API has at least one [documentation test] that demonstrates how to
|
||||
use the API. Documentation tests are run with `cargo test --doc`. This ensures
|
||||
that the example is correct and provides additional test coverage.
|
||||
|
||||
The trick to documentation tests is striking a balance between being succinct
|
||||
for a reader to understand and actually testing the API.
|
||||
|
||||
Same as with integration tests, when writing a documentation test, the full
|
||||
`tokio` crate is available. This is especially useful for getting access to the
|
||||
runtime to run the example.
|
||||
|
||||
The documentation tests will be visible from both the crate-specific
|
||||
documentation **and** the `tokio` facade documentation via the re-export. The
|
||||
example should be written from the point of view of a user that is using the
|
||||
`tokio` crate. As such, the example should use the API via the facade and not by
|
||||
directly referencing the crate.
|
||||
|
||||
The type level example for `tokio::time::timeout` provides a good example of a
|
||||
documentation test:
|
||||
|
||||
```
|
||||
/// Create a new `Timeout` set to expire in 10 milliseconds.
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::timeout;
|
||||
/// use tokio::sync::oneshot;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
/// # tx.send(()).unwrap();
|
||||
///
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// if let Err(_) = timeout(Duration::from_millis(10), rx).await {
|
||||
/// println!("did not receive value within 10 ms");
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
```
|
||||
|
||||
Lines that start with `/// #` are removed when the documentation is generated.
|
||||
|
||||
### Benchmarks
|
||||
|
||||
You can run benchmarks locally for the changes you've made to the tokio codebase.
|
||||
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
|
||||
against the changes you have made, for example, you can run;
|
||||
|
||||
```bash
|
||||
cd benches
|
||||
|
||||
# Run all benchmarks.
|
||||
cargo bench
|
||||
|
||||
# Run all tests in the `benches/fs.rs` file
|
||||
cargo bench --bench fs
|
||||
|
||||
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
|
||||
cargo bench async_read_buf
|
||||
|
||||
# After running benches, you can check the statistics under `tokio/target/criterion/`
|
||||
```
|
||||
|
||||
You can also refer to [Criterion] docs for additional options and details.
|
||||
|
||||
[Criterion]: https://docs.rs/criterion/latest/criterion/
|
||||
|
||||
### Commits
|
||||
|
||||
It is a recommended best practice to keep your changes as logically grouped as
|
||||
possible within individual commits. There is no limit to the number of commits
|
||||
any single Pull Request may have, and many contributors find it easier to review
|
||||
changes that are split across multiple commits.
|
||||
|
||||
That said, if you have a number of commits that are "checkpoints" and don't
|
||||
represent a single logical change, please squash those together.
|
||||
|
||||
Note that multiple commits often get squashed when they are landed (see the
|
||||
notes about [commit squashing](#commit-squashing)).
|
||||
|
||||
#### Commit message guidelines
|
||||
|
||||
A good commit message should describe what changed and why.
|
||||
|
||||
1. The first line should:
|
||||
|
||||
* contain a short description of the change (preferably 50 characters or less,
|
||||
and no more than 72 characters)
|
||||
* be entirely in lowercase with the exception of proper nouns, acronyms, and
|
||||
the words that refer to code, like function/variable names
|
||||
* start with an imperative verb
|
||||
* not have a period at the end
|
||||
* be prefixed with the name of the module being changed; usually this is the
|
||||
same as the M-* label on the PR
|
||||
|
||||
Examples:
|
||||
|
||||
* time: introduce `Timeout` and deprecate `Deadline`
|
||||
* codec: export `Encoder`, `Decoder`, `Framed*`
|
||||
* ci: fix the FreeBSD ci configuration
|
||||
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns (except for long URLs).
|
||||
4. If your patch fixes an open issue, you can add a reference to it at the end
|
||||
of the log. Use the `Fixes: #` prefix and the issue number. For other
|
||||
references use `Refs: #`. `Refs` may include multiple issues, separated by a
|
||||
comma.
|
||||
|
||||
Examples:
|
||||
|
||||
- `Fixes: #1337`
|
||||
- `Refs: #1234`
|
||||
|
||||
Sample complete commit message:
|
||||
|
||||
```txt
|
||||
module: explain the commit in one line
|
||||
|
||||
Body of commit message is a few lines of text, explaining things
|
||||
in more detail, possibly giving some background about the issue
|
||||
being fixed, etc.
|
||||
|
||||
The body of the commit message can be several paragraphs, and
|
||||
please do proper word-wrap and keep columns shorter than about
|
||||
72 characters or so. That way, `git log` will show things
|
||||
nicely even when it is indented.
|
||||
|
||||
Fixes: #1337
|
||||
Refs: #453, #154
|
||||
```
|
||||
|
||||
### Opening the Pull Request
|
||||
|
||||
From within GitHub, opening a new Pull Request will present you with a
|
||||
[template] that should be filled out. Please try to do your best at filling out
|
||||
the details, but feel free to skip parts if you're not sure what to put.
|
||||
|
||||
[template]: ../../.github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Discuss and update
|
||||
|
||||
You will probably get feedback or requests for changes to your Pull Request.
|
||||
This is a big part of the submission process so don't be discouraged! Some
|
||||
contributors may sign off on the Pull Request right away, others may have
|
||||
more detailed comments or feedback. This is a necessary part of the process
|
||||
in order to evaluate whether the changes are correct and necessary.
|
||||
|
||||
**Any community member can review a PR and you might get conflicting feedback**.
|
||||
Keep an eye out for comments from code owners to provide guidance on conflicting
|
||||
feedback.
|
||||
|
||||
**Once the PR is open, do not rebase the commits**. See [Commit Squashing](#commit-squashing) for
|
||||
more details.
|
||||
|
||||
### Commit Squashing
|
||||
|
||||
In most cases, **do not squash commits that you add to your Pull Request during
|
||||
the review process**. When the commits in your Pull Request land, they may be
|
||||
squashed into one commit per logical change. Metadata will be added to the
|
||||
commit message (including links to the Pull Request, links to relevant issues,
|
||||
and the names of the reviewers). The commit history of your Pull Request,
|
||||
however, will stay intact on the Pull Request page.
|
||||
|
||||
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
|
||||
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
|
||||
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
@@ -0,0 +1,80 @@
|
||||
## Reviewing Pull Requests
|
||||
|
||||
**Any Tokio community member is welcome to review any pull request**.
|
||||
|
||||
All Tokio contributors who choose to review and provide feedback on Pull
|
||||
Requests have a responsibility to both the project and the individual making the
|
||||
contribution. Reviews and feedback must be helpful, insightful, and geared
|
||||
towards improving the contribution as opposed to simply blocking it. If there
|
||||
are reasons why you feel the PR should not land, explain what those are. Do not
|
||||
expect to be able to block a Pull Request from advancing simply because you say
|
||||
"No" without giving an explanation. Be open to having your mind changed. Be open
|
||||
to working with the contributor to make the Pull Request better.
|
||||
|
||||
Reviews that are dismissive or disrespectful of the contributor or any other
|
||||
reviewers are strictly counter to the Code of Conduct.
|
||||
|
||||
When reviewing a Pull Request, the primary goals are for the codebase to improve
|
||||
and for the person submitting the request to succeed. **Even if a Pull Request
|
||||
does not land, the submitters should come away from the experience feeling like
|
||||
their effort was not wasted or unappreciated**. Every Pull Request from a new
|
||||
contributor is an opportunity to grow the community.
|
||||
|
||||
### Review a bit at a time
|
||||
|
||||
Do not overwhelm new contributors.
|
||||
|
||||
It is tempting to micro-optimize and make everything about relative performance,
|
||||
perfect grammar, or exact style matches. Do not succumb to that temptation.
|
||||
|
||||
Focus first on the most significant aspects of the change:
|
||||
|
||||
1. Does this change make sense for Tokio?
|
||||
2. Does this change make Tokio better, even if only incrementally?
|
||||
3. Are there clear bugs or larger scale issues that need attending to?
|
||||
4. Is the commit message readable and correct? If it contains a breaking change
|
||||
is it clear enough?
|
||||
|
||||
Note that only **incremental** improvement is needed to land a PR. This means
|
||||
that the PR does not need to be perfect, only better than the status quo. Follow
|
||||
up PRs may be opened to continue iterating.
|
||||
|
||||
When changes are necessary, *request* them, do not *demand* them, and **do not
|
||||
assume that the submitter already knows how to add a test or run a benchmark**.
|
||||
|
||||
Specific performance optimization techniques, coding styles and conventions
|
||||
change over time. The first impression you give to a new contributor never does.
|
||||
|
||||
Nits (requests for small changes that are not essential) are fine, but try to
|
||||
avoid stalling the Pull Request. Most nits can typically be fixed by the Tokio
|
||||
Collaborator landing the Pull Request but they can also be an opportunity for
|
||||
the contributor to learn a bit more about the project.
|
||||
|
||||
It is always good to clearly indicate nits when you comment: e.g.
|
||||
`Nit: change foo() to bar(). But this is not blocking.`
|
||||
|
||||
If your comments were addressed but were not folded automatically after new
|
||||
commits or if they proved to be mistaken, please, [hide them][hiding-a-comment]
|
||||
with the appropriate reason to keep the conversation flow concise and relevant.
|
||||
|
||||
### Be aware of the person behind the code
|
||||
|
||||
Be aware that *how* you communicate requests and reviews in your feedback can
|
||||
have a significant impact on the success of the Pull Request. Yes, we may land
|
||||
a particular change that makes Tokio better, but the individual might just not
|
||||
want to have anything to do with Tokio ever again. The goal is not just having
|
||||
good code.
|
||||
|
||||
### Abandoned or Stalled Pull Requests
|
||||
|
||||
If a Pull Request appears to be abandoned or stalled, it is polite to first
|
||||
check with the contributor to see if they intend to continue the work before
|
||||
checking if they would mind if you took it over (especially if it just has nits
|
||||
left). When doing so, it is courteous to give the original contributor credit
|
||||
for the work they started (either by preserving their name and email address in
|
||||
the commit log, or by using an `Author: ` meta-data tag in the commit.
|
||||
|
||||
_Adapted from the [Node.js contributing guide][node]_.
|
||||
|
||||
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
|
||||
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
|
||||
+26
-9
@@ -16,34 +16,47 @@ tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3.1", default-features = false, features = ["fmt", "ansi", "env-filter", "tracing-log"] }
|
||||
bytes = "1.0.0"
|
||||
futures = { version = "0.3.0", features = ["thread-pool"]}
|
||||
http = "0.2"
|
||||
http = "1"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
httparse = "1.0"
|
||||
httpdate = "1.0"
|
||||
once_cell = "1.5.2"
|
||||
rand = "0.8.3"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing", "taskdump"] }
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.windows-sys]
|
||||
version = "0.52"
|
||||
version = "0.61"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
path = "chat.rs"
|
||||
|
||||
[[example]]
|
||||
name = "connect"
|
||||
path = "connect.rs"
|
||||
name = "connect-tcp"
|
||||
path = "connect-tcp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "connect-udp"
|
||||
path = "connect-udp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-tcp"
|
||||
path = "echo-tcp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "graceful-shutdown"
|
||||
path = "graceful-shutdown.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-udp"
|
||||
path = "echo-udp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo"
|
||||
path = "echo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "hello_world.rs"
|
||||
@@ -96,5 +109,9 @@ path = "named-pipe-multi-client.rs"
|
||||
name = "dump"
|
||||
path = "dump.rs"
|
||||
|
||||
[[example]]
|
||||
name = "prewarm-fd-table"
|
||||
path = "prewarm-fd-table.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ cargo run --example $name
|
||||
```
|
||||
|
||||
A good starting point for the examples would be [`hello_world`](hello_world.rs)
|
||||
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
|
||||
and [`echo-tcp`](echo-tcp.rs). Additionally [the tokio website][tokioweb] contains
|
||||
additional guides for some of the examples.
|
||||
|
||||
For a larger "real world" example, see the [`mini-redis`][redis] repository.
|
||||
|
||||
+32
-19
@@ -39,6 +39,8 @@ use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
const DEFAULT_ADDR: &str = "127.0.0.1:6142";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter};
|
||||
@@ -70,14 +72,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:6142".to_string());
|
||||
.unwrap_or_else(|| DEFAULT_ADDR.to_string());
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
tracing::info!("server running on {}", addr);
|
||||
tracing::info!("server running on {addr}");
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound TcpStream.
|
||||
@@ -88,9 +90,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// Spawn our handler to be run asynchronously.
|
||||
tokio::spawn(async move {
|
||||
tracing::debug!("accepted connection");
|
||||
tracing::debug!("accepted connection from {addr}");
|
||||
if let Err(e) = process(state, stream, addr).await {
|
||||
tracing::info!("an error occurred; error = {:?}", e);
|
||||
tracing::warn!("Connection from {addr} failed: {e:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -138,12 +140,24 @@ impl Shared {
|
||||
|
||||
/// Send a `LineCodec` encoded message to every peer, except
|
||||
/// for the sender.
|
||||
///
|
||||
/// This function also cleans up disconnected peers automatically.
|
||||
async fn broadcast(&mut self, sender: SocketAddr, message: &str) {
|
||||
for peer in self.peers.iter_mut() {
|
||||
if *peer.0 != sender {
|
||||
let _ = peer.1.send(message.into());
|
||||
let mut failed_peers = Vec::new();
|
||||
let message = message.to_string(); // Clone once for all sends
|
||||
|
||||
for (addr, tx) in self.peers.iter() {
|
||||
if *addr != sender && tx.send(message.clone()).is_err() {
|
||||
// Receiver has been dropped, mark for removal
|
||||
failed_peers.push(*addr);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up disconnected peers
|
||||
for addr in failed_peers {
|
||||
self.peers.remove(&addr);
|
||||
tracing::debug!("Removed disconnected peer: {addr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,13 +192,10 @@ async fn process(
|
||||
lines.send("Please enter your username:").await?;
|
||||
|
||||
// Read the first line from the `LineCodec` stream to get the username.
|
||||
let username = match lines.next().await {
|
||||
Some(Ok(line)) => line,
|
||||
let Some(Ok(username)) = lines.next().await else {
|
||||
// We didn't get a line so we return early here.
|
||||
_ => {
|
||||
tracing::error!("Failed to get username from {}. Client disconnected.", addr);
|
||||
return Ok(());
|
||||
}
|
||||
tracing::error!("Failed to get username from {addr}. Client disconnected.");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Register our peer with state which internally sets up some channels.
|
||||
@@ -194,7 +205,7 @@ async fn process(
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{username} has joined the chat");
|
||||
tracing::info!("{}", msg);
|
||||
tracing::info!("{msg}");
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
@@ -203,7 +214,10 @@ async fn process(
|
||||
tokio::select! {
|
||||
// A message was received from a peer. Send it to the current user.
|
||||
Some(msg) = peer.rx.recv() => {
|
||||
peer.lines.send(&msg).await?;
|
||||
if let Err(e) = peer.lines.send(&msg).await {
|
||||
tracing::error!("Failed to send message to {username}: {e:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = peer.lines.next() => match result {
|
||||
// A message was received from the current user, we should
|
||||
@@ -217,10 +231,9 @@ async fn process(
|
||||
// An error occurred.
|
||||
Some(Err(e)) => {
|
||||
tracing::error!(
|
||||
"an error occurred while processing messages for {}; error = {:?}",
|
||||
username,
|
||||
e
|
||||
"an error occurred while processing messages for {username}; error = {e:?}"
|
||||
);
|
||||
break;
|
||||
}
|
||||
// The stream has been exhausted.
|
||||
None => break,
|
||||
@@ -235,7 +248,7 @@ async fn process(
|
||||
state.peers.remove(&addr);
|
||||
|
||||
let msg = format!("{username} has left the chat");
|
||||
tracing::info!("{}", msg);
|
||||
tracing::info!("{msg}");
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//! An example of hooking up stdin/stdout to a TCP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. Each line entered on stdin will be translated to a TCP
|
||||
//! packet which is then sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{stdin, stdout};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse what address we're going to connect to
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
|
||||
|
||||
connect(&addr, stdin, stdout).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; error={e}");
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
tokio::select! {
|
||||
r = sink.send_all(&mut stdin) => r?,
|
||||
r = stdout.send_all(&mut stream) => r?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//! An example of hooking up stdin/stdout to a UDP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. Each line entered on stdin will be translated to a UDP
|
||||
//! packet which is then sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{stdin, stdout};
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse what address we're going to connect to
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
|
||||
|
||||
connect(&addr, stdin, stdout).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
let bind_addr = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
};
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
|
||||
tokio::select! {
|
||||
r = send(stdin, &socket) => r?,
|
||||
r = recv(stdout, &socket) => r?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
writer: &UdpSocket,
|
||||
) -> Result<(), std::io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
writer.send(&buf[..]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
reader: &UdpSocket,
|
||||
) -> Result<(), std::io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(Bytes::copy_from_slice(&buf[..n])).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
//! An example of hooking up stdin/stdout to either a TCP or UDP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. An optional `--udp` argument can be passed to specify
|
||||
//! that the connection should be made over UDP instead of TCP, translating each
|
||||
//! line entered on stdin to a UDP packet to be sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures::StreamExt;
|
||||
use tokio::io;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Determine if we're going to run in TCP or UDP mode
|
||||
let mut args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let tcp = match args.iter().position(|a| a == "--udp") {
|
||||
Some(i) => {
|
||||
args.remove(i);
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
// Parse what address we're going to connect to
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(io::stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(io::stdout(), BytesCodec::new());
|
||||
|
||||
if tcp {
|
||||
tcp::connect(&addr, stdin, stdout).await?;
|
||||
} else {
|
||||
udp::connect(&addr, stdin, stdout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod tcp {
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::{error::Error, io, net::SocketAddr};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={e}");
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
|
||||
(Err(e), _) | (_, Err(e)) => Err(e.into()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use bytes::Bytes;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
let bind_addr = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
};
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
|
||||
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
writer: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
writer.send(&buf[..]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
reader: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(Bytes::from(buf)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
@@ -82,7 +81,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
#[cfg(not(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
)))]
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
//!
|
||||
//! To see this server in action, you can run this in one terminal:
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//! cargo run --example echo-tcp
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be echo'd back to
|
||||
//! you! If you open up multiple terminals running the `connect` example you
|
||||
//! Each line you type in to the `connect-tcp` terminal should be echo'd back to
|
||||
//! you! If you open up multiple terminals running the `connect-tcp` example you
|
||||
//! should be able to see them all make progress simultaneously.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
@@ -27,6 +27,9 @@ use tokio::net::TcpListener;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
||||
const DEFAULT_ADDR: &str = "127.0.0.1:8080";
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
@@ -34,7 +37,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
.unwrap_or_else(|| DEFAULT_ADDR.to_string());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
@@ -44,7 +47,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
let (mut socket, addr) = listener.accept().await?;
|
||||
|
||||
// And this is where much of the magic of this server happens. We
|
||||
// crucially want all clients to make progress concurrently, rather than
|
||||
@@ -55,23 +58,27 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// which will allow all of our clients to be processed concurrently.
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = vec![0; 1024];
|
||||
let mut buf = vec![0; BUFFER_SIZE];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
let n = socket
|
||||
.read(&mut buf)
|
||||
.await
|
||||
.expect("failed to read data from socket");
|
||||
|
||||
if n == 0 {
|
||||
return;
|
||||
match socket.read(&mut buf).await {
|
||||
Ok(0) => {
|
||||
// Connection closed by peer
|
||||
return;
|
||||
}
|
||||
Ok(n) => {
|
||||
// Write the data back. If writing fails, log the error and exit.
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
eprintln!("Failed to write to socket {}: {}", addr, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read from socket {}: {}", addr, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
socket
|
||||
.write_all(&buf[0..n])
|
||||
.await
|
||||
.expect("failed to write data to socket");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect -- --udp 127.0.0.1:8080
|
||||
//! cargo run --example connect-udp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `nc` terminal should be echo'd back to you!
|
||||
//! Each line you type in to the `connect-udp` terminal should be echo'd back to you!
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
//! Graceful shutdown example.
|
||||
//!
|
||||
//! This example follows the same approach described in the
|
||||
//! [Graceful Shutdown tutorial](https://tokio.rs/tokio/topics/shutdown):
|
||||
//!
|
||||
//! - A [`CancellationToken`] tells tasks to stop accepting new work.
|
||||
//! - A [`TaskTracker`] waits for in-flight work to complete.
|
||||
//!
|
||||
//! It runs a TCP echo server on `127.0.0.1:6142`. When Ctrl+C is
|
||||
//! pressed, the server stops accepting connections and waits for all
|
||||
//! active connections to finish before exiting.
|
||||
//!
|
||||
//! Start the server:
|
||||
//!
|
||||
//! cargo run --example graceful-shutdown
|
||||
//!
|
||||
//! Then connect with:
|
||||
//!
|
||||
//! nc 127.0.0.1 6142
|
||||
//!
|
||||
//! Press Ctrl+C on the server to trigger a graceful shutdown.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::time::{self, Duration};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:6142").await?;
|
||||
println!("listening on 127.0.0.1:6142");
|
||||
|
||||
let token = CancellationToken::new();
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = listener.accept() => {
|
||||
let (socket, addr) = match result {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
// Transient errors (e.g. fd exhaustion) are recoverable,
|
||||
// so we log and continue. A production server might add a
|
||||
// backoff or break on fatal errors to avoid a busy loop.
|
||||
eprintln!("failed to accept: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
println!("accepted connection from {addr}");
|
||||
|
||||
let token = token.clone();
|
||||
tracker.spawn(handle_connection(socket, addr, token));
|
||||
}
|
||||
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
println!("\nshutdown signal received, waiting for connections to finish");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Signal all tasks to stop and wait for them to complete.
|
||||
token.cancel();
|
||||
tracker.close();
|
||||
tracker.wait().await;
|
||||
|
||||
println!("shutdown complete");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_connection(mut socket: TcpStream, addr: SocketAddr, token: CancellationToken) {
|
||||
tokio::select! {
|
||||
_ = echo(&mut socket) => {}
|
||||
_ = token.cancelled() => {
|
||||
notify_shutdown(&mut socket).await;
|
||||
}
|
||||
}
|
||||
|
||||
println!("connection from {addr} closed");
|
||||
}
|
||||
|
||||
/// Reads lines from the client and writes them back.
|
||||
///
|
||||
/// Called for every accepted connection. Runs until the client disconnects
|
||||
/// or a read/write error occurs.
|
||||
async fn echo(socket: &mut TcpStream) {
|
||||
let (reader, mut writer) = socket.split();
|
||||
let mut reader = BufReader::new(reader);
|
||||
let mut line = String::new();
|
||||
|
||||
loop {
|
||||
match reader.read_line(&mut line).await {
|
||||
Ok(0) | Err(_) => return,
|
||||
Ok(_) => {
|
||||
if writer.write_all(line.as_bytes()).await.is_err() {
|
||||
return;
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a shutdown notice to the client before closing the connection.
|
||||
///
|
||||
/// Called when the cancellation token fires. Uses a timeout so that a
|
||||
/// slow or unresponsive client cannot hold up the server shutdown.
|
||||
async fn notify_shutdown(socket: &mut TcpStream) {
|
||||
let _ = time::timeout(
|
||||
Duration::from_secs(1),
|
||||
socket.write_all(b"server shutting down\n"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//! Demonstrates pre-warming the Linux file descriptor table to avoid latency
|
||||
//! spikes caused by file descriptor table growth in multi-threaded processes.
|
||||
//!
|
||||
//! On Linux, the kernel's FD table is grown lazily and protected by RCU
|
||||
//! synchronization. In multi-threaded processes, when a syscall like `socket()`
|
||||
//! triggers a table resize, the calling thread blocks until all RCU readers
|
||||
//! quiesce. This can cause stalls of tens of milliseconds on tokio worker threads,
|
||||
//! blocking the entire event loop (not just one task).
|
||||
//!
|
||||
//! The workaround is to force the kernel to expand the FD table once per process
|
||||
//! (before any runtime starts), by duplicating an FD to a high slot and then
|
||||
//! closing it. The kernel never shrinks the FD table during a process's lifetime,
|
||||
//! so the capacity persists.
|
||||
//!
|
||||
//! This is most relevant for services that open many connections concurrently
|
||||
//! (e.g. HTTP servers, connection pools). The pre-warm target should be at least
|
||||
//! your expected peak FD count, and must not exceed `RLIMIT_NOFILE`.
|
||||
//!
|
||||
//! See: <https://github.com/tokio-rs/tokio/issues/7970>
|
||||
//!
|
||||
//! Usage:
|
||||
//!
|
||||
//! cargo run --example prewarm-fd-table
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
/// Pre-warms the FD table using `fcntl(F_DUPFD_CLOEXEC)` to duplicate an FD
|
||||
/// into a high slot, expanding the table in a single syscall. `F_DUPFD_CLOEXEC`
|
||||
/// allocates the lowest available FD >= `target`, so it never clobbers an
|
||||
/// existing FD.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn prewarm_fd_table(target: i32) -> std::io::Result<()> {
|
||||
use std::os::unix::io::{FromRawFd, OwnedFd};
|
||||
|
||||
let dev_null = std::fs::File::open("/dev/null")?;
|
||||
let raw = unsafe {
|
||||
libc::fcntl(
|
||||
std::os::unix::io::AsRawFd::as_raw_fd(&dev_null),
|
||||
libc::F_DUPFD_CLOEXEC,
|
||||
target,
|
||||
)
|
||||
};
|
||||
if raw < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
// Close both FDs. The table capacity persists.
|
||||
let _owned = unsafe { OwnedFd::from_raw_fd(raw) };
|
||||
drop(dev_null);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fully safe alternative using only stdlib. Requires O(n) syscalls instead of
|
||||
/// one, but avoids `unsafe` entirely.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[allow(dead_code)]
|
||||
fn prewarm_fd_table_safe(target: i32) -> std::io::Result<()> {
|
||||
let f = std::fs::File::open("/dev/null")?;
|
||||
let _fds: Vec<_> = (0..target)
|
||||
.map(|_| f.try_clone())
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
const FD_TARGET: i32 = 10_000;
|
||||
|
||||
println!("Pre-warming FD table to {FD_TARGET} entries...");
|
||||
if let Err(e) = prewarm_fd_table(FD_TARGET) {
|
||||
eprintln!("Warning: failed to pre-warm FD table: {e}");
|
||||
} else {
|
||||
println!("FD table pre-warmed successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
// Build the runtime *after* pre-warming.
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {});
|
||||
}
|
||||
@@ -13,9 +13,9 @@
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be written to terminal!
|
||||
//! Each line you type in to the `connect-tcp` terminal should be written to terminal!
|
||||
//!
|
||||
//! Minimal js example:
|
||||
//!
|
||||
|
||||
+2
-2
@@ -11,11 +11,11 @@
|
||||
//!
|
||||
//! This in another terminal
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//! cargo run --example echo-tcp
|
||||
//!
|
||||
//! And finally this in another terminal
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8081
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8081
|
||||
//!
|
||||
//! This final terminal will connect to our proxy, which will in turn connect to
|
||||
//! the echo server, and you'll be able to see data flowing between them.
|
||||
|
||||
+3
-3
@@ -12,14 +12,14 @@
|
||||
//!
|
||||
//! and next in another windows run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! In the `connect` window you can type in commands where when you hit enter
|
||||
//! In the `connect-tcp` window you can type in commands where when you hit enter
|
||||
//! you'll get a response from the server for that command. An example session
|
||||
//! is:
|
||||
//!
|
||||
//!
|
||||
//! $ cargo run --example connect 127.0.0.1:8080
|
||||
//! $ cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//! GET foo
|
||||
//! foo = bar
|
||||
//! GET FOOBAR
|
||||
|
||||
+6
-14
@@ -82,9 +82,7 @@ async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
let response = response
|
||||
.body(body)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||
let response = response.body(body).map_err(io::Error::other)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -160,7 +158,7 @@ impl Decoder for Http {
|
||||
let mut r = httparse::Request::new(&mut parsed_headers);
|
||||
let status = r.parse(src).map_err(|e| {
|
||||
let msg = format!("failed to parse http request: {e:?}");
|
||||
io::Error::new(io::ErrorKind::Other, msg)
|
||||
io::Error::other(msg)
|
||||
})?;
|
||||
|
||||
let amt = match status {
|
||||
@@ -180,8 +178,7 @@ impl Decoder for Http {
|
||||
headers[i] = Some((k, v));
|
||||
}
|
||||
|
||||
let method = http::Method::try_from(r.method.unwrap())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let method = http::Method::try_from(r.method.unwrap()).map_err(io::Error::other)?;
|
||||
|
||||
(
|
||||
method,
|
||||
@@ -191,10 +188,7 @@ impl Decoder for Http {
|
||||
)
|
||||
};
|
||||
if version != 1 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"only HTTP/1.1 accepted",
|
||||
));
|
||||
return Err(io::Error::other("only HTTP/1.1 accepted"));
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
@@ -209,13 +203,11 @@ impl Decoder for Http {
|
||||
None => break,
|
||||
};
|
||||
let value = HeaderValue::from_bytes(data.slice(v.0..v.1).as_ref())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "header decode error"))?;
|
||||
.map_err(|_| io::Error::other("header decode error"))?;
|
||||
ret = ret.header(&data[k.0..k.1], value);
|
||||
}
|
||||
|
||||
let req = ret
|
||||
.body(())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let req = ret.body(()).map_err(io::Error::other)?;
|
||||
Ok(Some(req))
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -8,9 +8,11 @@
|
||||
RUSTDOCFLAGS="""
|
||||
--cfg docsrs \
|
||||
--cfg tokio_unstable \
|
||||
--cfg tokio_taskdump \
|
||||
"""
|
||||
RUSTFLAGS="--cfg tokio_unstable --cfg tokio_taskdump --cfg docsrs"
|
||||
RUSTFLAGS="""
|
||||
--cfg docsrs \
|
||||
--cfg tokio_unstable
|
||||
"""
|
||||
|
||||
[[redirects]]
|
||||
from = "/"
|
||||
|
||||
+26
-2
@@ -1,4 +1,4 @@
|
||||
300
|
||||
324
|
||||
&
|
||||
+
|
||||
<
|
||||
@@ -64,13 +64,19 @@ codec
|
||||
codecs
|
||||
combinator
|
||||
combinators
|
||||
condvar
|
||||
config
|
||||
Config
|
||||
connectionless
|
||||
coroutines
|
||||
cpu
|
||||
cpus
|
||||
cqe
|
||||
CQE
|
||||
cqe's
|
||||
customizable
|
||||
Customizable
|
||||
Cygwin
|
||||
datagram
|
||||
Datagram
|
||||
datagrams
|
||||
@@ -83,6 +89,7 @@ decrementing
|
||||
demangled
|
||||
dequeued
|
||||
dereferenced
|
||||
derefs
|
||||
deregister
|
||||
deregistered
|
||||
deregistering
|
||||
@@ -132,6 +139,7 @@ hashmaps
|
||||
HashMaps
|
||||
hashsets
|
||||
HdrHistogram
|
||||
ICMP
|
||||
ie
|
||||
Illumos
|
||||
impl
|
||||
@@ -158,6 +166,7 @@ Lauck
|
||||
libc
|
||||
lifecycle
|
||||
lifo
|
||||
LLVM
|
||||
lookups
|
||||
macOS
|
||||
MacOS
|
||||
@@ -169,6 +178,7 @@ mio's
|
||||
miri
|
||||
misconfigured
|
||||
mock's
|
||||
monomorphization
|
||||
mpmc
|
||||
mpsc
|
||||
multi
|
||||
@@ -179,15 +189,20 @@ mut
|
||||
mutex
|
||||
Mutex
|
||||
Nagle
|
||||
namespace
|
||||
NetBSD
|
||||
nonblocking
|
||||
nondecreasing
|
||||
noop
|
||||
ntasks
|
||||
NTO
|
||||
NUMA
|
||||
ok
|
||||
oneshot
|
||||
opcode
|
||||
OpenBSD
|
||||
ORed
|
||||
os
|
||||
overweighing
|
||||
parker
|
||||
parsers
|
||||
peekable
|
||||
@@ -199,8 +214,10 @@ POSIX
|
||||
proxied
|
||||
qos
|
||||
RAII
|
||||
RCU
|
||||
reallocations
|
||||
recv's
|
||||
Redox
|
||||
refactors
|
||||
refcount
|
||||
refcounting
|
||||
@@ -230,6 +247,7 @@ spawner
|
||||
Splitter
|
||||
spmc
|
||||
spsc
|
||||
SQE
|
||||
src
|
||||
stabilised
|
||||
startup
|
||||
@@ -266,6 +284,7 @@ tokio's
|
||||
Tokio's
|
||||
tuple
|
||||
Tuple
|
||||
tvOS
|
||||
tx
|
||||
udp
|
||||
UDP
|
||||
@@ -285,6 +304,7 @@ unsets
|
||||
Unsets
|
||||
unsynchronized
|
||||
untrusted
|
||||
uring
|
||||
usecases
|
||||
Valgrind
|
||||
Varghese
|
||||
@@ -292,10 +312,14 @@ vec
|
||||
versa
|
||||
versioned
|
||||
versioning
|
||||
visionOS
|
||||
vtable
|
||||
waker
|
||||
wakers
|
||||
Wakers
|
||||
wakeup
|
||||
wakeups
|
||||
WASI
|
||||
watchOS
|
||||
workstealing
|
||||
ZST
|
||||
|
||||
@@ -12,7 +12,7 @@ publish = false
|
||||
tokio = { version = "1.0.0", path = "../tokio/", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
rand = "0.9"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"crt-objects-fallback": "false",
|
||||
"crt-static-respected": true,
|
||||
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
|
||||
"default-uwtable": true,
|
||||
"dynamic-linking": true,
|
||||
"env": "gnu",
|
||||
"has-rpath": true,
|
||||
@@ -12,10 +13,10 @@
|
||||
"llvm-target": "i686-unknown-linux-gnu",
|
||||
"max-atomic-width": 32,
|
||||
"metadata": {
|
||||
"description": null,
|
||||
"host_tools": null,
|
||||
"std": null,
|
||||
"tier": null
|
||||
"description": "32-bit Linux (kernel 3.2, glibc 2.17+)",
|
||||
"host_tools": true,
|
||||
"std": true,
|
||||
"tier": 1
|
||||
},
|
||||
"os": "linux",
|
||||
"position-independent-executables": true,
|
||||
@@ -28,6 +29,7 @@
|
||||
]
|
||||
},
|
||||
"relro-level": "full",
|
||||
"rustc-abi": "x86-sse2",
|
||||
"stack-probes": {
|
||||
"kind": "inline"
|
||||
},
|
||||
@@ -42,5 +44,5 @@
|
||||
"target-family": [
|
||||
"unix"
|
||||
],
|
||||
"target-pointer-width": "32"
|
||||
"target-pointer-width": 32
|
||||
}
|
||||
|
||||
@@ -68,4 +68,7 @@ async fn test_has_second_test_attr_rust_2021() {}
|
||||
#[tokio::test]
|
||||
async fn test_has_generated_second_test_attr() {}
|
||||
|
||||
#[tokio::test(name = 123)]
|
||||
async fn test_name_not_string() {}
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -4,7 +4,7 @@ error: the `async` keyword is missing from the function declaration
|
||||
6 | fn main_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
|
||||
--> tests/fail/macros_invalid_input.rs:8:15
|
||||
|
|
||||
8 | #[tokio::main(foo)]
|
||||
@@ -22,13 +22,13 @@ error: the `async` keyword is missing from the function declaration
|
||||
15 | fn test_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
|
||||
--> tests/fail/macros_invalid_input.rs:17:15
|
||||
|
|
||||
17 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
|
||||
--> tests/fail/macros_invalid_input.rs:20:15
|
||||
|
|
||||
20 | #[tokio::test(foo = 123)]
|
||||
@@ -40,7 +40,7 @@ error: Failed to parse value of `flavor` as string.
|
||||
23 | #[tokio::test(flavor = 123)]
|
||||
| ^^^
|
||||
|
||||
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
|
||||
error: No such runtime flavor `foo`. The runtime flavors are `current_thread`, `local`, and `multi_thread`.
|
||||
--> tests/fail/macros_invalid_input.rs:26:24
|
||||
|
|
||||
26 | #[tokio::test(flavor = "foo")]
|
||||
@@ -119,3 +119,9 @@ error: second test attribute is supplied, consider removing or changing the orde
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `tokio::test` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: Failed to parse value of `name` as string.
|
||||
--> tests/fail/macros_invalid_input.rs:71:22
|
||||
|
|
||||
71 | #[tokio::test(name = 123)]
|
||||
| ^^^
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// do not leak `RotatorSelect`
|
||||
let _ = tokio::join!(async {
|
||||
fn foo(_: impl RotatorSelect) {}
|
||||
});
|
||||
|
||||
// do not leak `std::task::Poll::Pending`
|
||||
let _ = tokio::join!(async { Pending });
|
||||
|
||||
// do not leak `std::task::Poll::Ready`
|
||||
let _ = tokio::join!(async { Ready(0) });
|
||||
|
||||
// do not leak `std::future::Future`
|
||||
let _ = tokio::join!(async {
|
||||
struct MyFuture;
|
||||
|
||||
impl Future for MyFuture {
|
||||
type Output = ();
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// do not leak `std::pin::Pin`
|
||||
let _ = tokio::join!(async {
|
||||
let mut x = 5;
|
||||
let _ = Pin::new(&mut x);
|
||||
});
|
||||
|
||||
// do not leak `std::future::poll_fn`
|
||||
let _ = tokio::join!(async {
|
||||
let _ = poll_fn(|_cx| todo!());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
error[E0405]: cannot find trait `RotatorSelect` in this scope
|
||||
--> tests/fail/macros_join.rs:7:24
|
||||
|
|
||||
7 | fn foo(_: impl RotatorSelect) {}
|
||||
| ^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `Pending` in this scope
|
||||
--> tests/fail/macros_join.rs:11:34
|
||||
|
|
||||
11 | let _ = tokio::join!(async { Pending });
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this unit variant
|
||||
|
|
||||
1 + use std::task::Poll::Pending;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
|
||||
--> tests/fail/macros_join.rs:14:34
|
||||
|
|
||||
14 | let _ = tokio::join!(async { Ready(0) });
|
||||
| ^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this tuple variant
|
||||
|
|
||||
1 + use std::task::Poll::Ready;
|
||||
|
|
||||
|
||||
error[E0405]: cannot find trait `Future` in this scope
|
||||
--> tests/fail/macros_join.rs:20:14
|
||||
|
|
||||
20 | impl Future for MyFuture {
|
||||
| ^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this trait
|
||||
|
|
||||
1 + use std::future::Future;
|
||||
|
|
||||
|
||||
error[E0433]: cannot find type `Pin` in this scope
|
||||
--> tests/fail/macros_join.rs:35:17
|
||||
|
|
||||
35 | let _ = Pin::new(&mut x);
|
||||
| ^^^ use of undeclared type `Pin`
|
||||
|
|
||||
help: consider importing this struct
|
||||
|
|
||||
1 + use std::pin::Pin;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function `poll_fn` in this scope
|
||||
--> tests/fail/macros_join.rs:40:17
|
||||
|
|
||||
40 | let _ = poll_fn(|_cx| todo!());
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this function
|
||||
|
|
||||
1 + use std::future::poll_fn;
|
||||
|
|
||||
@@ -0,0 +1,42 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// do not leak `RotatorSelect`
|
||||
let _ = tokio::try_join!(async {
|
||||
fn foo(_: impl RotatorSelect) {}
|
||||
});
|
||||
|
||||
// do not leak `std::task::Poll::Pending`
|
||||
let _ = tokio::try_join!(async { Pending });
|
||||
|
||||
// do not leak `std::task::Poll::Ready`
|
||||
let _ = tokio::try_join!(async { Ready(0) });
|
||||
|
||||
// do not leak `std::future::Future`
|
||||
let _ = tokio::try_join!(async {
|
||||
struct MyFuture;
|
||||
|
||||
impl Future for MyFuture {
|
||||
type Output = ();
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// do not leak `std::pin::Pin`
|
||||
let _ = tokio::try_join!(async {
|
||||
let mut x = 5;
|
||||
let _ = Pin::new(&mut x);
|
||||
});
|
||||
|
||||
// do not leak `std::future::poll_fn`
|
||||
let _ = tokio::try_join!(async {
|
||||
let _ = poll_fn(|_cx| todo!());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
error[E0405]: cannot find trait `RotatorSelect` in this scope
|
||||
--> tests/fail/macros_try_join.rs:7:24
|
||||
|
|
||||
7 | fn foo(_: impl RotatorSelect) {}
|
||||
| ^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `Pending` in this scope
|
||||
--> tests/fail/macros_try_join.rs:11:38
|
||||
|
|
||||
11 | let _ = tokio::try_join!(async { Pending });
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this unit variant
|
||||
|
|
||||
1 + use std::task::Poll::Pending;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
|
||||
--> tests/fail/macros_try_join.rs:14:38
|
||||
|
|
||||
14 | let _ = tokio::try_join!(async { Ready(0) });
|
||||
| ^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this tuple variant
|
||||
|
|
||||
1 + use std::task::Poll::Ready;
|
||||
|
|
||||
|
||||
error[E0405]: cannot find trait `Future` in this scope
|
||||
--> tests/fail/macros_try_join.rs:20:14
|
||||
|
|
||||
20 | impl Future for MyFuture {
|
||||
| ^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this trait
|
||||
|
|
||||
1 + use std::future::Future;
|
||||
|
|
||||
|
||||
error[E0433]: cannot find type `Pin` in this scope
|
||||
--> tests/fail/macros_try_join.rs:35:17
|
||||
|
|
||||
35 | let _ = Pin::new(&mut x);
|
||||
| ^^^ use of undeclared type `Pin`
|
||||
|
|
||||
help: consider importing this struct
|
||||
|
|
||||
1 + use std::pin::Pin;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function `poll_fn` in this scope
|
||||
--> tests/fail/macros_try_join.rs:40:17
|
||||
|
|
||||
40 | let _ = poll_fn(|_cx| todo!());
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this function
|
||||
|
|
||||
1 + use std::future::poll_fn;
|
||||
|
|
||||
@@ -1,3 +1,14 @@
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:3:1: 3:15}` to be a future that resolves to `()`, but it resolves to `Result<(), _>`
|
||||
--> tests/fail/macros_type_mismatch.rs:3:1
|
||||
|
|
||||
3 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:3:1: 3:15}` to `&dyn Future<Output = ()>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:5:5
|
||||
|
|
||||
@@ -15,6 +26,17 @@ help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panic
|
||||
5 | Ok(()).expect("REASON")
|
||||
| +++++++++++++++++
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:8:1: 8:15}` to be a future that resolves to `()`, but it resolves to `Result<(), _>`
|
||||
--> tests/fail/macros_type_mismatch.rs:8:1
|
||||
|
|
||||
8 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:8:1: 8:15}` to `&dyn Future<Output = ()>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:10:5
|
||||
|
|
||||
@@ -25,13 +47,24 @@ error[E0308]: mismatched types
|
||||
found enum `Result<(), _>`
|
||||
help: a return type might be missing here
|
||||
|
|
||||
9 | async fn missing_return_type() -> _ {
|
||||
9 | async fn missing_return_type() -> _ {
|
||||
| ++++
|
||||
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
||||
|
|
||||
10 | return Ok(());.expect("REASON")
|
||||
| +++++++++++++++++
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:13:1: 13:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
|
||||
--> tests/fail/macros_type_mismatch.rs:13:1
|
||||
|
|
||||
13 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:13:1: 13:15}` to `&dyn Future<Output = Result<(), ()>>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:23:5
|
||||
|
|
||||
@@ -57,8 +90,17 @@ error[E0277]: the `?` operator can only be used in an async block that returns `
|
||||
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
|
||||
40 | None?;
|
||||
| ^ cannot use the `?` operator in an async block that returns `()`
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to be a future that resolves to `Option<()>`, but it resolves to `()`
|
||||
--> tests/fail/macros_type_mismatch.rs:38:1
|
||||
|
|
||||
= help: the trait `FromResidual<Option<Infallible>>` is not implemented for `()`
|
||||
38 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `Option<()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Option<()>`
|
||||
found unit type `()`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to `&dyn Future<Output = Option<()>>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:40:5
|
||||
@@ -87,8 +129,17 @@ error[E0277]: the `?` operator can only be used in an async block that returns `
|
||||
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
|
||||
57 | Ok(())?;
|
||||
| ^ cannot use the `?` operator in an async block that returns `()`
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
|
||||
--> tests/fail/macros_type_mismatch.rs:55:1
|
||||
|
|
||||
= help: the trait `FromResidual<Result<Infallible, _>>` is not implemented for `()`
|
||||
55 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to `&dyn Future<Output = Result<(), ()>>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:57:5
|
||||
@@ -106,6 +157,15 @@ help: try adding an expression at the end of the block
|
||||
58 + Ok(())
|
||||
|
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:63:1: 63:15}` to be a future that resolves to `()`, but it resolves to `{integer}`
|
||||
--> tests/fail/macros_type_mismatch.rs:63:1
|
||||
|
|
||||
63 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found integer
|
||||
|
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:63:1: 63:15}` to `&dyn Future<Output = ()>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:66:5
|
||||
|
|
||||
|
||||
@@ -12,12 +12,24 @@ fn compile_fail_full() {
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/macros_main_loop.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/impl_trait.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/use_builder_outer.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_dead_code.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_join.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_try_join.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_type_mismatch.rs");
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn never() -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn impl_trait() -> impl Iterator<Item = impl core::fmt::Debug> {
|
||||
[()].into_iter()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn impl_trait2() -> Result<(), impl core::fmt::Debug> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if impl_trait().count() == 10 {
|
||||
never();
|
||||
}
|
||||
let _ = impl_trait2();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#![deny(unused_qualifications)]
|
||||
|
||||
use tests_build::tokio;
|
||||
pub use tokio::runtime;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
if true {}
|
||||
}
|
||||
@@ -16,6 +16,13 @@ async fn spawning() -> usize {
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
#[tokio::main(flavor = "local")]
|
||||
async fn local_main() -> usize {
|
||||
let join = tokio::task::spawn_local(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
@@ -24,5 +31,8 @@ fn main_with_spawn() {
|
||||
#[test]
|
||||
fn shell() {
|
||||
assert_eq!(1, basic_main());
|
||||
assert_eq!(bool::default(), generic_fun::<bool>())
|
||||
assert_eq!(bool::default(), generic_fun::<bool>());
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
assert_eq!(1, local_main());
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
// Produce n lines on the child's stdout.
|
||||
let write = async {
|
||||
for i in 0..n {
|
||||
let bytes = format!("line {}\n", i).into_bytes();
|
||||
let bytes = format!("line {i}\n").into_bytes();
|
||||
stdin.write_all(&bytes).await.unwrap();
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
(false, 0) => panic!("broken pipe"),
|
||||
(true, n) if n != 0 => panic!("extraneous data"),
|
||||
_ => {
|
||||
let expected = format!("line {}", num_lines);
|
||||
let expected = format!("line {num_lines}");
|
||||
assert_eq!(expected, data);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,32 @@
|
||||
# 2.7.0 (April 3rd, 2026)
|
||||
|
||||
- macros: stabilize `LocalRuntime` ([#7557])
|
||||
- macros: add runtime name ([#7924])
|
||||
|
||||
[#7557]: https://github.com/tokio-rs/tokio/pull/7557
|
||||
[#7924]: https://github.com/tokio-rs/tokio/pull/7924
|
||||
|
||||
# 2.6.1 (Mar 2nd, 2026)
|
||||
|
||||
- macros: improve error message for return type mismatch in #[tokio::main] ([#7856])
|
||||
- macros: use call_site hygiene to avoid unused qualification ([#7866])
|
||||
|
||||
[#7856]: https://github.com/tokio-rs/tokio/pull/7856
|
||||
[#7866]: https://github.com/tokio-rs/tokio/pull/7866
|
||||
|
||||
# 2.6.0 (Oct 14th, 2025)
|
||||
|
||||
The MSRV is raised to 1.71.
|
||||
|
||||
- msrv: increase MSRV to 1.71 ([#7658])
|
||||
- macros: add `local` runtime flavor ([#7375], [#7597])
|
||||
- macros: suppress `clippy::unwrap_in_result` in `#[tokio::main]` ([#7651])
|
||||
|
||||
[#7375]: https://github.com/tokio-rs/tokio/pull/7375
|
||||
[#7597]: https://github.com/tokio-rs/tokio/pull/7597
|
||||
[#7651]: https://github.com/tokio-rs/tokio/pull/7651
|
||||
[#7658]: https://github.com/tokio-rs/tokio/pull/7658
|
||||
|
||||
# 2.5.0 (Jan 8th, 2025)
|
||||
|
||||
- macros: suppress `clippy::needless_return` in `#[tokio::main]` ([#6874])
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "tokio-macros"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Remove path dependencies (if any)
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.x.y" git tag.
|
||||
version = "2.5.0"
|
||||
# - Create "tokio-macros-x.y.z" git tag.
|
||||
version = "2.7.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.71"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -27,7 +27,7 @@ quote = "1"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.0.0", features = ["full", "test-util"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
+116
-22
@@ -10,6 +10,7 @@ type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
|
||||
enum RuntimeFlavor {
|
||||
CurrentThread,
|
||||
Threaded,
|
||||
Local,
|
||||
}
|
||||
|
||||
impl RuntimeFlavor {
|
||||
@@ -17,10 +18,11 @@ impl RuntimeFlavor {
|
||||
match s {
|
||||
"current_thread" => Ok(RuntimeFlavor::CurrentThread),
|
||||
"multi_thread" => Ok(RuntimeFlavor::Threaded),
|
||||
"local" => Ok(RuntimeFlavor::Local),
|
||||
"single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()),
|
||||
"basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()),
|
||||
"threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()),
|
||||
_ => Err(format!("No such runtime flavor `{s}`. The runtime flavors are `current_thread` and `multi_thread`.")),
|
||||
_ => Err(format!("No such runtime flavor `{s}`. The runtime flavors are `current_thread`, `local`, and `multi_thread`.")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +53,7 @@ impl UnhandledPanic {
|
||||
}
|
||||
|
||||
struct FinalConfig {
|
||||
name: Option<String>,
|
||||
flavor: RuntimeFlavor,
|
||||
worker_threads: Option<usize>,
|
||||
start_paused: Option<bool>,
|
||||
@@ -60,6 +63,7 @@ struct FinalConfig {
|
||||
|
||||
/// Config used in case of the attribute not being able to build a valid config
|
||||
const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
|
||||
name: None,
|
||||
flavor: RuntimeFlavor::CurrentThread,
|
||||
worker_threads: None,
|
||||
start_paused: None,
|
||||
@@ -68,6 +72,7 @@ const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
name: Option<String>,
|
||||
rt_multi_thread_available: bool,
|
||||
default_flavor: RuntimeFlavor,
|
||||
flavor: Option<RuntimeFlavor>,
|
||||
@@ -81,6 +86,7 @@ struct Configuration {
|
||||
impl Configuration {
|
||||
fn new(is_test: bool, rt_multi_thread: bool) -> Self {
|
||||
Configuration {
|
||||
name: None,
|
||||
rt_multi_thread_available: rt_multi_thread,
|
||||
default_flavor: match is_test {
|
||||
true => RuntimeFlavor::CurrentThread,
|
||||
@@ -95,6 +101,16 @@ impl Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> {
|
||||
if self.name.is_some() {
|
||||
return Err(syn::Error::new(span, "`name` set multiple times."));
|
||||
}
|
||||
|
||||
let runtime_name = parse_string(name, span, "name")?;
|
||||
self.name = Some(runtime_name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_flavor(&mut self, runtime: syn::Lit, span: Span) -> Result<(), syn::Error> {
|
||||
if self.flavor.is_some() {
|
||||
return Err(syn::Error::new(span, "`flavor` set multiple times."));
|
||||
@@ -177,15 +193,16 @@ impl Configuration {
|
||||
use RuntimeFlavor as F;
|
||||
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
|
||||
let worker_threads = match (flavor, self.worker_threads) {
|
||||
(F::CurrentThread, Some((_, worker_threads_span))) => {
|
||||
(F::CurrentThread | F::Local, Some((_, worker_threads_span))) => {
|
||||
let msg = format!(
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(worker_threads_span, msg));
|
||||
}
|
||||
(F::CurrentThread, None) => None,
|
||||
(F::CurrentThread | F::Local, None) => None,
|
||||
(F::Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
worker_threads.map(|(val, _span)| val)
|
||||
}
|
||||
@@ -207,7 +224,7 @@ impl Configuration {
|
||||
);
|
||||
return Err(syn::Error::new(start_paused_span, msg));
|
||||
}
|
||||
(F::CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(F::CurrentThread | F::Local, Some((start_paused, _))) => Some(start_paused),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
@@ -219,11 +236,12 @@ impl Configuration {
|
||||
);
|
||||
return Err(syn::Error::new(unhandled_panic_span, msg));
|
||||
}
|
||||
(F::CurrentThread, Some((unhandled_panic, _))) => Some(unhandled_panic),
|
||||
(F::CurrentThread | F::Local, Some((unhandled_panic, _))) => Some(unhandled_panic),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
Ok(FinalConfig {
|
||||
name: self.name.clone(),
|
||||
crate_name: self.crate_name.clone(),
|
||||
flavor,
|
||||
worker_threads,
|
||||
@@ -290,6 +308,35 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_impl_trait(ty: &syn::Type) -> bool {
|
||||
match ty {
|
||||
syn::Type::ImplTrait(_) => true,
|
||||
syn::Type::Array(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Ptr(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Reference(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Slice(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Tuple(t) => t.elems.iter().any(contains_impl_trait),
|
||||
syn::Type::Paren(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Group(t) => contains_impl_trait(&t.elem),
|
||||
syn::Type::Path(t) => match t.path.segments.last() {
|
||||
Some(segment) => match &segment.arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => args.args.iter().any(|arg| match arg {
|
||||
syn::GenericArgument::Type(t) => contains_impl_trait(t),
|
||||
syn::GenericArgument::AssocType(t) => contains_impl_trait(&t.ty),
|
||||
_ => false,
|
||||
}),
|
||||
syn::PathArguments::Parenthesized(args) => {
|
||||
args.inputs.iter().any(contains_impl_trait)
|
||||
|| matches!(&args.output, syn::ReturnType::Type(_, t) if contains_impl_trait(t))
|
||||
}
|
||||
syn::PathArguments::None => false,
|
||||
},
|
||||
None => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_config(
|
||||
input: &ItemFn,
|
||||
args: AttributeArgs,
|
||||
@@ -340,9 +387,12 @@ fn build_config(
|
||||
config
|
||||
.set_unhandled_panic(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"name" => {
|
||||
config.set_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
name => {
|
||||
let msg = format!(
|
||||
"Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
|
||||
"Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.",
|
||||
);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
@@ -365,11 +415,12 @@ fn build_config(
|
||||
"Set the runtime flavor with #[{macro_name}(flavor = \"current_thread\")]."
|
||||
)
|
||||
}
|
||||
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
|
||||
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic"
|
||||
| "name" => {
|
||||
format!("The `{name}` attribute requires an argument.")
|
||||
}
|
||||
name => {
|
||||
format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.")
|
||||
format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.")
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
@@ -405,16 +456,32 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
let crate_path = config
|
||||
.crate_name
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream());
|
||||
.unwrap_or_else(|| {
|
||||
Ident::new("tokio", Span::call_site().located_at(last_stmt_start_span))
|
||||
.into_token_stream()
|
||||
});
|
||||
|
||||
let use_builder = quote_spanned! {Span::call_site().located_at(last_stmt_start_span)=>
|
||||
use #crate_path::runtime::Builder;
|
||||
};
|
||||
|
||||
let mut rt = match config.flavor {
|
||||
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_path::runtime::Builder::new_current_thread()
|
||||
},
|
||||
RuntimeFlavor::CurrentThread | RuntimeFlavor::Local => {
|
||||
quote_spanned! {last_stmt_start_span=>
|
||||
Builder::new_current_thread()
|
||||
}
|
||||
}
|
||||
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_path::runtime::Builder::new_multi_thread()
|
||||
Builder::new_multi_thread()
|
||||
},
|
||||
};
|
||||
|
||||
let build = if let RuntimeFlavor::Local = config.flavor {
|
||||
quote_spanned! {last_stmt_start_span=> build_local(Default::default())}
|
||||
} else {
|
||||
quote_spanned! {last_stmt_start_span=> build()}
|
||||
};
|
||||
|
||||
if let Some(v) = config.worker_threads {
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) };
|
||||
}
|
||||
@@ -425,6 +492,9 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
let unhandled_panic = v.into_tokens(&crate_path);
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) };
|
||||
}
|
||||
if let Some(v) = config.name {
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.name(#v) };
|
||||
}
|
||||
|
||||
let generated_attrs = if is_test {
|
||||
quote! {
|
||||
@@ -437,14 +507,18 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
let body_ident = quote! { body };
|
||||
// This explicit `return` is intentional. See tokio-rs/tokio#4636
|
||||
let last_block = quote_spanned! {last_stmt_end_span=>
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return)]
|
||||
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return, clippy::unwrap_in_result)]
|
||||
{
|
||||
#use_builder
|
||||
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.#build
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(#body_ident);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let body = input.body();
|
||||
@@ -458,22 +532,42 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
//
|
||||
// We don't do this for the main function as it should only be used once so
|
||||
// there will be no benefit.
|
||||
let output_type = match &input.sig.output {
|
||||
// For functions with no return value syn doesn't print anything,
|
||||
// but that doesn't work as `Output` for our boxed `Future`, so
|
||||
// default to `()` (the same type as the function output).
|
||||
syn::ReturnType::Default => quote! { () },
|
||||
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
|
||||
};
|
||||
|
||||
let body = if is_test {
|
||||
let output_type = match &input.sig.output {
|
||||
// For functions with no return value syn doesn't print anything,
|
||||
// but that doesn't work as `Output` for our boxed `Future`, so
|
||||
// default to `()` (the same type as the function output).
|
||||
syn::ReturnType::Default => quote! { () },
|
||||
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
|
||||
};
|
||||
quote! {
|
||||
let body = async #body;
|
||||
#crate_path::pin!(body);
|
||||
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
|
||||
}
|
||||
} else {
|
||||
// force typecheck without runtime overhead
|
||||
let check_block = match &input.sig.output {
|
||||
syn::ReturnType::Type(_, t)
|
||||
if matches!(**t, syn::Type::Never(_)) || contains_impl_trait(t) =>
|
||||
{
|
||||
quote! {}
|
||||
}
|
||||
_ => quote! {
|
||||
if false {
|
||||
let _: &dyn ::core::future::Future<Output = #output_type> = &body;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
quote! {
|
||||
let body = async #body;
|
||||
// Compile-time assertion that the future's output matches the return type.
|
||||
let body = {
|
||||
#check_block
|
||||
body
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+106
-20
@@ -12,11 +12,6 @@
|
||||
|
||||
//! Macros for use with Tokio
|
||||
|
||||
// This `extern` is required for older `rustc` versions but newer `rustc`
|
||||
// versions warn about the unused `extern crate`.
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate proc_macro;
|
||||
|
||||
mod entry;
|
||||
mod select;
|
||||
|
||||
@@ -46,7 +41,12 @@ use proc_macro::TokenStream;
|
||||
/// Awaiting on other futures from the function provided here will not
|
||||
/// perform as fast as those spawned as workers.
|
||||
///
|
||||
/// # Multi-threaded runtime
|
||||
/// # Runtime flavors
|
||||
///
|
||||
/// The macro can be configured with a `flavor` parameter to select
|
||||
/// different runtime configurations.
|
||||
///
|
||||
/// ## Multi-threaded
|
||||
///
|
||||
/// To use the multi-threaded runtime, the macro can be configured using
|
||||
///
|
||||
@@ -61,23 +61,56 @@ use proc_macro::TokenStream;
|
||||
/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
|
||||
/// flag.
|
||||
///
|
||||
/// # Current thread runtime
|
||||
/// ## Current-thread
|
||||
///
|
||||
/// To use the single-threaded runtime known as the `current_thread` runtime,
|
||||
/// the macro can be configured using
|
||||
///
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// ## Function arguments:
|
||||
/// ## Local
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
/// To use the [local runtime], the macro can be configured using
|
||||
///
|
||||
/// ## Usage
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "local")]
|
||||
/// # async fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// ### Using the multi-thread runtime
|
||||
/// # Function arguments
|
||||
///
|
||||
/// Arguments are allowed for any functions, aside from `main` which is special.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ## Set the name of the runtime
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(name = "my-runtime")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .name("my-runtime")
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Using the multi-threaded runtime
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
@@ -100,7 +133,7 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using current thread runtime
|
||||
/// ## Using the current-thread runtime
|
||||
///
|
||||
/// The basic scheduler is single-threaded.
|
||||
///
|
||||
@@ -125,7 +158,34 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Set number of worker threads
|
||||
/// ## Using the local runtime
|
||||
///
|
||||
/// The [local runtime] is similar to the current-thread runtime but
|
||||
/// supports [`task::spawn_local`](../tokio/task/fn.spawn_local.html).
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "local")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .build_local(tokio::runtime::LocalOptions::default())
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// ## Set number of worker threads
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(worker_threads = 2)]
|
||||
@@ -149,7 +209,7 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Configure the runtime to start with time paused
|
||||
/// ## Configure the runtime to start with time paused
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
@@ -175,7 +235,7 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// Note that `start_paused` requires the `test-util` feature to be enabled.
|
||||
///
|
||||
/// ### Rename package
|
||||
/// ## Rename package
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio as tokio1;
|
||||
@@ -202,7 +262,7 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Configure unhandled panic behavior
|
||||
/// ## Configure unhandled panic behavior
|
||||
///
|
||||
/// Available options are `shutdown_runtime` and `ignore`. For more details, see
|
||||
/// [`Builder::unhandled_panic`].
|
||||
@@ -228,7 +288,7 @@ use proc_macro::TokenStream;
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
|
||||
/// .unhandled_panic(tokio::runtime::UnhandledPanic::ShutdownRuntime)
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
@@ -247,6 +307,7 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
|
||||
/// [unstable]: ../tokio/index.html#unstable-features
|
||||
/// [local runtime]: ../tokio/runtime/struct.LocalRuntime.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args.into(), item.into(), true).into()
|
||||
@@ -358,6 +419,31 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Set the name of the runtime
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(name = "my-test-runtime")]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::test]`
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[test]
|
||||
/// fn my_test() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .name("my-test-runtime")
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// assert!(true);
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using the multi-thread runtime
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -484,7 +570,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
///
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
@@ -505,7 +591,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// }).await;
|
||||
/// })
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
///
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
# 0.1.18 (January 4th, 2026)
|
||||
|
||||
### Added
|
||||
|
||||
- stream: add `ChunksTimeout::into_remainder` ([#7715])
|
||||
- stream: add examples to wrapper types ([#7024])
|
||||
- sync: implement `Stream::size_hint` for `ReceiverStream` and `UnboundedReceiverStream` ([#7492])
|
||||
|
||||
### Fixed
|
||||
|
||||
- stream: work around the rustc bug in `StreamExt::collect` ([#7754])
|
||||
|
||||
### Documented
|
||||
|
||||
- stream: improve the docs of `TcpListenerStream` ([#7578])
|
||||
|
||||
[#7024]: https://github.com/tokio-rs/tokio/pull/7024
|
||||
[#7492]: https://github.com/tokio-rs/tokio/pull/7492
|
||||
[#7578]: https://github.com/tokio-rs/tokio/pull/7578
|
||||
[#7715]: https://github.com/tokio-rs/tokio/pull/7715
|
||||
[#7754]: https://github.com/tokio-rs/tokio/pull/7754
|
||||
|
||||
# 0.1.17 (December 6th, 2024)
|
||||
|
||||
- deps: fix dev-dependency on tokio-test ([#6931], [#7019])
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "tokio-stream"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Remove path dependencies (if any)
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.17"
|
||||
version = "0.1.18"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.71"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -38,14 +38,14 @@ signal = ["tokio/signal"]
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.11"
|
||||
tokio = { version = "1.15.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
|
||||
tokio = { version = "1.38.0", features = ["sync"] }
|
||||
tokio-util = { version = "0.7.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
|
||||
tokio = { version = "1.38.0", features = ["full", "test-util"] }
|
||||
async-stream = "0.3"
|
||||
parking_lot = "0.12.0"
|
||||
tokio-test = { version = "0.4", path = "../tokio-test" }
|
||||
tokio-test = "0.4"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -26,12 +26,12 @@ unsafe impl<T> Sync for Empty<T> {}
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut none = stream::empty::<i32>();
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let mut none = stream::empty::<i32>();
|
||||
///
|
||||
/// assert_eq!(None, none.next().await);
|
||||
/// }
|
||||
/// assert_eq!(None, none.next().await);
|
||||
/// # }
|
||||
/// ```
|
||||
pub const fn empty<T>() -> Empty<T> {
|
||||
Empty(PhantomData)
|
||||
|
||||
@@ -34,14 +34,14 @@
|
||||
//! ```rust
|
||||
//! use tokio_stream::{self as stream, StreamExt};
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let mut stream = stream::iter(vec![0, 1, 2]);
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! let mut stream = stream::iter(vec![0, 1, 2]);
|
||||
//!
|
||||
//! while let Some(value) = stream.next().await {
|
||||
//! println!("Got {}", value);
|
||||
//! }
|
||||
//! while let Some(value) = stream.next().await {
|
||||
//! println!("Got {}", value);
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! # Returning a Stream from a function
|
||||
|
||||
@@ -22,16 +22,16 @@ impl<I> Unpin for Once<I> {}
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// // one is the loneliest number
|
||||
/// let mut one = stream::once(1);
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// // one is the loneliest number
|
||||
/// let mut one = stream::once(1);
|
||||
///
|
||||
/// assert_eq!(Some(1), one.next().await);
|
||||
/// assert_eq!(Some(1), one.next().await);
|
||||
///
|
||||
/// // just one, that's all we get
|
||||
/// assert_eq!(None, one.next().await);
|
||||
/// }
|
||||
/// // just one, that's all we get
|
||||
/// assert_eq!(None, one.next().await);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn once<T>(value: T) -> Once<T> {
|
||||
Once {
|
||||
|
||||
@@ -17,7 +17,7 @@ unsafe impl<T> Sync for Pending<T> {}
|
||||
///
|
||||
/// The returned stream is never ready. Attempting to call
|
||||
/// [`next()`](crate::StreamExt::next) will never complete. Use
|
||||
/// [`stream::empty()`](super::empty()) to obtain a stream that is is
|
||||
/// [`stream::empty()`](super::empty()) to obtain a stream that is
|
||||
/// immediately empty but returns no values.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@@ -17,20 +17,20 @@ pin_project! {
|
||||
/// ```
|
||||
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct StreamNotifyClose<S> {
|
||||
@@ -83,7 +83,7 @@ where
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if let Some(inner) = &self.inner {
|
||||
// We always return +1 because when there's stream there's atleast one more item.
|
||||
// We always return +1 because when there's a stream there's at least one more item.
|
||||
let (l, u) = inner.size_hint();
|
||||
(l.saturating_add(1), u.and_then(|u| u.checked_add(1)))
|
||||
} else {
|
||||
|
||||
@@ -129,7 +129,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -171,8 +171,9 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
///
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter(vec![Ok(1), Ok(2), Err("nope")]);
|
||||
@@ -203,7 +204,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -239,7 +240,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -283,7 +284,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -418,7 +419,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -454,7 +455,7 @@ pub trait StreamExt: Stream {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -514,7 +515,10 @@ pub trait StreamExt: Stream {
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # /*
|
||||
/// #[tokio::main]
|
||||
/// # */
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// let mut stream = Alternate { state: 0 };
|
||||
///
|
||||
@@ -551,7 +555,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -580,7 +584,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -606,7 +610,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -637,7 +641,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// let mut stream = stream::iter(vec![1,2,3,4,1]).skip_while(|x| *x < 3);
|
||||
@@ -680,7 +684,7 @@ pub trait StreamExt: Stream {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -695,7 +699,7 @@ pub trait StreamExt: Stream {
|
||||
/// Stopping at the first `false`:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -739,7 +743,7 @@ pub trait StreamExt: Stream {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -754,7 +758,7 @@ pub trait StreamExt: Stream {
|
||||
/// Stopping at the first `true`:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
@@ -787,21 +791,21 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let one = stream::iter(vec![1, 2, 3]);
|
||||
/// let two = stream::iter(vec![4, 5, 6]);
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let one = stream::iter(vec![1, 2, 3]);
|
||||
/// let two = stream::iter(vec![4, 5, 6]);
|
||||
///
|
||||
/// let mut stream = one.chain(two);
|
||||
/// let mut stream = one.chain(two);
|
||||
///
|
||||
/// assert_eq!(stream.next().await, Some(1));
|
||||
/// assert_eq!(stream.next().await, Some(2));
|
||||
/// assert_eq!(stream.next().await, Some(3));
|
||||
/// assert_eq!(stream.next().await, Some(4));
|
||||
/// assert_eq!(stream.next().await, Some(5));
|
||||
/// assert_eq!(stream.next().await, Some(6));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// }
|
||||
/// assert_eq!(stream.next().await, Some(1));
|
||||
/// assert_eq!(stream.next().await, Some(2));
|
||||
/// assert_eq!(stream.next().await, Some(3));
|
||||
/// assert_eq!(stream.next().await, Some(4));
|
||||
/// assert_eq!(stream.next().await, Some(5));
|
||||
/// assert_eq!(stream.next().await, Some(6));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// # }
|
||||
/// ```
|
||||
fn chain<U>(self, other: U) -> Chain<Self, U>
|
||||
where
|
||||
@@ -823,7 +827,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
/// Basic usage:
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, *};
|
||||
///
|
||||
@@ -874,16 +878,16 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let doubled: Vec<i32> =
|
||||
/// stream::iter(vec![1, 2, 3])
|
||||
/// .map(|x| x * 2)
|
||||
/// .collect()
|
||||
/// .await;
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let doubled: Vec<i32> =
|
||||
/// stream::iter(vec![1, 2, 3])
|
||||
/// .map(|x| x * 2)
|
||||
/// .collect()
|
||||
/// .await;
|
||||
///
|
||||
/// assert_eq!(vec![2, 4, 6], doubled);
|
||||
/// }
|
||||
/// assert_eq!(vec![2, 4, 6], doubled);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Collecting a stream of `Result` values
|
||||
@@ -891,28 +895,28 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// // A stream containing only `Ok` values will be collected
|
||||
/// let values: Result<Vec<i32>, &str> =
|
||||
/// stream::iter(vec![Ok(1), Ok(2), Ok(3)])
|
||||
/// .collect()
|
||||
/// .await;
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// // A stream containing only `Ok` values will be collected
|
||||
/// let values: Result<Vec<i32>, &str> =
|
||||
/// stream::iter(vec![Ok(1), Ok(2), Ok(3)])
|
||||
/// .collect()
|
||||
/// .await;
|
||||
///
|
||||
/// assert_eq!(Ok(vec![1, 2, 3]), values);
|
||||
/// assert_eq!(Ok(vec![1, 2, 3]), values);
|
||||
///
|
||||
/// // A stream containing `Err` values will return the first error.
|
||||
/// let results = vec![Ok(1), Err("no"), Ok(2), Ok(3), Err("nein")];
|
||||
/// // A stream containing `Err` values will return the first error.
|
||||
/// let results = vec![Ok(1), Err("no"), Ok(2), Ok(3), Err("nein")];
|
||||
///
|
||||
/// let values: Result<Vec<i32>, &str> =
|
||||
/// stream::iter(results)
|
||||
/// .collect()
|
||||
/// .await;
|
||||
/// let values: Result<Vec<i32>, &str> =
|
||||
/// stream::iter(results)
|
||||
/// .collect()
|
||||
/// .await;
|
||||
///
|
||||
/// assert_eq!(Err("no"), values);
|
||||
/// }
|
||||
/// assert_eq!(Err("no"), values);
|
||||
/// # }
|
||||
/// ```
|
||||
fn collect<T>(self) -> Collect<Self, T>
|
||||
fn collect<T>(self) -> Collect<Self, T, T::InternalCollection>
|
||||
where
|
||||
T: FromStream<Self::Item>,
|
||||
Self: Sized,
|
||||
@@ -945,7 +949,7 @@ pub trait StreamExt: Stream {
|
||||
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// use std::time::Duration;
|
||||
@@ -1031,7 +1035,7 @@ pub trait StreamExt: Stream {
|
||||
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// use std::time::Duration;
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Stream;
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{ready, Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -48,3 +49,13 @@ where
|
||||
super::merge_size_hints(self.a.size_hint(), self.b.size_hint())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> FusedStream for Chain<T, U>
|
||||
where
|
||||
T: Stream,
|
||||
U: FusedStream<Item = T::Item>,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.a.is_terminated() && self.b.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ impl<S: Stream> ChunksTimeout<S> {
|
||||
cap: max_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the [`ChunksTimeout`] and then returns all buffered items.
|
||||
pub fn into_remainder(mut self: Pin<&mut Self>) -> Vec<S::Item> {
|
||||
let me = self.as_mut().project();
|
||||
std::mem::take(me.items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for ChunksTimeout<S> {
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::marker::{PhantomData, PhantomPinned};
|
||||
use core::mem;
|
||||
use core::pin::Pin;
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
|
||||
use std::hash::Hash;
|
||||
|
||||
// Do not export this struct until `FromStream` can be unsealed.
|
||||
pin_project! {
|
||||
/// Future returned by the [`collect`](super::StreamExt::collect) method.
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
#[derive(Debug)]
|
||||
pub struct Collect<T, U>
|
||||
where
|
||||
T: Stream,
|
||||
U: FromStream<T::Item>,
|
||||
pub struct Collect<T, U, C>
|
||||
{
|
||||
#[pin]
|
||||
stream: T,
|
||||
collection: U::InternalCollection,
|
||||
collection: C,
|
||||
_output: PhantomData<U>,
|
||||
// Make this future `!Unpin` for compatibility with async trait methods.
|
||||
#[pin]
|
||||
_pin: PhantomPinned,
|
||||
@@ -38,24 +38,25 @@ pin_project! {
|
||||
/// enhancements to the Rust language.
|
||||
pub trait FromStream<T>: sealed::FromStreamPriv<T> {}
|
||||
|
||||
impl<T, U> Collect<T, U>
|
||||
impl<T, U> Collect<T, U, U::InternalCollection>
|
||||
where
|
||||
T: Stream,
|
||||
U: FromStream<T::Item>,
|
||||
{
|
||||
pub(super) fn new(stream: T) -> Collect<T, U> {
|
||||
pub(super) fn new(stream: T) -> Collect<T, U, U::InternalCollection> {
|
||||
let (lower, upper) = stream.size_hint();
|
||||
let collection = U::initialize(sealed::Internal, lower, upper);
|
||||
|
||||
Collect {
|
||||
stream,
|
||||
collection,
|
||||
_output: PhantomData,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Future for Collect<T, U>
|
||||
impl<T, U> Future for Collect<T, U, U::InternalCollection>
|
||||
where
|
||||
T: Stream,
|
||||
U: FromStream<T::Item>,
|
||||
@@ -136,6 +137,139 @@ impl<T> sealed::FromStreamPriv<T> for Vec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromStream<T> for VecDeque<T> {}
|
||||
|
||||
impl<T> sealed::FromStreamPriv<T> for VecDeque<T> {
|
||||
type InternalCollection = VecDeque<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> VecDeque<T> {
|
||||
VecDeque::with_capacity(lower)
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut VecDeque<T>, item: T) -> bool {
|
||||
collection.push_back(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut VecDeque<T>) -> VecDeque<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromStream<T> for LinkedList<T> {}
|
||||
|
||||
impl<T> sealed::FromStreamPriv<T> for LinkedList<T> {
|
||||
type InternalCollection = LinkedList<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> LinkedList<T> {
|
||||
LinkedList::new()
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut LinkedList<T>, item: T) -> bool {
|
||||
collection.push_back(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut LinkedList<T>) -> LinkedList<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ord> FromStream<T> for BTreeSet<T> {}
|
||||
|
||||
impl<T: Ord> sealed::FromStreamPriv<T> for BTreeSet<T> {
|
||||
type InternalCollection = BTreeSet<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> BTreeSet<T> {
|
||||
BTreeSet::new()
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut BTreeSet<T>, item: T) -> bool {
|
||||
collection.insert(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut BTreeSet<T>) -> BTreeSet<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Ord, V> FromStream<(K, V)> for BTreeMap<K, V> {}
|
||||
|
||||
impl<K: Ord, V> sealed::FromStreamPriv<(K, V)> for BTreeMap<K, V> {
|
||||
type InternalCollection = BTreeMap<K, V>;
|
||||
|
||||
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> BTreeMap<K, V> {
|
||||
BTreeMap::new()
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut BTreeMap<K, V>, (key, value): (K, V)) -> bool {
|
||||
collection.insert(key, value);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut BTreeMap<K, V>) -> BTreeMap<K, V> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash> FromStream<T> for HashSet<T> {}
|
||||
|
||||
impl<T: Eq + Hash> sealed::FromStreamPriv<T> for HashSet<T> {
|
||||
type InternalCollection = HashSet<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> HashSet<T> {
|
||||
HashSet::with_capacity(lower)
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut HashSet<T>, item: T) -> bool {
|
||||
collection.insert(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut HashSet<T>) -> HashSet<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq + Hash, V> FromStream<(K, V)> for HashMap<K, V> {}
|
||||
|
||||
impl<K: Eq + Hash, V> sealed::FromStreamPriv<(K, V)> for HashMap<K, V> {
|
||||
type InternalCollection = HashMap<K, V>;
|
||||
|
||||
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> HashMap<K, V> {
|
||||
HashMap::with_capacity(lower)
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut HashMap<K, V>, (key, value): (K, V)) -> bool {
|
||||
collection.insert(key, value);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut HashMap<K, V>) -> HashMap<K, V> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ord> FromStream<T> for BinaryHeap<T> {}
|
||||
|
||||
impl<T: Ord> sealed::FromStreamPriv<T> for BinaryHeap<T> {
|
||||
type InternalCollection = BinaryHeap<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> BinaryHeap<T> {
|
||||
BinaryHeap::with_capacity(lower)
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut BinaryHeap<T>, item: T) -> bool {
|
||||
collection.push(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut BinaryHeap<T>) -> BinaryHeap<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromStream<T> for Box<[T]> {}
|
||||
|
||||
impl<T> sealed::FromStreamPriv<T> for Box<[T]> {
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Stream;
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{ready, Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -56,3 +57,13 @@ where
|
||||
(0, self.stream.size_hint().1) // can't know a lower bound, due to the predicate
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F> FusedStream for Filter<St, F>
|
||||
where
|
||||
St: FusedStream,
|
||||
F: FnMut(&St::Item) -> bool,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.stream.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Stream;
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{ready, Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -56,3 +57,13 @@ where
|
||||
(0, self.stream.size_hint().1) // can't know a lower bound, due to the predicate
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F, T> FusedStream for FilterMap<St, F>
|
||||
where
|
||||
St: FusedStream,
|
||||
F: FnMut(St::Item) -> Option<T>,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.stream.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::Stream;
|
||||
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
use std::task::{ready, Context, Poll};
|
||||
@@ -51,3 +52,12 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FusedStream for Fuse<T>
|
||||
where
|
||||
T: Stream,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.stream.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Stream;
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -49,3 +50,13 @@ where
|
||||
self.stream.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F, T> FusedStream for Map<St, F>
|
||||
where
|
||||
St: FusedStream,
|
||||
F: FnMut(St::Item) -> T,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.stream.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Stream;
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -57,6 +58,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> FusedStream for Merge<T, U>
|
||||
where
|
||||
T: Stream,
|
||||
U: Stream<Item = T::Item>,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.a.is_terminated() && self.b.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next<T, U>(
|
||||
first: Pin<&mut T>,
|
||||
second: Pin<&mut U>,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Stream;
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{ready, Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -61,3 +62,12 @@ where
|
||||
(lower, upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl<St> FusedStream for Skip<St>
|
||||
where
|
||||
St: FusedStream,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.stream.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Stream;
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{ready, Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -71,3 +72,13 @@ where
|
||||
(lower, upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F> FusedStream for SkipWhile<St, F>
|
||||
where
|
||||
St: FusedStream,
|
||||
F: FnMut(&St::Item) -> bool,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.stream.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use core::cmp;
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -74,3 +75,12 @@ where
|
||||
(lower, upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl<St> FusedStream for Take<St>
|
||||
where
|
||||
St: Stream,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.remaining == 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Stream;
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -77,3 +78,13 @@ where
|
||||
(0, upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F> FusedStream for TakeWhile<St, F>
|
||||
where
|
||||
St: Stream,
|
||||
F: FnMut(&St::Item) -> bool,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.done
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use core::fmt;
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
@@ -81,3 +82,14 @@ where
|
||||
(lower, upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F, Fut> FusedStream for Then<St, Fut, F>
|
||||
where
|
||||
St: FusedStream,
|
||||
Fut: Future,
|
||||
F: FnMut(St::Item) -> Fut,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.future.is_none() && self.stream.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,57 +65,57 @@ use std::task::{ready, Context, Poll};
|
||||
/// use tokio::sync::mpsc;
|
||||
/// use std::pin::Pin;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx1, mut rx1) = mpsc::channel::<usize>(10);
|
||||
/// let (tx2, mut rx2) = mpsc::channel::<usize>(10);
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let (tx1, mut rx1) = mpsc::channel::<usize>(10);
|
||||
/// let (tx2, mut rx2) = mpsc::channel::<usize>(10);
|
||||
///
|
||||
/// // Convert the channels to a `Stream`.
|
||||
/// let rx1 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx1.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// let rx2 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx2.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx1.send(1).await.unwrap();
|
||||
///
|
||||
/// // This value will never be received. The send may or may not return
|
||||
/// // `Err` depending on if the remote end closed first or not.
|
||||
/// let _ = tx1.send(2).await;
|
||||
/// });
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx2.send(3).await.unwrap();
|
||||
/// let _ = tx2.send(4).await;
|
||||
/// });
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
/// // Insert both streams
|
||||
/// map.insert("one", rx1);
|
||||
/// map.insert("two", rx2);
|
||||
///
|
||||
/// // Read twice
|
||||
/// for _ in 0..2 {
|
||||
/// let (key, val) = map.next().await.unwrap();
|
||||
///
|
||||
/// if key == "one" {
|
||||
/// assert_eq!(val, 1);
|
||||
/// } else {
|
||||
/// assert_eq!(val, 3);
|
||||
/// }
|
||||
///
|
||||
/// // Remove the stream to prevent reading the next value
|
||||
/// map.remove(key);
|
||||
/// // Convert the channels to a `Stream`.
|
||||
/// let rx1 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx1.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// let rx2 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx2.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx1.send(1).await.unwrap();
|
||||
///
|
||||
/// // This value will never be received. The send may or may not return
|
||||
/// // `Err` depending on if the remote end closed first or not.
|
||||
/// let _ = tx1.send(2).await;
|
||||
/// });
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx2.send(3).await.unwrap();
|
||||
/// let _ = tx2.send(4).await;
|
||||
/// });
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
/// // Insert both streams
|
||||
/// map.insert("one", rx1);
|
||||
/// map.insert("two", rx2);
|
||||
///
|
||||
/// // Read twice
|
||||
/// for _ in 0..2 {
|
||||
/// let (key, val) = map.next().await.unwrap();
|
||||
///
|
||||
/// if key == "one" {
|
||||
/// assert_eq!(val, 1);
|
||||
/// } else {
|
||||
/// assert_eq!(val, 3);
|
||||
/// }
|
||||
///
|
||||
/// // Remove the stream to prevent reading the next value
|
||||
/// map.remove(key);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// This example models a read-only client to a chat system with channels. The
|
||||
@@ -185,20 +185,20 @@ use std::task::{ready, Context, Poll};
|
||||
/// ```
|
||||
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -579,9 +579,11 @@ where
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. If `next_many` is used as the event in a
|
||||
/// [`tokio::select!`](tokio::select) statement and some other branch
|
||||
/// completes first, it is guaranteed that no items were received on any of
|
||||
/// the underlying streams.
|
||||
/// [`tokio::select!`] statement and some other branch completes first,
|
||||
/// it is guaranteed that no items were received on any of the underlying
|
||||
/// streams.
|
||||
///
|
||||
/// [`tokio::select!`]: https://docs.rs/tokio/latest/tokio/macro.select.html
|
||||
pub async fn next_many(&mut self, buffer: &mut Vec<(K, V::Item)>, limit: usize) -> usize {
|
||||
poll_fn(|cx| self.poll_next_many(cx, buffer, limit)).await
|
||||
}
|
||||
@@ -734,22 +736,15 @@ mod rand {
|
||||
#[cfg(not(loom))]
|
||||
pub(crate) mod rand {
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::hash::BuildHasher;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
pub(crate) fn seed() -> u64 {
|
||||
let rand_state = RandomState::new();
|
||||
|
||||
let mut hasher = rand_state.build_hasher();
|
||||
|
||||
// Hash some unique-ish data to generate some new state
|
||||
COUNTER.fetch_add(1, Relaxed).hash(&mut hasher);
|
||||
|
||||
// Get the seed
|
||||
hasher.finish()
|
||||
RandomState::new().hash_one(COUNTER.fetch_add(1, Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ cfg_sync! {
|
||||
}
|
||||
|
||||
cfg_signal! {
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(loom)))]
|
||||
mod signal_unix;
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(loom)))]
|
||||
pub use signal_unix::SignalStream;
|
||||
|
||||
#[cfg(any(windows, docsrs))]
|
||||
@@ -39,12 +39,14 @@ cfg_time! {
|
||||
}
|
||||
|
||||
cfg_net! {
|
||||
#[cfg(not(loom))]
|
||||
mod tcp_listener;
|
||||
#[cfg(not(loom))]
|
||||
pub use tcp_listener::TcpListenerStream;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(loom)))]
|
||||
mod unix_listener;
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(loom)))]
|
||||
pub use unix_listener::UnixListenerStream;
|
||||
}
|
||||
|
||||
@@ -57,6 +59,8 @@ cfg_io_util! {
|
||||
}
|
||||
|
||||
cfg_fs! {
|
||||
#[cfg(not(loom))]
|
||||
mod read_dir;
|
||||
#[cfg(not(loom))]
|
||||
pub use read_dir::ReadDirStream;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ pub enum BroadcastStreamRecvError {
|
||||
impl fmt::Display for BroadcastStreamRecvError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BroadcastStreamRecvError::Lagged(amt) => write!(f, "channel lagged by {}", amt),
|
||||
BroadcastStreamRecvError::Lagged(amt) => write!(f, "channel lagged by {amt}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::Stream;
|
||||
use futures_core::stream::FusedStream;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::time::{Instant, Interval};
|
||||
@@ -57,6 +58,12 @@ impl Stream for IntervalStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedStream for IntervalStream {
|
||||
fn is_terminated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Interval> for IntervalStream {
|
||||
fn as_ref(&self) -> &Interval {
|
||||
&self.inner
|
||||
|
||||
@@ -67,6 +67,25 @@ impl<T> Stream for ReceiverStream<T> {
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
|
||||
/// Returns the bounds of the stream based on the underlying receiver.
|
||||
///
|
||||
/// For open channels, it returns `(receiver.len(), None)`.
|
||||
///
|
||||
/// For closed channels, it returns `(receiver.len(), Some(used_capacity))`
|
||||
/// where `used_capacity` is calculated as `receiver.max_capacity() -
|
||||
/// receiver.capacity()`. This accounts for any [`Permit`] that is still
|
||||
/// able to send a message.
|
||||
///
|
||||
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if self.inner.is_closed() {
|
||||
let used_capacity = self.inner.max_capacity() - self.inner.capacity();
|
||||
(self.inner.len(), Some(used_capacity))
|
||||
} else {
|
||||
(self.inner.len(), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<Receiver<T>> for ReceiverStream<T> {
|
||||
|
||||
@@ -61,6 +61,20 @@ impl<T> Stream for UnboundedReceiverStream<T> {
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
|
||||
/// Returns the bounds of the stream based on the underlying receiver.
|
||||
///
|
||||
/// For open channels, it returns `(receiver.len(), None)`.
|
||||
///
|
||||
/// For closed channels, it returns `(receiver.len(), receiver.len())`.
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if self.inner.is_closed() {
|
||||
let len = self.inner.len();
|
||||
(len, Some(len))
|
||||
} else {
|
||||
(self.inner.len(), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<UnboundedReceiver<T>> for UnboundedReceiverStream<T> {
|
||||
|
||||
@@ -11,6 +11,8 @@ use tokio::net::{TcpListener, TcpStream};
|
||||
/// Accept connections from both IPv4 and IPv6 listeners in the same loop:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[cfg(not(target_family = "wasm"))]
|
||||
/// # {
|
||||
/// use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
///
|
||||
/// use tokio::net::TcpListener;
|
||||
@@ -18,12 +20,12 @@ use tokio::net::{TcpListener, TcpStream};
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let ipv4_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 8080)).await?;
|
||||
/// let ipv6_listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 8080)).await?;
|
||||
/// let ipv4_listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 8080)).await?;
|
||||
/// let ipv6_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 8080)).await?;
|
||||
/// let ipv4_connections = TcpListenerStream::new(ipv4_listener);
|
||||
/// let ipv6_connections = TcpListenerStream::new(ipv6_listener);
|
||||
///
|
||||
/// let mut connections = ipv4_connections.chain(ipv6_connections);
|
||||
/// let mut connections = ipv4_connections.merge(ipv6_connections);
|
||||
/// while let Some(tcp_stream) = connections.next().await {
|
||||
/// let stream = tcp_stream?;
|
||||
/// let peer_addr = stream.peer_addr()?;
|
||||
@@ -31,6 +33,7 @@ use tokio::net::{TcpListener, TcpStream};
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`TcpListener`]: struct@tokio::net::TcpListener
|
||||
|
||||
@@ -17,7 +17,7 @@ use tokio::sync::watch::error::RecvError;
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
|
||||
/// use tokio::sync::watch;
|
||||
@@ -33,7 +33,7 @@ use tokio::sync::watch::error::RecvError;
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
|
||||
/// use tokio::sync::watch;
|
||||
@@ -51,7 +51,7 @@ use tokio::sync::watch::error::RecvError;
|
||||
/// Example with [`WatchStream<T>::from_changes`]:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// use futures::future::FutureExt;
|
||||
/// use tokio::sync::watch;
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
use futures::{Stream, StreamExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
|
||||
#[tokio::test]
|
||||
async fn size_hint_stream_open() {
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
|
||||
tx.send(1).await.unwrap();
|
||||
tx.send(2).await.unwrap();
|
||||
|
||||
let mut stream = ReceiverStream::new(rx);
|
||||
|
||||
assert_eq!(stream.size_hint(), (2, None));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (1, None));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn size_hint_stream_closed() {
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
|
||||
tx.send(1).await.unwrap();
|
||||
tx.send(2).await.unwrap();
|
||||
|
||||
let mut stream = ReceiverStream::new(rx);
|
||||
stream.close();
|
||||
|
||||
assert_eq!(stream.size_hint(), (2, Some(2)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (1, Some(1)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn size_hint_sender_dropped() {
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
|
||||
tx.send(1).await.unwrap();
|
||||
tx.send(2).await.unwrap();
|
||||
|
||||
let mut stream = ReceiverStream::new(rx);
|
||||
drop(tx);
|
||||
|
||||
assert_eq!(stream.size_hint(), (2, Some(2)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (1, Some(1)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_hint_stream_instantly_closed() {
|
||||
let (_tx, rx) = mpsc::channel::<i32>(4);
|
||||
|
||||
let mut stream = ReceiverStream::new(rx);
|
||||
stream.close();
|
||||
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn size_hint_stream_closed_permits_send() {
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
|
||||
tx.send(1).await.unwrap();
|
||||
let permit1 = tx.reserve().await.unwrap();
|
||||
let permit2 = tx.reserve().await.unwrap();
|
||||
|
||||
let mut stream = ReceiverStream::new(rx);
|
||||
stream.close();
|
||||
|
||||
assert_eq!(stream.size_hint(), (1, Some(3)));
|
||||
permit1.send(2);
|
||||
assert_eq!(stream.size_hint(), (2, Some(3)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (1, Some(2)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, Some(1)));
|
||||
permit2.send(3);
|
||||
assert_eq!(stream.size_hint(), (1, Some(1)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
assert_eq!(stream.next().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn size_hint_stream_closed_permits_drop() {
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
|
||||
tx.send(1).await.unwrap();
|
||||
let permit1 = tx.reserve().await.unwrap();
|
||||
let permit2 = tx.reserve().await.unwrap();
|
||||
|
||||
let mut stream = ReceiverStream::new(rx);
|
||||
stream.close();
|
||||
|
||||
assert_eq!(stream.size_hint(), (1, Some(3)));
|
||||
drop(permit1);
|
||||
assert_eq!(stream.size_hint(), (1, Some(2)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, Some(1)));
|
||||
drop(permit2);
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
assert_eq!(stream.next().await, None);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use futures::{Stream, StreamExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
#[tokio::test]
|
||||
async fn size_hint_stream_open() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
tx.send(1).unwrap();
|
||||
tx.send(2).unwrap();
|
||||
|
||||
let mut stream = UnboundedReceiverStream::new(rx);
|
||||
|
||||
assert_eq!(stream.size_hint(), (2, None));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (1, None));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn size_hint_stream_closed() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
tx.send(1).unwrap();
|
||||
tx.send(2).unwrap();
|
||||
|
||||
let mut stream = UnboundedReceiverStream::new(rx);
|
||||
stream.close();
|
||||
|
||||
assert_eq!(stream.size_hint(), (2, Some(2)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (1, Some(1)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn size_hint_sender_dropped() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
tx.send(1).unwrap();
|
||||
tx.send(2).unwrap();
|
||||
|
||||
let mut stream = UnboundedReceiverStream::new(rx);
|
||||
drop(tx);
|
||||
|
||||
assert_eq!(stream.size_hint(), (2, Some(2)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (1, Some(1)));
|
||||
stream.next().await;
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_hint_stream_instantly_closed() {
|
||||
let (_tx, rx) = mpsc::unbounded_channel::<i32>();
|
||||
|
||||
let mut stream = UnboundedReceiverStream::new(rx);
|
||||
stream.close();
|
||||
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures::FutureExt;
|
||||
use std::error::Error;
|
||||
use tokio::time;
|
||||
use tokio::time::Duration;
|
||||
use tokio_stream::{self as stream, StreamExt};
|
||||
use tokio_test::assert_pending;
|
||||
use tokio_test::task;
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn stream_chunks_remainder() -> Result<(), Box<dyn Error>> {
|
||||
let stream1 =
|
||||
stream::iter([5]).then(move |n| time::sleep(Duration::from_secs(1)).map(move |_| n));
|
||||
|
||||
let inner = stream::iter([1, 2, 3, 4]).chain(stream1);
|
||||
tokio::pin!(inner);
|
||||
|
||||
let chunked = (&mut inner).chunks_timeout(10, Duration::from_millis(20));
|
||||
|
||||
let mut chunked = task::spawn(chunked);
|
||||
assert_pending!(chunked.poll_next());
|
||||
|
||||
let remainder = chunked.enter(|_, stream| stream.into_remainder());
|
||||
|
||||
assert_eq!(remainder, vec![1, 2, 3, 4]);
|
||||
time::advance(Duration::from_secs(2)).await;
|
||||
assert_eq!(inner.next().await, Some(5));
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
|
||||
|
||||
use tokio_stream::{self as stream, StreamExt};
|
||||
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok, task};
|
||||
|
||||
@@ -61,6 +63,155 @@ async fn collect_vec_items() {
|
||||
assert_eq!(vec![1, 2], coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_vecdeque_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<VecDeque<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
let xs = [1, 2, 42, 3];
|
||||
for x in xs {
|
||||
tx.send(x).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
|
||||
assert_eq!(coll, VecDeque::from(xs));
|
||||
assert_eq!(coll.into_iter().collect::<Vec<_>>(), xs);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_linkedlist_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<LinkedList<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
let xs = [1, 2, 42, 3];
|
||||
for x in xs {
|
||||
tx.send(x).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
|
||||
assert_eq!(coll, LinkedList::from(xs));
|
||||
assert_eq!(coll.into_iter().collect::<Vec<_>>(), xs);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_btreeset_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<BTreeSet<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(2).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(1).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(BTreeSet::from([1, 2]), coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_btreemap_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<BTreeMap<i32, i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send((3, 4)).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send((1, 2)).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(BTreeMap::from([(1, 2), (3, 4)]), coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_hashset_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<HashSet<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(1).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(2).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(HashSet::from([1, 2]), coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_hashmap_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<HashMap<i32, i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send((1, 2)).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send((3, 4)).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(HashMap::from([(1, 2), (3, 4)]), coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_binaryheap_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<BinaryHeap<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(2).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(1).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(vec![1, 2], coll.into_sorted_vec());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_string_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use futures_core::FusedStream;
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
|
||||
use std::pin::Pin;
|
||||
@@ -37,14 +38,32 @@ async fn basic_usage() {
|
||||
// however, once it is fused
|
||||
let mut stream = stream.fuse();
|
||||
|
||||
assert!(!stream.is_terminated());
|
||||
assert_eq!(stream.size_hint(), (0, None));
|
||||
assert_eq!(stream.next().await, Some(4));
|
||||
|
||||
assert!(!stream.is_terminated());
|
||||
assert_eq!(stream.size_hint(), (0, None));
|
||||
assert_eq!(stream.next().await, None);
|
||||
|
||||
assert!(stream.is_terminated());
|
||||
|
||||
// it will always return `None` after the first time.
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "time")]
|
||||
async fn interval_stream_is_never_terminated() {
|
||||
use futures_core::stream::FusedStream;
|
||||
use tokio_stream::wrappers::IntervalStream;
|
||||
|
||||
let interval = tokio::time::interval(std::time::Duration::from_millis(1));
|
||||
let stream = IntervalStream::new(interval);
|
||||
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
use futures_core::FusedStream;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
// Helper: a fused base stream built from a vec
|
||||
fn fused_iter<T>(items: Vec<T>) -> impl FusedStream<Item = T> {
|
||||
tokio_stream::iter(items).fuse()
|
||||
}
|
||||
|
||||
// ── map ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn map_not_terminated_before_done() {
|
||||
let stream = fused_iter(vec![1, 2]).map(|x| x * 2);
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn map_terminated_after_inner_done() {
|
||||
let mut stream = fused_iter(vec![1]).map(|x| x * 2);
|
||||
assert_eq!(stream.next().await, Some(2));
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
// ── filter ───────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_not_terminated_before_done() {
|
||||
let stream = fused_iter(vec![1, 2]).filter(|x| *x > 0);
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_terminated_after_inner_done() {
|
||||
let mut stream = fused_iter(vec![1]).filter(|x| *x > 0);
|
||||
assert_eq!(stream.next().await, Some(1));
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
// ── filter_map ───────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_map_not_terminated_before_done() {
|
||||
let stream = fused_iter(vec![1, 2]).filter_map(Some);
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn filter_map_terminated_after_inner_done() {
|
||||
let mut stream = fused_iter(vec![1]).filter_map(|x| Some(x * 10));
|
||||
assert_eq!(stream.next().await, Some(10));
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
// ── skip ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn skip_not_terminated_before_done() {
|
||||
let stream = fused_iter(vec![1, 2, 3]).skip(1);
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn skip_terminated_after_inner_done() {
|
||||
let mut stream = fused_iter(vec![1, 2]).skip(1);
|
||||
assert_eq!(stream.next().await, Some(2));
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
// ── skip_while ───────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn skip_while_not_terminated_before_done() {
|
||||
let stream = fused_iter(vec![1, 2, 3]).skip_while(|x| *x < 2);
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn skip_while_terminated_after_inner_done() {
|
||||
let mut stream = fused_iter(vec![1, 2]).skip_while(|x| *x < 2);
|
||||
assert_eq!(stream.next().await, Some(2));
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
// ── take ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn take_not_terminated_before_limit() {
|
||||
let stream = tokio_stream::iter(vec![1, 2, 3]).take(2);
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn take_terminated_when_remaining_zero() {
|
||||
let mut stream = tokio_stream::iter(vec![1, 2]).take(2);
|
||||
assert_eq!(stream.next().await, Some(1));
|
||||
assert!(!stream.is_terminated());
|
||||
assert_eq!(stream.next().await, Some(2));
|
||||
// remaining hits 0 after getting the second item
|
||||
assert!(stream.is_terminated());
|
||||
assert_eq!(stream.next().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn take_zero_is_immediately_terminated() {
|
||||
let stream = tokio_stream::iter(vec![1, 2]).take(0);
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
// ── take_while ───────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn take_while_not_terminated_before_predicate_fails() {
|
||||
let stream = tokio_stream::iter(vec![1, 2, 3]).take_while(|x| *x < 10);
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn take_while_terminated_after_predicate_fails() {
|
||||
let mut stream = tokio_stream::iter(vec![1, 5, 2]).take_while(|x| *x < 3);
|
||||
assert_eq!(stream.next().await, Some(1));
|
||||
assert!(!stream.is_terminated());
|
||||
// predicate fails on 5 → done flag set
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
// ── then ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn then_not_terminated_before_done() {
|
||||
let stream = fused_iter(vec![1, 2]).then(|x| async move { x * 2 });
|
||||
tokio::pin!(stream);
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn then_terminated_after_inner_done_and_no_pending_future() {
|
||||
let stream = fused_iter(vec![1]).then(|x| async move { x * 2 });
|
||||
tokio::pin!(stream);
|
||||
assert_eq!(stream.next().await, Some(2));
|
||||
assert_eq!(stream.next().await, None);
|
||||
// inner stream done AND no in-flight future
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
// ── chain ────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn chain_not_terminated_while_either_has_items() {
|
||||
let stream = fused_iter(vec![1]).chain(fused_iter(vec![2]));
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn chain_terminated_only_after_both_done() {
|
||||
let mut stream = fused_iter(vec![1]).chain(fused_iter(vec![2]));
|
||||
assert_eq!(stream.next().await, Some(1));
|
||||
assert!(!stream.is_terminated()); // b still has items
|
||||
assert_eq!(stream.next().await, Some(2));
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert!(stream.is_terminated()); // both done now
|
||||
}
|
||||
|
||||
// ── merge ────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn merge_not_terminated_while_either_has_items() {
|
||||
let stream = fused_iter(vec![1]).merge(fused_iter(vec![2]));
|
||||
assert!(!stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn merge_terminated_only_after_both_done() {
|
||||
let mut stream = fused_iter(vec![1]).merge(fused_iter(vec![2]));
|
||||
// drain both
|
||||
let mut collected = vec![];
|
||||
while let Some(x) = stream.next().await {
|
||||
collected.push(x);
|
||||
}
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert!(stream.is_terminated());
|
||||
assert_eq!(collected.len(), 2);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ async fn watch_stream_message_not_twice() {
|
||||
|
||||
let mut counter = 0;
|
||||
let mut stream = WatchStream::new(rx).map(move |payload| {
|
||||
println!("{}", payload);
|
||||
println!("{payload}");
|
||||
if payload == "goodbye" {
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
# 0.4.5 (January 4th, 2026)
|
||||
|
||||
### Added
|
||||
|
||||
- test: add `io::Builder::name` for better panic messages ([#7212])
|
||||
|
||||
### Fixed
|
||||
|
||||
- test: make `Spawn` forward `size_hint` ([#6607])
|
||||
|
||||
### Changed
|
||||
|
||||
- test: remove unused `async-stream` and `bytes` dependencies ([#7214])
|
||||
|
||||
[#6607]: https://github.com/tokio-rs/tokio/pull/6607
|
||||
[#7212]: https://github.com/tokio-rs/tokio/pull/7212
|
||||
[#7214]: https://github.com/tokio-rs/tokio/pull/7214
|
||||
|
||||
# 0.4.4 (March 14, 2024)
|
||||
|
||||
- task: mark `Spawn` as `#[must_use]` ([#6371])
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user