mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
268
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f84b209126 | ||
|
|
eacb98e189 | ||
|
|
fd63094ee0 | ||
|
|
8c600d0fd2 | ||
|
|
64834ec701 | ||
|
|
967f5715a7 | ||
|
|
a97cf12ed9 | ||
|
|
bde3f20b0f | ||
|
|
98df02d7a4 | ||
|
|
3ea11e2a5f | ||
|
|
c79121391d | ||
|
|
ad8c59add6 | ||
|
|
654d38b132 | ||
|
|
857ba80933 | ||
|
|
95b9342da7 | ||
|
|
0af06b7bab | ||
|
|
01a7f1dfab | ||
|
|
eeb55c733b | ||
|
|
1fc450aefb | ||
|
|
324218f9bb | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
86cbf81e15 | ||
|
|
e47565b086 | ||
|
|
1bc50825f3 | ||
|
|
ad2e19ffe1 | ||
|
|
5f04d14d81 | ||
|
|
01ea8f22ea | ||
|
|
9f423053fb | ||
|
|
0e5c5d64f5 | ||
|
|
1b27e17ff8 | ||
|
|
8fc62c06c7 | ||
|
|
4b96af6040 |
@@ -1,3 +1,6 @@
|
||||
R-loom-blocking:
|
||||
- tokio/src/runtime/blocking/*
|
||||
- tokio/src/runtime/blocking/**/*
|
||||
|
||||
R-loom-sync:
|
||||
- tokio/src/sync/*
|
||||
@@ -19,3 +22,7 @@ R-loom-multi-thread:
|
||||
- tokio/src/runtime/scheduler/multi_thread/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-util:
|
||||
- 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@v5
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
+267
-206
@@ -15,10 +15,10 @@ env:
|
||||
RUST_BACKTRACE: 1
|
||||
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2025-01-25
|
||||
rust_stable: 1.94
|
||||
rust_nightly: nightly-2025-10-12
|
||||
# Pin a specific miri version
|
||||
rust_miri_nightly: nightly-2025-06-02
|
||||
rust_miri_nightly: nightly-2025-11-13
|
||||
rust_clippy: '1.88'
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
@@ -28,7 +28,10 @@ env:
|
||||
# - tokio-util/Cargo.toml
|
||||
# - tokio-test/Cargo.toml
|
||||
# - tokio-stream/Cargo.toml
|
||||
rust_min: '1.70'
|
||||
rust_min: '1.71'
|
||||
# This excludes unstable features like io_uring,
|
||||
# which require '--cfg tokio_unstable'.
|
||||
TOKIO_STABLE_FEATURES: "full,test-util"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -38,48 +41,6 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Depends on all actions that are required for a "successful" CI run.
|
||||
tests-pass:
|
||||
name: all systems go
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-tokio-full
|
||||
- test-workspace-all-features
|
||||
- test-integration-tests-per-feature
|
||||
- test-parking_lot
|
||||
- valgrind
|
||||
- test-unstable
|
||||
- miri-lib
|
||||
- miri-test
|
||||
- miri-doc
|
||||
- asan
|
||||
- cross-check
|
||||
- cross-check-tier3
|
||||
- cross-test-with-parking_lot
|
||||
- cross-test-without-parking_lot
|
||||
- no-atomic-u64-test
|
||||
- no-atomic-u64-check
|
||||
- features
|
||||
- minrust
|
||||
- minimal-versions
|
||||
- fmt
|
||||
- clippy
|
||||
- docs
|
||||
- loom-compile
|
||||
- check-readme
|
||||
- test-hyper
|
||||
- test-quinn
|
||||
- x86_64-fortanix-unknown-sgx
|
||||
- check-redox
|
||||
- wasm32-unknown-unknown
|
||||
- wasm32-wasip1
|
||||
- check-external-types
|
||||
- check-fuzzing
|
||||
- check-unstable-mt-counters
|
||||
- check-spelling
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
# Basic actions that must pass before we kick off more expensive tests.
|
||||
basics:
|
||||
name: basic checks
|
||||
@@ -103,7 +64,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -115,7 +76,7 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Run `tokio` with `full` features. This excludes testing utilities which
|
||||
# Run `tokio` with stable features. This excludes testing utilities which
|
||||
# can alter the runtime behavior of Tokio.
|
||||
- name: test tokio full
|
||||
run: |
|
||||
@@ -135,7 +96,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -147,12 +108,21 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Test **all** crates in the workspace with all features.
|
||||
- name: test all --all-features
|
||||
- name: test --features ${{ env.TOKIO_STABLE_FEATURES }}
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --workspace --all-features
|
||||
cargo test --doc --workspace --all-features
|
||||
cargo nextest run --workspace --features $TOKIO_STABLE_FEATURES
|
||||
# Removing workspace patches to run tests without path dependencies
|
||||
# (if not specified differently in the crate)
|
||||
perl -0 -i -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
|
||||
cargo nextest run \
|
||||
--workspace \
|
||||
--exclude tokio \
|
||||
--exclude examples \
|
||||
--features $TOKIO_STABLE_FEATURES
|
||||
# Cargo nextest does not support doctest, so we run them separately
|
||||
# (see https://github.com/nextest-rs/nextest/issues/16)
|
||||
cargo test --doc --workspace --features $TOKIO_STABLE_FEATURES
|
||||
|
||||
test-workspace-all-features-panic-abort:
|
||||
needs: basics
|
||||
@@ -165,7 +135,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -177,10 +147,15 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: test all --all-features panic=abort
|
||||
- name: test --features ${{ env.TOKIO_STABLE_FEATURES }} panic=abort
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run --workspace --exclude tokio-macros --exclude tests-build --all-features --tests
|
||||
RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run \
|
||||
--workspace \
|
||||
--exclude tokio-macros \
|
||||
--exclude tests-build \
|
||||
--features $TOKIO_STABLE_FEATURES \
|
||||
--tests
|
||||
|
||||
test-integration-tests-per-feature:
|
||||
needs: basics
|
||||
@@ -193,7 +168,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -233,7 +208,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -244,15 +219,22 @@ jobs:
|
||||
run: sed -i '/\[features\]/a plsend = ["parking_lot/send_guard"]' tokio/Cargo.toml
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Check tests with all features enabled
|
||||
run: cargo check --workspace --all-features --tests
|
||||
|
||||
- name: Check tests --features ${{ env.TOKIO_STABLE_FEATURES }}
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo check --workspace --tests --features $TOKIO_STABLE_FEATURES
|
||||
# Removing the tokio workspace patch to run tests without path dependencies
|
||||
# (if not specified differently in the crate)
|
||||
perl -0 -i -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
|
||||
cargo check --workspace --exclude tokio --tests --features $TOKIO_STABLE_FEATURES
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -287,11 +269,13 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
- os: ubuntu-latest
|
||||
- os: macos-latest
|
||||
- { os: windows-latest, extra_features: "" }
|
||||
- { os: ubuntu-latest, extra_features: "" }
|
||||
# only Linux supports io_uring
|
||||
- { os: ubuntu-latest, extra_features: io-uring }
|
||||
- { os: macos-latest, extra_features: "" }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -307,8 +291,8 @@ jobs:
|
||||
- name: test tokio full --cfg unstable
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
cargo nextest run --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }}
|
||||
cargo test --doc --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }}
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
@@ -325,7 +309,7 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -341,47 +325,14 @@ jobs:
|
||||
- name: test tokio full --cfg unstable --cfg taskdump
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
cargo nextest run --features $TOKIO_STABLE_FEATURES,taskdump
|
||||
cargo test --doc --features $TOKIO_STABLE_FEATURES
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_taskdump
|
||||
|
||||
test-uring:
|
||||
name: test tokio full --cfg tokio_uring
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test tokio full --cfg tokio_uring
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_uring -Dwarnings
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_uring
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable
|
||||
|
||||
check-unstable-mt-counters:
|
||||
name: check tokio full --internal-mt-counters
|
||||
@@ -392,7 +343,7 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -402,7 +353,8 @@ jobs:
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" and "taskdump" cfg flags.
|
||||
# Since the internal-mt-counters feature is only for debugging purposes,
|
||||
# we can enable all features including unstable.
|
||||
- name: check tokio full --cfg unstable --cfg internal-mt-counters
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
@@ -420,7 +372,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -436,14 +388,14 @@ jobs:
|
||||
cargo miri nextest run --features full --lib --no-fail-fast
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
|
||||
|
||||
miri-test:
|
||||
name: miri-test
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -459,14 +411,14 @@ jobs:
|
||||
cargo miri nextest run --features full --test '*' --no-fail-fast
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
|
||||
|
||||
miri-doc:
|
||||
name: miri-doc
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -475,17 +427,17 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: miri-doc-test
|
||||
run: |
|
||||
cargo miri test --doc --all-features --no-fail-fast
|
||||
cargo miri test --doc --features $TOKIO_STABLE_FEATURES --no-fail-fast
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
|
||||
|
||||
asan:
|
||||
name: asan
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install llvm
|
||||
# Required to resolve symbols in sanitizer output
|
||||
run: sudo apt-get install -y llvm
|
||||
@@ -496,7 +448,7 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: asan
|
||||
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1 --nocapture
|
||||
run: cargo test --workspace --features $TOKIO_STABLE_FEATURES --target x86_64-unknown-linux-gnu --tests -- --test-threads 1 --nocapture
|
||||
env:
|
||||
RUSTFLAGS: -Z sanitizer=address --cfg tokio_no_tuning_tests
|
||||
# Ignore `trybuild` errors as they are irrelevant and flaky on nightly
|
||||
@@ -507,13 +459,16 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Check `tokio` semver
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: ${{ env.rust_stable }}
|
||||
package: tokio
|
||||
release-type: minor
|
||||
feature-group: only-explicit-features
|
||||
# We don't care about the semver of unstable tokio features.
|
||||
features: ${{ env.TOKIO_STABLE_FEATURES }}
|
||||
- name: Check semver for rest of the workspace
|
||||
if: ${{ !startsWith(github.event.pull_request.base.ref, 'tokio-1.') }}
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
@@ -533,7 +488,7 @@ jobs:
|
||||
- powerpc64-unknown-linux-gnu
|
||||
- arm-linux-androideabi
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -541,7 +496,8 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo check --workspace --all-features --target ${{ matrix.target }}
|
||||
# We don't use --all-features since io-uring will be enabled and is not supported on those targets.
|
||||
- run: cargo check --workspace --features $TOKIO_STABLE_FEATURES --target ${{ matrix.target }}
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
@@ -553,10 +509,11 @@ jobs:
|
||||
matrix:
|
||||
target:
|
||||
- name: x86_64-unknown-haiku
|
||||
- name: armv7-sony-vita-newlibeabihf
|
||||
exclude_features: "process,signal,rt-process-signal,full"
|
||||
exclude_features: "taskdump" # taskdump is only available on Linux
|
||||
# - name: armv7-sony-vita-newlibeabihf
|
||||
# exclude_features: "process,signal,rt-process-signal,full,taskdump"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
@@ -579,14 +536,14 @@ jobs:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
rustflags: --cfg tokio_taskdump
|
||||
extra_features: "taskdump"
|
||||
- target: armv5te-unknown-linux-gnueabi
|
||||
os: ubuntu-latest
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-24.04-arm
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-24.04-arm
|
||||
rustflags: --cfg tokio_taskdump
|
||||
extra_features: "io-uring,taskdump"
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
@@ -611,11 +568,15 @@ jobs:
|
||||
- name: Tests run with all features (including parking_lot)
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run -p tokio --all-features --target ${{ matrix.target }}
|
||||
cargo test --doc -p tokio --all-features --target ${{ matrix.target }}
|
||||
# We use `--features "$TOKIO_STABLE_FEATURES"` instead of `--all-features` since
|
||||
# `--all-features` includes `io_uring` and `taskdump`,
|
||||
# which is not available on all targets.
|
||||
cargo nextest run -p tokio --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }} --target ${{ matrix.target }}
|
||||
cargo test --doc -p tokio --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }} --target ${{ matrix.target }}
|
||||
env:
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_tuning_tests
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
cross-test-without-parking_lot:
|
||||
needs: basics
|
||||
@@ -625,14 +586,14 @@ jobs:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
rustflags: --cfg tokio_taskdump
|
||||
extra_features: "taskdump"
|
||||
- target: armv5te-unknown-linux-gnueabi
|
||||
os: ubuntu-latest
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-24.04-arm
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-24.04-arm
|
||||
rustflags: --cfg tokio_taskdump
|
||||
extra_features: "io-uring,taskdump"
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
@@ -661,11 +622,15 @@ jobs:
|
||||
- name: Tests run with all features (without parking_lot)
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run -p tokio --features full,test-util --target ${{ matrix.target }}
|
||||
cargo test --doc -p tokio --features full,test-util --target ${{ matrix.target }}
|
||||
# We use `--features "$TOKIO_STABLE_FEATURES"` instead of `--all-features` since
|
||||
# `--all-features` includes `io_uring` and `taskdump`,
|
||||
# which is not available on all targets.
|
||||
cargo nextest run -p tokio --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }} --target ${{ matrix.target }}
|
||||
cargo test --doc -p tokio --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }} --target ${{ matrix.target }}
|
||||
env:
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_parking_lot --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
# See https://github.com/tokio-rs/tokio/issues/5187
|
||||
no-atomic-u64-test:
|
||||
@@ -673,7 +638,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -693,18 +658,19 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test tokio --all-features
|
||||
run: |
|
||||
cargo nextest run -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
cargo test --doc -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
cargo nextest run -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --features $TOKIO_STABLE_FEATURES,taskdump
|
||||
cargo test --doc -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --features $TOKIO_STABLE_FEATURES,taskdump
|
||||
env:
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_tuning_tests
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_tuning_tests
|
||||
|
||||
no-atomic-u64-check:
|
||||
name: Check tokio --feature-powerset --depth 2 on i686-unknown-linux-gnu without AtomicU64
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -720,26 +686,29 @@ jobs:
|
||||
# https://github.com/tokio-rs/tokio/pull/5356
|
||||
# https://github.com/tokio-rs/tokio/issues/5373
|
||||
- name: Check
|
||||
run: cargo hack check -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --feature-powerset --depth 2 --keep-going
|
||||
# We use `--skip io-uring` since io-uring crate doesn't provide a binding for the i686 target.
|
||||
run: cargo hack check -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --feature-powerset --skip io-uring --depth 2 --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
features:
|
||||
name: features ${{ matrix.name }}
|
||||
name: features exclude ${{ matrix.name }}
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { name: "", rustflags: "" }
|
||||
# Try with unstable feature flags
|
||||
- { name: "--unstable", rustflags: "--cfg tokio_unstable -Dwarnings" }
|
||||
# Try with unstable and taskdump feature flags
|
||||
- { name: "--unstable --taskdump", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump" }
|
||||
- { name: "--tokio_uring", rustflags: "-Dwarnings --cfg tokio_uring" }
|
||||
- { name: "--unstable --taskdump --tokio_uring", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump --cfg tokio_uring" }
|
||||
- name: ""
|
||||
rustflags: ""
|
||||
exclude_features: "io-uring,taskdump"
|
||||
- name: "--unstable"
|
||||
rustflags: "--cfg tokio_unstable -Dwarnings"
|
||||
exclude_features: "io-uring,taskdump"
|
||||
- name: "--unstable io-uring,taskdump"
|
||||
rustflags: "--cfg tokio_unstable -Dwarnings"
|
||||
exclude_features: ""
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -750,7 +719,7 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: check --feature-powerset ${{ matrix.name }}
|
||||
run: cargo hack check --all --feature-powerset --depth 2 --keep-going
|
||||
run: cargo hack check --all --feature-powerset --exclude-features "${{ matrix.exclude_features }}" --depth 2 --keep-going
|
||||
env:
|
||||
RUSTFLAGS: ${{ matrix.rustflags }}
|
||||
|
||||
@@ -758,30 +727,36 @@ jobs:
|
||||
name: minrust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_min }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_min }}
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "check --workspace --all-features"
|
||||
- name: "cargo check"
|
||||
run: |
|
||||
if [[ "${{ github.event.pull_request.base.ref }}" =~ ^tokio-1\..* ]]; then
|
||||
# Only check `tokio` crate as the PR is backporting to an earlier tokio release.
|
||||
cargo check -p tokio --all-features
|
||||
|
||||
cargo check -p tokio --features $TOKIO_STABLE_FEATURES
|
||||
else
|
||||
# Check all crates in the workspace
|
||||
cargo check --workspace --all-features
|
||||
|
||||
cargo check -p tokio --features $TOKIO_STABLE_FEATURES
|
||||
# Other crates doesn't have unstable features, so we can use --all-features.
|
||||
cargo hack check -p tokio-macros -p tokio-stream -p tokio-util -p tokio-test --all-features
|
||||
fi
|
||||
env:
|
||||
RUSTFLAGS: "" # remove -Dwarnings
|
||||
|
||||
minimal-versions:
|
||||
name: minimal-versions
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -790,17 +765,18 @@ jobs:
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "check --all-features -Z minimal-versions"
|
||||
- name: "check -Z minimal-versions"
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
cargo hack --remove-dev-deps --workspace
|
||||
# Update Cargo.lock to minimal version dependencies.
|
||||
cargo update -Z minimal-versions
|
||||
cargo hack check --all-features --ignore-private
|
||||
cargo hack check -p tokio --features $TOKIO_STABLE_FEATURES --ignore-private
|
||||
cargo hack check -p tokio-macros -p tokio-stream -p tokio-util -p tokio-test --all-features --ignore-private
|
||||
- name: "check --all-features --unstable -Z minimal-versions"
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump --cfg tokio_uring -Dwarnings
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
@@ -813,7 +789,7 @@ jobs:
|
||||
name: fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -833,7 +809,7 @@ jobs:
|
||||
name: clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_clippy }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -841,8 +817,25 @@ jobs:
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run clippy
|
||||
- name: "clippy --all"
|
||||
run: cargo clippy --all --tests --all-features --no-deps
|
||||
- name: "clippy --workspace --features ${{ env.TOKIO_STABLE_FEATURES }}"
|
||||
run: |
|
||||
cargo clippy --workspace --tests --no-deps --features $TOKIO_STABLE_FEATURES
|
||||
# Removing the tokio workspace patch to check without path dependencies
|
||||
# (if not specified differently in the crate)
|
||||
perl -0 -i.bak -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
|
||||
cargo clippy --workspace --exclude tokio --tests --no-deps --features $TOKIO_STABLE_FEATURES
|
||||
- name: "clippy --workspace --all-features --unstable"
|
||||
run: |
|
||||
# Forcing the cargo lock regeneration to apply the tokio patch
|
||||
rm Cargo.lock
|
||||
mv Cargo.toml Cargo.toml.nopatch
|
||||
mv Cargo.toml.bak Cargo.toml
|
||||
cargo clippy --workspace --tests --no-deps --all-features
|
||||
# check without path dependencies
|
||||
mv Cargo.toml.nopatch Cargo.toml
|
||||
cargo clippy --workspace --exclude tokio --tests --no-deps --all-features
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
docs:
|
||||
name: docs
|
||||
@@ -851,30 +844,29 @@ jobs:
|
||||
matrix:
|
||||
run:
|
||||
- os: windows-latest
|
||||
extra_features: "tracing"
|
||||
- os: ubuntu-latest
|
||||
RUSTFLAGS: --cfg tokio_taskdump --cfg tokio_uring
|
||||
RUSTDOCFLAGS: --cfg tokio_taskdump --cfg tokio_uring
|
||||
extra_features: "tracing,io-uring,taskdump"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "doc --lib --all-features"
|
||||
run: |
|
||||
cargo doc --lib --no-deps --all-features --document-private-items
|
||||
run: cargo doc --lib --no-deps --document-private-items --features $TOKIO_STABLE_FEATURES,${{ matrix.run.extra_features }}
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable ${{ matrix.run.RUSTFLAGS }}
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings ${{ matrix.run.RUSTDOCFLAGS }}
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
|
||||
loom-compile:
|
||||
name: build loom tests
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -890,7 +882,7 @@ jobs:
|
||||
name: Check README
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Verify that both READMEs are identical
|
||||
run: diff README.md tokio/README.md
|
||||
|
||||
@@ -909,7 +901,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -961,7 +953,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1005,7 +997,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1022,32 +1014,41 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
target: x86_64-unknown-redox
|
||||
- name: check tokio on redox
|
||||
run: cargo check --target x86_64-unknown-redox --all-features
|
||||
run: cargo check --target x86_64-unknown-redox --features $TOKIO_STABLE_FEATURES
|
||||
working-directory: tokio
|
||||
|
||||
wasm32-unknown-unknown:
|
||||
name: test tokio for wasm32-unknown-unknown
|
||||
name: test tokio for wasm32-unknown-unknown (${{ matrix.name }})
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: macros sync
|
||||
features: "macros sync"
|
||||
- name: macros sync rt
|
||||
features: "macros sync rt"
|
||||
- name: macros sync time rt
|
||||
features: "macros sync time rt"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust 1.88.0
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install wasm-pack
|
||||
uses: taiki-e/install-action@wasm-pack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test tokio
|
||||
run: wasm-pack test --node -- --features "macros sync"
|
||||
- name: test tokio (${{ matrix.name }})
|
||||
run: wasm-pack test --node -- --features "${{ matrix.features }}"
|
||||
working-directory: tokio
|
||||
|
||||
wasm32-wasip1:
|
||||
@@ -1061,10 +1062,10 @@ jobs:
|
||||
- wasm32-wasip1-threads
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust 1.88.0
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
# Install dependencies
|
||||
@@ -1075,11 +1076,14 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: WASI test tokio full
|
||||
run: cargo test -p tokio --target ${{ matrix.target }} --features full
|
||||
run: cargo test -p tokio --target ${{ matrix.target }} --features "sync,macros,io-util,rt,time"
|
||||
env:
|
||||
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
|
||||
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -W shared-memory=y -S threads=y --"
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable
|
||||
|
||||
- name: WASI test tokio-util full
|
||||
run: cargo test -p tokio-util --target ${{ matrix.target }} --features full
|
||||
@@ -1087,9 +1091,10 @@ jobs:
|
||||
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
|
||||
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -W shared-memory=y -S threads=y --"
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864
|
||||
RUSTDOCFLAGS: -C link-args=--max-memory=67108864
|
||||
|
||||
- name: WASI test tokio-stream
|
||||
run: cargo test -p tokio-stream --target ${{ matrix.target }} --features time,net,io-util,sync
|
||||
run: cargo test --manifest-path=tokio-stream/Cargo.toml --target ${{ matrix.target }} --features time,net,io-util,sync
|
||||
env:
|
||||
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
|
||||
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -W shared-memory=y -S threads=y --"
|
||||
@@ -1112,32 +1117,63 @@ jobs:
|
||||
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -W shared-memory=y -S threads=y --"
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864
|
||||
|
||||
wasm32-wasip2:
|
||||
name: test tokio for wasm32-wasip2
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
targets: wasm32-wasip2
|
||||
|
||||
- name: Install cargo-nextest, wasmtime
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest,wasmtime-cli
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: test tokio --target wasm32-wasip2
|
||||
run: cargo nextest run --target wasm32-wasip2 --features net,macros,rt,io-util
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable
|
||||
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -Sinherit-network
|
||||
|
||||
check-external-types:
|
||||
name: check-external-types (${{ matrix.os }})
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
rust:
|
||||
# `check-external-types` requires a specific Rust nightly version. See
|
||||
# the README for details: https://github.com/awslabs/cargo-check-external-types
|
||||
- nightly-2024-06-30
|
||||
include:
|
||||
- os: windows-latest
|
||||
# Windows neither supports io-uring nor taskdump.
|
||||
extra_features: "tracing"
|
||||
- os: ubuntu-latest
|
||||
# includes all unstable features.
|
||||
extra_features: "tracing,io-uring,taskdump"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
# `check-external-types` requires a specific Rust nightly version. See
|
||||
# the README for details: https://github.com/awslabs/cargo-check-external-types
|
||||
toolchain: nightly-2025-08-06
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v1
|
||||
with:
|
||||
tool: cargo-check-external-types@0.1.13
|
||||
tool: cargo-check-external-types@0.3.0
|
||||
- name: check-external-types
|
||||
run: cargo check-external-types --all-features
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable
|
||||
run: cargo check-external-types --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }}
|
||||
working-directory: tokio
|
||||
|
||||
check-fuzzing:
|
||||
@@ -1165,7 +1201,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1232,6 +1268,31 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get-latest-kernel-version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
kernel_version: ${{ steps.fetch.outputs.kernel_version }}
|
||||
steps:
|
||||
- name: Fetch latest stable kernel
|
||||
id: fetch
|
||||
run: |
|
||||
KERNEL_VERSION=$(curl -s https://www.kernel.org/releases.json | jq -r '.latest_stable.version')
|
||||
echo "kernel_version=$KERNEL_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
test-io-uring-on-specific-kernel-versions:
|
||||
name: Test io_uring on Linux ${{ matrix.kernel_version }}
|
||||
needs: [get-latest-kernel-version, basics]
|
||||
strategy:
|
||||
matrix:
|
||||
kernel_version:
|
||||
# A latest stable kernel version
|
||||
- ${{ needs.get-latest-kernel-version.outputs.kernel_version }}
|
||||
# A kernel version that doesn't support io_uring
|
||||
- '4.19.325'
|
||||
uses: ./.github/workflows/uring-kernel-version-test.yml
|
||||
with:
|
||||
kernel_version: ${{ matrix.kernel_version }}
|
||||
|
||||
freebsd-x86_64:
|
||||
name: FreeBSD x86_64
|
||||
needs: basics
|
||||
@@ -1242,19 +1303,19 @@ jobs:
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: '14.4'
|
||||
envs: "RUSTFLAGS"
|
||||
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
|
||||
prepare: |
|
||||
pkg install -y curl
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
sh rustup.sh -y --profile minimal --default-toolchain ${{ env.rust_stable }}
|
||||
run: |
|
||||
. $HOME/.cargo/env
|
||||
cargo test --workspace --features full,test-util
|
||||
cargo test --workspace --features $TOKIO_STABLE_FEATURES
|
||||
# Enable all unstable features except `io_uring` and `taskdump`,
|
||||
# which are Linux-only features.
|
||||
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable" \
|
||||
cargo test \
|
||||
--features full,test-util,tracing
|
||||
--features $TOKIO_STABLE_FEATURES,tracing
|
||||
|
||||
freebsd-docs:
|
||||
name: FreeBSD docs
|
||||
@@ -1269,17 +1330,17 @@ jobs:
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
with:
|
||||
release: '14.4'
|
||||
envs: "RUST_NIGHTLY RUSTDOCFLAGS RUSTFLAGS"
|
||||
envs: "TOKIO_STABLE_FEATURES RUSTDOCFLAGS RUSTFLAGS"
|
||||
prepare: |
|
||||
pkg install -y curl
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
sh rustup.sh -y --profile minimal --default-toolchain ${{ env.rust_nightly }}
|
||||
run: |
|
||||
. $HOME/.cargo/env
|
||||
# We use `--features full,test-util,io-uring,tracing` instead of
|
||||
# We use `--features $TOKIO_STABLE_FEATURES,io-uring,tracing` instead of
|
||||
# `--all-features` to exclude `taskdump` and `io_uring`, which are Linux-only
|
||||
# features.
|
||||
cargo doc --lib --no-deps --features full,test-util,tracing \
|
||||
cargo doc --lib --no-deps --features $TOKIO_STABLE_FEATURES,tracing \
|
||||
--document-private-items
|
||||
|
||||
freebsd-i686:
|
||||
@@ -1292,7 +1353,7 @@ jobs:
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: '14.4'
|
||||
envs: "RUSTFLAGS"
|
||||
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
|
||||
prepare: |
|
||||
pkg install -y curl
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
@@ -1300,5 +1361,5 @@ jobs:
|
||||
run: |
|
||||
. $HOME/.cargo/env
|
||||
rustup target add i686-unknown-freebsd
|
||||
cargo test --workspace --features full,test-util \
|
||||
cargo test --workspace --features $TOKIO_STABLE_FEATURES \
|
||||
--target i686-unknown-freebsd
|
||||
|
||||
@@ -23,13 +23,29 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
loom-blocking:
|
||||
name: loom tokio::runtime::spawn_blocking
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-blocking') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture loom_blocking
|
||||
working-directory: tokio
|
||||
|
||||
loom-sync:
|
||||
name: loom tokio::sync
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-sync') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -45,14 +61,14 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture runtime::time::tests
|
||||
run: cargo test --lib --release --features full -- --nocapture runtime::time
|
||||
working-directory: tokio
|
||||
|
||||
loom-current-thread:
|
||||
@@ -61,7 +77,7 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -84,7 +100,7 @@ jobs:
|
||||
- scope: loom_multi_thread::group_c
|
||||
- scope: loom_multi_thread::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -95,3 +111,19 @@ jobs:
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
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-util') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture
|
||||
working-directory: tokio-util
|
||||
|
||||
@@ -19,5 +19,5 @@ jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
stress-test:
|
||||
- simple_echo_tcp
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
name: Uring Kernel Version Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
kernel_version:
|
||||
description: 'Version of the Linux kernel to build'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
KERNEL_VERSION: ${{ inputs.kernel_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential bison flex libssl-dev libelf-dev \
|
||||
qemu-system-x86 busybox-static cpio xz-utils wget
|
||||
|
||||
- name: Cache Linux source
|
||||
id: cache-kernel
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: linux-${{ env.KERNEL_VERSION }}
|
||||
key: kernel-${{ env.KERNEL_VERSION }}
|
||||
|
||||
- name: Download & build Linux kernel
|
||||
if: steps.cache-kernel.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
MAJOR=${KERNEL_VERSION%%.*}
|
||||
wget https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KERNEL_VERSION}.tar.xz
|
||||
tar xf linux-${KERNEL_VERSION}.tar.xz
|
||||
cd linux-${KERNEL_VERSION}
|
||||
make defconfig
|
||||
make -j$(nproc)
|
||||
|
||||
- name: Generate test binaries with io_uring enabled
|
||||
run: |
|
||||
# Build both integration (tokio/tests/) and unit (e.g., tokio/src/fs/file/tests.rs) tests with io_uring enabled
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
RUSTFLAGS="--cfg tokio_unstable" \
|
||||
cargo test -p tokio --features full,io-uring \
|
||||
--target x86_64-unknown-linux-musl --test 'fs*' --lib --no-run
|
||||
|
||||
- name: Prepare initramfs + tests binaries
|
||||
run: |
|
||||
set -e
|
||||
rm -rf initramfs
|
||||
mkdir -p initramfs/{bin,bin/tests,sbin,proc,sys,tmp}
|
||||
|
||||
# Copy test binaries into initramfs
|
||||
for bin in target/x86_64-unknown-linux-musl/debug/deps/{fs_*,tokio-*}; do
|
||||
if [ -f "$bin" ] && [ -x "$bin" ]; then
|
||||
cp "$bin" initramfs/bin/tests
|
||||
fi
|
||||
done
|
||||
|
||||
# Add BusyBox & symlinks
|
||||
cp /usr/bin/busybox initramfs/bin/
|
||||
for cmd in sh mount uname true sleep; do ln -sf busybox initramfs/bin/$cmd; done
|
||||
ln -sf ../bin/busybox initramfs/sbin/poweroff
|
||||
|
||||
# Generate init script
|
||||
cat > initramfs/init << 'EOF'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
mkdir -p /dev
|
||||
# create device nodes, as some tests require them
|
||||
mknod /dev/null c 1 3
|
||||
mknod /dev/zero c 1 5
|
||||
mknod /dev/tty c 5 0
|
||||
mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
mkdir -p /tmp && mount -t tmpfs -o mode=1777 tmpfs /tmp
|
||||
for f in /bin/tests/*; do RUST_BACKTRACE=1 "$f" ; done
|
||||
EOF
|
||||
chmod +x initramfs/init
|
||||
|
||||
# Pack into a CPIO archive
|
||||
(cd initramfs && find . -print0 \
|
||||
| cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz)
|
||||
|
||||
- name: Run tests in QEMU
|
||||
run: |
|
||||
qemu-system-x86_64 \
|
||||
-kernel linux-${{ env.KERNEL_VERSION }}/arch/x86/boot/bzImage \
|
||||
-initrd initramfs.cpio.gz \
|
||||
-append "console=ttyS0 rootfstype=ramfs panic=1" \
|
||||
-nographic -no-reboot -m 1024 -action panic=exit-failure 2>&1 | tee qemu-output.log
|
||||
|
||||
# qemu always exits with 0, so we check if the tests passed by using grep.
|
||||
if grep -q "test result: FAILED" qemu-output.log; then
|
||||
echo "tests failed (QEMU exited abnormally)"
|
||||
exit 1
|
||||
else
|
||||
echo "all tests passed"
|
||||
fi
|
||||
@@ -3,3 +3,4 @@ Cargo.lock
|
||||
|
||||
.cargo/config.toml
|
||||
.cargo/config
|
||||
|
||||
|
||||
+18
-698
@@ -1,7 +1,6 @@
|
||||
# Contributing to Tokio
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
you!
|
||||
Thanks for your help improving Tokio! We are so happy to have you!
|
||||
|
||||
There are opportunities to contribute to Tokio at any level. It doesn't matter if
|
||||
you are just getting started with Rust or are the most weathered expert, we can
|
||||
@@ -9,15 +8,11 @@ use your help.
|
||||
|
||||
**No contribution is too small and all contributions are valued.**
|
||||
|
||||
This guide will help you get started. **Do not let this guide intimidate you**.
|
||||
It should be considered a map to help you navigate the process.
|
||||
See the [contributing guidelines] to get started.
|
||||
|
||||
The [dev channel][dev] is available for any concerns not covered in this guide, please join
|
||||
us!
|
||||
[contributing guidelines]: docs/contributing/README.md
|
||||
|
||||
[dev]: https://discord.gg/tokio
|
||||
|
||||
## Conduct
|
||||
## Code of Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
|
||||
the _minimum_ behavior expected from all contributors. Instances of violations of the
|
||||
@@ -26,709 +21,34 @@ Code of Conduct can be reported by contacting the project team at
|
||||
|
||||
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
## Contributing in Issues
|
||||
## Need Help?
|
||||
|
||||
For any issue, there are fundamentally three ways an individual can contribute:
|
||||
Reach out to us on the [Discord server] for any concern not covered in this guide.
|
||||
|
||||
1. By opening the issue for discussion: For instance, if you believe that you
|
||||
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
|
||||
issue tracker][issue] is the way to report it.
|
||||
|
||||
2. By helping to triage the issue: This can be done by providing
|
||||
supporting details (a test case that demonstrates a bug), providing
|
||||
suggestions on how to address the issue, or ensuring that the issue is tagged
|
||||
correctly.
|
||||
|
||||
3. By helping to resolve the issue: Typically this is done either in the form of
|
||||
demonstrating that the issue reported is not a problem after all, or more
|
||||
often, by opening a Pull Request that changes some bit of something in
|
||||
Tokio in a concrete and reviewable manner.
|
||||
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues
|
||||
|
||||
**Anybody can participate in any stage of contribution**. We urge you to
|
||||
participate in the discussion around bugs and participate in reviewing PRs.
|
||||
|
||||
### Asking for General Help
|
||||
|
||||
If you have reviewed existing documentation and still have questions or are
|
||||
having problems, you can [open a discussion] asking for help.
|
||||
|
||||
In exchange for receiving help, we ask that you contribute back a documentation
|
||||
PR that helps others avoid the problems that you encountered.
|
||||
|
||||
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, you will be presented
|
||||
with a basic template that should be filled in. If you believe that you have
|
||||
uncovered a bug, please fill out this form, following the template to the best
|
||||
of your ability. Do not worry if you cannot answer every detail, just fill in
|
||||
what you can.
|
||||
|
||||
The two most important pieces of information we need in order to properly
|
||||
evaluate the report is a description of the behavior you are seeing and a simple
|
||||
test case we can use to recreate the problem on our own. If we cannot recreate
|
||||
the issue, it becomes impossible for us to fix.
|
||||
|
||||
In order to rule out the possibility of bugs introduced by userland code, test
|
||||
cases should be limited, as much as possible, to using only Tokio APIs.
|
||||
|
||||
See [How to create a Minimal, Complete, and Verifiable example][mcve].
|
||||
|
||||
[mcve]: https://stackoverflow.com/help/mcve
|
||||
|
||||
### Triaging a Bug Report
|
||||
|
||||
Once an issue has been opened, it is not uncommon for there to be discussion
|
||||
around it. Some contributors may have differing opinions about the issue,
|
||||
including whether the behavior being seen is a bug or a feature. This discussion
|
||||
is part of the process and should be kept focused, helpful, and professional.
|
||||
|
||||
Short, clipped responses—that provide neither additional context nor supporting
|
||||
detail—are not helpful or professional. To many, such responses are simply
|
||||
annoying and unfriendly.
|
||||
|
||||
Contributors are encouraged to help one another make forward progress as much as
|
||||
possible, empowering one another to solve issues collaboratively. If you choose
|
||||
to comment on an issue that you feel either is not a problem that needs to be
|
||||
fixed, or if you encounter information in an issue that you feel is incorrect,
|
||||
explain why you feel that way with additional supporting context, and be willing
|
||||
to be convinced that you may be wrong. By doing so, we can often reach the
|
||||
correct outcome much faster.
|
||||
|
||||
### Resolving a Bug Report
|
||||
|
||||
In the majority of cases, issues are resolved by opening a Pull Request. The
|
||||
process for opening and reviewing a Pull Request is similar to that of opening
|
||||
and triaging issues, but carries with it a necessary review and approval
|
||||
workflow that ensures that the proposed changes meet the minimal quality and
|
||||
functional guidelines of the Tokio project.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull Requests are the way concrete changes are made to the code, documentation,
|
||||
and dependencies in the Tokio repository.
|
||||
|
||||
Even tiny pull requests (e.g., one character pull request fixing a typo in API
|
||||
documentation) are greatly appreciated. Before making a large change, it is
|
||||
usually a good idea to first open an issue describing the change to solicit
|
||||
feedback and guidance. This will increase the likelihood of the PR getting
|
||||
merged.
|
||||
|
||||
### Cargo Commands
|
||||
|
||||
Due to the extensive use of features in Tokio, you will often need to add extra
|
||||
arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
Ideally, you should use the same version of clippy as the one used in CI
|
||||
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
|
||||
might have new lints:
|
||||
|
||||
[ci.yml]: .github/workflows/ci.yml
|
||||
|
||||
<!--
|
||||
When updating this, also update:
|
||||
- .github/workflows/ci.yml
|
||||
- README.md
|
||||
- tokio/README.md
|
||||
- tokio/Cargo.toml
|
||||
- tokio-util/Cargo.toml
|
||||
- tokio-test/Cargo.toml
|
||||
- tokio-stream/Cargo.toml
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.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 -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
|
||||
|
||||
Generated
-2100
File diff suppressed because it is too large
Load Diff
+7
-2
@@ -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,8 +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(tokio_uring)',
|
||||
'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
|
||||
@@ -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.47.5", features = ["full"] }
|
||||
tokio = { version = "1.51.4", 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.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.43", features = [...] }
|
||||
tokio = { version = "~1.47", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -240,6 +245,7 @@ tokio = { version = "~1.43", features = [...] }
|
||||
* `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
|
||||
|
||||
@@ -250,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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,46 @@
|
||||
# Contributing
|
||||
|
||||
This guide will help you get started. **Do not let this guide intimidate you**.
|
||||
It should be considered a map to help you navigate the process.
|
||||
|
||||
## Quick start
|
||||
|
||||
If you are unsure where to begin, use the following guides:
|
||||
|
||||
- Want to report or triage a bug? Start with [Contributing in Issues](contributing-in-issues.md).
|
||||
- Looking for something to work on? Filter issues by [`E-help-wanted`](https://github.com/tokio-rs/tokio/labels/E-help-wanted).
|
||||
- Planning to submit a PR? Read [Pull Requests](pull-requests.md) for the full workflow and required checks.
|
||||
- Want to understand what the labels on issues mean? See [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md).
|
||||
- Interested in code review? See [Reviewing Pull Requests](reviewing-pull-requests.md).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Contributing in Issues](contributing-in-issues.md)
|
||||
- [Asking for General Help](contributing-in-issues.md#asking-for-general-help)
|
||||
- [Submitting a Bug Report](contributing-in-issues.md#submitting-a-bug-report)
|
||||
- [Triaging a Bug Report](contributing-in-issues.md#triaging-a-bug-report)
|
||||
- [Resolving a Bug Report](contributing-in-issues.md#resolving-a-bug-report)
|
||||
- [Pull Requests](pull-requests.md)
|
||||
- [Cargo Commands](pull-requests.md#cargo-commands)
|
||||
- [Performing spellcheck on tokio codebase](pull-requests.md#performing-spellcheck-on-tokio-codebase)
|
||||
- [Tests](pull-requests.md#tests)
|
||||
- [Integration tests](pull-requests.md#integration-tests)
|
||||
- [Fuzz tests](pull-requests.md#fuzz-tests)
|
||||
- [Documentation tests](pull-requests.md#documentation-tests)
|
||||
- [Benchmarks](pull-requests.md#benchmarks)
|
||||
- [Commits](pull-requests.md#commits)
|
||||
- [Commit message guidelines](pull-requests.md#commit-message-guidelines)
|
||||
- [Opening the Pull Request](pull-requests.md#opening-the-pull-request)
|
||||
- [Discuss and update](pull-requests.md#discuss-and-update)
|
||||
- [Commit Squashing](pull-requests.md#commit-squashing)
|
||||
- [Reviewing Pull Requests](reviewing-pull-requests.md)
|
||||
- [Review a bit at a time](reviewing-pull-requests.md#review-a-bit-at-a-time)
|
||||
- [Be aware of the person behind the code](reviewing-pull-requests.md#be-aware-of-the-person-behind-the-code)
|
||||
- [Abandoned or Stalled Pull Requests](reviewing-pull-requests.md#abandoned-or-stalled-pull-requests)
|
||||
- [How to specify crates dependencies versions](how-to-specify-crates-dependencies-versions.md)
|
||||
- [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md)
|
||||
- [Area](keeping-track-of-issues-and-prs.md#area)
|
||||
- [Category](keeping-track-of-issues-and-prs.md#category)
|
||||
- [Calls for participation](keeping-track-of-issues-and-prs.md#calls-for-participation)
|
||||
- [Module](keeping-track-of-issues-and-prs.md#module)
|
||||
- [Topic](keeping-track-of-issues-and-prs.md#topic)
|
||||
@@ -0,0 +1,79 @@
|
||||
## Contributing in Issues
|
||||
|
||||
For any issue, there are fundamentally three ways an individual can contribute:
|
||||
|
||||
1. By opening the issue for discussion: For instance, if you believe that you
|
||||
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
|
||||
issue tracker][issue] is the way to report it.
|
||||
|
||||
2. By helping to triage the issue: This can be done by providing
|
||||
supporting details (a test case that demonstrates a bug), providing
|
||||
suggestions on how to address the issue, or ensuring that the issue is tagged
|
||||
correctly.
|
||||
|
||||
3. By helping to resolve the issue: Typically this is done either in the form of
|
||||
demonstrating that the issue reported is not a problem after all, or more
|
||||
often, by opening a Pull Request that changes some bit of something in
|
||||
Tokio in a concrete and reviewable manner.
|
||||
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues
|
||||
|
||||
**Anybody can participate in any stage of contribution**. We urge you to
|
||||
participate in the discussion around bugs and participate in reviewing PRs.
|
||||
|
||||
### Asking for General Help
|
||||
|
||||
If you have reviewed existing documentation and still have questions or are
|
||||
having problems, you can [open a discussion] asking for help.
|
||||
|
||||
In exchange for receiving help, we ask that you contribute back a documentation
|
||||
PR that helps others avoid the problems that you encountered.
|
||||
|
||||
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new/choose
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, you will be presented
|
||||
with a basic template that should be filled in. If you believe that you have
|
||||
uncovered a bug, please fill out this form, following the template to the best
|
||||
of your ability. Do not worry if you cannot answer every detail, just fill in
|
||||
what you can.
|
||||
|
||||
The two most important pieces of information we need in order to properly
|
||||
evaluate the report is a description of the behavior you are seeing and a simple
|
||||
test case we can use to recreate the problem on our own. If we cannot recreate
|
||||
the issue, it becomes impossible for us to fix.
|
||||
|
||||
In order to rule out the possibility of bugs introduced by userland code, test
|
||||
cases should be limited, as much as possible, to using only Tokio APIs.
|
||||
|
||||
See [How to create a Minimal, Complete, and Verifiable example][mcve].
|
||||
|
||||
[mcve]: https://stackoverflow.com/help/mcve
|
||||
|
||||
### Triaging a Bug Report
|
||||
|
||||
Once an issue has been opened, it is not uncommon for there to be discussion
|
||||
around it. Some contributors may have differing opinions about the issue,
|
||||
including whether the behavior being seen is a bug or a feature. This discussion
|
||||
is part of the process and should be kept focused, helpful, and professional.
|
||||
|
||||
Short, clipped responses—that provide neither additional context nor supporting
|
||||
detail—are not helpful or professional. To many, such responses are simply
|
||||
annoying and unfriendly.
|
||||
|
||||
Contributors are encouraged to help one another make forward progress as much as
|
||||
possible, empowering one another to solve issues collaboratively. If you choose
|
||||
to comment on an issue that you feel either is not a problem that needs to be
|
||||
fixed, or if you encounter information in an issue that you feel is incorrect,
|
||||
explain why you feel that way with additional supporting context, and be willing
|
||||
to be convinced that you may be wrong. By doing so, we can often reach the
|
||||
correct outcome much faster.
|
||||
|
||||
### Resolving a Bug Report
|
||||
|
||||
In the majority of cases, issues are resolved by opening a Pull Request. The
|
||||
process for opening and reviewing a Pull Request is similar to that of opening
|
||||
and triaging issues, but carries with it a necessary review and approval
|
||||
workflow that ensures that the proposed changes meet the minimal quality and
|
||||
functional guidelines of the Tokio project.
|
||||
@@ -0,0 +1,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,89 @@
|
||||
## Keeping track of issues and PRs
|
||||
|
||||
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
|
||||
section explains the meaning of various labels, as well as our [GitHub
|
||||
project][project]. The section is primarily targeted at maintainers. Most
|
||||
contributors aren't able to set these labels.
|
||||
|
||||
### Area
|
||||
|
||||
The area label describes the crates relevant to this issue or PR.
|
||||
|
||||
- **A-ci** This issue concerns our GitHub Actions setup.
|
||||
- **A-tokio** This issue concerns the main Tokio crate.
|
||||
- **A-readme** This issue is related to documentation such as README.md.
|
||||
- **A-benches** This issue concerns the benchmarks.
|
||||
- **A-examples** This issue concerns the examples.
|
||||
- **A-tokio-test** The issue concerns the `tokio-test` crate.
|
||||
- **A-tokio-util** This issue concerns the `tokio-util` crate.
|
||||
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
|
||||
be used for the procedural macros, and not `join!` or `select!`.
|
||||
- **A-tokio-stream** This issue concerns the `tokio-stream` crate.
|
||||
|
||||
### Category
|
||||
|
||||
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
|
||||
- **C-enhancement** This is a PR that adds a new features.
|
||||
- **C-maintenance** This is an issue or PR about stuff such as documentation,
|
||||
GitHub Actions or code quality.
|
||||
- **C-feature-request** This is a feature request. Implementations of feature
|
||||
requests use `C-enhancement` instead.
|
||||
- **C-feature-accepted** If you submit a PR for this feature request, we won't
|
||||
close it with the reason "we don't want this". Issues with this label should
|
||||
also have the `C-feature-request` label.
|
||||
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
|
||||
world"
|
||||
- **C-proposal** A proposal of some kind, and a request for comments.
|
||||
- **C-question** A user question. Large overlap with GitHub discussions.
|
||||
- **C-request** A non-feature request, e.g. "please add deprecation notices to
|
||||
`-alpha.*` versions of crates"
|
||||
|
||||
### Calls for participation
|
||||
|
||||
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
|
||||
or `C-feature-accepted`.
|
||||
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
|
||||
can do after reading the tutorial on our website.
|
||||
- **E-medium** This is not `E-easy` or `E-hard`.
|
||||
- **E-hard** This either involves very tricky code, is something we don't know
|
||||
how to solve, or is challenging for some other reason.
|
||||
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
|
||||
example.
|
||||
|
||||
The "E-" prefix is the same as used in the Rust compiler repository. Some
|
||||
issues are missing a difficulty rating, but feel free to ask on our Discord
|
||||
server if you want to know how challenging an issue likely is.
|
||||
|
||||
### Module
|
||||
|
||||
The module label provides a more fine grained categorization than **Area**.
|
||||
|
||||
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
|
||||
- **M-codec** The `tokio_util::codec` module.
|
||||
- **M-compat** The `tokio_util::compat` module.
|
||||
- **M-coop** Things relevant to coop.
|
||||
- **M-fs** The `tokio::fs` module.
|
||||
- **M-io** The `tokio::io` module.
|
||||
- **M-macros** Issues about any kind of macro.
|
||||
- **M-metrics** Things relevant to `tokio::runtime::metrics`.
|
||||
- **M-net** The `tokio::net` module.
|
||||
- **M-process** The `tokio::process` module.
|
||||
- **M-runtime** The `tokio::runtime` module.
|
||||
- **M-signal** The `tokio::signal` module.
|
||||
- **M-sync** The `tokio::sync` module.
|
||||
- **M-task** The `tokio::task` module.
|
||||
- **M-time** The `tokio::time` module.
|
||||
- **M-tracing** Tracing support in Tokio.
|
||||
- **M-taskdump** Things relevant to taskdump.
|
||||
|
||||
### Topic
|
||||
|
||||
Some extra information.
|
||||
|
||||
- **T-docs** This is about documentation.
|
||||
- **T-performance** This is about performance.
|
||||
- **T-v0.1.x** This is about old Tokio.
|
||||
|
||||
Any label not listed here is not in active use.
|
||||
|
||||
[project]: https://github.com/orgs/tokio-rs/projects/1
|
||||
@@ -0,0 +1,346 @@
|
||||
## Pull Requests
|
||||
|
||||
Pull Requests are the way concrete changes are made to the code, documentation,
|
||||
and dependencies in the Tokio repository.
|
||||
|
||||
Even tiny pull requests (e.g., one-character pull request fixing a typo in API
|
||||
documentation) are greatly appreciated. Before making a large change, it is
|
||||
usually a good idea to first open an issue describing the change to solicit
|
||||
feedback and guidance. This will increase the likelihood of the PR getting
|
||||
merged.
|
||||
|
||||
### Cargo Commands
|
||||
|
||||
Due to the extensive use of features in Tokio, you will often need to add extra
|
||||
arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
**NOTE**: there are some features that are not supported in every system, so you might
|
||||
need to specify which features you want to pass to cargo (e.g., `cargo check --features=full,io-uring`)
|
||||
|
||||
Ideally, you should use the same version of clippy as the one used in CI
|
||||
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
|
||||
might have new lints:
|
||||
|
||||
[ci.yml]: ../../.github/workflows/ci.yml
|
||||
|
||||
<!--
|
||||
When updating this, also update:
|
||||
- .github/workflows/ci.yml
|
||||
- README.md
|
||||
- tokio/README.md
|
||||
- tokio/Cargo.toml
|
||||
- tokio-util/Cargo.toml
|
||||
- tokio-test/Cargo.toml
|
||||
- tokio-stream/Cargo.toml
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.88 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation, a simple `cargo doc` is not sufficient. To produce
|
||||
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
|
||||
docs, please use:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
|
||||
```
|
||||
|
||||
This turns on indicators to display the Cargo features required for
|
||||
conditionally compiled APIs in Tokio, and it enables documentation of unstable
|
||||
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
|
||||
*and* rustc.
|
||||
|
||||
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
|
||||
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
|
||||
as docs.rs itself does.
|
||||
|
||||
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
|
||||
|
||||
```
|
||||
cargo install --locked cargo-docs-rs
|
||||
cargo +nightly docs-rs [--open]
|
||||
```
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
|
||||
You can run loom tests with
|
||||
```
|
||||
cd tokio # tokio crate in workspace
|
||||
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
|
||||
run loom tests that test unstable features.
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance" \
|
||||
cargo +nightly miri test --features full --lib --tests
|
||||
```
|
||||
|
||||
### Performing spellcheck on tokio codebase
|
||||
|
||||
You can perform a spell-check on the Tokio codebase. For details of how to use the spellcheck tool, feel free to visit
|
||||
https://github.com/drahnr/cargo-spellcheck
|
||||
```
|
||||
# First install the spell-check plugin
|
||||
cargo install --locked cargo-spellcheck
|
||||
|
||||
# Then run the cargo spell check command
|
||||
cargo spellcheck check
|
||||
```
|
||||
|
||||
If the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
|
||||
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
|
||||
the total number of words included in the file
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: [integration tests][integration-tests]
|
||||
and [documentation tests][documentation-tests].
|
||||
(Tokio avoids [unit tests][unit-tests] as much as possible).
|
||||
|
||||
Tokio uses [conditional compilation attributes][conditional-compilation]
|
||||
throughout the codebase, to modify rustc's behavior. Code marked with such
|
||||
attributes can be enabled using `RUSTFLAGS` and `RUSTDOCFLAGS` environment
|
||||
variables. One of the most prevalent flags passed in these variables is
|
||||
the `--cfg` option. To run tests in a particular file, check first what
|
||||
options #![cfg] declaration defines for that file.
|
||||
|
||||
For instance, to run a test marked with the 'tokio_unstable' cfg option,
|
||||
you must pass this flag to the compiler when running the test.
|
||||
```
|
||||
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
|
||||
```
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Integration tests go in the same crate as the code they are testing. Each sub
|
||||
crate should have a `dev-dependency` on `tokio` itself. This makes all Tokio
|
||||
utilities available to use in tests, no matter the crate being tested.
|
||||
|
||||
The best strategy for writing a new integration test is to look at existing
|
||||
integration tests in the crate and follow the style.
|
||||
|
||||
#### Fuzz tests
|
||||
|
||||
Some of our crates include a set of fuzz tests, this will be marked by a
|
||||
directory `fuzz`. It is a good idea to run fuzz tests after each change.
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install --locked cargo-fuzz`
|
||||
|
||||
To list the available fuzzing harnesses you can run;
|
||||
|
||||
```bash
|
||||
$ cd tokio
|
||||
$ cargo fuzz list
|
||||
fuzz_linked_list
|
||||
```
|
||||
|
||||
Running a fuzz test is as simple as;
|
||||
|
||||
`cargo fuzz run fuzz_linked_list`
|
||||
|
||||
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
|
||||
harness will run forever and will only exit if you `ctrl-c` or it finds
|
||||
a bug.
|
||||
|
||||
#### Documentation tests
|
||||
|
||||
Ideally, every API has at least one [documentation test] that demonstrates how to
|
||||
use the API. Documentation tests are run with `cargo test --doc`. This ensures
|
||||
that the example is correct and provides additional test coverage.
|
||||
|
||||
The trick to documentation tests is striking a balance between being succinct
|
||||
for a reader to understand and actually testing the API.
|
||||
|
||||
Same as with integration tests, when writing a documentation test, the full
|
||||
`tokio` crate is available. This is especially useful for getting access to the
|
||||
runtime to run the example.
|
||||
|
||||
The documentation tests will be visible from both the crate-specific
|
||||
documentation **and** the `tokio` facade documentation via the re-export. The
|
||||
example should be written from the point of view of a user that is using the
|
||||
`tokio` crate. As such, the example should use the API via the facade and not by
|
||||
directly referencing the crate.
|
||||
|
||||
The type level example for `tokio::time::timeout` provides a good example of a
|
||||
documentation test:
|
||||
|
||||
```
|
||||
/// Create a new `Timeout` set to expire in 10 milliseconds.
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::timeout;
|
||||
/// use tokio::sync::oneshot;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
/// # tx.send(()).unwrap();
|
||||
///
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// if let Err(_) = timeout(Duration::from_millis(10), rx).await {
|
||||
/// println!("did not receive value within 10 ms");
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
```
|
||||
|
||||
Lines that start with `/// #` are removed when the documentation is generated.
|
||||
|
||||
### Benchmarks
|
||||
|
||||
You can run benchmarks locally for the changes you've made to the tokio codebase.
|
||||
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
|
||||
against the changes you have made, for example, you can run;
|
||||
|
||||
```bash
|
||||
cd benches
|
||||
|
||||
# Run all benchmarks.
|
||||
cargo bench
|
||||
|
||||
# Run all tests in the `benches/fs.rs` file
|
||||
cargo bench --bench fs
|
||||
|
||||
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
|
||||
cargo bench async_read_buf
|
||||
|
||||
# After running benches, you can check the statistics under `tokio/target/criterion/`
|
||||
```
|
||||
|
||||
You can also refer to [Criterion] docs for additional options and details.
|
||||
|
||||
[Criterion]: https://docs.rs/criterion/latest/criterion/
|
||||
|
||||
### Commits
|
||||
|
||||
It is a recommended best practice to keep your changes as logically grouped as
|
||||
possible within individual commits. There is no limit to the number of commits
|
||||
any single Pull Request may have, and many contributors find it easier to review
|
||||
changes that are split across multiple commits.
|
||||
|
||||
That said, if you have a number of commits that are "checkpoints" and don't
|
||||
represent a single logical change, please squash those together.
|
||||
|
||||
Note that multiple commits often get squashed when they are landed (see the
|
||||
notes about [commit squashing](#commit-squashing)).
|
||||
|
||||
#### Commit message guidelines
|
||||
|
||||
A good commit message should describe what changed and why.
|
||||
|
||||
1. The first line should:
|
||||
|
||||
* contain a short description of the change (preferably 50 characters or less,
|
||||
and no more than 72 characters)
|
||||
* be entirely in lowercase with the exception of proper nouns, acronyms, and
|
||||
the words that refer to code, like function/variable names
|
||||
* start with an imperative verb
|
||||
* not have a period at the end
|
||||
* be prefixed with the name of the module being changed; usually this is the
|
||||
same as the M-* label on the PR
|
||||
|
||||
Examples:
|
||||
|
||||
* time: introduce `Timeout` and deprecate `Deadline`
|
||||
* codec: export `Encoder`, `Decoder`, `Framed*`
|
||||
* ci: fix the FreeBSD ci configuration
|
||||
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns (except for long URLs).
|
||||
4. If your patch fixes an open issue, you can add a reference to it at the end
|
||||
of the log. Use the `Fixes: #` prefix and the issue number. For other
|
||||
references use `Refs: #`. `Refs` may include multiple issues, separated by a
|
||||
comma.
|
||||
|
||||
Examples:
|
||||
|
||||
- `Fixes: #1337`
|
||||
- `Refs: #1234`
|
||||
|
||||
Sample complete commit message:
|
||||
|
||||
```txt
|
||||
module: explain the commit in one line
|
||||
|
||||
Body of commit message is a few lines of text, explaining things
|
||||
in more detail, possibly giving some background about the issue
|
||||
being fixed, etc.
|
||||
|
||||
The body of the commit message can be several paragraphs, and
|
||||
please do proper word-wrap and keep columns shorter than about
|
||||
72 characters or so. That way, `git log` will show things
|
||||
nicely even when it is indented.
|
||||
|
||||
Fixes: #1337
|
||||
Refs: #453, #154
|
||||
```
|
||||
|
||||
### Opening the Pull Request
|
||||
|
||||
From within GitHub, opening a new Pull Request will present you with a
|
||||
[template] that should be filled out. Please try to do your best at filling out
|
||||
the details, but feel free to skip parts if you're not sure what to put.
|
||||
|
||||
[template]: ../../.github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Discuss and update
|
||||
|
||||
You will probably get feedback or requests for changes to your Pull Request.
|
||||
This is a big part of the submission process so don't be discouraged! Some
|
||||
contributors may sign off on the Pull Request right away, others may have
|
||||
more detailed comments or feedback. This is a necessary part of the process
|
||||
in order to evaluate whether the changes are correct and necessary.
|
||||
|
||||
**Any community member can review a PR and you might get conflicting feedback**.
|
||||
Keep an eye out for comments from code owners to provide guidance on conflicting
|
||||
feedback.
|
||||
|
||||
**Once the PR is open, do not rebase the commits**. See [Commit Squashing](#commit-squashing) for
|
||||
more details.
|
||||
|
||||
### Commit Squashing
|
||||
|
||||
In most cases, **do not squash commits that you add to your Pull Request during
|
||||
the review process**. When the commits in your Pull Request land, they may be
|
||||
squashed into one commit per logical change. Metadata will be added to the
|
||||
commit message (including links to the Pull Request, links to relevant issues,
|
||||
and the names of the reviewers). The commit history of your Pull Request,
|
||||
however, will stay intact on the Pull Request page.
|
||||
|
||||
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
|
||||
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
|
||||
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
@@ -0,0 +1,80 @@
|
||||
## Reviewing Pull Requests
|
||||
|
||||
**Any Tokio community member is welcome to review any pull request**.
|
||||
|
||||
All Tokio contributors who choose to review and provide feedback on Pull
|
||||
Requests have a responsibility to both the project and the individual making the
|
||||
contribution. Reviews and feedback must be helpful, insightful, and geared
|
||||
towards improving the contribution as opposed to simply blocking it. If there
|
||||
are reasons why you feel the PR should not land, explain what those are. Do not
|
||||
expect to be able to block a Pull Request from advancing simply because you say
|
||||
"No" without giving an explanation. Be open to having your mind changed. Be open
|
||||
to working with the contributor to make the Pull Request better.
|
||||
|
||||
Reviews that are dismissive or disrespectful of the contributor or any other
|
||||
reviewers are strictly counter to the Code of Conduct.
|
||||
|
||||
When reviewing a Pull Request, the primary goals are for the codebase to improve
|
||||
and for the person submitting the request to succeed. **Even if a Pull Request
|
||||
does not land, the submitters should come away from the experience feeling like
|
||||
their effort was not wasted or unappreciated**. Every Pull Request from a new
|
||||
contributor is an opportunity to grow the community.
|
||||
|
||||
### Review a bit at a time
|
||||
|
||||
Do not overwhelm new contributors.
|
||||
|
||||
It is tempting to micro-optimize and make everything about relative performance,
|
||||
perfect grammar, or exact style matches. Do not succumb to that temptation.
|
||||
|
||||
Focus first on the most significant aspects of the change:
|
||||
|
||||
1. Does this change make sense for Tokio?
|
||||
2. Does this change make Tokio better, even if only incrementally?
|
||||
3. Are there clear bugs or larger scale issues that need attending to?
|
||||
4. Is the commit message readable and correct? If it contains a breaking change
|
||||
is it clear enough?
|
||||
|
||||
Note that only **incremental** improvement is needed to land a PR. This means
|
||||
that the PR does not need to be perfect, only better than the status quo. Follow
|
||||
up PRs may be opened to continue iterating.
|
||||
|
||||
When changes are necessary, *request* them, do not *demand* them, and **do not
|
||||
assume that the submitter already knows how to add a test or run a benchmark**.
|
||||
|
||||
Specific performance optimization techniques, coding styles and conventions
|
||||
change over time. The first impression you give to a new contributor never does.
|
||||
|
||||
Nits (requests for small changes that are not essential) are fine, but try to
|
||||
avoid stalling the Pull Request. Most nits can typically be fixed by the Tokio
|
||||
Collaborator landing the Pull Request but they can also be an opportunity for
|
||||
the contributor to learn a bit more about the project.
|
||||
|
||||
It is always good to clearly indicate nits when you comment: e.g.
|
||||
`Nit: change foo() to bar(). But this is not blocking.`
|
||||
|
||||
If your comments were addressed but were not folded automatically after new
|
||||
commits or if they proved to be mistaken, please, [hide them][hiding-a-comment]
|
||||
with the appropriate reason to keep the conversation flow concise and relevant.
|
||||
|
||||
### Be aware of the person behind the code
|
||||
|
||||
Be aware that *how* you communicate requests and reviews in your feedback can
|
||||
have a significant impact on the success of the Pull Request. Yes, we may land
|
||||
a particular change that makes Tokio better, but the individual might just not
|
||||
want to have anything to do with Tokio ever again. The goal is not just having
|
||||
good code.
|
||||
|
||||
### Abandoned or Stalled Pull Requests
|
||||
|
||||
If a Pull Request appears to be abandoned or stalled, it is polite to first
|
||||
check with the contributor to see if they intend to continue the work before
|
||||
checking if they would mind if you took it over (especially if it just has nits
|
||||
left). When doing so, it is courteous to give the original contributor credit
|
||||
for the work they started (either by preserving their name and email address in
|
||||
the commit log, or by using an `Author: ` meta-data tag in the commit.
|
||||
|
||||
_Adapted from the [Node.js contributing guide][node]_.
|
||||
|
||||
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
|
||||
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
|
||||
+16
-2
@@ -16,7 +16,7 @@ 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"
|
||||
@@ -24,8 +24,14 @@ httparse = "1.0"
|
||||
httpdate = "1.0"
|
||||
once_cell = "1.5.2"
|
||||
|
||||
[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.59"
|
||||
version = "0.61"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
@@ -43,6 +49,10 @@ path = "connect-udp.rs"
|
||||
name = "echo-tcp"
|
||||
path = "echo-tcp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "graceful-shutdown"
|
||||
path = "graceful-shutdown.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-udp"
|
||||
path = "echo-udp.rs"
|
||||
@@ -99,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
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,14 +58,16 @@ pub async fn connect(
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={e}");
|
||||
eprintln!("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(()),
|
||||
tokio::select! {
|
||||
r = sink.send_all(&mut stdin) => r?,
|
||||
r = stdout.send_all(&mut stream) => r?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -59,7 +59,10 @@ pub async fn connect(
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
|
||||
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
|
||||
tokio::select! {
|
||||
r = send(stdin, &socket) => r?,
|
||||
r = recv(stdout, &socket) => r?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -85,7 +88,7 @@ async fn recv(
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(Bytes::from(buf)).await?;
|
||||
stdout.send(Bytes::copy_from_slice(&buf[..n])).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")
|
||||
)))]
|
||||
|
||||
+23
-16
@@ -16,7 +16,7 @@
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! 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` example you
|
||||
//! 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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 {});
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
//! 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
|
||||
|
||||
+5
-12
@@ -1,15 +1,6 @@
|
||||
[build]
|
||||
# TODO: unfreeze toolchain
|
||||
# error[E0557]: feature has been removed
|
||||
# --> /opt/buildhome/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lock_api-0.4.13/src/lib.rs:89:29
|
||||
# |
|
||||
# 89 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
# | ^^^^^^^^^^^^ feature has been removed
|
||||
# |
|
||||
# = note: removed in 1.58.0; see <https://github.com/rust-lang/rust/pull/138907; for more information
|
||||
# = note: merged into `doc_cfg`
|
||||
command = """
|
||||
rustup install nightly-2025-01-25 --profile minimal && cargo doc --no-deps --all-features
|
||||
rustup install nightly --profile minimal && cargo doc --no-deps --all-features
|
||||
"""
|
||||
publish = "target/doc"
|
||||
|
||||
@@ -17,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 = "/"
|
||||
|
||||
+10
-2
@@ -1,4 +1,4 @@
|
||||
306
|
||||
314
|
||||
&
|
||||
+
|
||||
<
|
||||
@@ -64,6 +64,7 @@ codec
|
||||
codecs
|
||||
combinator
|
||||
combinators
|
||||
condvar
|
||||
config
|
||||
Config
|
||||
connectionless
|
||||
@@ -75,6 +76,7 @@ CQE
|
||||
cqe's
|
||||
customizable
|
||||
Customizable
|
||||
Cygwin
|
||||
datagram
|
||||
Datagram
|
||||
datagrams
|
||||
@@ -162,6 +164,7 @@ Lauck
|
||||
libc
|
||||
lifecycle
|
||||
lifo
|
||||
LLVM
|
||||
lookups
|
||||
macOS
|
||||
MacOS
|
||||
@@ -184,15 +187,17 @@ mut
|
||||
mutex
|
||||
Mutex
|
||||
Nagle
|
||||
namespace
|
||||
nonblocking
|
||||
nondecreasing
|
||||
noop
|
||||
ntasks
|
||||
NUMA
|
||||
ok
|
||||
oneshot
|
||||
opcode
|
||||
ORed
|
||||
os
|
||||
overweighing
|
||||
parker
|
||||
parsers
|
||||
peekable
|
||||
@@ -204,6 +209,7 @@ POSIX
|
||||
proxied
|
||||
qos
|
||||
RAII
|
||||
RCU
|
||||
reallocations
|
||||
recv's
|
||||
refactors
|
||||
@@ -304,4 +310,6 @@ wakers
|
||||
Wakers
|
||||
wakeup
|
||||
wakeups
|
||||
WASI
|
||||
workstealing
|
||||
ZST
|
||||
|
||||
@@ -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]: failed to resolve: use of undeclared type `Pin`
|
||||
--> tests/fail/macros_join.rs:35:17
|
||||
|
|
||||
35 | let _ = Pin::new(&mut x);
|
||||
| ^^^ use of undeclared type `Pin`
|
||||
|
|
||||
help: consider importing this struct
|
||||
|
|
||||
1 + use std::pin::Pin;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function `poll_fn` in this scope
|
||||
--> tests/fail/macros_join.rs:40:17
|
||||
|
|
||||
40 | let _ = poll_fn(|_cx| todo!());
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this function
|
||||
|
|
||||
1 + use std::future::poll_fn;
|
||||
|
|
||||
@@ -0,0 +1,42 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// do not leak `RotatorSelect`
|
||||
let _ = tokio::try_join!(async {
|
||||
fn foo(_: impl RotatorSelect) {}
|
||||
});
|
||||
|
||||
// do not leak `std::task::Poll::Pending`
|
||||
let _ = tokio::try_join!(async { Pending });
|
||||
|
||||
// do not leak `std::task::Poll::Ready`
|
||||
let _ = tokio::try_join!(async { Ready(0) });
|
||||
|
||||
// do not leak `std::future::Future`
|
||||
let _ = tokio::try_join!(async {
|
||||
struct MyFuture;
|
||||
|
||||
impl Future for MyFuture {
|
||||
type Output = ();
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// do not leak `std::pin::Pin`
|
||||
let _ = tokio::try_join!(async {
|
||||
let mut x = 5;
|
||||
let _ = Pin::new(&mut x);
|
||||
});
|
||||
|
||||
// do not leak `std::future::poll_fn`
|
||||
let _ = tokio::try_join!(async {
|
||||
let _ = poll_fn(|_cx| todo!());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
error[E0405]: cannot find trait `RotatorSelect` in this scope
|
||||
--> tests/fail/macros_try_join.rs:7:24
|
||||
|
|
||||
7 | fn foo(_: impl RotatorSelect) {}
|
||||
| ^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `Pending` in this scope
|
||||
--> tests/fail/macros_try_join.rs:11:38
|
||||
|
|
||||
11 | let _ = tokio::try_join!(async { Pending });
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this unit variant
|
||||
|
|
||||
1 + use std::task::Poll::Pending;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
|
||||
--> tests/fail/macros_try_join.rs:14:38
|
||||
|
|
||||
14 | let _ = tokio::try_join!(async { Ready(0) });
|
||||
| ^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this tuple variant
|
||||
|
|
||||
1 + use std::task::Poll::Ready;
|
||||
|
|
||||
|
||||
error[E0405]: cannot find trait `Future` in this scope
|
||||
--> tests/fail/macros_try_join.rs:20:14
|
||||
|
|
||||
20 | impl Future for MyFuture {
|
||||
| ^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this trait
|
||||
|
|
||||
1 + use std::future::Future;
|
||||
|
|
||||
|
||||
error[E0433]: failed to resolve: use of undeclared type `Pin`
|
||||
--> tests/fail/macros_try_join.rs:35:17
|
||||
|
|
||||
35 | let _ = Pin::new(&mut x);
|
||||
| ^^^ use of undeclared type `Pin`
|
||||
|
|
||||
help: consider importing this struct
|
||||
|
|
||||
1 + use std::pin::Pin;
|
||||
|
|
||||
|
||||
error[E0425]: cannot find function `poll_fn` in this scope
|
||||
--> tests/fail/macros_try_join.rs:40:17
|
||||
|
|
||||
40 | let _ = poll_fn(|_cx| todo!());
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
|
||||
help: consider importing this function
|
||||
|
|
||||
1 + use std::future::poll_fn;
|
||||
|
|
||||
@@ -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
|
||||
|
|
||||
@@ -32,6 +54,17 @@ help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panic
|
||||
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
|
||||
|
|
||||
@@ -58,6 +91,17 @@ error[E0277]: the `?` operator can only be used in an async block that returns `
|
||||
40 | None?;
|
||||
| ^ cannot use the `?` operator in an async block that returns `()`
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to be a future that resolves to `Option<()>`, but it resolves to `()`
|
||||
--> tests/fail/macros_type_mismatch.rs:38:1
|
||||
|
|
||||
38 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `Option<()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Option<()>`
|
||||
found unit type `()`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to `&dyn Future<Output = Option<()>>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:40:5
|
||||
|
|
||||
@@ -86,6 +130,17 @@ error[E0277]: the `?` operator can only be used in an async block that returns `
|
||||
57 | Ok(())?;
|
||||
| ^ cannot use the `?` operator in an async block that returns `()`
|
||||
|
||||
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
|
||||
--> tests/fail/macros_type_mismatch.rs:55:1
|
||||
|
|
||||
55 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to `&dyn Future<Output = Result<(), ()>>`
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:57:5
|
||||
|
|
||||
@@ -102,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());
|
||||
}
|
||||
|
||||
@@ -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 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,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;
|
||||
|
||||
@@ -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]> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -48,3 +48,15 @@ async fn basic_usage() {
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "tokio-test"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Remove path dependencies (if any)
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.71"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -17,12 +17,12 @@ Testing utilities for Tokio- and futures-based code
|
||||
categories = ["asynchronous", "development-tools::testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio-stream = { version = "0.1.1", path = "../tokio-stream" }
|
||||
tokio = { version = "1.2.0", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio-stream = "0.1.1"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.2.0", features = ["full"] }
|
||||
futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
+24
-1
@@ -438,7 +438,30 @@ impl AsyncWrite for Mock {
|
||||
let until = Instant::now() + rem;
|
||||
self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
|
||||
} else {
|
||||
panic!("unexpected WouldBlock {}", self.pmsg());
|
||||
// A race condition (TOCTOU) can occur if the
|
||||
// timer expires between the `write()` call
|
||||
// and `remaining_wait()` due to preemption or other
|
||||
// delays. In this case, the `Wait` action is already popped by
|
||||
// `action()`, so we continue to the next one.
|
||||
//
|
||||
// Consider the following sequence:
|
||||
//
|
||||
// poll_write Inner action()
|
||||
// |--write()--->| |
|
||||
// | |--action()---->| (returns Wait)
|
||||
// |<-WouldBlk---| |
|
||||
// | | |
|
||||
// | <--- TIMEOUT! ---> |
|
||||
// | (due to preemption, etc.) |
|
||||
// | | |
|
||||
// |-rem_wait()->| |
|
||||
// | |--action()---->| (time's up, pop Wait)
|
||||
// |<--None------| |
|
||||
// | | |
|
||||
// |---continue->| (process next action)
|
||||
//
|
||||
// See <https://github.com/tokio-rs/tokio/issues/7881>.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Ok(0) => {
|
||||
|
||||
+45
-11
@@ -26,10 +26,11 @@
|
||||
//! ```
|
||||
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::task::{Context, Poll, Wake, Waker};
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use tokio_stream::Stream;
|
||||
|
||||
@@ -170,7 +171,7 @@ impl MockTask {
|
||||
F: FnOnce(&mut Context<'_>) -> R,
|
||||
{
|
||||
self.waker.clear();
|
||||
let waker = self.clone().into_waker();
|
||||
let waker = self.waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
f(&mut cx)
|
||||
@@ -189,8 +190,11 @@ impl MockTask {
|
||||
Arc::strong_count(&self.waker)
|
||||
}
|
||||
|
||||
fn into_waker(self) -> Waker {
|
||||
self.waker.into()
|
||||
fn waker(&self) -> Waker {
|
||||
unsafe {
|
||||
let raw = to_raw(self.waker.clone());
|
||||
Waker::from_raw(raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,14 +226,8 @@ impl ThreadWaker {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Wake for ThreadWaker {
|
||||
fn wake(self: Arc<Self>) {
|
||||
self.wake_by_ref();
|
||||
}
|
||||
|
||||
fn wake_by_ref(self: &Arc<Self>) {
|
||||
fn wake(&self) {
|
||||
// First, try transitioning from IDLE -> NOTIFY, this does not require a lock.
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let prev = *state;
|
||||
@@ -249,3 +247,39 @@ impl Wake for ThreadWaker {
|
||||
self.condvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
|
||||
|
||||
unsafe fn to_raw(waker: Arc<ThreadWaker>) -> RawWaker {
|
||||
RawWaker::new(Arc::into_raw(waker) as *const (), &VTABLE)
|
||||
}
|
||||
|
||||
unsafe fn from_raw(raw: *const ()) -> Arc<ThreadWaker> {
|
||||
Arc::from_raw(raw as *const ThreadWaker)
|
||||
}
|
||||
|
||||
unsafe fn clone(raw: *const ()) -> RawWaker {
|
||||
let waker = from_raw(raw);
|
||||
|
||||
// Increment the ref count
|
||||
mem::forget(waker.clone());
|
||||
|
||||
to_raw(waker)
|
||||
}
|
||||
|
||||
unsafe fn wake(raw: *const ()) {
|
||||
let waker = from_raw(raw);
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
unsafe fn wake_by_ref(raw: *const ()) {
|
||||
let waker = from_raw(raw);
|
||||
waker.wake();
|
||||
|
||||
// We don't actually own a reference to the unparker
|
||||
mem::forget(waker);
|
||||
}
|
||||
|
||||
unsafe fn drop_waker(raw: *const ()) {
|
||||
let _ = from_raw(raw);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,90 @@
|
||||
# 0.7.18 (January 4th, 2026)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `tokio_util::io::simplex` ([#7565])
|
||||
|
||||
### Changed
|
||||
|
||||
- task: remove unnecessary trait bounds on the `Debug` implementation for `JoinQueue` and `AbortOnDropHandle` ([#7720])
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: bump `tokio` to `1.44.0` ([#7733])
|
||||
|
||||
### Documented
|
||||
|
||||
- io: document the default capacity of the `ReaderStream` ([#7147])
|
||||
- sync: fix a typo in the docs of `PollSender::is_closed` ([#7737])
|
||||
|
||||
[#7147]: https://github.com/tokio-rs/tokio/pull/7147
|
||||
[#7565]: https://github.com/tokio-rs/tokio/pull/7565
|
||||
[#7720]: https://github.com/tokio-rs/tokio/pull/7720
|
||||
[#7733]: https://github.com/tokio-rs/tokio/pull/7733
|
||||
[#7737]: https://github.com/tokio-rs/tokio/pull/7737
|
||||
|
||||
# 0.7.17 (November 2nd, 2025)
|
||||
|
||||
The MSRV is increased to 1.71.
|
||||
|
||||
### Added
|
||||
|
||||
- codec: add `{FramedRead,FramedWrite}::into_parts()` ([#7566])
|
||||
- time: add `#[track_caller]` to `FutureExt::timeout` ([#7588])
|
||||
- task: add `tokio_util::task::JoinQueue` ([#7590])
|
||||
|
||||
### Changed
|
||||
|
||||
- codec: remove unnecessary trait bounds on all Framed constructors ([#7716])
|
||||
|
||||
### Documented
|
||||
|
||||
- time: clarify the cancellation safety of the `DelayQueue` ([#7564])
|
||||
- docs: fix some docs links ([#7654])
|
||||
- task: simplify the example of `TaskTracker` ([#7657])
|
||||
- task: clarify the behavior of several `spawn_local` methods ([#7669])
|
||||
|
||||
[#7564]: https://github.com/tokio-rs/tokio/pull/7564
|
||||
[#7566]: https://github.com/tokio-rs/tokio/pull/7566
|
||||
[#7588]: https://github.com/tokio-rs/tokio/pull/7588
|
||||
[#7590]: https://github.com/tokio-rs/tokio/pull/7590
|
||||
[#7654]: https://github.com/tokio-rs/tokio/pull/7654
|
||||
[#7657]: https://github.com/tokio-rs/tokio/pull/7657
|
||||
[#7669]: https://github.com/tokio-rs/tokio/pull/7669
|
||||
[#7716]: https://github.com/tokio-rs/tokio/pull/7716
|
||||
|
||||
# 0.7.16 (August 3rd, 2025)
|
||||
|
||||
### Added
|
||||
|
||||
- codec: add `FramedWrite::with_capacity` ([#7493])
|
||||
- future: add adapters of `CancellationToken` for `FutureExt` ([#7475])
|
||||
- sync: add `DropGuardRef` for `CancellationToken` ([#7407])
|
||||
- task: add `AbortOnDropHandle::detach` ([#7400])
|
||||
- task: stabilise `JoinMap` ([#7075])
|
||||
|
||||
### Changed
|
||||
|
||||
- codec: also apply capacity to read buffer in `Framed::with_capacity` ([#7500])
|
||||
- sync: make `CancellationToken::run_until_cancelled` biased towards the token ([#7462])
|
||||
- task: remove raw-entry feature from hashbrown dep ([#7252])
|
||||
|
||||
### Documented
|
||||
|
||||
- compat: add more documentation to `tokio_util::compat` ([#7279])
|
||||
- sync: improve docs of `tokio_util::sync::CancellationToken` ([#7408])
|
||||
|
||||
[#7075]: https://github.com/tokio-rs/tokio/pull/7075
|
||||
[#7252]: https://github.com/tokio-rs/tokio/pull/7252
|
||||
[#7279]: https://github.com/tokio-rs/tokio/pull/7279
|
||||
[#7400]: https://github.com/tokio-rs/tokio/pull/7400
|
||||
[#7407]: https://github.com/tokio-rs/tokio/pull/7407
|
||||
[#7408]: https://github.com/tokio-rs/tokio/pull/7408
|
||||
[#7462]: https://github.com/tokio-rs/tokio/pull/7462
|
||||
[#7475]: https://github.com/tokio-rs/tokio/pull/7475
|
||||
[#7493]: https://github.com/tokio-rs/tokio/pull/7493
|
||||
[#7500]: https://github.com/tokio-rs/tokio/pull/7500
|
||||
|
||||
# 0.7.15 (April 23rd, 2025)
|
||||
|
||||
### Fixed
|
||||
|
||||
+15
-13
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "tokio-util"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Remove path dependencies (if any)
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.7.x" git tag.
|
||||
version = "0.7.15"
|
||||
version = "0.7.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"
|
||||
@@ -21,20 +21,21 @@ categories = ["asynchronous"]
|
||||
default = []
|
||||
|
||||
# Shorthand for enabling everything
|
||||
full = ["codec", "compat", "io-util", "time", "net", "rt"]
|
||||
full = ["codec", "compat", "io-util", "time", "net", "rt", "join-map"]
|
||||
|
||||
net = ["tokio/net"]
|
||||
compat = ["futures-io",]
|
||||
compat = ["futures-io"]
|
||||
codec = []
|
||||
time = ["tokio/time","slab"]
|
||||
time = ["tokio/time", "slab"]
|
||||
io = []
|
||||
io-util = ["io", "tokio/rt", "tokio/io-util"]
|
||||
rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
|
||||
rt = ["tokio/rt", "tokio/sync", "futures-util"]
|
||||
join-map = ["rt", "hashbrown"]
|
||||
|
||||
__docs_rs = ["futures-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.28.0", path = "../tokio", features = ["sync"] }
|
||||
tokio = { version = "1.47.0", features = ["sync"] }
|
||||
bytes = "1.5.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
@@ -43,14 +44,12 @@ futures-util = { version = "0.3.0", optional = true }
|
||||
pin-project-lite = "0.2.11"
|
||||
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
|
||||
tracing = { version = "0.1.29", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
[target.'cfg(tokio_unstable)'.dependencies]
|
||||
hashbrown = { version = "0.15.0", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.4.0", path = "../tokio-test" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
tokio = { version = "1.0.0", features = ["full"] }
|
||||
tokio-test = "0.4.0"
|
||||
tokio-stream = "0.1"
|
||||
|
||||
async-stream = "0.3.0"
|
||||
futures = "0.3.0"
|
||||
@@ -58,6 +57,9 @@ futures-test = "0.3.5"
|
||||
parking_lot = "0.12.0"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[target.'cfg(loom)'.dev-dependencies]
|
||||
loom = { version = "0.7", features = ["futures", "checkpoint"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# enable unstable features in the documentation
|
||||
|
||||
@@ -60,6 +60,15 @@ macro_rules! cfg_rt {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_not_rt {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(not(feature = "rt"))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_time {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
|
||||
@@ -41,10 +41,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
impl<T, U> Framed<T, U> {
|
||||
/// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this
|
||||
/// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data.
|
||||
///
|
||||
@@ -119,14 +116,15 @@ where
|
||||
buffer: BytesMut::with_capacity(capacity),
|
||||
has_errored: false,
|
||||
},
|
||||
write: WriteFrame::default(),
|
||||
write: WriteFrame {
|
||||
buffer: BytesMut::with_capacity(capacity),
|
||||
backpressure_boundary: capacity,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U> {
|
||||
/// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this
|
||||
/// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data.
|
||||
///
|
||||
@@ -374,7 +372,7 @@ pub struct FramedParts<T, U> {
|
||||
|
||||
/// This private field allows us to add additional fields in the future in a
|
||||
/// backwards compatible way.
|
||||
_priv: (),
|
||||
pub(crate) _priv: (),
|
||||
}
|
||||
|
||||
impl<T, U> FramedParts<T, U> {
|
||||
|
||||
@@ -65,6 +65,7 @@ impl Default for WriteFrame {
|
||||
|
||||
impl From<BytesMut> for ReadFrame {
|
||||
fn from(mut buffer: BytesMut) -> Self {
|
||||
let is_readable = !buffer.is_empty();
|
||||
let size = buffer.capacity();
|
||||
if size < INITIAL_CAPACITY {
|
||||
buffer.reserve(INITIAL_CAPACITY - size);
|
||||
@@ -72,7 +73,7 @@ impl From<BytesMut> for ReadFrame {
|
||||
|
||||
Self {
|
||||
buffer,
|
||||
is_readable: size > 0,
|
||||
is_readable,
|
||||
eof: false,
|
||||
has_errored: false,
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use super::FramedParts;
|
||||
|
||||
pin_project! {
|
||||
/// A [`Stream`] of messages decoded from an [`AsyncRead`].
|
||||
///
|
||||
@@ -34,11 +36,7 @@ pin_project! {
|
||||
|
||||
// ===== impl FramedRead =====
|
||||
|
||||
impl<T, D> FramedRead<T, D>
|
||||
where
|
||||
T: AsyncRead,
|
||||
D: Decoder,
|
||||
{
|
||||
impl<T, D> FramedRead<T, D> {
|
||||
/// Creates a new `FramedRead` with the given `decoder`.
|
||||
pub fn new(inner: T, decoder: D) -> FramedRead<T, D> {
|
||||
FramedRead {
|
||||
@@ -66,9 +64,7 @@ where
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, D> FramedRead<T, D> {
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `FramedRead`.
|
||||
///
|
||||
@@ -153,6 +149,18 @@ impl<T, D> FramedRead<T, D> {
|
||||
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.inner.state.buffer
|
||||
}
|
||||
|
||||
/// Consumes the `FramedRead`, returning its underlying I/O stream, the buffer
|
||||
/// with unprocessed data, and the codec.
|
||||
pub fn into_parts(self) -> FramedParts<T, D> {
|
||||
FramedParts {
|
||||
io: self.inner.inner,
|
||||
codec: self.inner.codec,
|
||||
read_buf: self.inner.state.buffer,
|
||||
write_buf: BytesMut::new(),
|
||||
_priv: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
|
||||
@@ -12,6 +12,8 @@ use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use super::FramedParts;
|
||||
|
||||
pin_project! {
|
||||
/// A [`Sink`] of frames encoded to an `AsyncWrite`.
|
||||
///
|
||||
@@ -33,10 +35,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> FramedWrite<T, E>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
{
|
||||
impl<T, E> FramedWrite<T, E> {
|
||||
/// Creates a new `FramedWrite` with the given `encoder`.
|
||||
pub fn new(inner: T, encoder: E) -> FramedWrite<T, E> {
|
||||
FramedWrite {
|
||||
@@ -47,9 +46,22 @@ where
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> FramedWrite<T, E> {
|
||||
/// Creates a new `FramedWrite` with the given `encoder` and a buffer of `capacity`
|
||||
/// initial size.
|
||||
pub fn with_capacity(inner: T, encoder: E, capacity: usize) -> FramedWrite<T, E> {
|
||||
FramedWrite {
|
||||
inner: FramedImpl {
|
||||
inner,
|
||||
codec: encoder,
|
||||
state: WriteFrame {
|
||||
buffer: BytesMut::with_capacity(capacity),
|
||||
backpressure_boundary: capacity,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying I/O stream wrapped by
|
||||
/// `FramedWrite`.
|
||||
///
|
||||
@@ -144,6 +156,18 @@ impl<T, E> FramedWrite<T, E> {
|
||||
pub fn set_backpressure_boundary(&mut self, boundary: usize) {
|
||||
self.inner.state.backpressure_boundary = boundary;
|
||||
}
|
||||
|
||||
/// Consumes the `FramedWrite`, returning its underlying I/O stream, the buffer
|
||||
/// with unprocessed data, and the codec.
|
||||
pub fn into_parts(self) -> FramedParts<T, E> {
|
||||
FramedParts {
|
||||
io: self.inner.inner,
|
||||
codec: self.inner.codec,
|
||||
read_buf: BytesMut::new(),
|
||||
write_buf: self.inner.state.buffer,
|
||||
_priv: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
//! ```
|
||||
//! # use tokio_stream::StreamExt;
|
||||
//! # use tokio_util::codec::LengthDelimitedCodec;
|
||||
//! # #[tokio::main]
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! # let io: &[u8] = b"\x00\x0BHello world";
|
||||
//! let mut reader = LengthDelimitedCodec::builder()
|
||||
@@ -117,7 +117,7 @@
|
||||
//! ```
|
||||
//! # use tokio_stream::StreamExt;
|
||||
//! # use tokio_util::codec::LengthDelimitedCodec;
|
||||
//! # #[tokio::main]
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! # let io: &[u8] = b"\x00\x0BHello world";
|
||||
//! let mut reader = LengthDelimitedCodec::builder()
|
||||
@@ -154,7 +154,7 @@
|
||||
//! ```
|
||||
//! # use tokio_stream::StreamExt;
|
||||
//! # use tokio_util::codec::LengthDelimitedCodec;
|
||||
//! # #[tokio::main]
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! # let io: &[u8] = b"\x00\x0DHello world";
|
||||
//! let mut reader = LengthDelimitedCodec::builder()
|
||||
@@ -190,7 +190,7 @@
|
||||
//! ```
|
||||
//! # use tokio_stream::StreamExt;
|
||||
//! # use tokio_util::codec::LengthDelimitedCodec;
|
||||
//! # #[tokio::main]
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! # let io: &[u8] = b"\x00\x00\x0B\xCA\xFEHello world";
|
||||
//! let mut reader = LengthDelimitedCodec::builder()
|
||||
@@ -237,7 +237,7 @@
|
||||
//! ```
|
||||
//! # use tokio_stream::StreamExt;
|
||||
//! # use tokio_util::codec::LengthDelimitedCodec;
|
||||
//! # #[tokio::main]
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! # let io: &[u8] = b"\xCA\x00\x0B\xFEHello world";
|
||||
//! let mut reader = LengthDelimitedCodec::builder()
|
||||
@@ -286,7 +286,7 @@
|
||||
//! ```
|
||||
//! # use tokio_stream::StreamExt;
|
||||
//! # use tokio_util::codec::LengthDelimitedCodec;
|
||||
//! # #[tokio::main]
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! # let io: &[u8] = b"\xCA\x00\x0F\xFEHello world";
|
||||
//! let mut reader = LengthDelimitedCodec::builder()
|
||||
@@ -329,7 +329,7 @@
|
||||
//! ```
|
||||
//! # use tokio_stream::StreamExt;
|
||||
//! # use tokio_util::codec::LengthDelimitedCodec;
|
||||
//! # #[tokio::main]
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! # let io: &[u8] = b"\x00\x00\x0B\xFFHello world";
|
||||
//! let mut reader = LengthDelimitedCodec::builder()
|
||||
|
||||
+32
-32
@@ -19,25 +19,25 @@
|
||||
//! use tokio_util::codec::LinesCodec;
|
||||
//! use tokio_util::codec::FramedWrite;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let buffer = Vec::new();
|
||||
//! let messages = vec!["Hello", "World"];
|
||||
//! let encoder = LinesCodec::new();
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! let buffer = Vec::new();
|
||||
//! let messages = vec!["Hello", "World"];
|
||||
//! let encoder = LinesCodec::new();
|
||||
//!
|
||||
//! // FramedWrite is a sink which means you can send values into it
|
||||
//! // asynchronously.
|
||||
//! let mut writer = FramedWrite::new(buffer, encoder);
|
||||
//! // FramedWrite is a sink which means you can send values into it
|
||||
//! // asynchronously.
|
||||
//! let mut writer = FramedWrite::new(buffer, encoder);
|
||||
//!
|
||||
//! // To be able to send values into a FramedWrite, you need to bring the
|
||||
//! // `SinkExt` trait into scope.
|
||||
//! writer.send(messages[0]).await.unwrap();
|
||||
//! writer.send(messages[1]).await.unwrap();
|
||||
//! // To be able to send values into a FramedWrite, you need to bring the
|
||||
//! // `SinkExt` trait into scope.
|
||||
//! writer.send(messages[0]).await.unwrap();
|
||||
//! writer.send(messages[1]).await.unwrap();
|
||||
//!
|
||||
//! let buffer = writer.get_ref();
|
||||
//! let buffer = writer.get_ref();
|
||||
//!
|
||||
//! assert_eq!(buffer.as_slice(), "Hello\nWorld\n".as_bytes());
|
||||
//! }
|
||||
//! assert_eq!(buffer.as_slice(), "Hello\nWorld\n".as_bytes());
|
||||
//! # }
|
||||
//!```
|
||||
//!
|
||||
//! # Example decoding using `LinesCodec`
|
||||
@@ -51,25 +51,25 @@
|
||||
//! use tokio_util::codec::LinesCodec;
|
||||
//! use tokio_util::codec::FramedRead;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let message = "Hello\nWorld".as_bytes();
|
||||
//! let decoder = LinesCodec::new();
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! let message = "Hello\nWorld".as_bytes();
|
||||
//! let decoder = LinesCodec::new();
|
||||
//!
|
||||
//! // FramedRead can be used to read a stream of values that are framed according to
|
||||
//! // a codec. FramedRead will read from its input (here `buffer`) until a whole frame
|
||||
//! // can be parsed.
|
||||
//! let mut reader = FramedRead::new(message, decoder);
|
||||
//! // FramedRead can be used to read a stream of values that are framed according to
|
||||
//! // a codec. FramedRead will read from its input (here `buffer`) until a whole frame
|
||||
//! // can be parsed.
|
||||
//! let mut reader = FramedRead::new(message, decoder);
|
||||
//!
|
||||
//! // To read values from a FramedRead, you need to bring the
|
||||
//! // `StreamExt` trait into scope.
|
||||
//! let frame1 = reader.next().await.unwrap().unwrap();
|
||||
//! let frame2 = reader.next().await.unwrap().unwrap();
|
||||
//! // To read values from a FramedRead, you need to bring the
|
||||
//! // `StreamExt` trait into scope.
|
||||
//! let frame1 = reader.next().await.unwrap().unwrap();
|
||||
//! let frame2 = reader.next().await.unwrap().unwrap();
|
||||
//!
|
||||
//! assert!(reader.next().await.is_none());
|
||||
//! assert_eq!(frame1, "Hello");
|
||||
//! assert_eq!(frame2, "World");
|
||||
//! }
|
||||
//! assert!(reader.next().await.is_none());
|
||||
//! assert_eq!(frame1, "Hello");
|
||||
//! assert_eq!(frame2, "World");
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! # The Decoder trait
|
||||
@@ -313,7 +313,7 @@
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
//! [`Stream`]: futures_core::Stream
|
||||
//! [`Sink`]: futures_sink::Sink
|
||||
//! [`SinkExt`]: futures::sink::SinkExt
|
||||
//! [`SinkExt`]: https://docs.rs/futures/0.3/futures/sink/trait.SinkExt.html
|
||||
//! [`SinkExt::close`]: https://docs.rs/futures/0.3/futures/sink/trait.SinkExt.html#method.close
|
||||
//! [`FramedRead`]: struct@crate::codec::FramedRead
|
||||
//! [`FramedWrite`]: struct@crate::codec::FramedWrite
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
//! stream via [`compat()`].
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # #[cfg(not(target_family = "wasm"))]
|
||||
//! # {
|
||||
//! use tokio::net::{TcpListener, TcpStream};
|
||||
//! use tokio::io::AsyncWriteExt;
|
||||
//! use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
@@ -58,6 +60,7 @@
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Example 2: Futures -> Tokio (`AsyncRead`)
|
||||
@@ -66,6 +69,8 @@
|
||||
//! adapt it to be used with [`tokio::io::AsyncReadExt::read_to_end`]
|
||||
//!
|
||||
//! ```
|
||||
//! # #[cfg(not(target_family = "wasm"))]
|
||||
//! # {
|
||||
//! use futures::io::Cursor;
|
||||
//! use tokio_util::compat::FuturesAsyncReadCompatExt;
|
||||
//! use tokio::io::AsyncReadExt;
|
||||
@@ -82,6 +87,7 @@
|
||||
//! // Run the future inside a Tokio runtime
|
||||
//! tokio::runtime::Runtime::new().unwrap().block_on(future);
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Common Use Cases
|
||||
|
||||
@@ -37,6 +37,8 @@ pin_project! {
|
||||
/// them. It then uses the context of the runtime with the timer enabled to
|
||||
/// execute a [`sleep`] future on the runtime with timing disabled.
|
||||
/// ```
|
||||
/// # #[cfg(not(target_family = "wasm"))]
|
||||
/// # {
|
||||
/// use tokio::time::{sleep, Duration};
|
||||
/// use tokio_util::context::RuntimeExt;
|
||||
///
|
||||
@@ -56,6 +58,7 @@ pin_project! {
|
||||
///
|
||||
/// // Execute the future on rt2.
|
||||
/// rt2.block_on(fut);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Handle`]: struct@tokio::runtime::Handle
|
||||
@@ -88,6 +91,8 @@ impl<F> TokioContext<F> {
|
||||
/// [`RuntimeExt::wrap`]: fn@RuntimeExt::wrap
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(not(target_family = "wasm"))]
|
||||
/// # {
|
||||
/// use tokio::time::{sleep, Duration};
|
||||
/// use tokio_util::context::TokioContext;
|
||||
///
|
||||
@@ -109,6 +114,7 @@ impl<F> TokioContext<F> {
|
||||
///
|
||||
/// // Execute the future on rt2.
|
||||
/// rt2.block_on(fut);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new(future: F, handle: Handle) -> TokioContext<F> {
|
||||
TokioContext {
|
||||
@@ -153,6 +159,8 @@ pub trait RuntimeExt {
|
||||
/// execute a [`sleep`] future on the runtime with timing disabled.
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(not(target_family = "wasm"))]
|
||||
/// # {
|
||||
/// use tokio::time::{sleep, Duration};
|
||||
/// use tokio_util::context::RuntimeExt;
|
||||
///
|
||||
@@ -172,6 +180,7 @@ pub trait RuntimeExt {
|
||||
///
|
||||
/// // Execute the future on rt2.
|
||||
/// rt2.block_on(fut);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`TokioContext`]: struct@crate::context::TokioContext
|
||||
|
||||
+12
-12
@@ -46,18 +46,18 @@ use tokio::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf, Result}
|
||||
/// # async fn some_async_function() -> u32 { 10 }
|
||||
/// # async fn other_async_function() -> u32 { 20 }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let result = if some_condition() {
|
||||
/// Either::Left(some_async_function())
|
||||
/// } else {
|
||||
/// Either::Right(other_async_function())
|
||||
/// };
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let result = if some_condition() {
|
||||
/// Either::Left(some_async_function())
|
||||
/// } else {
|
||||
/// Either::Right(other_async_function())
|
||||
/// };
|
||||
///
|
||||
/// let value = result.await;
|
||||
/// println!("Result is {}", value);
|
||||
/// # assert_eq!(value, 10);
|
||||
/// }
|
||||
/// let value = result.await;
|
||||
/// println!("Result is {}", value);
|
||||
/// # assert_eq!(value, 10);
|
||||
/// # }
|
||||
/// ```
|
||||
#[allow(missing_docs)] // Doc-comments for variants in this particular case don't make much sense.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -212,7 +212,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, not(loom)))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tokio::io::{repeat, AsyncReadExt, Repeat};
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
//! An extension trait for Futures that provides a variety of convenient adapters.
|
||||
|
||||
mod with_cancellation_token;
|
||||
use with_cancellation_token::{WithCancellationTokenFuture, WithCancellationTokenFutureOwned};
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
use crate::sync::CancellationToken;
|
||||
|
||||
/// A trait which contains a variety of convenient adapters and utilities for `Future`s.
|
||||
pub trait FutureExt: Future {
|
||||
cfg_time! {
|
||||
/// A wrapper around [`tokio::time::timeout`], with the advantage that it is easier to write
|
||||
/// fluent call chains.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::{sync::oneshot, time::Duration};
|
||||
/// use tokio_util::future::FutureExt;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let (_tx, rx) = oneshot::channel::<()>();
|
||||
///
|
||||
/// let res = rx.timeout(Duration::from_millis(10)).await;
|
||||
/// assert!(res.is_err());
|
||||
/// # }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
fn timeout(self, timeout: std::time::Duration) -> tokio::time::Timeout<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
tokio::time::timeout(timeout, self)
|
||||
}
|
||||
|
||||
/// A wrapper around [`tokio::time::timeout_at`], with the advantage that it is easier to write
|
||||
/// fluent call chains.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::{sync::oneshot, time::{Duration, Instant}};
|
||||
/// use tokio_util::future::FutureExt;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let (_tx, rx) = oneshot::channel::<()>();
|
||||
/// let deadline = Instant::now() + Duration::from_millis(10);
|
||||
///
|
||||
/// let res = rx.timeout_at(deadline).await;
|
||||
/// assert!(res.is_err());
|
||||
/// # }
|
||||
/// ```
|
||||
fn timeout_at(self, deadline: tokio::time::Instant) -> tokio::time::Timeout<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
tokio::time::timeout_at(deadline, self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to [`CancellationToken::run_until_cancelled`],
|
||||
/// but with the advantage that it is easier to write fluent call chains.
|
||||
///
|
||||
/// # Fairness
|
||||
///
|
||||
/// Calling this on an already-cancelled token directly returns `None`.
|
||||
/// For all subsequent polls, in case of concurrent completion and
|
||||
/// cancellation, this is biased towards the `self` future completion.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::sync::oneshot;
|
||||
/// use tokio_util::future::FutureExt;
|
||||
/// use tokio_util::sync::CancellationToken;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let (_tx, rx) = oneshot::channel::<()>();
|
||||
/// let token = CancellationToken::new();
|
||||
/// let token_clone = token.clone();
|
||||
/// tokio::spawn(async move {
|
||||
/// tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
/// token.cancel();
|
||||
/// });
|
||||
/// assert!(rx.with_cancellation_token(&token_clone).await.is_none())
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_cancellation_token(
|
||||
self,
|
||||
cancellation_token: &CancellationToken,
|
||||
) -> WithCancellationTokenFuture<'_, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
WithCancellationTokenFuture::new(cancellation_token, self)
|
||||
}
|
||||
|
||||
/// Similar to [`CancellationToken::run_until_cancelled_owned`],
|
||||
/// but with the advantage that it is easier to write fluent call chains.
|
||||
///
|
||||
/// # Fairness
|
||||
///
|
||||
/// Calling this on an already-cancelled token directly returns `None`.
|
||||
/// For all subsequent polls, in case of concurrent completion and
|
||||
/// cancellation, this is biased towards the `self` future completion.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::sync::oneshot;
|
||||
/// use tokio_util::future::FutureExt;
|
||||
/// use tokio_util::sync::CancellationToken;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let (_tx, rx) = oneshot::channel::<()>();
|
||||
/// let token = CancellationToken::new();
|
||||
/// let token_clone = token.clone();
|
||||
/// tokio::spawn(async move {
|
||||
/// tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
/// token.cancel();
|
||||
/// });
|
||||
/// assert!(rx.with_cancellation_token_owned(token_clone).await.is_none())
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_cancellation_token_owned(
|
||||
self,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> WithCancellationTokenFutureOwned<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
WithCancellationTokenFutureOwned::new(cancellation_token, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Future + ?Sized> FutureExt for T {}
|
||||
@@ -0,0 +1,79 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::sync::{CancellationToken, RunUntilCancelledFuture, RunUntilCancelledFutureOwned};
|
||||
|
||||
pin_project! {
|
||||
/// A [`Future`] that is resolved once the corresponding [`CancellationToken`]
|
||||
/// is cancelled or a given [`Future`] gets resolved.
|
||||
///
|
||||
/// This future is immediately resolved if the corresponding [`CancellationToken`]
|
||||
/// is already cancelled, otherwise, in case of concurrent completion and
|
||||
/// cancellation, this is biased towards the future completion.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct WithCancellationTokenFuture<'a, F: Future> {
|
||||
#[pin]
|
||||
run_until_cancelled: Option<RunUntilCancelledFuture<'a, F>>
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: Future> WithCancellationTokenFuture<'a, F> {
|
||||
pub(crate) fn new(cancellation_token: &'a CancellationToken, future: F) -> Self {
|
||||
Self {
|
||||
run_until_cancelled: (!cancellation_token.is_cancelled())
|
||||
.then(|| RunUntilCancelledFuture::new(cancellation_token, future)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: Future> Future for WithCancellationTokenFuture<'a, F> {
|
||||
type Output = Option<F::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
match this.run_until_cancelled.as_pin_mut() {
|
||||
Some(fut) => fut.poll(cx),
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A [`Future`] that is resolved once the corresponding [`CancellationToken`]
|
||||
/// is cancelled or a given [`Future`] gets resolved.
|
||||
///
|
||||
/// This future is immediately resolved if the corresponding [`CancellationToken`]
|
||||
/// is already cancelled, otherwise, in case of concurrent completion and
|
||||
/// cancellation, this is biased towards the future completion.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct WithCancellationTokenFutureOwned<F: Future> {
|
||||
#[pin]
|
||||
run_until_cancelled: Option<RunUntilCancelledFutureOwned<F>>
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> WithCancellationTokenFutureOwned<F> {
|
||||
pub(crate) fn new(cancellation_token: CancellationToken, future: F) -> Self {
|
||||
Self {
|
||||
run_until_cancelled: (!cancellation_token.is_cancelled())
|
||||
.then(|| RunUntilCancelledFutureOwned::new(cancellation_token, future)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for WithCancellationTokenFutureOwned<F> {
|
||||
type Output = Option<F::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
match this.run_until_cancelled.as_pin_mut() {
|
||||
Some(fut) => fut.poll(cx),
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,10 @@ mod copy_to_bytes;
|
||||
mod inspect;
|
||||
mod read_buf;
|
||||
mod reader_stream;
|
||||
pub mod simplex;
|
||||
mod sink_writer;
|
||||
mod stream_reader;
|
||||
mod write_all_vectored;
|
||||
|
||||
cfg_io_util! {
|
||||
mod read_arc;
|
||||
@@ -31,4 +33,5 @@ pub use self::read_buf::read_buf;
|
||||
pub use self::reader_stream::ReaderStream;
|
||||
pub use self::sink_writer::SinkWriter;
|
||||
pub use self::stream_reader::StreamReader;
|
||||
pub use self::write_all_vectored::{write_all_vectored, WriteAllVectored};
|
||||
pub use crate::util::{poll_read_buf, poll_write_buf};
|
||||
|
||||
@@ -10,7 +10,7 @@ use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// use tokio_util::io::read_exact_arc;
|
||||
///
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use bytes::BufMut;
|
||||
use std::future::Future;
|
||||
use std::future::poll_fn;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
/// Read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
|
||||
@@ -16,7 +15,7 @@ use tokio::io::AsyncRead;
|
||||
/// use tokio_stream as stream;
|
||||
/// use tokio::io::Result;
|
||||
/// use tokio_util::io::{StreamReader, read_buf};
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a reader from an iterator. This particular reader will always be
|
||||
@@ -46,20 +45,5 @@ where
|
||||
R: AsyncRead + Unpin,
|
||||
B: BufMut,
|
||||
{
|
||||
return ReadBufFn(read, buf).await;
|
||||
|
||||
struct ReadBufFn<'a, R, B>(&'a mut R, &'a mut B);
|
||||
|
||||
impl<'a, R, B> Future for ReadBufFn<'a, R, B>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
B: BufMut,
|
||||
{
|
||||
type Output = io::Result<usize>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = &mut *self;
|
||||
crate::util::poll_read_buf(Pin::new(this.0), cx, this.1)
|
||||
}
|
||||
}
|
||||
poll_fn(|cx| crate::util::poll_read_buf(Pin::new(read), cx, buf)).await
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ pin_project! {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// use tokio_stream::StreamExt;
|
||||
/// use tokio_util::io::ReaderStream;
|
||||
@@ -58,6 +58,11 @@ impl<R: AsyncRead> ReaderStream<R> {
|
||||
/// Convert an [`AsyncRead`] into a [`Stream`] with item type
|
||||
/// `Result<Bytes, std::io::Error>`.
|
||||
///
|
||||
/// Currently, the default capacity 4096 bytes (4 KiB).
|
||||
/// This capacity is not part of the semver contract
|
||||
/// and may be tweaked in future releases without
|
||||
/// requiring a major version bump.
|
||||
///
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
pub fn new(reader: R) -> Self {
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
//! Unidirectional byte-oriented channel.
|
||||
|
||||
use crate::util::poll_proceed;
|
||||
|
||||
use bytes::Buf;
|
||||
use bytes::BytesMut;
|
||||
use futures_core::ready;
|
||||
use std::io::Error as IoError;
|
||||
use std::io::ErrorKind as IoErrorKind;
|
||||
use std::io::IoSlice;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
type IoResult<T> = Result<T, IoError>;
|
||||
|
||||
const CLOSED_ERROR_MSG: &str = "simplex has been closed";
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
/// `poll_write` will return [`Poll::Pending`] if the backpressure boundary is reached
|
||||
backpressure_boundary: usize,
|
||||
|
||||
/// either [`Sender`] or [`Receiver`] is closed
|
||||
is_closed: bool,
|
||||
|
||||
/// Waker used to wake the [`Receiver`]
|
||||
receiver_waker: Option<Waker>,
|
||||
|
||||
/// Waker used to wake the [`Sender`]
|
||||
sender_waker: Option<Waker>,
|
||||
|
||||
/// Buffer used to read and write data
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
backpressure_boundary: capacity,
|
||||
is_closed: false,
|
||||
receiver_waker: None,
|
||||
sender_waker: None,
|
||||
buf: BytesMut::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_receiver_waker(&mut self, waker: &Waker) -> Option<Waker> {
|
||||
match self.receiver_waker.as_mut() {
|
||||
Some(old) if old.will_wake(waker) => None,
|
||||
_ => self.receiver_waker.replace(waker.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_sender_waker(&mut self, waker: &Waker) -> Option<Waker> {
|
||||
match self.sender_waker.as_mut() {
|
||||
Some(old) if old.will_wake(waker) => None,
|
||||
_ => self.sender_waker.replace(waker.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_receiver_waker(&mut self) -> Option<Waker> {
|
||||
self.receiver_waker.take()
|
||||
}
|
||||
|
||||
fn take_sender_waker(&mut self) -> Option<Waker> {
|
||||
self.sender_waker.take()
|
||||
}
|
||||
|
||||
fn is_closed(&self) -> bool {
|
||||
self.is_closed
|
||||
}
|
||||
|
||||
fn close_receiver(&mut self) -> Option<Waker> {
|
||||
self.is_closed = true;
|
||||
self.take_sender_waker()
|
||||
}
|
||||
|
||||
fn close_sender(&mut self) -> Option<Waker> {
|
||||
self.is_closed = true;
|
||||
self.take_receiver_waker()
|
||||
}
|
||||
}
|
||||
|
||||
/// Receiver of the simplex channel.
|
||||
///
|
||||
/// # Cancellation safety
|
||||
///
|
||||
/// The `Receiver` is cancel safe. If it is used as the event in a
|
||||
/// [`tokio::select!`] statement and some other branch completes
|
||||
/// first, it is guaranteed that no bytes were received on this
|
||||
/// channel.
|
||||
///
|
||||
/// You can still read the remaining data from the buffer
|
||||
/// even if the write half has been dropped.
|
||||
/// See [`Sender::poll_shutdown`] and [`Sender::drop`] for more details.
|
||||
///
|
||||
/// [`tokio::select!`]: https://docs.rs/tokio/latest/tokio/macro.select.html
|
||||
#[derive(Debug)]
|
||||
pub struct Receiver {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
}
|
||||
|
||||
impl Drop for Receiver {
|
||||
/// This also wakes up the [`Sender`].
|
||||
fn drop(&mut self) {
|
||||
let maybe_waker = {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.close_receiver()
|
||||
};
|
||||
|
||||
if let Some(waker) = maybe_waker {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Receiver {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<IoResult<()>> {
|
||||
let coop = ready!(poll_proceed(cx));
|
||||
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
let to_read = buf.remaining().min(inner.buf.remaining());
|
||||
if to_read == 0 {
|
||||
if inner.is_closed() || buf.remaining() == 0 {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
let old_waker = inner.register_receiver_waker(cx.waker());
|
||||
let maybe_waker = inner.take_sender_waker();
|
||||
|
||||
// unlock before waking up and dropping old waker
|
||||
drop(inner);
|
||||
drop(old_waker);
|
||||
if let Some(waker) = maybe_waker {
|
||||
waker.wake();
|
||||
}
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// this is to avoid starving other tasks
|
||||
coop.made_progress();
|
||||
|
||||
buf.put_slice(&inner.buf[..to_read]);
|
||||
inner.buf.advance(to_read);
|
||||
|
||||
let waker = inner.take_sender_waker();
|
||||
drop(inner); // unlock before waking up
|
||||
if let Some(waker) = waker {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Sender of the simplex channel.
|
||||
///
|
||||
/// # Cancellation safety
|
||||
///
|
||||
/// The `Sender` is cancel safe. If it is used as the event in a
|
||||
/// [`tokio::select!`] statement and some other branch completes
|
||||
/// first, it is guaranteed that no bytes were sent on this channel.
|
||||
///
|
||||
/// # Shutdown
|
||||
///
|
||||
/// See [`Sender::poll_shutdown`].
|
||||
///
|
||||
/// [`tokio::select!`]: https://docs.rs/tokio/latest/tokio/macro.select.html
|
||||
#[derive(Debug)]
|
||||
pub struct Sender {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
}
|
||||
|
||||
impl Drop for Sender {
|
||||
/// This also wakes up the [`Receiver`].
|
||||
fn drop(&mut self) {
|
||||
let maybe_waker = {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.close_sender()
|
||||
};
|
||||
|
||||
if let Some(waker) = maybe_waker {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Sender {
|
||||
/// # Errors
|
||||
///
|
||||
/// This method will return [`IoErrorKind::BrokenPipe`]
|
||||
/// if the channel has been closed.
|
||||
fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<IoResult<usize>> {
|
||||
let coop = ready!(poll_proceed(cx));
|
||||
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
if inner.is_closed() {
|
||||
return Poll::Ready(Err(IoError::new(IoErrorKind::BrokenPipe, CLOSED_ERROR_MSG)));
|
||||
}
|
||||
|
||||
let free = inner
|
||||
.backpressure_boundary
|
||||
.checked_sub(inner.buf.len())
|
||||
.expect("backpressure boundary overflow");
|
||||
let to_write = buf.len().min(free);
|
||||
if to_write == 0 {
|
||||
if buf.is_empty() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let old_waker = inner.register_sender_waker(cx.waker());
|
||||
let waker = inner.take_receiver_waker();
|
||||
|
||||
// unlock before waking up and dropping old waker
|
||||
drop(inner);
|
||||
drop(old_waker);
|
||||
if let Some(waker) = waker {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// this is to avoid starving other tasks
|
||||
coop.made_progress();
|
||||
|
||||
inner.buf.extend_from_slice(&buf[..to_write]);
|
||||
|
||||
let waker = inner.take_receiver_waker();
|
||||
drop(inner); // unlock before waking up
|
||||
if let Some(waker) = waker {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(to_write))
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
///
|
||||
/// This method will return [`IoErrorKind::BrokenPipe`]
|
||||
/// if the channel has been closed.
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<IoResult<()>> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
if inner.is_closed() {
|
||||
Poll::Ready(Err(IoError::new(IoErrorKind::BrokenPipe, CLOSED_ERROR_MSG)))
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
/// After returns [`Poll::Ready`], all the following call to
|
||||
/// [`Sender::poll_write`] and [`Sender::poll_flush`]
|
||||
/// will return error.
|
||||
///
|
||||
/// The [`Receiver`] can still be used to read remaining data
|
||||
/// until all bytes have been consumed.
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<IoResult<()>> {
|
||||
let maybe_waker = {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.close_sender()
|
||||
};
|
||||
|
||||
if let Some(waker) = maybe_waker {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<Result<usize, IoError>> {
|
||||
let coop = ready!(poll_proceed(cx));
|
||||
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if inner.is_closed() {
|
||||
return Poll::Ready(Err(IoError::new(IoErrorKind::BrokenPipe, CLOSED_ERROR_MSG)));
|
||||
}
|
||||
|
||||
let free = inner
|
||||
.backpressure_boundary
|
||||
.checked_sub(inner.buf.len())
|
||||
.expect("backpressure boundary overflow");
|
||||
if free == 0 {
|
||||
let old_waker = inner.register_sender_waker(cx.waker());
|
||||
let maybe_waker = inner.take_receiver_waker();
|
||||
|
||||
// unlock before waking up and dropping old waker
|
||||
drop(inner);
|
||||
drop(old_waker);
|
||||
if let Some(waker) = maybe_waker {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// this is to avoid starving other tasks
|
||||
coop.made_progress();
|
||||
|
||||
let mut rem = free;
|
||||
for buf in bufs {
|
||||
if rem == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let to_write = buf.len().min(rem);
|
||||
if to_write == 0 {
|
||||
assert_ne!(rem, 0);
|
||||
assert_eq!(buf.len(), 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
inner.buf.extend_from_slice(&buf[..to_write]);
|
||||
rem -= to_write;
|
||||
}
|
||||
|
||||
let waker = inner.take_receiver_waker();
|
||||
drop(inner); // unlock before waking up
|
||||
if let Some(waker) = waker {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(free - rem))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a simplex channel.
|
||||
///
|
||||
/// The `capacity` parameter specifies the maximum number of bytes that can be
|
||||
/// stored in the channel without making the [`Sender::poll_write`]
|
||||
/// return [`Poll::Pending`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if `capacity` is zero.
|
||||
pub fn new(capacity: usize) -> (Sender, Receiver) {
|
||||
assert_ne!(capacity, 0, "capacity must be greater than zero");
|
||||
|
||||
let inner = Arc::new(Mutex::new(Inner::with_capacity(capacity)));
|
||||
let tx = Sender {
|
||||
inner: Arc::clone(&inner),
|
||||
};
|
||||
let rx = Receiver { inner };
|
||||
(tx, rx)
|
||||
}
|
||||
@@ -57,15 +57,15 @@ use tokio::io::{
|
||||
/// let hash = blake3::hash(&data);
|
||||
///
|
||||
/// Ok(hash)
|
||||
///}
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// hash_contents(reader).await
|
||||
/// }
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// hash_contents(reader).await
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// When the data doesn't fit into memory, the hashing library will usually
|
||||
@@ -88,7 +88,7 @@ use tokio::io::{
|
||||
/// /// and hashes the data incrementally.
|
||||
/// async fn hash_stream(mut reader: impl AsyncRead + Unpin, mut hasher: Hasher) -> Result<(), std::io::Error> {
|
||||
/// // Create a buffer to read data into, sized for performance.
|
||||
/// let mut data = vec![0; 64 * 1024];
|
||||
/// let mut data = vec![0; 16 * 1024];
|
||||
/// loop {
|
||||
/// // Read data from the reader into the buffer.
|
||||
/// let len = reader.read(&mut data).await?;
|
||||
@@ -102,16 +102,16 @@ use tokio::io::{
|
||||
/// let hash = hasher.finalize();
|
||||
///
|
||||
/// Ok(hash)
|
||||
///}
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// let hasher = Hasher;
|
||||
/// hash_stream(reader, hasher).await
|
||||
/// }
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// let hasher = Hasher;
|
||||
/// hash_stream(reader, hasher).await
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
@@ -218,6 +218,8 @@ use tokio::io::{
|
||||
/// thread pool, preventing it from interfering with the async tasks.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(not(target_family = "wasm"))]
|
||||
/// # {
|
||||
/// use tokio::task::spawn_blocking;
|
||||
/// use tokio_util::io::SyncIoBridge;
|
||||
/// use tokio::io::AsyncRead;
|
||||
@@ -255,6 +257,7 @@ use tokio::io::{
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
use tokio::io::AsyncWrite;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::marker::PhantomPinned;
|
||||
use std::pin::Pin;
|
||||
use std::task::{ready, Context, Poll};
|
||||
use std::{future::Future, io::IoSlice};
|
||||
use std::{io, mem};
|
||||
|
||||
pin_project! {
|
||||
/// A future that writes all data from multiple buffers to a writer.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct WriteAllVectored<'a, 'b, W: ?Sized> {
|
||||
writer: &'a mut W,
|
||||
bufs: &'a mut [IoSlice<'b>],
|
||||
// Make this future `!Unpin` for compatibility with async trait methods.
|
||||
#[pin]
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
/// Like [`write_all`] but writes all data from multiple buffers into this writer.
|
||||
///
|
||||
/// This function writes multiple (possibly non-contiguous) buffers into the writer,
|
||||
/// using the `writev` syscall to potentially write in a single system call.
|
||||
///
|
||||
/// Equivalent to:
|
||||
///
|
||||
/// ```ignore
|
||||
/// async fn write_all_vectored<W: AsyncWrite + Unpin + ?Sized>(
|
||||
/// writer: &mut W,
|
||||
/// mut bufs: &mut [IoSlice<'_>]
|
||||
/// ) -> io::Result<()> {
|
||||
/// while !bufs.is_empty() {
|
||||
/// let n = write_vectored(writer, bufs).await?;
|
||||
/// if n == 0 {
|
||||
/// return Err(io::ErrorKind::WriteZero.into());
|
||||
/// }
|
||||
/// IoSlice::advance_slices(&mut bufs, n);
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is not cancellation safe. If it is used as the event
|
||||
/// in a `tokio::select!` statement and some other
|
||||
/// branch completes first, then the provided buffer may have been
|
||||
/// partially written, but future calls to `write_all_vectored` will
|
||||
/// have lost its place in the buffer.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio_util::io::write_all_vectored;
|
||||
/// use std::io::IoSlice;
|
||||
///
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// let mut writer = Vec::new();
|
||||
/// let bufs = &mut [
|
||||
/// IoSlice::new(&[1]),
|
||||
/// IoSlice::new(&[2, 3]),
|
||||
/// IoSlice::new(&[4, 5, 6]),
|
||||
/// ];
|
||||
///
|
||||
/// write_all_vectored(&mut writer, bufs).await?;
|
||||
///
|
||||
/// // Note: `bufs` has been modified by `IoSlice::advance_slices` and should not be reused.
|
||||
/// assert_eq!(writer, &[1, 2, 3, 4, 5, 6]);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// See the documentation for [`Write::write_all_vectored`] from std.
|
||||
/// After calling this function, the buffer slices may have
|
||||
/// been advanced and should not be reused.
|
||||
///
|
||||
/// [`Write::write_all_vectored`]: std::io::Write::write_all_vectored
|
||||
/// [`write_all`]: tokio::io::AsyncWriteExt::write_all
|
||||
/// [`writev`]: https://man7.org/linux/man-pages/man3/writev.3p.html
|
||||
pub fn write_all_vectored<'a, 'b, W>(
|
||||
writer: &'a mut W,
|
||||
bufs: &'a mut [IoSlice<'b>],
|
||||
) -> WriteAllVectored<'a, 'b, W>
|
||||
where
|
||||
W: AsyncWrite + Unpin + ?Sized,
|
||||
{
|
||||
WriteAllVectored {
|
||||
writer,
|
||||
bufs,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Future for WriteAllVectored<'_, '_, W>
|
||||
where
|
||||
W: AsyncWrite + Unpin + ?Sized,
|
||||
{
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
let me = self.project();
|
||||
while !me.bufs.is_empty() {
|
||||
// advance to first non-empty buffer
|
||||
let non_empty = match me.bufs.iter().position(|b| !b.is_empty()) {
|
||||
Some(pos) => pos,
|
||||
None => return Poll::Ready(Ok(())),
|
||||
};
|
||||
|
||||
// drop empty buffers at the start
|
||||
*me.bufs = &mut mem::take(me.bufs)[non_empty..];
|
||||
|
||||
let n = ready!(Pin::new(&mut *me.writer).poll_write_vectored(cx, me.bufs))?;
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
|
||||
}
|
||||
self::advance_slices(me.bufs, n);
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
// copied from `std::IoSlice::advance_slices`
|
||||
// replace with method when MSRV is 1.81.0
|
||||
fn advance_slices<'a>(bufs: &mut &mut [IoSlice<'a>], n: usize) {
|
||||
// Number of buffers to remove.
|
||||
let mut remove = 0;
|
||||
// Remaining length before reaching n. This prevents overflow
|
||||
// that could happen if the length of slices in `bufs` were instead
|
||||
// accumulated. Those slice may be aliased and, if they are large
|
||||
// enough, their added length may overflow a `usize`.
|
||||
let mut left = n;
|
||||
for buf in bufs.iter() {
|
||||
if let Some(remainder) = left.checked_sub(buf.len()) {
|
||||
left = remainder;
|
||||
remove += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*bufs = &mut std::mem::take(bufs)[remove..];
|
||||
if let Some(first) = bufs.first_mut() {
|
||||
let buf = &first[..left];
|
||||
// necessary due to limitating in the borrow checker,
|
||||
// when tokio MSRV reaches 1.81.0 this entire function
|
||||
// can be replaced with `IoSlice::advance_slices`
|
||||
//
|
||||
// SAFETY: transmute a sub-slice of an IoSlice<'a> back to
|
||||
// the lifetime `'a`. This is safe because the underlying memory
|
||||
// is guaranteed to live for 'a, we have shared access, and no
|
||||
// underlying data is reinterpreted to a different type.
|
||||
unsafe {
|
||||
*first = IoSlice::new(std::mem::transmute::<&[u8], &'a [u8]>(buf));
|
||||
}
|
||||
} else {
|
||||
assert!(left == 0, "advancing io slices beyond their length");
|
||||
}
|
||||
}
|
||||
@@ -45,9 +45,11 @@ cfg_io! {
|
||||
|
||||
cfg_rt! {
|
||||
pub mod context;
|
||||
pub mod task;
|
||||
}
|
||||
|
||||
#[cfg(feature = "rt")]
|
||||
pub mod task;
|
||||
|
||||
cfg_time! {
|
||||
pub mod time;
|
||||
}
|
||||
@@ -59,3 +61,5 @@ pub mod either;
|
||||
pub use bytes;
|
||||
|
||||
mod util;
|
||||
|
||||
pub mod future;
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
pub(crate) use std::sync;
|
||||
//! This module abstracts over `loom` and `std::sync` types depending on whether we
|
||||
//! are running loom tests or not.
|
||||
|
||||
pub(crate) mod sync {
|
||||
#[cfg(all(test, loom))]
|
||||
pub(crate) use loom::sync::{Arc, Mutex, MutexGuard};
|
||||
#[cfg(not(all(test, loom)))]
|
||||
pub(crate) use std::sync::{Arc, Mutex, MutexGuard};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg(not(loom))]
|
||||
|
||||
//! TCP/UDP/Unix helpers for tokio.
|
||||
|
||||
use crate::either::Either;
|
||||
|
||||
@@ -281,34 +281,6 @@ impl CancellationToken {
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
pin_project! {
|
||||
/// A Future that is resolved once the corresponding [`CancellationToken`]
|
||||
/// is cancelled or a given Future gets resolved. It is biased towards the
|
||||
/// Future completion.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
struct RunUntilCancelledFuture<'a, F: Future> {
|
||||
#[pin]
|
||||
cancellation: WaitForCancellationFuture<'a>,
|
||||
#[pin]
|
||||
future: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: Future> Future for RunUntilCancelledFuture<'a, F> {
|
||||
type Output = Option<F::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
if let Poll::Ready(res) = this.future.poll(cx) {
|
||||
Poll::Ready(Some(res))
|
||||
} else if this.cancellation.poll(cx).is_ready() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.is_cancelled() {
|
||||
None
|
||||
} else {
|
||||
@@ -439,3 +411,77 @@ impl Future for WaitForCancellationFutureOwned {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A Future that is resolved once the corresponding [`CancellationToken`]
|
||||
/// is cancelled or a given Future gets resolved. It is biased towards the
|
||||
/// Future completion.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub(crate) struct RunUntilCancelledFuture<'a, F: Future> {
|
||||
#[pin]
|
||||
cancellation: WaitForCancellationFuture<'a>,
|
||||
#[pin]
|
||||
future: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: Future> RunUntilCancelledFuture<'a, F> {
|
||||
pub(crate) fn new(cancellation_token: &'a CancellationToken, future: F) -> Self {
|
||||
Self {
|
||||
cancellation: cancellation_token.cancelled(),
|
||||
future,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: Future> Future for RunUntilCancelledFuture<'a, F> {
|
||||
type Output = Option<F::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
if let Poll::Ready(res) = this.future.poll(cx) {
|
||||
Poll::Ready(Some(res))
|
||||
} else if this.cancellation.poll(cx).is_ready() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A Future that is resolved once the corresponding [`CancellationToken`]
|
||||
/// is cancelled or a given Future gets resolved. It is biased towards the
|
||||
/// Future completion.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub(crate) struct RunUntilCancelledFutureOwned<F: Future> {
|
||||
#[pin]
|
||||
cancellation: WaitForCancellationFutureOwned,
|
||||
#[pin]
|
||||
future: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for RunUntilCancelledFutureOwned<F> {
|
||||
type Output = Option<F::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
if let Poll::Ready(res) = this.future.poll(cx) {
|
||||
Poll::Ready(Some(res))
|
||||
} else if this.cancellation.poll(cx).is_ready() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> RunUntilCancelledFutureOwned<F> {
|
||||
pub(crate) fn new(cancellation_token: CancellationToken, future: F) -> Self {
|
||||
Self {
|
||||
cancellation: cancellation_token.cancelled_owned(),
|
||||
future,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ pub use cancellation_token::{
|
||||
guard::DropGuard, guard_ref::DropGuardRef, CancellationToken, WaitForCancellationFuture,
|
||||
WaitForCancellationFutureOwned,
|
||||
};
|
||||
pub(crate) use cancellation_token::{RunUntilCancelledFuture, RunUntilCancelledFutureOwned};
|
||||
|
||||
mod mpsc;
|
||||
pub use mpsc::{PollSendError, PollSender};
|
||||
@@ -14,3 +15,6 @@ pub use poll_semaphore::PollSemaphore;
|
||||
|
||||
mod reusable_box;
|
||||
pub use reusable_box::ReusableBoxFuture;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user