mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
241
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46ff36386d | ||
|
|
0a1695ebfb | ||
|
|
c4f0178b74 | ||
|
|
22b3a65934 | ||
|
|
0d362339d5 | ||
|
|
ab7313ff6b | ||
|
|
92a3455c66 | ||
|
|
1968565825 | ||
|
|
c9273f1aee | ||
|
|
e05d0f8c2c | ||
|
|
debcb2254a | ||
|
|
83b7397e44 | ||
|
|
3991f9f9a4 | ||
|
|
48c0e6283f | ||
|
|
d561b5850a | ||
|
|
3a4aef17b2 | ||
|
|
a0a58d7edd | ||
|
|
ed4f766c98 | ||
|
|
3ac4cfb68a | ||
|
|
7232424a81 | ||
|
|
4c33ed33f6 | ||
|
|
3468b4b72f | ||
|
|
2400769b54 | ||
|
|
a8e8fa6681 | ||
|
|
498288cd31 | ||
|
|
7a30504fd4 | ||
|
|
340d4e5238 | ||
|
|
7b555185ff | ||
|
|
135d7ca38e | ||
|
|
e6720f985d | ||
|
|
d44e995bb0 | ||
|
|
06660ef00a | ||
|
|
2e5773a6fe | ||
|
|
49eb26f159 | ||
|
|
19d96c0674 | ||
|
|
30b2eb17c8 | ||
|
|
8ec3e0d94d | ||
|
|
161ecec156 | ||
|
|
61fcc3bc0b | ||
|
|
944024e8eb | ||
|
|
65f861f478 | ||
|
|
4c8580152d | ||
|
|
ed32cd194c | ||
|
|
593dbf55d1 | ||
|
|
d8a4a5f24b | ||
|
|
cc86fef9c0 | ||
|
|
f3949cc56d | ||
|
|
503fad7908 | ||
|
|
58acb56a17 | ||
|
|
d22c549d97 | ||
|
|
bc48a6fa8d | ||
|
|
70410836ae | ||
|
|
881b510a07 | ||
|
|
6871084629 | ||
|
|
1b8ebfcffb | ||
|
|
654a3d5acf | ||
|
|
a08ad926b1 | ||
|
|
723934242b | ||
|
|
944f769cd5 | ||
|
|
f3ad6cffd9 | ||
|
|
f1e41a4ad4 | ||
|
|
f9335b8186 | ||
|
|
1134cbb168 | ||
|
|
339c78a680 | ||
|
|
c00861210b | ||
|
|
0f296d2089 | ||
|
|
d420d528f9 | ||
|
|
0457690d01 | ||
|
|
4557451257 | ||
|
|
2bd43765d9 | ||
|
|
f306bd02c3 | ||
|
|
6b010ac80f | ||
|
|
8cd3383913 | ||
|
|
d6ed00c292 | ||
|
|
5d29136a83 | ||
|
|
52b29b33bb | ||
|
|
eaba9712e8 | ||
|
|
310adf7ca6 | ||
|
|
0700d6a7cd | ||
|
|
ca89c5b2ec | ||
|
|
453c720709 | ||
|
|
e76c06ba38 | ||
|
|
02aacf5110 | ||
|
|
707fb4d0df | ||
|
|
b161633b5f | ||
|
|
f5b8cf9dac | ||
|
|
aa36807c02 | ||
|
|
74c7a87985 | ||
|
|
ccb37c4f39 | ||
|
|
9ab4ca68ac | ||
|
|
60a0ca58fa | ||
|
|
938c7eb023 | ||
|
|
bfa9ea8d9b | ||
|
|
9bc782acfc | ||
|
|
3f6165d82e | ||
|
|
ad7f988da3 | ||
|
|
9d51b76d01 | ||
|
|
804511822b | ||
|
|
e6553c4ee3 | ||
|
|
98bb3be094 | ||
|
|
d247e7f5df | ||
|
|
65e7715909 | ||
|
|
61042b4d90 | ||
|
|
1c428cc558 | ||
|
|
61f095fdc1 | ||
|
|
65027b60bc | ||
|
|
b046c0dcbb | ||
|
|
737dff40cb | ||
|
|
a6be73eecb | ||
|
|
9fafe783d3 | ||
|
|
fb3ae0a254 | ||
|
|
aad1892ab5 | ||
|
|
8ea303e027 | ||
|
|
84ed35ef70 | ||
|
|
95fb599664 | ||
|
|
8b312ee571 | ||
|
|
fd7d0ad5e5 | ||
|
|
37bb47c4a2 | ||
|
|
cb1e10b745 | ||
|
|
fb3028f3a2 | ||
|
|
b45f5831cf | ||
|
|
0fe24fcffa | ||
|
|
d1dae25cd2 | ||
|
|
59c9364689 | ||
|
|
3b79be624d | ||
|
|
8955ed5f85 | ||
|
|
bc26934e3b | ||
|
|
3d64a06600 | ||
|
|
a7d52c2fed | ||
|
|
f5f2b58b8d | ||
|
|
718dcc8dff | ||
|
|
10e141d211 | ||
|
|
6e42c26c80 | ||
|
|
82bef00db4 | ||
|
|
40633fc678 | ||
|
|
3dd5f7ae2e | ||
|
|
2c92cad9db | ||
|
|
197757d440 | ||
|
|
8b8005ebdd | ||
|
|
6cb106c353 | ||
|
|
dd23f08c3a | ||
|
|
5d29bdfb0c | ||
|
|
4c220af777 | ||
|
|
0a631f88e8 | ||
|
|
ee44dc98d8 | ||
|
|
51cffbb74f | ||
|
|
8832e936b1 | ||
|
|
dbda2045f1 | ||
|
|
7c54fdce3e | ||
|
|
38d1bcd9df | ||
|
|
52e6510215 | ||
|
|
e5e88551d2 | ||
|
|
efe3ab679a | ||
|
|
fb08591b43 | ||
|
|
6aca07bee7 | ||
|
|
5128601898 | ||
|
|
c445e467ce | ||
|
|
a58beb3aca | ||
|
|
4165601b1b | ||
|
|
63577cd8d3 | ||
|
|
02544540f1 | ||
|
|
d64c8e3ae0 | ||
|
|
f24b9824e6 | ||
|
|
267a231581 | ||
|
|
05feb2b0bb | ||
|
|
33d6d4f63c | ||
|
|
6166e9bcad | ||
|
|
e52d56e807 | ||
|
|
304d140361 | ||
|
|
91ad76c00c | ||
|
|
74a5a458ea | ||
|
|
0d382faa4e | ||
|
|
d8847cf891 | ||
|
|
918cf08a5f | ||
|
|
fc69666f8a | ||
|
|
bb4512eae0 | ||
|
|
9dbf1879ee | ||
|
|
1b1b9dc7e3 | ||
|
|
012c848401 | ||
|
|
6e990eb2c8 | ||
|
|
b573adc733 | ||
|
|
0c7d8d10fb | ||
|
|
ec1f52e1d3 | ||
|
|
1bfe778acb | ||
|
|
ce23db6bc7 | ||
|
|
48c55768fd | ||
|
|
657fd883d2 | ||
|
|
6b076a2743 | ||
|
|
910a1e2fcf | ||
|
|
6d25a00145 | ||
|
|
78bf8a9e5e | ||
|
|
b8af5aad16 | ||
|
|
2e62374e4a | ||
|
|
56c4365584 | ||
|
|
fb0d305a7a | ||
|
|
848482d2bb | ||
|
|
00af6eff77 | ||
|
|
b7290910f7 | ||
|
|
af6c87a045 | ||
|
|
6257712d68 | ||
|
|
c5d52c17ae | ||
|
|
2a54ad01d0 | ||
|
|
cb18b0a231 | ||
|
|
7ccd3e0c6d | ||
|
|
e63d0f10bf | ||
|
|
a2941e48be | ||
|
|
1c8d22c18b | ||
|
|
cbb3c155dd | ||
|
|
038c4d9999 | ||
|
|
7b24b22901 | ||
|
|
779b9c19d5 | ||
|
|
1204da7300 | ||
|
|
e75ca93d30 | ||
|
|
15712018da | ||
|
|
076d77c186 | ||
|
|
e2853c1b49 | ||
|
|
8f0103f6c5 | ||
|
|
fb4d43017d | ||
|
|
1e14ef0093 | ||
|
|
a8b6353535 | ||
|
|
c748f4965e | ||
|
|
a96dab1089 | ||
|
|
79a7e78c0d | ||
|
|
7c12e41d07 | ||
|
|
7a99f87df2 | ||
|
|
0b2c9b8bab | ||
|
|
98c8c38e96 | ||
|
|
080d52902f | ||
|
|
e87ff8a83a | ||
|
|
1605279abf | ||
|
|
25258d572a | ||
|
|
8ddb58bf6c | ||
|
|
4b032a25a4 | ||
|
|
edd172cd32 | ||
|
|
9877fa2a97 | ||
|
|
0f898a3148 | ||
|
|
d6a9ef5333 | ||
|
|
2a180188c6 | ||
|
|
9f9db7da63 | ||
|
|
d274ef3748 | ||
|
|
5e6d4c7999 |
+1
-1
@@ -4,7 +4,7 @@ freebsd_instance:
|
||||
image_family: freebsd-13-1
|
||||
env:
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2022-10-25
|
||||
RUST_NIGHTLY: nightly-2023-10-21
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
|
||||
+25
-5
@@ -1,8 +1,28 @@
|
||||
|
||||
R-loom:
|
||||
R-loom-sync:
|
||||
- tokio/src/sync/*
|
||||
- tokio/src/sync/**/*
|
||||
- tokio-util/src/sync/*
|
||||
- tokio-util/src/sync/**/*
|
||||
- tokio/src/runtime/*
|
||||
- tokio/src/runtime/**/*
|
||||
|
||||
R-loom-time-driver:
|
||||
- tokio/src/runtime/time/*
|
||||
- tokio/src/runtime/time/**/*
|
||||
|
||||
R-loom-current-thread:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/current_thread/*
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-multi-thread:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-multi-thread-alt:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread_alt/*
|
||||
- tokio/src/runtime/scheduler/multi_thread_alt/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
+382
-148
@@ -6,12 +6,16 @@ on:
|
||||
|
||||
name: CI
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2023-05-18
|
||||
rust_nightly: nightly-2023-10-21
|
||||
rust_clippy: 1.65.0
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
@@ -21,7 +25,7 @@ env:
|
||||
# - tokio-util/Cargo.toml
|
||||
# - tokio-test/Cargo.toml
|
||||
# - tokio-stream/Cargo.toml
|
||||
rust_min: 1.56.0
|
||||
rust_min: 1.63.0
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -36,16 +40,21 @@ jobs:
|
||||
name: all systems go
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
- test-tokio-full
|
||||
- test-workspace-all-features
|
||||
- test-integration-tests-per-feature
|
||||
- test-parking_lot
|
||||
- test-tracing-instrumentation
|
||||
- valgrind
|
||||
- test-unstable
|
||||
- miri
|
||||
- asan
|
||||
- semver
|
||||
- cross-check
|
||||
- cross-test
|
||||
- no-atomic-u64
|
||||
- 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
|
||||
@@ -56,6 +65,7 @@ jobs:
|
||||
- check-readme
|
||||
- test-hyper
|
||||
- x86_64-fortanix-unknown-sgx
|
||||
- check-redox
|
||||
- wasm32-unknown-unknown
|
||||
- wasm32-wasi
|
||||
- check-external-types
|
||||
@@ -76,7 +86,7 @@ jobs:
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
test:
|
||||
test-tokio-full:
|
||||
needs: basics
|
||||
name: test tokio full
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -89,24 +99,77 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
# Run `tokio` with `full` features. This excludes testing utilities which
|
||||
# can alter the runtime behavior of Tokio.
|
||||
- name: test tokio full
|
||||
run: cargo test --features full
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --features full
|
||||
cargo test --doc --features full
|
||||
working-directory: tokio
|
||||
|
||||
test-workspace-all-features:
|
||||
needs: basics
|
||||
name: test all crates in the workspace with all features
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Test **all** crates in the workspace with all features.
|
||||
- name: test all --all-features
|
||||
run: cargo test --workspace --all-features
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --workspace --all-features
|
||||
cargo test --doc --workspace --all-features
|
||||
|
||||
test-integration-tests-per-feature:
|
||||
needs: basics
|
||||
name: Run integration tests for each feature
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# Run integration tests for each feature
|
||||
- name: test tests-integration --each-feature
|
||||
@@ -118,9 +181,9 @@ jobs:
|
||||
run: cargo hack test --each-feature
|
||||
working-directory: tests-build
|
||||
|
||||
# Build benchmarks. Run of benchmarks is done by bench.yml workflow.
|
||||
- name: build benches
|
||||
run: cargo build --benches
|
||||
# Check benchmarks. Run of benchmarks is done by bench.yml workflow.
|
||||
- name: Check benches
|
||||
run: cargo check --benches
|
||||
working-directory: benches
|
||||
# bench.yml workflow runs benchmarks only on linux.
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
@@ -139,15 +202,45 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Enable parking_lot send_guard feature
|
||||
# Inserts the line "plsend = ["parking_lot/send_guard"]" right after [features]
|
||||
run: sed -i '/\[features\]/a plsend = ["parking_lot/send_guard"]' tokio/Cargo.toml
|
||||
- name: Compile tests with all features enabled
|
||||
run: cargo build --workspace --all-features --tests
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Check tests with all features enabled
|
||||
run: cargo check --workspace --all-features --tests
|
||||
|
||||
test-tracing-instrumentation:
|
||||
# These tests use the as-yet unpublished `tracing-mock` crate to test the
|
||||
# tracing instrumentation present in Tokio. As such they are placed in
|
||||
# their own test crate outside of the workspace.
|
||||
needs: basics
|
||||
name: test tokio instrumentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: test tracing-instrumentation
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run
|
||||
working-directory: tokio/tests/tracing-instrumentation
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
@@ -156,14 +249,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Valgrind
|
||||
uses: taiki-e/install-action@valgrind
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Compile tests
|
||||
- name: cargo build test-mem
|
||||
run: cargo build --features rt-net --bin test-mem
|
||||
@@ -171,7 +264,7 @@ jobs:
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind test-mem
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/debug/test-mem
|
||||
|
||||
# Compile tests
|
||||
- name: cargo build test-process-signal
|
||||
@@ -180,7 +273,7 @@ jobs:
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind test-process-signal
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/test-process-signal
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/debug/test-process-signal
|
||||
|
||||
test-unstable:
|
||||
name: test tokio full --unstable
|
||||
@@ -195,20 +288,29 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" cfg flag.
|
||||
- name: test tokio full --cfg unstable
|
||||
run: cargo test --all-features
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable
|
||||
|
||||
|
||||
test-unstable-taskdump:
|
||||
name: test tokio full --unstable --taskdump
|
||||
needs: basics
|
||||
@@ -220,13 +322,22 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" and "taskdump" cfg flags.
|
||||
- name: test tokio full --cfg unstable --cfg taskdump
|
||||
run: cargo test --all-features
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
@@ -245,13 +356,20 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" and "taskdump" cfg flags.
|
||||
- name: check tokio full --cfg unstable --cfg internal-mt-counters
|
||||
run: cargo test --all-features
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_internal_mt_counters -Dwarnings
|
||||
@@ -266,7 +384,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: miri
|
||||
@@ -274,7 +392,8 @@ jobs:
|
||||
- name: miri
|
||||
# Many of tests in tokio/tests and doctests use #[tokio::test] or
|
||||
# #[tokio::main] that calls epoll_create1 that Miri does not support.
|
||||
run: cargo miri test --features full --lib --no-fail-fast
|
||||
run: |
|
||||
cargo miri test --features full --lib --no-fail-fast
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
@@ -289,14 +408,15 @@ jobs:
|
||||
# Required to resolve symbols in sanitizer output
|
||||
run: sudo apt-get install -y llvm
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: asan
|
||||
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1
|
||||
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1 --nocapture
|
||||
env:
|
||||
RUSTFLAGS: -Z sanitizer=address
|
||||
RUSTFLAGS: -Z sanitizer=address --cfg tokio_no_tuning_tests
|
||||
# Ignore `trybuild` errors as they are irrelevant and flaky on nightly
|
||||
TRYBUILD: overwrite
|
||||
|
||||
@@ -321,24 +441,46 @@ jobs:
|
||||
target:
|
||||
- powerpc-unknown-linux-gnu
|
||||
- powerpc64-unknown-linux-gnu
|
||||
- mips-unknown-linux-gnu
|
||||
- arm-linux-androideabi
|
||||
- mipsel-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
target: ${{ matrix.target }}
|
||||
- name: Install cross
|
||||
uses: taiki-e/install-action@cross
|
||||
- run: cross check --workspace --all-features --target ${{ matrix.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo check --workspace --all-features --target ${{ matrix.target }}
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
cross-test:
|
||||
name: cross-test
|
||||
cross-check-tier3:
|
||||
name: cross-check-tier3
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- name: armv7-sony-vita-newlibeabihf
|
||||
exclude_features: "process,signal,rt-process-signal,full"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: rust-src
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo hack check -Zbuild-std --workspace --each-feature --target ${{ matrix.target.name }} --exclude-features "${{ matrix.target.exclude_features }}"
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
cross-test-with-parking_lot:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -346,91 +488,163 @@ jobs:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
- target: armv5te-unknown-linux-gnueabi
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
|
||||
# Run a platform without AtomicU64 and no const Mutex::new
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
rustflags: --cfg tokio_no_const_mutex_new
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
target: ${{ matrix.target }}
|
||||
- name: Install cross
|
||||
uses: taiki-e/install-action@cross
|
||||
# First run with all features (including parking_lot)
|
||||
- run: cross test -p tokio --all-features --target ${{ matrix.target }} --tests
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Tests run with all features (including parking_lot)
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run -p tokio --all-features --target ${{ matrix.target }}
|
||||
cargo test --doc -p tokio --all-features --target ${{ matrix.target }}
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 ${{ matrix.rustflags }}
|
||||
# Now run without parking_lot
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
|
||||
|
||||
cross-test-without-parking_lot:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: armv5te-unknown-linux-gnueabi
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Remove `parking_lot` from `full` feature
|
||||
run: sed -i '0,/parking_lot/{/parking_lot/d;}' tokio/Cargo.toml
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# The `tokio_no_parking_lot` cfg is here to ensure the `sed` above does not silently break.
|
||||
- run: cross test -p tokio --features full,test-util --target ${{ matrix.target }} --tests
|
||||
- name: Tests run with all features (without parking_lot)
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run -p tokio --features full,test-util --target ${{ matrix.target }}
|
||||
cargo test --doc -p tokio --features full,test-util --target ${{ matrix.target }}
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 --cfg tokio_no_parking_lot ${{ matrix.rustflags }}
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_parking_lot --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
|
||||
|
||||
# See https://github.com/tokio-rs/tokio/issues/5187
|
||||
no-atomic-u64:
|
||||
name: Test i686-unknown-linux-gnu without AtomicU64
|
||||
no-atomic-u64-test:
|
||||
name: Test tokio --all-features on i686-unknown-linux-gnu without AtomicU64
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: rust-src
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: i686-unknown-linux-gnu
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test tokio --all-features
|
||||
run: |
|
||||
cargo nextest run -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
cargo test --doc -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
env:
|
||||
RUST_TEST_THREADS: 1
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_tuning_tests
|
||||
|
||||
no-atomic-u64-check:
|
||||
name: Check tokio --feature-powerset --depth 2 on i686-unknown-linux-gnu without AtomicU64
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: rust-src
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
# Install linker and libraries for i686-unknown-linux-gnu
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
target: i686-unknown-linux-gnu
|
||||
- run: cargo test -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64
|
||||
tool: cargo-hack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# https://github.com/tokio-rs/tokio/pull/5356
|
||||
# https://github.com/tokio-rs/tokio/issues/5373
|
||||
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
- name: Check
|
||||
run: cargo hack check -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --feature-powerset --depth 2 --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64 --cfg tokio_no_const_mutex_new
|
||||
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
|
||||
features:
|
||||
name: features
|
||||
name: features ${{ matrix.name }}
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { name: "", rustflags: "" }
|
||||
# Try with unstable feature flags
|
||||
- { name: "--unstable", rustflags: "--cfg tokio_unstable -Dwarnings" }
|
||||
# Try with unstable and taskdump feature flags
|
||||
- { name: "--unstable --taskdump", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump" }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
target: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
- name: check --feature-powerset
|
||||
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
# Try with unstable feature flags
|
||||
- name: check --feature-powerset --unstable
|
||||
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: check --feature-powerset ${{ matrix.name }}
|
||||
run: cargo hack check --all --feature-powerset --depth 2 --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
# Try with unstable and taskdump feature flags
|
||||
- name: check --feature-powerset --unstable --taskdump
|
||||
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
RUSTFLAGS: ${{ matrix.rustflags }}
|
||||
|
||||
minrust:
|
||||
name: minrust
|
||||
@@ -438,23 +652,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_min }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_min }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# First compile just the main tokio crate with minrust and newest version
|
||||
# of all dependencies, then pin once_cell and compile the rest of the
|
||||
# crates with the pinned once_cell version.
|
||||
#
|
||||
# This is necessary because tokio-util transitively depends on once_cell,
|
||||
# which is not compatible with the current minrust after the 1.15.0
|
||||
# release.
|
||||
- name: "check -p tokio --all-features"
|
||||
run: cargo check -p tokio --all-features
|
||||
env:
|
||||
RUSTFLAGS: "" # remove -Dwarnings
|
||||
- name: "pin once_cell version"
|
||||
run: cargo update -p once_cell --precise 1.14.0
|
||||
- name: "check --workspace --all-features"
|
||||
run: cargo check --workspace --all-features
|
||||
env:
|
||||
@@ -467,12 +668,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "check --all-features -Z minimal-versions"
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
@@ -498,7 +700,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
components: rustfmt
|
||||
@@ -507,8 +709,8 @@ jobs:
|
||||
- name: "rustfmt --check"
|
||||
# Workaround for rust-lang/cargo#7732
|
||||
run: |
|
||||
if ! rustfmt --check --edition 2018 $(git ls-files '*.rs'); then
|
||||
printf "Please run \`rustfmt --edition 2018 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
if ! rustfmt --check --edition 2021 $(git ls-files '*.rs'); then
|
||||
printf "Please run \`rustfmt --edition 2021 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -518,14 +720,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_clippy }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_clippy }}
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run clippy
|
||||
- name: "clippy --all"
|
||||
run: cargo clippy --all --tests --all-features
|
||||
run: cargo clippy --all --tests --all-features --no-deps
|
||||
|
||||
docs:
|
||||
name: docs
|
||||
@@ -533,12 +735,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
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 --all-features --document-private-items
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
@@ -550,7 +753,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
@@ -585,18 +788,23 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Test hyper
|
||||
|
||||
- name: Clone hyper
|
||||
run: git clone https://github.com/hyperium/hyper.git
|
||||
|
||||
- name: Checkout the latest release because HEAD maybe contains breakage.
|
||||
run: |
|
||||
set -x
|
||||
git clone https://github.com/hyperium/hyper.git
|
||||
cd hyper
|
||||
# checkout the latest release because HEAD maybe contains breakage.
|
||||
tag=$(git describe --abbrev=0 --tags)
|
||||
git checkout "${tag}"
|
||||
working-directory: hyper
|
||||
|
||||
- name: Patch hyper to use tokio from this repository
|
||||
run: |
|
||||
set -x
|
||||
echo '[workspace]' >>Cargo.toml
|
||||
echo '[patch.crates-io]' >>Cargo.toml
|
||||
echo 'tokio = { path = "../tokio" }' >>Cargo.toml
|
||||
@@ -604,7 +812,20 @@ jobs:
|
||||
echo 'tokio-stream = { path = "../tokio-stream" }' >>Cargo.toml
|
||||
echo 'tokio-test = { path = "../tokio-test" }' >>Cargo.toml
|
||||
git diff
|
||||
cargo test --features full
|
||||
working-directory: hyper
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
# The cargo workspaces and target directory configuration.
|
||||
# These entries are separated by newlines and have the form
|
||||
# `$workspace -> $target`. The `$target` part is treated as a directory
|
||||
# relative to the `$workspace` and defaults to "target" if not explicitly given.
|
||||
# default: ". -> target"
|
||||
workspaces: "./hyper"
|
||||
|
||||
- name: Test hyper
|
||||
run: cargo test --features full
|
||||
working-directory: hyper
|
||||
|
||||
x86_64-fortanix-unknown-sgx:
|
||||
name: build tokio for x86_64-fortanix-unknown-sgx
|
||||
@@ -613,7 +834,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
target: x86_64-fortanix-unknown-sgx
|
||||
@@ -623,6 +844,21 @@ jobs:
|
||||
run: cargo build --target x86_64-fortanix-unknown-sgx --features rt,sync
|
||||
working-directory: tokio
|
||||
|
||||
check-redox:
|
||||
name: build tokio for redox-os
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
target: x86_64-unknown-redox
|
||||
- name: check tokio on redox
|
||||
run: cargo check --target x86_64-unknown-redox --all-features
|
||||
working-directory: tokio
|
||||
|
||||
wasm32-unknown-unknown:
|
||||
name: test tokio for wasm32-unknown-unknown
|
||||
needs: basics
|
||||
@@ -630,12 +866,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
uses: taiki-e/install-action@wasm-pack
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test tokio
|
||||
run: wasm-pack test --node -- --features "macros sync"
|
||||
working-directory: tokio
|
||||
@@ -647,24 +884,18 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
targets: wasm32-wasi
|
||||
|
||||
# Install dependencies
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- name: Install wasm32-wasi target
|
||||
run: rustup target add wasm32-wasi
|
||||
|
||||
- name: Install wasmtime
|
||||
uses: taiki-e/install-action@wasmtime
|
||||
|
||||
- name: Install cargo-wasi
|
||||
run: cargo install cargo-wasi
|
||||
- name: Install cargo-hack, wasmtime, and cargo-wasi
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack,wasmtime,cargo-wasi
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: WASI test tokio full
|
||||
run: cargo test -p tokio --target wasm32-wasi --features full
|
||||
env:
|
||||
@@ -689,7 +920,7 @@ jobs:
|
||||
working-directory: tests-integration
|
||||
|
||||
check-external-types:
|
||||
name: check-external-types
|
||||
name: check-external-types (${{ matrix.os }})
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -697,20 +928,23 @@ jobs:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust nightly-2022-11-16
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
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-2022-11-16
|
||||
- nightly-2023-05-31
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v1
|
||||
with:
|
||||
tool: [email protected]
|
||||
- name: check-external-types
|
||||
run: |
|
||||
set -x
|
||||
cargo install cargo-check-external-types --locked --version 0.1.6
|
||||
cargo check-external-types --all-features --config external-types.toml
|
||||
run: cargo check-external-types --all-features --config external-types.toml
|
||||
working-directory: tokio
|
||||
|
||||
check-fuzzing:
|
||||
@@ -720,7 +954,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
@@ -4,6 +4,10 @@ on:
|
||||
|
||||
# See .github/labeler.yml file
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
+88
-20
@@ -7,8 +7,14 @@ on:
|
||||
|
||||
name: Loom
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUSTFLAGS: -Dwarnings --cfg loom --cfg tokio_unstable -C debug_assertions
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
LOOM_MAX_BRANCHES: 10000
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
@@ -17,26 +23,66 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
loom:
|
||||
name: loom
|
||||
loom-sync:
|
||||
name: loom tokio::sync
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom') || (github.base_ref == null))
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-sync') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture sync::tests
|
||||
working-directory: tokio
|
||||
|
||||
loom-time-driver:
|
||||
name: loom time driver
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture runtime::time::tests
|
||||
working-directory: tokio
|
||||
|
||||
loom-current-thread:
|
||||
name: loom current-thread scheduler
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture loom_current_thread
|
||||
working-directory: tokio
|
||||
|
||||
loom-multi-thread:
|
||||
name: loom multi-thread scheduler
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- scope: --skip loom_pool
|
||||
max_preemptions: 2
|
||||
- scope: loom_pool::group_a
|
||||
max_preemptions: 1
|
||||
- scope: loom_pool::group_b
|
||||
max_preemptions: 2
|
||||
- scope: loom_pool::group_c
|
||||
max_preemptions: 1
|
||||
- scope: loom_pool::group_d
|
||||
max_preemptions: 1
|
||||
- scope: time::driver
|
||||
max_preemptions: 2
|
||||
- scope: loom_multi_thread::group_a
|
||||
- scope: loom_multi_thread::group_b
|
||||
- scope: loom_multi_thread::group_c
|
||||
- scope: loom_multi_thread::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
@@ -45,10 +91,32 @@ jobs:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
loom-multi-thread-alt:
|
||||
name: loom ALT multi-thread scheduler
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread-alt') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- scope: loom_multi_thread_alt::group_a
|
||||
- scope: loom_multi_thread_alt::group_b
|
||||
- scope: loom_multi_thread_alt::group_c
|
||||
- scope: loom_multi_thread_alt::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings -C debug-assertions
|
||||
LOOM_MAX_PREEMPTIONS: ${{ matrix.max_preemptions }}
|
||||
LOOM_MAX_BRANCHES: 10000
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
@@ -8,6 +8,10 @@ on:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -28,14 +32,14 @@ jobs:
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install Valgrind
|
||||
uses: taiki-e/install-action@valgrind
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Compiles each of the stress test examples.
|
||||
- name: Compile stress test examples
|
||||
run: cargo build -p stress-test --release --example ${{ matrix.stress-test }}
|
||||
|
||||
# Runs each of the examples using Valgrind. Detects leaks and displays them.
|
||||
- name: Run valgrind
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/release/examples/${{ matrix.stress-test }}
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/release/examples/${{ matrix.stress-test }}
|
||||
|
||||
+4
-4
@@ -173,10 +173,10 @@ command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2018 $(git ls-files '*.rs')
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
@@ -230,7 +230,7 @@ integration tests in the crate and follow the style.
|
||||
|
||||
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
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install cargo-fuzz`
|
||||
@@ -678,4 +678,4 @@ When releasing a new version of a crate, follow these steps:
|
||||
[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
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
|
||||
resolver = "2"
|
||||
members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
tokio = { version = "1.35.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -132,6 +132,8 @@ project.
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`axum`]: A web application framework that focuses on ergonomics and modularity.
|
||||
|
||||
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
|
||||
|
||||
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
|
||||
@@ -142,21 +144,18 @@ several other libraries, including:
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers `tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
* [`loom`]: A testing tool for concurrent Rust code
|
||||
* [`loom`]: A testing tool for concurrent Rust code.
|
||||
|
||||
[`axum`]: https://github.com/tokio-rs/axum
|
||||
[`warp`]: https://github.com/seanmonstar/warp
|
||||
[`hyper`]: https://github.com/hyperium/hyper
|
||||
[`tonic`]: https://github.com/hyperium/tonic
|
||||
[`tower`]: https://github.com/tower-rs/tower
|
||||
[`loom`]: https://github.com/tokio-rs/loom
|
||||
[`rdbc`]: https://github.com/tokio-rs/rdbc
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
@@ -187,12 +186,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.56.0.
|
||||
released at least six months ago. The current MSRV is 1.63.
|
||||
|
||||
Note that the MSRV is not increased automatically, and only as part of a minor
|
||||
release. The MSRV history for past minor releases can be found below:
|
||||
|
||||
* 1.27 to now - Rust 1.56
|
||||
* 1.30 to now - Rust 1.63
|
||||
* 1.27 to 1.29 - Rust 1.56
|
||||
* 1.17 to 1.26 - Rust 1.49
|
||||
* 1.15 to 1.16 - Rust 1.46
|
||||
* 1.0 to 1.14 - Rust 1.45
|
||||
@@ -215,21 +215,27 @@ 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.18.x` - LTS release until June 2023. (MSRV 1.49)
|
||||
* `1.20.x` - LTS release until September 2023. (MSRV 1.49)
|
||||
* `1.25.x` - LTS release until March 2024. (MSRV 1.49)
|
||||
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
|
||||
|
||||
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.18.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.25.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.18", features = [...] }
|
||||
tokio = { version = "~1.25", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
|
||||
* `1.8.x` - LTS release until February 2022.
|
||||
* `1.14.x` - LTS release until June 2022.
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
+3
-2
@@ -2,16 +2,17 @@
|
||||
name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
test-util = ["tokio/test-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
criterion = "0.5.1"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
num_cpus = "1.16.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
|
||||
|
||||
+51
-39
@@ -1,4 +1,4 @@
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
@@ -174,65 +174,77 @@ fn rt() -> tokio::runtime::Runtime {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn copy_mem_to_mem(b: &mut Bencher) {
|
||||
fn copy_mem_to_mem(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
c.bench_function("copy_mem_to_mem", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_mem_to_slow_hdd(b: &mut Bencher) {
|
||||
fn copy_mem_to_slow_hdd(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
c.bench_function("copy_mem_to_slow_hdd", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_chunk_to_mem(b: &mut Bencher) {
|
||||
fn copy_chunk_to_mem(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
c.bench_function("copy_chunk_to_mem", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source =
|
||||
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_chunk_to_slow_hdd(b: &mut Bencher) {
|
||||
fn copy_chunk_to_slow_hdd(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
c.bench_function("copy_chunk_to_slow_hdd", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source =
|
||||
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
copy_bench,
|
||||
copy_mem_to_mem,
|
||||
copy_mem_to_slow_hdd,
|
||||
copy_chunk_to_mem,
|
||||
copy_chunk_to_slow_hdd,
|
||||
);
|
||||
benchmark_main!(copy_bench);
|
||||
criterion_main!(copy_bench);
|
||||
|
||||
+66
-57
@@ -6,7 +6,7 @@ use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead /*FramedWrite*/};
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use std::fs::File as StdFile;
|
||||
use std::io::Read as StdRead;
|
||||
@@ -23,81 +23,90 @@ const BLOCK_COUNT: usize = 1_000;
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
const DEV_ZERO: &str = "/dev/zero";
|
||||
|
||||
fn async_read_codec(b: &mut Bencher) {
|
||||
fn async_read_codec(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut input_stream = FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
|
||||
c.bench_function("async_read_codec", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut input_stream =
|
||||
FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let _bytes = input_stream.next().await.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_buf(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let count = file.read(&mut buffer).await.unwrap();
|
||||
if count == 0 {
|
||||
break;
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let _bytes = input_stream.next().await.unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_std_file(b: &mut Bencher) {
|
||||
fn async_read_buf(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
let task = || async {
|
||||
let mut file = tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
|
||||
c.bench_function("async_read_buf", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let count = file.read(&mut buffer).await.unwrap();
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_std_file(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("async_read_std_file", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file =
|
||||
tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
let mut file_ref = file.as_mut();
|
||||
|
||||
tokio::task::block_in_place(move || {
|
||||
file_ref.read_exact(&mut buffer).unwrap();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn sync_read(c: &mut Criterion) {
|
||||
c.bench_function("sync_read", |b| {
|
||||
b.iter(|| {
|
||||
let mut file = StdFile::open(DEV_ZERO).unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
let mut file_ref = file.as_mut();
|
||||
|
||||
tokio::task::block_in_place(move || {
|
||||
file_ref.read_exact(&mut buffer).unwrap();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(task());
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
file.read_exact(&mut buffer).unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn sync_read(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let mut file = StdFile::open(DEV_ZERO).unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
file.read_exact(&mut buffer).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
file,
|
||||
async_read_std_file,
|
||||
async_read_buf,
|
||||
async_read_codec,
|
||||
sync_read
|
||||
);
|
||||
|
||||
benchmark_main!(file);
|
||||
criterion_main!(file);
|
||||
|
||||
@@ -4,46 +4,50 @@
|
||||
|
||||
use tokio::runtime::{self, Runtime};
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
fn spawn_many_local(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(tokio::spawn(async move {}));
|
||||
}
|
||||
c.bench_function("spawn_many_local", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(tokio::spawn(async move {}));
|
||||
}
|
||||
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
fn spawn_many_remote_idle(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_idle", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_busy(b: &mut Bencher) {
|
||||
fn spawn_many_remote_busy(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
@@ -56,16 +60,18 @@ fn spawn_many_remote_busy(b: &mut Bencher) {
|
||||
iter()
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_busy", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,11 +79,11 @@ fn rt() -> Runtime {
|
||||
runtime::Builder::new_current_thread().build().unwrap()
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
criterion_main!(scheduler);
|
||||
|
||||
+169
-99
@@ -5,114 +5,173 @@
|
||||
use tokio::runtime::{self, Runtime};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
const NUM_WORKERS: usize = 4;
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const STALL_DUR: Duration = Duration::from_micros(10);
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
fn spawn_many_local(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(|| {
|
||||
rem.store(NUM_SPAWN, Relaxed);
|
||||
c.bench_function("spawn_many_local", |b| {
|
||||
b.iter(|| {
|
||||
rem.store(NUM_SPAWN, Relaxed);
|
||||
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
tokio::spawn(async move {
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
fn spawn_many_remote_idle(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_idle", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_busy(b: &mut Bencher) {
|
||||
// The runtime is busy with tasks that consume CPU time and yield. Yielding is a
|
||||
// lower notification priority than spawning / regular notification.
|
||||
fn spawn_many_remote_busy1(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
let flag = Arc::new(AtomicBool::new(true));
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(2 * NUM_WORKERS) {
|
||||
rt.spawn(async {
|
||||
loop {
|
||||
let flag = flag.clone();
|
||||
rt.spawn(async move {
|
||||
while flag.load(Relaxed) {
|
||||
tokio::task::yield_now().await;
|
||||
std::thread::sleep(std::time::Duration::from_micros(10));
|
||||
stall();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_busy1", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
fn yield_many(b: &mut Bencher) {
|
||||
// The runtime is busy with tasks that consume CPU time and spawn new high-CPU
|
||||
// tasks. Spawning goes via a higher notification priority than yielding.
|
||||
fn spawn_many_remote_busy2(c: &mut Criterion) {
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
let flag = Arc::new(AtomicBool::new(true));
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(NUM_WORKERS) {
|
||||
let flag = flag.clone();
|
||||
fn iter(flag: Arc<AtomicBool>) {
|
||||
tokio::spawn(async {
|
||||
if flag.load(Relaxed) {
|
||||
stall();
|
||||
iter(flag);
|
||||
}
|
||||
});
|
||||
}
|
||||
rt.spawn(async {
|
||||
iter(flag);
|
||||
});
|
||||
}
|
||||
|
||||
c.bench_function("spawn_many_remote_busy2", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
fn yield_many(c: &mut Criterion) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS: usize = 200;
|
||||
|
||||
let rt = rt();
|
||||
c.bench_function("yield_many", |b| {
|
||||
let rt = rt();
|
||||
let (tx, rx) = mpsc::sync_channel(TASKS);
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(TASKS);
|
||||
b.iter(move || {
|
||||
for _ in 0..TASKS {
|
||||
let tx = tx.clone();
|
||||
|
||||
b.iter(move || {
|
||||
for _ in 0..TASKS {
|
||||
let tx = tx.clone();
|
||||
rt.spawn(async move {
|
||||
for _ in 0..NUM_YIELD {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
rt.spawn(async move {
|
||||
for _ in 0..NUM_YIELD {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..TASKS {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
for _ in 0..TASKS {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn ping_pong(b: &mut Bencher) {
|
||||
fn ping_pong(c: &mut Criterion) {
|
||||
const NUM_PINGS: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
@@ -120,46 +179,46 @@ fn ping_pong(b: &mut Bencher) {
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(|| {
|
||||
let done_tx = done_tx.clone();
|
||||
let rem = rem.clone();
|
||||
rem.store(NUM_PINGS, Relaxed);
|
||||
c.bench_function("ping_pong", |b| {
|
||||
b.iter(|| {
|
||||
let done_tx = done_tx.clone();
|
||||
let rem = rem.clone();
|
||||
rem.store(NUM_PINGS, Relaxed);
|
||||
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..NUM_PINGS {
|
||||
let rem = rem.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..NUM_PINGS {
|
||||
let rem = rem.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
rx1.await.unwrap();
|
||||
tx2.send(()).unwrap();
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
rx1.await.unwrap();
|
||||
tx2.send(()).unwrap();
|
||||
});
|
||||
|
||||
tx1.send(()).unwrap();
|
||||
rx2.await.unwrap();
|
||||
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
done_tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tx1.send(()).unwrap();
|
||||
rx2.await.unwrap();
|
||||
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
done_tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn chained_spawn(b: &mut Bencher) {
|
||||
fn chained_spawn(c: &mut Criterion) {
|
||||
const ITER: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
|
||||
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
done_tx.send(()).unwrap();
|
||||
@@ -170,18 +229,21 @@ fn chained_spawn(b: &mut Bencher) {
|
||||
}
|
||||
}
|
||||
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
c.bench_function("chained_spawn", |b| {
|
||||
let rt = rt();
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
|
||||
b.iter(move || {
|
||||
let done_tx = done_tx.clone();
|
||||
b.iter(move || {
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
iter(done_tx, ITER);
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
iter(done_tx, ITER);
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -193,14 +255,22 @@ fn rt() -> Runtime {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
fn stall() {
|
||||
let now = Instant::now();
|
||||
while now.elapsed() < STALL_DUR {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy,
|
||||
spawn_many_remote_busy1,
|
||||
spawn_many_remote_busy2,
|
||||
ping_pong,
|
||||
yield_many,
|
||||
chained_spawn,
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
criterion_main!(scheduler);
|
||||
|
||||
+17
-15
@@ -1,7 +1,7 @@
|
||||
//! Benchmark the delay in propagating OS signals to any listeners.
|
||||
#![cfg(unix)]
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -41,7 +41,7 @@ pub fn send_signal(signal: libc::c_int) {
|
||||
}
|
||||
}
|
||||
|
||||
fn many_signals(bench: &mut Bencher) {
|
||||
fn many_signals(c: &mut Criterion) {
|
||||
let num_signals = 10;
|
||||
let (tx, mut rx) = mpsc::channel(num_signals);
|
||||
|
||||
@@ -75,21 +75,23 @@ fn many_signals(bench: &mut Bencher) {
|
||||
// tasks have been polled at least once
|
||||
rt.block_on(Spinner::new());
|
||||
|
||||
bench.iter(|| {
|
||||
rt.block_on(async {
|
||||
send_signal(libc::SIGCHLD);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
c.bench_function("many_signals", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
send_signal(libc::SIGCHLD);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
|
||||
send_signal(libc::SIGIO);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
});
|
||||
send_signal(libc::SIGIO);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(signal_group, many_signals,);
|
||||
criterion_group!(signal_group, many_signals);
|
||||
|
||||
benchmark_main!(signal_group);
|
||||
criterion_main!(signal_group);
|
||||
|
||||
+47
-40
@@ -2,10 +2,7 @@
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bencher;
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
async fn work() -> usize {
|
||||
let val = 1 + 1;
|
||||
@@ -13,67 +10,77 @@ async fn work() -> usize {
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(bench: &mut Bencher) {
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
|
||||
c.bench_function("basic_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn10(bench: &mut Bencher) {
|
||||
fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
|
||||
c.bench_function("basic_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn(bench: &mut Bencher) {
|
||||
fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
c.bench_function("threaded_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn10(bench: &mut Bencher) {
|
||||
fn threaded_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
c.bench_function("threaded_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
criterion_group!(
|
||||
spawn,
|
||||
basic_scheduler_spawn,
|
||||
basic_scheduler_spawn10,
|
||||
@@ -81,4 +88,4 @@ bencher::benchmark_group!(
|
||||
threaded_scheduler_spawn10,
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(spawn);
|
||||
criterion_main!(spawn);
|
||||
|
||||
+282
-130
@@ -1,8 +1,23 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
type Medium = [usize; 64];
|
||||
type Large = [Medium; 64];
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Medium([usize; 64]);
|
||||
impl Default for Medium {
|
||||
fn default() -> Self {
|
||||
Medium([0; 64])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Large([Medium; 64]);
|
||||
impl Default for Large {
|
||||
fn default() -> Self {
|
||||
Large([Medium::default(); 64])
|
||||
}
|
||||
}
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
@@ -11,169 +26,306 @@ fn rt() -> tokio::runtime::Runtime {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create_1_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(1));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_000_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100_000));
|
||||
});
|
||||
}
|
||||
|
||||
fn send_medium(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
|
||||
let _ = rt.block_on(tx.send([0; 64]));
|
||||
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn send_large(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
let _ = rt.block_on(tx.send([[0; 64]; 64]));
|
||||
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
fn create_medium<const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
g.bench_function(SIZE.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(SIZE));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded_full(b: &mut Bencher) {
|
||||
fn send_data<T: Default, const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>, prefix: &str) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
g.bench_function(format!("{}_{}", prefix, SIZE), |b| {
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<T>(SIZE);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
let _ = rt.block_on(tx.send(T::default()));
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded(b: &mut Bencher) {
|
||||
fn contention_bounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
g.bench_function("bounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded(b: &mut Bencher) {
|
||||
fn contention_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
g.bench_function("bounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(b: &mut Bencher) {
|
||||
fn contention_bounded_full(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
g.bench_function("bounded_full", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
create,
|
||||
create_1_medium,
|
||||
create_100_medium,
|
||||
create_100_000_medium
|
||||
);
|
||||
fn contention_bounded_full_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
bencher::benchmark_group!(send, send_medium, send_large);
|
||||
g.bench_function("bounded_full_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
bencher::benchmark_group!(
|
||||
contention,
|
||||
contention_bounded,
|
||||
contention_bounded_full,
|
||||
contention_unbounded,
|
||||
uncontented_bounded,
|
||||
uncontented_unbounded
|
||||
);
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_main!(create, send, contention);
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_create_medium(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("create_medium");
|
||||
create_medium::<1>(&mut group);
|
||||
create_medium::<100>(&mut group);
|
||||
create_medium::<100_000>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_send(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("send");
|
||||
send_data::<Medium, 1000>(&mut group, "medium");
|
||||
send_data::<Large, 1000>(&mut group, "large");
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
contention_bounded(&mut group);
|
||||
contention_bounded_recv_many(&mut group);
|
||||
contention_bounded_full(&mut group);
|
||||
contention_bounded_full_recv_many(&mut group);
|
||||
contention_unbounded(&mut group);
|
||||
contention_unbounded_recv_many(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
uncontented_bounded(&mut group);
|
||||
uncontented_bounded_recv_many(&mut group);
|
||||
uncontented_unbounded(&mut group);
|
||||
uncontented_unbounded_recv_many(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(create, bench_create_medium);
|
||||
criterion_group!(send, bench_send);
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(create, send, contention, uncontented);
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
sync::{mpsc, oneshot},
|
||||
};
|
||||
|
||||
fn request_reply_current_thread(b: &mut Bencher) {
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
fn request_reply_current_thread(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(b, rt);
|
||||
request_reply(c, rt);
|
||||
}
|
||||
|
||||
fn request_reply_multi_threaded(b: &mut Bencher) {
|
||||
fn request_reply_multi_threaded(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(b, rt);
|
||||
request_reply(c, rt);
|
||||
}
|
||||
|
||||
fn request_reply(b: &mut Bencher, rt: Runtime) {
|
||||
fn request_reply(b: &mut Criterion, rt: Runtime) {
|
||||
let tx = rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<oneshot::Sender<()>>(10);
|
||||
tokio::spawn(async move {
|
||||
@@ -32,22 +33,24 @@ fn request_reply(b: &mut Bencher, rt: Runtime) {
|
||||
tx
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
let task_tx = tx.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..1_000 {
|
||||
let (o_tx, o_rx) = oneshot::channel();
|
||||
task_tx.send(o_tx).await.unwrap();
|
||||
let _ = o_rx.await;
|
||||
}
|
||||
b.bench_function("request_reply", |b| {
|
||||
b.iter(|| {
|
||||
let task_tx = tx.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..1_000 {
|
||||
let (o_tx, o_rx) = oneshot::channel();
|
||||
task_tx.send(o_tx).await.unwrap();
|
||||
let _ = o_rx.await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
sync_mpsc_oneshot_group,
|
||||
request_reply_current_thread,
|
||||
request_reply_multi_threaded,
|
||||
);
|
||||
|
||||
benchmark_main!(sync_mpsc_oneshot_group);
|
||||
criterion_main!(sync_mpsc_oneshot_group);
|
||||
|
||||
+47
-33
@@ -1,9 +1,11 @@
|
||||
use bencher::Bencher;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
@@ -11,7 +13,7 @@ fn rt() -> tokio::runtime::Runtime {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn notify_waiters<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
fn notify_waiters<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
@@ -29,18 +31,20 @@ fn notify_waiters<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_waiters();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
g.bench_function(N_WAITERS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_waiters();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn notify_one<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
fn notify_one<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
@@ -58,33 +62,43 @@ fn notify_one<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_one();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
g.bench_function(N_WAITERS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_one();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
fn bench_notify_one(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("notify_one");
|
||||
notify_one::<10>(&mut group);
|
||||
notify_one::<50>(&mut group);
|
||||
notify_one::<100>(&mut group);
|
||||
notify_one::<200>(&mut group);
|
||||
notify_one::<500>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_notify_waiters(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("notify_waiters");
|
||||
notify_waiters::<10>(&mut group);
|
||||
notify_waiters::<50>(&mut group);
|
||||
notify_waiters::<100>(&mut group);
|
||||
notify_waiters::<200>(&mut group);
|
||||
notify_waiters::<500>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
notify_waiters_simple,
|
||||
notify_waiters::<10>,
|
||||
notify_waiters::<50>,
|
||||
notify_waiters::<100>,
|
||||
notify_waiters::<200>,
|
||||
notify_waiters::<500>
|
||||
bench_notify_one,
|
||||
bench_notify_waiters
|
||||
);
|
||||
|
||||
bencher::benchmark_group!(
|
||||
notify_one_simple,
|
||||
notify_one::<10>,
|
||||
notify_one::<50>,
|
||||
notify_one::<100>,
|
||||
notify_one::<200>,
|
||||
notify_one::<500>
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(notify_waiters_simple, notify_one_simple);
|
||||
criterion_main!(notify_waiters_simple);
|
||||
|
||||
+91
-70
@@ -1,26 +1,30 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use std::sync::Arc;
|
||||
use tokio::{sync::RwLock, task};
|
||||
|
||||
fn read_uncontended(b: &mut Bencher) {
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn read_uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let read = lock.read().await;
|
||||
let _read = black_box(read);
|
||||
}
|
||||
g.bench_function("read", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let read = lock.read().await;
|
||||
let _read = black_box(read);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
fn read_concurrent_uncontended_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
@@ -32,23 +36,25 @@ fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
g.bench_function("read_concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
fn read_concurrent_uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
@@ -59,22 +65,24 @@ fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone())
|
||||
};
|
||||
g.bench_function("read_concurrent", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone())
|
||||
};
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
fn read_concurrent_contended_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
@@ -86,24 +94,26 @@ fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
let j = tokio::try_join! {
|
||||
async move { drop(write); Ok(()) },
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
};
|
||||
j.unwrap();
|
||||
g.bench_function("read_concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
let j = tokio::try_join! {
|
||||
async move { drop(write); Ok(()) },
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_contended(b: &mut Bencher) {
|
||||
fn read_concurrent_contended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
@@ -114,29 +124,40 @@ fn read_concurrent_contended(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
tokio::join! {
|
||||
async move { drop(write) },
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
};
|
||||
g.bench_function("read_concurrent", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
tokio::join! {
|
||||
async move { drop(write) },
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
};
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
sync_rwlock,
|
||||
read_uncontended,
|
||||
read_concurrent_uncontended,
|
||||
read_concurrent_uncontended_multi,
|
||||
read_concurrent_contended,
|
||||
read_concurrent_contended_multi
|
||||
);
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
read_concurrent_contended(&mut group);
|
||||
read_concurrent_contended_multi(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
bencher::benchmark_main!(sync_rwlock);
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
read_uncontended(&mut group);
|
||||
read_concurrent_uncontended(&mut group);
|
||||
read_concurrent_uncontended_multi(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(contention, uncontented);
|
||||
|
||||
+106
-84
@@ -1,21 +1,36 @@
|
||||
use bencher::Bencher;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::{sync::Semaphore, task};
|
||||
|
||||
fn uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn single_rt() -> Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn multi_rt() -> Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let permit = s.acquire().await;
|
||||
drop(permit);
|
||||
}
|
||||
g.bench_function("multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let permit = s.acquire().await;
|
||||
drop(permit);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -25,101 +40,108 @@ async fn task(s: Arc<Semaphore>) {
|
||||
drop(permit);
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
fn uncontended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
g.bench_function("concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
fn uncontended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = single_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
g.bench_function("concurrent_single", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
fn contended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(5));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
g.bench_function("concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
fn contended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = single_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(5));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
g.bench_function("concurrent_single", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
sync_semaphore,
|
||||
uncontended,
|
||||
uncontended_concurrent_multi,
|
||||
uncontended_concurrent_single,
|
||||
contended_concurrent_multi,
|
||||
contended_concurrent_single
|
||||
);
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
contended_concurrent_multi(&mut group);
|
||||
contended_concurrent_single(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
bencher::benchmark_main!(sync_semaphore);
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
uncontended(&mut group);
|
||||
uncontended_concurrent_multi(&mut group);
|
||||
uncontended_concurrent_single(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(contention, uncontented);
|
||||
|
||||
+40
-19
@@ -1,9 +1,11 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use rand::prelude::*;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{watch, Notify};
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
@@ -24,16 +26,15 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
.fold(0, u32::wrapping_add)
|
||||
}
|
||||
|
||||
fn contention_resubscribe(b: &mut Bencher) {
|
||||
const NTASK: u64 = 1000;
|
||||
|
||||
fn contention_resubscribe<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let (snd, rcv) = watch::channel(0i32);
|
||||
let (snd, _) = watch::channel(0i32);
|
||||
let snd = Arc::new(snd);
|
||||
let wg = Arc::new((AtomicU64::new(0), Notify::new()));
|
||||
for n in 0..NTASK {
|
||||
let mut rcv = rcv.clone();
|
||||
for n in 0..N_TASKS {
|
||||
let mut rcv = snd.subscribe();
|
||||
let wg = wg.clone();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n);
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
|
||||
rt.spawn(async move {
|
||||
while rcv.changed().await.is_ok() {
|
||||
let _ = *rcv.borrow(); // contend on rwlock
|
||||
@@ -46,19 +47,39 @@ fn contention_resubscribe(b: &mut Bencher) {
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..100 {
|
||||
assert_eq!(wg.0.fetch_add(NTASK, Ordering::Relaxed), 0);
|
||||
let _ = snd.send(black_box(42));
|
||||
while wg.0.load(Ordering::Acquire) > 0 {
|
||||
wg.1.notified().await;
|
||||
const N_ITERS: usize = 100;
|
||||
g.bench_function(N_TASKS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on({
|
||||
let snd = snd.clone();
|
||||
let wg = wg.clone();
|
||||
async move {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..N_ITERS {
|
||||
assert_eq!(wg.0.fetch_add(N_TASKS as u64, Ordering::Relaxed), 0);
|
||||
let _ = snd.send(black_box(42));
|
||||
while wg.0.load(Ordering::Acquire) > 0 {
|
||||
wg.1.notified().await;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(contention, contention_resubscribe);
|
||||
fn bench_contention_resubscribe(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention_resubscribe");
|
||||
contention_resubscribe::<10>(&mut group);
|
||||
contention_resubscribe::<100>(&mut group);
|
||||
contention_resubscribe::<500>(&mut group);
|
||||
contention_resubscribe::<1000>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
bencher::benchmark_main!(contention);
|
||||
criterion_group!(contention, bench_contention_resubscribe);
|
||||
|
||||
criterion_main!(contention);
|
||||
|
||||
+10
-11
@@ -2,24 +2,23 @@
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bencher;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
|
||||
fn time_now_current_thread(bench: &mut Bencher) {
|
||||
fn time_now_current_thread(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
bench.iter(|| {
|
||||
rt.block_on(async {
|
||||
black_box(tokio::time::Instant::now());
|
||||
c.bench_function("time_now_current_thread", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
black_box(tokio::time::Instant::now());
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(time_now, time_now_current_thread,);
|
||||
criterion_group!(time_now, time_now_current_thread);
|
||||
|
||||
bencher::benchmark_main!(time_now);
|
||||
criterion_main!(time_now);
|
||||
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Publish a new release of a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-v, --verbose Use verbose Cargo output
|
||||
-d, --dry-run Perform a dry run (do not publish or tag the release)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
DRY_RUN=""
|
||||
VERBOSE=""
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
verify() {
|
||||
status "Verifying" "if $CRATE v$VERSION can be released"
|
||||
ACTUAL=$(cargo pkgid | sed -n 's/.*#\(.*\)/\1/p')
|
||||
|
||||
if [ "$ACTUAL" != "$VERSION" ]; then
|
||||
err "expected to release version $VERSION, but Cargo.toml contained $ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git tag -l | grep -Fxq "$TAG" ; then
|
||||
err "git tag \`$TAG\` already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATH_DEPS=$(grep -F "path = \"" Cargo.toml | sed -e 's/^/ /')
|
||||
if [ -n "$PATH_DEPS" ]; then
|
||||
err "crate \`$CRATE\` contained path dependencies:\n$PATH_DEPS"
|
||||
echo "path dependencies must be removed prior to release"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
release() {
|
||||
status "Releasing" "$CRATE v$VERSION"
|
||||
cargo package $VERBOSE
|
||||
cargo publish $VERBOSE $DRY_RUN
|
||||
|
||||
status "Tagging" "$TAG"
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
echo "# git tag $TAG && git push --tags"
|
||||
else
|
||||
git tag "$TAG" && git push --tags
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE="--verbose"
|
||||
set +x
|
||||
shift
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
(cd "$CRATE" && verify && release )
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Update links to docs.rs in a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-d, --dry-run Perform a dry run (do not modify any file)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
c1grep() { grep "$@" || test $? = 1; }
|
||||
|
||||
update_versions_in_doc() {
|
||||
# Print what is being/would be done
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
local MSG="Would change:"
|
||||
else
|
||||
local MSG="Updating:"
|
||||
fi
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed --quiet \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|gp" \
|
||||
| sed -e "s/^/$MSG /"
|
||||
|
||||
# Apply changes if not in dry run
|
||||
if [ -z "$DRY_RUN" ]; then
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed -i \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|g"
|
||||
fi
|
||||
}
|
||||
|
||||
update() {
|
||||
update_versions_in_doc
|
||||
}
|
||||
|
||||
show_outdated() {
|
||||
OUTDATED=$(git grep -rn "docs.rs/$CRATE/" \
|
||||
| c1grep -v "$VERSION" \
|
||||
| sed -e 's/^/ - /')
|
||||
if [[ -n "$OUTDATED" ]]; then
|
||||
echo "Found the following links to docs.rs with an outdated version:"
|
||||
echo "$OUTDATED"
|
||||
echo
|
||||
else
|
||||
echo "Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
# Does not cd in order to update everywhere
|
||||
show_outdated && update
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead, and delete the **path**.
|
||||
|
||||
+60
-20
@@ -1,4 +1,6 @@
|
||||
//! This example demonstrates tokio's experimental taskdumping functionality.
|
||||
//! This example demonstrates tokio's experimental task dumping functionality.
|
||||
//! This application deadlocks. Input CTRL+C to display traces of each task, or
|
||||
//! input CTRL+C twice within 1 second to quit.
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
@@ -6,37 +8,75 @@
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
use std::hint::black_box;
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Barrier;
|
||||
|
||||
#[inline(never)]
|
||||
async fn a() {
|
||||
black_box(b()).await
|
||||
async fn a(barrier: Arc<Barrier>) {
|
||||
b(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn b() {
|
||||
black_box(c()).await
|
||||
async fn b(barrier: Arc<Barrier>) {
|
||||
c(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn c() {
|
||||
black_box(tokio::task::yield_now()).await
|
||||
async fn c(barrier: Arc<Barrier>) {
|
||||
barrier.wait().await;
|
||||
}
|
||||
|
||||
tokio::spawn(a());
|
||||
tokio::spawn(b());
|
||||
tokio::spawn(c());
|
||||
// Prints a task dump upon receipt of CTRL+C, or returns if CTRL+C is
|
||||
// inputted twice within a second.
|
||||
async fn dump_or_quit() {
|
||||
use tokio::time::{timeout, Duration, Instant};
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
let mut last_signal: Option<Instant> = None;
|
||||
// wait for CTRL+C
|
||||
while let Ok(_) = tokio::signal::ctrl_c().await {
|
||||
// exit if a CTRL+C is inputted twice within 1 second
|
||||
if let Some(time_since_last_signal) = last_signal.map(|i| i.elapsed()) {
|
||||
if time_since_last_signal < Duration::from_secs(1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
last_signal = Some(Instant::now());
|
||||
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
let dump = handle.dump();
|
||||
|
||||
for (i, task) in dump.tasks().iter().enumerate() {
|
||||
let trace = task.trace();
|
||||
println!("task {i} trace:");
|
||||
println!("{trace}");
|
||||
// capture a dump, and print each trace
|
||||
println!("{:-<80}", "");
|
||||
if let Ok(dump) = timeout(Duration::from_secs(2), handle.dump()).await {
|
||||
for (i, task) in dump.tasks().iter().enumerate() {
|
||||
let trace = task.trace();
|
||||
println!("TASK {i}:");
|
||||
println!("{trace}\n");
|
||||
}
|
||||
} else {
|
||||
println!("Task dumping timed out. Use a native debugger (like gdb) to debug the deadlock.");
|
||||
}
|
||||
println!("{:-<80}", "");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
}
|
||||
}
|
||||
|
||||
println!("This program has a deadlock.");
|
||||
println!("Input CTRL+C to print a task dump.");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
|
||||
// oops! this barrier waits for one more task than will ever come.
|
||||
let barrier = Arc::new(Barrier::new(3));
|
||||
|
||||
let task_1 = tokio::spawn(a(barrier.clone()));
|
||||
let task_2 = tokio::spawn(a(barrier));
|
||||
|
||||
tokio::select!(
|
||||
_ = dump_or_quit() => {},
|
||||
_ = task_1 => {},
|
||||
_ = task_2 => {},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
|
||||
+12
-30
@@ -22,8 +22,7 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::copy_bidirectional;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use futures::FutureExt;
|
||||
@@ -44,36 +43,19 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
}
|
||||
});
|
||||
while let Ok((mut inbound, _)) = listener.accept().await {
|
||||
let mut outbound = TcpStream::connect(server_addr.clone()).await?;
|
||||
|
||||
tokio::spawn(transfer);
|
||||
tokio::spawn(async move {
|
||||
copy_bidirectional(&mut inbound, &mut outbound)
|
||||
.map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
}
|
||||
})
|
||||
.await
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<dyn Error>> {
|
||||
let mut outbound = TcpStream::connect(proxy_addr).await?;
|
||||
|
||||
let (mut ri, mut wi) = inbound.split();
|
||||
let (mut ro, mut wo) = outbound.split();
|
||||
|
||||
let client_to_server = async {
|
||||
io::copy(&mut ri, &mut wo).await?;
|
||||
wo.shutdown().await
|
||||
};
|
||||
|
||||
let server_to_client = async {
|
||||
io::copy(&mut ro, &mut wi).await?;
|
||||
wi.shutdown().await
|
||||
};
|
||||
|
||||
tokio::try_join!(client_to_server, server_to_client)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use futures::SinkExt;
|
||||
use http::{header::HeaderValue, Request, Response, StatusCode};
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
use std::{convert::TryFrom, env, error::Error, fmt, io};
|
||||
use std::{env, error::Error, fmt, io};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed};
|
||||
|
||||
+2
-1
@@ -8,8 +8,9 @@
|
||||
RUSTDOCFLAGS="""
|
||||
--cfg docsrs \
|
||||
--cfg tokio_unstable \
|
||||
--cfg tokio_taskdump \
|
||||
"""
|
||||
RUSTFLAGS="--cfg tokio_unstable --cfg docsrs"
|
||||
RUSTFLAGS="--cfg tokio_unstable --cfg tokio_taskdump --cfg docsrs"
|
||||
|
||||
[[redirects]]
|
||||
from = "/"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "stress-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tests-build"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -7,7 +7,6 @@ use tokio::process::{Child, Command};
|
||||
use tokio_test::assert_ok;
|
||||
|
||||
use futures::future::{self, FutureExt};
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# 2.2.0 (November 19th, 2023)
|
||||
|
||||
### Changed
|
||||
|
||||
- use `::core` qualified imports instead of `::std` inside `tokio::test` macro ([#5973])
|
||||
|
||||
[#5973]: https://github.com/tokio-rs/tokio/pull/5973
|
||||
|
||||
# 2.1.0 (April 25th, 2023)
|
||||
|
||||
- macros: fix typo in `#[tokio::test]` docs ([#5636])
|
||||
|
||||
@@ -4,9 +4,9 @@ name = "tokio-macros"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.x.y" git tag.
|
||||
version = "2.1.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.56"
|
||||
version = "2.2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -22,7 +22,7 @@ proc-macro = true
|
||||
[features]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.7"
|
||||
proc-macro2 = "1.0.60"
|
||||
quote = "1"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
|
||||
+12
-12
@@ -126,22 +126,22 @@ impl Configuration {
|
||||
}
|
||||
|
||||
fn build(&self) -> Result<FinalConfig, syn::Error> {
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
use RuntimeFlavor::*;
|
||||
use RuntimeFlavor as F;
|
||||
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
let worker_threads = match (flavor, self.worker_threads) {
|
||||
(CurrentThread, Some((_, worker_threads_span))) => {
|
||||
(F::CurrentThread, 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));
|
||||
}
|
||||
(CurrentThread, None) => None,
|
||||
(Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
(F::CurrentThread, None) => None,
|
||||
(F::Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
worker_threads.map(|(val, _span)| val)
|
||||
}
|
||||
(Threaded, _) => {
|
||||
(F::Threaded, _) => {
|
||||
let msg = if self.flavor.is_none() {
|
||||
"The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled."
|
||||
} else {
|
||||
@@ -152,14 +152,14 @@ impl Configuration {
|
||||
};
|
||||
|
||||
let start_paused = match (flavor, self.start_paused) {
|
||||
(Threaded, Some((_, start_paused_span))) => {
|
||||
(F::Threaded, Some((_, start_paused_span))) => {
|
||||
let msg = format!(
|
||||
"The `start_paused` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(start_paused_span, msg));
|
||||
}
|
||||
(CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(F::CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
@@ -354,10 +354,10 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
},
|
||||
};
|
||||
if let Some(v) = config.worker_threads {
|
||||
rt = quote! { #rt.worker_threads(#v) };
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) };
|
||||
}
|
||||
if let Some(v) = config.start_paused {
|
||||
rt = quote! { #rt.start_paused(#v) };
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) };
|
||||
}
|
||||
|
||||
let header = if is_test {
|
||||
@@ -402,7 +402,7 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
quote! {
|
||||
let body = async #body;
|
||||
#crate_path::pin!(body);
|
||||
let body: ::std::pin::Pin<&mut dyn ::std::future::Future<Output = #output_type>> = body;
|
||||
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
@@ -586,6 +586,6 @@ impl ToTokens for Body<'_> {
|
||||
for stmt in self.stmts {
|
||||
stmt.to_tokens(tokens);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,27 +73,27 @@ fn clean_pattern(pat: &mut syn::Pat) {
|
||||
}
|
||||
}
|
||||
syn::Pat::Or(or) => {
|
||||
for case in or.cases.iter_mut() {
|
||||
for case in &mut or.cases {
|
||||
clean_pattern(case);
|
||||
}
|
||||
}
|
||||
syn::Pat::Slice(slice) => {
|
||||
for elem in slice.elems.iter_mut() {
|
||||
for elem in &mut slice.elems {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::Struct(struct_pat) => {
|
||||
for field in struct_pat.fields.iter_mut() {
|
||||
for field in &mut struct_pat.fields {
|
||||
clean_pattern(&mut field.pat);
|
||||
}
|
||||
}
|
||||
syn::Pat::Tuple(tuple) => {
|
||||
for elem in tuple.elems.iter_mut() {
|
||||
for elem in &mut tuple.elems {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::TupleStruct(tuple) => {
|
||||
for elem in tuple.elems.iter_mut() {
|
||||
for elem in &mut tuple.elems {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ name = "tokio-stream"
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.14"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.63"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -37,7 +37,7 @@ signal = ["tokio/signal"]
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.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 }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tokio-stream-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
@@ -35,7 +35,7 @@ impl<I> Unpin for Once<I> {}
|
||||
/// ```
|
||||
pub fn once<T>(value: T) -> Once<T> {
|
||||
Once {
|
||||
iter: crate::iter(Some(value).into_iter()),
|
||||
iter: crate::iter(Some(value)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,9 @@ use then::Then;
|
||||
mod try_next;
|
||||
use try_next::TryNext;
|
||||
|
||||
mod peekable;
|
||||
use peekable::Peekable;
|
||||
|
||||
cfg_time! {
|
||||
pub(crate) mod timeout;
|
||||
pub(crate) mod timeout_repeating;
|
||||
@@ -848,8 +851,7 @@ pub trait StreamExt: Stream {
|
||||
///
|
||||
/// `collect` streams all values, awaiting as needed. Values are pushed into
|
||||
/// a collection. A number of different target collection types are
|
||||
/// supported, including [`Vec`](std::vec::Vec),
|
||||
/// [`String`](std::string::String), and [`Bytes`].
|
||||
/// supported, including [`Vec`], [`String`], and [`Bytes`].
|
||||
///
|
||||
/// [`Bytes`]: https://docs.rs/bytes/0.6.0/bytes/struct.Bytes.html
|
||||
///
|
||||
@@ -994,7 +996,7 @@ pub trait StreamExt: Stream {
|
||||
/// assert!(timeout_stream.try_next().await.is_ok(), "expected no more timeouts");
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(all(feature = "time"))]
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn timeout(self, duration: Duration) -> Timeout<Self>
|
||||
where
|
||||
@@ -1005,9 +1007,8 @@ pub trait StreamExt: Stream {
|
||||
|
||||
/// Applies a per-item timeout to the passed stream.
|
||||
///
|
||||
/// `timeout_repeating()` takes an [`Interval`](tokio::time::Interval) that
|
||||
/// controls the time each element of the stream has to complete before
|
||||
/// timing out.
|
||||
/// `timeout_repeating()` takes an [`Interval`] that controls the time each
|
||||
/// element of the stream has to complete before timing out.
|
||||
///
|
||||
/// If the wrapped stream yields a value before the deadline is reached, the
|
||||
/// value is returned. Otherwise, an error is returned. The caller may decide
|
||||
@@ -1083,7 +1084,7 @@ pub trait StreamExt: Stream {
|
||||
/// assert!(timeout_stream.try_next().await.is_ok(), "expected non-timeout");
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(all(feature = "time"))]
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn timeout_repeating(self, interval: Interval) -> TimeoutRepeating<Self>
|
||||
where
|
||||
@@ -1113,7 +1114,7 @@ pub trait StreamExt: Stream {
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(all(feature = "time"))]
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn throttle(self, duration: Duration) -> Throttle<Self>
|
||||
where
|
||||
@@ -1178,6 +1179,31 @@ pub trait StreamExt: Stream {
|
||||
assert!(max_size > 0, "`max_size` must be non-zero.");
|
||||
ChunksTimeout::new(self, max_size, duration)
|
||||
}
|
||||
|
||||
/// Turns the stream into a peekable stream, whose next element can be peeked at without being
|
||||
/// consumed.
|
||||
/// ```rust
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// # async fn _unused() {}
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// async fn main() {
|
||||
/// let iter = vec![1, 2, 3, 4].into_iter();
|
||||
/// let mut stream = stream::iter(iter).peekable();
|
||||
///
|
||||
/// assert_eq!(*stream.peek().await.unwrap(), 1);
|
||||
/// assert_eq!(*stream.peek().await.unwrap(), 1);
|
||||
/// assert_eq!(stream.next().await.unwrap(), 1);
|
||||
/// assert_eq!(*stream.peek().await.unwrap(), 2);
|
||||
/// }
|
||||
/// ```
|
||||
fn peekable(self) -> Peekable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Peekable::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<St: ?Sized> StreamExt for St where St: Stream {}
|
||||
|
||||
@@ -26,7 +26,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from a [`Stream`](crate::Stream).
|
||||
/// Convert from a [`Stream`].
|
||||
///
|
||||
/// This trait is not intended to be used directly. Instead, call
|
||||
/// [`StreamExt::collect()`](super::StreamExt::collect).
|
||||
|
||||
@@ -66,25 +66,23 @@ where
|
||||
T: Stream,
|
||||
U: Stream<Item = T::Item>,
|
||||
{
|
||||
use Poll::*;
|
||||
|
||||
let mut done = true;
|
||||
|
||||
match first.poll_next(cx) {
|
||||
Ready(Some(val)) => return Ready(Some(val)),
|
||||
Ready(None) => {}
|
||||
Pending => done = false,
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some(val)),
|
||||
Poll::Ready(None) => {}
|
||||
Poll::Pending => done = false,
|
||||
}
|
||||
|
||||
match second.poll_next(cx) {
|
||||
Ready(Some(val)) => return Ready(Some(val)),
|
||||
Ready(None) => {}
|
||||
Pending => done = false,
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some(val)),
|
||||
Poll::Ready(None) => {}
|
||||
Poll::Pending => done = false,
|
||||
}
|
||||
|
||||
if done {
|
||||
Ready(None)
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Pending
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures_core::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::stream_ext::Fuse;
|
||||
use crate::StreamExt;
|
||||
|
||||
pin_project! {
|
||||
/// Stream returned by the [`chain`](super::StreamExt::peekable) method.
|
||||
pub struct Peekable<T: Stream> {
|
||||
peek: Option<T::Item>,
|
||||
#[pin]
|
||||
stream: Fuse<T>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Stream> Peekable<T> {
|
||||
pub(crate) fn new(stream: T) -> Self {
|
||||
let stream = stream.fuse();
|
||||
Self { peek: None, stream }
|
||||
}
|
||||
|
||||
/// Peek at the next item in the stream.
|
||||
pub async fn peek(&mut self) -> Option<&T::Item>
|
||||
where
|
||||
T: Unpin,
|
||||
{
|
||||
if let Some(ref it) = self.peek {
|
||||
Some(it)
|
||||
} else {
|
||||
self.peek = self.next().await;
|
||||
self.peek.as_ref()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Stream> Stream for Peekable<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let this = self.project();
|
||||
if let Some(it) = this.peek.take() {
|
||||
Poll::Ready(Some(it))
|
||||
} else {
|
||||
this.stream.poll_next(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,11 +64,11 @@ where
|
||||
|
||||
let (lower, upper) = self.stream.size_hint();
|
||||
|
||||
let lower = cmp::min(lower, self.remaining as usize);
|
||||
let lower = cmp::min(lower, self.remaining);
|
||||
|
||||
let upper = match upper {
|
||||
Some(x) if x < self.remaining as usize => Some(x),
|
||||
_ => Some(self.remaining as usize),
|
||||
Some(x) if x < self.remaining => Some(x),
|
||||
_ => Some(self.remaining),
|
||||
};
|
||||
|
||||
(lower, upper)
|
||||
|
||||
@@ -518,8 +518,6 @@ where
|
||||
{
|
||||
/// Polls the next value, includes the vec entry index
|
||||
fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<Option<(usize, V::Item)>> {
|
||||
use Poll::*;
|
||||
|
||||
let start = self::rand::thread_rng_n(self.entries.len() as u32) as usize;
|
||||
let mut idx = start;
|
||||
|
||||
@@ -527,8 +525,8 @@ where
|
||||
let (_, stream) = &mut self.entries[idx];
|
||||
|
||||
match Pin::new(stream).poll_next(cx) {
|
||||
Ready(Some(val)) => return Ready(Some((idx, val))),
|
||||
Ready(None) => {
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some((idx, val))),
|
||||
Poll::Ready(None) => {
|
||||
// Remove the entry
|
||||
self.entries.swap_remove(idx);
|
||||
|
||||
@@ -542,7 +540,7 @@ where
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
Pending => {
|
||||
Poll::Pending => {
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
@@ -550,9 +548,9 @@ where
|
||||
|
||||
// If the map is empty, then the stream is complete.
|
||||
if self.entries.is_empty() {
|
||||
Ready(None)
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Pending
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ impl<T> ReceiverStream<T> {
|
||||
///
|
||||
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close()
|
||||
self.inner.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ impl<T> UnboundedReceiverStream<T> {
|
||||
/// This prevents any further messages from being sent on the channel while
|
||||
/// still enabling the receiver to drain messages that are buffered.
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close()
|
||||
self.inner.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
# 0.4.3 (August 23, 2023)
|
||||
|
||||
- deps: fix minimum required version of `async-stream` ([#5347])
|
||||
- deps: fix minimum required version of `tokio-stream` ([#4376])
|
||||
- docs: improve `tokio_test::task` docs ([#5132])
|
||||
- io: fetch actions from mock handle before write ([#5814])
|
||||
- io: fix wait operation on mock ([#5554])
|
||||
|
||||
[#4376]: https://github.com/tokio-rs/tokio/pull/4376
|
||||
[#5132]: https://github.com/tokio-rs/tokio/pull/5132
|
||||
[#5347]: https://github.com/tokio-rs/tokio/pull/5347
|
||||
[#5554]: https://github.com/tokio-rs/tokio/pull/5554
|
||||
[#5814]: https://github.com/tokio-rs/tokio/pull/5814
|
||||
|
||||
# 0.4.2 (May 14, 2021)
|
||||
|
||||
- test: add `assert_elapsed!` macro ([#3728])
|
||||
|
||||
@@ -4,9 +4,9 @@ name = "tokio-test"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.63"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -14,7 +14,7 @@ homepage = "https://tokio.rs"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
categories = ["asynchronous", "testing"]
|
||||
categories = ["asynchronous", "development-tools::testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
|
||||
+16
-2
@@ -74,7 +74,7 @@ struct Inner {
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Return a new, empty `Builder.
|
||||
/// Return a new, empty `Builder`.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
@@ -409,6 +409,20 @@ impl AsyncWrite for Mock {
|
||||
// If a sleep is set, it has already fired
|
||||
self.inner.sleep = None;
|
||||
|
||||
if self.inner.actions.is_empty() {
|
||||
match self.inner.poll_action(cx) {
|
||||
Poll::Pending => {
|
||||
// do not propagate pending
|
||||
}
|
||||
Poll::Ready(Some(action)) => {
|
||||
self.inner.actions.push_back(action);
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
panic!("unexpected write");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.inner.write(buf) {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if let Some(rem) = self.inner.remaining_wait() {
|
||||
@@ -464,7 +478,7 @@ impl Drop for Mock {
|
||||
Action::Read(data) => assert!(data.is_empty(), "There is still data left to read."),
|
||||
Action::Write(data) => assert!(data.is_empty(), "There is still data left to write."),
|
||||
_ => (),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
//! Tokio and Futures based testing utilities
|
||||
|
||||
pub mod io;
|
||||
pub mod stream_mock;
|
||||
|
||||
mod macros;
|
||||
pub mod task;
|
||||
|
||||
+12
-12
@@ -22,17 +22,17 @@
|
||||
#[macro_export]
|
||||
macro_rules! assert_ready {
|
||||
($e:expr) => {{
|
||||
use core::task::Poll::*;
|
||||
use core::task::Poll;
|
||||
match $e {
|
||||
Ready(v) => v,
|
||||
Pending => panic!("pending"),
|
||||
Poll::Ready(v) => v,
|
||||
Poll::Pending => panic!("pending"),
|
||||
}
|
||||
}};
|
||||
($e:expr, $($msg:tt)+) => {{
|
||||
use core::task::Poll::*;
|
||||
use core::task::Poll;
|
||||
match $e {
|
||||
Ready(v) => v,
|
||||
Pending => {
|
||||
Poll::Ready(v) => v,
|
||||
Poll::Pending => {
|
||||
panic!("pending; {}", format_args!($($msg)+))
|
||||
}
|
||||
}
|
||||
@@ -127,17 +127,17 @@ macro_rules! assert_ready_err {
|
||||
#[macro_export]
|
||||
macro_rules! assert_pending {
|
||||
($e:expr) => {{
|
||||
use core::task::Poll::*;
|
||||
use core::task::Poll;
|
||||
match $e {
|
||||
Pending => {}
|
||||
Ready(v) => panic!("ready; value = {:?}", v),
|
||||
Poll::Pending => {}
|
||||
Poll::Ready(v) => panic!("ready; value = {:?}", v),
|
||||
}
|
||||
}};
|
||||
($e:expr, $($msg:tt)+) => {{
|
||||
use core::task::Poll::*;
|
||||
use core::task::Poll;
|
||||
match $e {
|
||||
Pending => {}
|
||||
Ready(v) => {
|
||||
Poll::Pending => {}
|
||||
Poll::Ready(v) => {
|
||||
panic!("ready; value = {:?}; {}", v, format_args!($($msg)+))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#![cfg(not(loom))]
|
||||
|
||||
//! A mock stream implementing [`Stream`].
|
||||
//!
|
||||
//! # Overview
|
||||
//! This crate provides a `StreamMock` that can be used to test code that interacts with streams.
|
||||
//! It allows you to mock the behavior of a stream and control the items it yields and the waiting
|
||||
//! intervals between items.
|
||||
//!
|
||||
//! # Usage
|
||||
//! To use the `StreamMock`, you need to create a builder using[`StreamMockBuilder`]. The builder
|
||||
//! allows you to enqueue actions such as returning items or waiting for a certain duration.
|
||||
//!
|
||||
//! # Example
|
||||
//! ```rust
|
||||
//!
|
||||
//! use futures_util::StreamExt;
|
||||
//! use std::time::Duration;
|
||||
//! use tokio_test::stream_mock::StreamMockBuilder;
|
||||
//!
|
||||
//! async fn test_stream_mock_wait() {
|
||||
//! let mut stream_mock = StreamMockBuilder::new()
|
||||
//! .next(1)
|
||||
//! .wait(Duration::from_millis(300))
|
||||
//! .next(2)
|
||||
//! .build();
|
||||
//!
|
||||
//! assert_eq!(stream_mock.next().await, Some(1));
|
||||
//! let start = std::time::Instant::now();
|
||||
//! assert_eq!(stream_mock.next().await, Some(2));
|
||||
//! let elapsed = start.elapsed();
|
||||
//! assert!(elapsed >= Duration::from_millis(300));
|
||||
//! assert_eq!(stream_mock.next().await, None);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures_core::{ready, Stream};
|
||||
use std::future::Future;
|
||||
use tokio::time::{sleep_until, Instant, Sleep};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Action<T: Unpin> {
|
||||
Next(T),
|
||||
Wait(Duration),
|
||||
}
|
||||
|
||||
/// A builder for [`StreamMock`]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StreamMockBuilder<T: Unpin> {
|
||||
actions: VecDeque<Action<T>>,
|
||||
}
|
||||
|
||||
impl<T: Unpin> StreamMockBuilder<T> {
|
||||
/// Create a new empty [`StreamMockBuilder`]
|
||||
pub fn new() -> Self {
|
||||
StreamMockBuilder::default()
|
||||
}
|
||||
|
||||
/// Queue an item to be returned by the stream
|
||||
pub fn next(mut self, value: T) -> Self {
|
||||
self.actions.push_back(Action::Next(value));
|
||||
self
|
||||
}
|
||||
|
||||
// Queue an item to be consumed by the sink,
|
||||
// commented out until Sink is implemented.
|
||||
//
|
||||
// pub fn consume(mut self, value: T) -> Self {
|
||||
// self.actions.push_back(Action::Consume(value));
|
||||
// self
|
||||
// }
|
||||
|
||||
/// Queue the stream to wait for a duration
|
||||
pub fn wait(mut self, duration: Duration) -> Self {
|
||||
self.actions.push_back(Action::Wait(duration));
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the [`StreamMock`]
|
||||
pub fn build(self) -> StreamMock<T> {
|
||||
StreamMock {
|
||||
actions: self.actions,
|
||||
sleep: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> Default for StreamMockBuilder<T> {
|
||||
fn default() -> Self {
|
||||
StreamMockBuilder {
|
||||
actions: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mock stream implementing [`Stream`]
|
||||
///
|
||||
/// See [`StreamMockBuilder`] for more information.
|
||||
#[derive(Debug)]
|
||||
pub struct StreamMock<T: Unpin> {
|
||||
actions: VecDeque<Action<T>>,
|
||||
sleep: Option<Pin<Box<Sleep>>>,
|
||||
}
|
||||
|
||||
impl<T: Unpin> StreamMock<T> {
|
||||
fn next_action(&mut self) -> Option<Action<T>> {
|
||||
self.actions.pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> Stream for StreamMock<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
// Try polling the sleep future first
|
||||
if let Some(ref mut sleep) = self.sleep {
|
||||
ready!(Pin::new(sleep).poll(cx));
|
||||
// Since we're ready, discard the sleep future
|
||||
self.sleep.take();
|
||||
}
|
||||
|
||||
match self.next_action() {
|
||||
Some(action) => match action {
|
||||
Action::Next(item) => Poll::Ready(Some(item)),
|
||||
Action::Wait(duration) => {
|
||||
// Set up a sleep future and schedule this future to be polled again for it.
|
||||
self.sleep = Some(Box::pin(sleep_until(Instant::now() + duration)));
|
||||
cx.waker().wake_by_ref();
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
},
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> Drop for StreamMock<T> {
|
||||
fn drop(&mut self) {
|
||||
// Avoid double panicking to make debugging easier.
|
||||
if std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
|
||||
let undropped_count = self
|
||||
.actions
|
||||
.iter()
|
||||
.filter(|action| match action {
|
||||
Action::Next(_) => true,
|
||||
Action::Wait(_) => false,
|
||||
})
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
undropped_count == 0,
|
||||
"StreamMock was dropped before all actions were consumed, {} actions were not consumed",
|
||||
undropped_count
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ impl<T: Future> Spawn<T> {
|
||||
}
|
||||
|
||||
impl<T: Stream> Spawn<T> {
|
||||
/// If `T` is a [`Stream`] then poll_next it. This will handle pinning and the context
|
||||
/// If `T` is a [`Stream`] then `poll_next` it. This will handle pinning and the context
|
||||
/// type for the stream.
|
||||
pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
|
||||
let stream = self.future.as_mut();
|
||||
|
||||
@@ -51,6 +51,29 @@ async fn write() {
|
||||
mock.write_all(b"world!").await.expect("write 2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_with_handle() {
|
||||
let (mut mock, mut handle) = Builder::new().build_with_handle();
|
||||
handle.write(b"hello ");
|
||||
handle.write(b"world!");
|
||||
|
||||
mock.write_all(b"hello ").await.expect("write 1");
|
||||
mock.write_all(b"world!").await.expect("write 2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_with_handle() {
|
||||
let (mut mock, mut handle) = Builder::new().build_with_handle();
|
||||
handle.read(b"hello ");
|
||||
handle.read(b"world!");
|
||||
|
||||
let mut buf = vec![0; 6];
|
||||
mock.read_exact(&mut buf).await.expect("read 1");
|
||||
assert_eq!(&buf[..], b"hello ");
|
||||
mock.read_exact(&mut buf).await.expect("read 2");
|
||||
assert_eq!(&buf[..], b"world!");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_error() {
|
||||
let error = io::Error::new(io::ErrorKind::Other, "cruel");
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
use futures_util::StreamExt;
|
||||
use std::time::Duration;
|
||||
use tokio_test::stream_mock::StreamMockBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_mock_empty() {
|
||||
let mut stream_mock = StreamMockBuilder::<u32>::new().build();
|
||||
|
||||
assert_eq!(stream_mock.next().await, None);
|
||||
assert_eq!(stream_mock.next().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_mock_items() {
|
||||
let mut stream_mock = StreamMockBuilder::new().next(1).next(2).build();
|
||||
|
||||
assert_eq!(stream_mock.next().await, Some(1));
|
||||
assert_eq!(stream_mock.next().await, Some(2));
|
||||
assert_eq!(stream_mock.next().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_mock_wait() {
|
||||
let mut stream_mock = StreamMockBuilder::new()
|
||||
.next(1)
|
||||
.wait(Duration::from_millis(300))
|
||||
.next(2)
|
||||
.build();
|
||||
|
||||
assert_eq!(stream_mock.next().await, Some(1));
|
||||
let start = std::time::Instant::now();
|
||||
assert_eq!(stream_mock.next().await, Some(2));
|
||||
let elapsed = start.elapsed();
|
||||
assert!(elapsed >= Duration::from_millis(300));
|
||||
assert_eq!(stream_mock.next().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "StreamMock was dropped before all actions were consumed")]
|
||||
async fn test_stream_mock_drop_without_consuming_all() {
|
||||
let stream_mock = StreamMockBuilder::new().next(1).next(2).build();
|
||||
drop(stream_mock);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "test panic was not masked")]
|
||||
async fn test_stream_mock_drop_during_panic_doesnt_mask_panic() {
|
||||
let _stream_mock = StreamMockBuilder::new().next(1).next(2).build();
|
||||
panic!("test panic was not masked");
|
||||
}
|
||||
@@ -1,3 +1,48 @@
|
||||
# 0.7.10 (October 24th, 2023)
|
||||
|
||||
### Added
|
||||
|
||||
- task: add `TaskTracker` ([#6033])
|
||||
- task: add `JoinMap::keys` ([#6046])
|
||||
- io: implement `Seek` for `SyncIoBridge` ([#6058])
|
||||
|
||||
### Changed
|
||||
|
||||
- deps: update hashbrown to 0.14 ([#6102])
|
||||
|
||||
[#6033]: https://github.com/tokio-rs/tokio/pull/6033
|
||||
[#6046]: https://github.com/tokio-rs/tokio/pull/6046
|
||||
[#6058]: https://github.com/tokio-rs/tokio/pull/6058
|
||||
[#6102]: https://github.com/tokio-rs/tokio/pull/6102
|
||||
|
||||
# 0.7.9 (September 20th, 2023)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add passthrough `AsyncRead`/`AsyncWrite` to `InspectWriter`/`InspectReader` ([#5739])
|
||||
- task: add spawn blocking methods to `JoinMap` ([#5797])
|
||||
- io: pass through traits for `StreamReader` and `SinkWriter` ([#5941])
|
||||
- io: add `SyncIoBridge::into_inner` ([#5971])
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: handle possibly dangling reference safely ([#5812])
|
||||
- util: fix broken intra-doc link ([#5849])
|
||||
- compat: fix clippy warnings ([#5891])
|
||||
|
||||
### Documented
|
||||
|
||||
- codec: Specify the line ending of `LinesCodec` ([#5982])
|
||||
|
||||
[#5739]: https://github.com/tokio-rs/tokio/pull/5739
|
||||
[#5797]: https://github.com/tokio-rs/tokio/pull/5797
|
||||
[#5941]: https://github.com/tokio-rs/tokio/pull/5941
|
||||
[#5971]: https://github.com/tokio-rs/tokio/pull/5971
|
||||
[#5812]: https://github.com/tokio-rs/tokio/pull/5812
|
||||
[#5849]: https://github.com/tokio-rs/tokio/pull/5849
|
||||
[#5891]: https://github.com/tokio-rs/tokio/pull/5891
|
||||
[#5982]: https://github.com/tokio-rs/tokio/pull/5982
|
||||
|
||||
# 0.7.8 (April 25th, 2023)
|
||||
|
||||
This release bumps the MSRV of tokio-util to 1.56.
|
||||
|
||||
@@ -4,9 +4,9 @@ name = "tokio-util"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.7.x" git tag.
|
||||
version = "0.7.8"
|
||||
version = "0.7.10"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.63"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -34,18 +34,18 @@ rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
|
||||
__docs_rs = ["futures-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.22.0", path = "../tokio", features = ["sync"] }
|
||||
tokio = { version = "1.28.0", path = "../tokio", features = ["sync"] }
|
||||
bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
futures-io = { version = "0.3.0", optional = true }
|
||||
futures-util = { version = "0.3.0", optional = true }
|
||||
pin-project-lite = "0.2.0"
|
||||
pin-project-lite = "0.2.11"
|
||||
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
|
||||
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
[target.'cfg(tokio_unstable)'.dependencies]
|
||||
hashbrown = { version = "0.12.0", optional = true }
|
||||
hashbrown = { version = "0.14.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
@@ -56,6 +56,7 @@ async-stream = "0.3.0"
|
||||
futures = "0.3.0"
|
||||
futures-test = "0.3.5"
|
||||
parking_lot = "0.12.0"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -6,6 +6,8 @@ use std::{cmp, fmt, io, str, usize};
|
||||
|
||||
/// A simple [`Decoder`] and [`Encoder`] implementation that splits up data into lines.
|
||||
///
|
||||
/// This uses the `\n` character as the line ending on all platforms.
|
||||
///
|
||||
/// [`Decoder`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
|
||||
@@ -227,12 +227,14 @@ impl<T: tokio::io::AsyncSeek> futures_io::AsyncSeek for Compat<T> {
|
||||
pos: io::SeekFrom,
|
||||
) -> Poll<io::Result<u64>> {
|
||||
if self.seek_pos != Some(pos) {
|
||||
// Ensure previous seeks have finished before starting a new one
|
||||
ready!(self.as_mut().project().inner.poll_complete(cx))?;
|
||||
self.as_mut().project().inner.start_seek(pos)?;
|
||||
*self.as_mut().project().seek_pos = Some(pos);
|
||||
}
|
||||
let res = ready!(self.as_mut().project().inner.poll_complete(cx));
|
||||
*self.as_mut().project().seek_pos = None;
|
||||
Poll::Ready(res.map(|p| p as u64))
|
||||
Poll::Ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +257,7 @@ impl<T: futures_io::AsyncSeek> tokio::io::AsyncSeek for Compat<T> {
|
||||
};
|
||||
let res = ready!(self.as_mut().project().inner.poll_seek(cx, pos));
|
||||
*self.as_mut().project().seek_pos = None;
|
||||
Poll::Ready(res.map(|p| p as u64))
|
||||
Poll::Ready(res)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ where
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
delegate_call!(self.consume(amt))
|
||||
delegate_call!(self.consume(amt));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use bytes::Bytes;
|
||||
use futures_core::stream::Stream;
|
||||
use futures_sink::Sink;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
@@ -66,3 +67,10 @@ where
|
||||
self.project().inner.poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for CopyToBytes<S> {
|
||||
type Item = S::Item;
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.project().inner.poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,42 @@ impl<R: AsyncRead, F: FnMut(&[u8])> AsyncRead for InspectReader<R, F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncWrite, F> AsyncWrite for InspectReader<R, F> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::result::Result<usize, std::io::Error>> {
|
||||
self.project().reader.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), std::io::Error>> {
|
||||
self.project().reader.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), std::io::Error>> {
|
||||
self.project().reader.poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<Result<usize>> {
|
||||
self.project().reader.poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.reader.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// An adapter that lets you inspect the data that's being written.
|
||||
///
|
||||
@@ -132,3 +168,13 @@ impl<W: AsyncWrite, F: FnMut(&[u8])> AsyncWrite for InspectWriter<W, F> {
|
||||
self.writer.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: AsyncRead, F> AsyncRead for InspectWriter<W, F> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
self.project().writer.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
|
||||
use futures_core::stream::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
pin_project! {
|
||||
/// Convert a [`Sink`] of byte chunks into an [`AsyncWrite`].
|
||||
@@ -59,7 +60,7 @@ pin_project! {
|
||||
/// [`CopyToBytes`]: crate::io::CopyToBytes
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`codec`]: tokio_util::codec
|
||||
/// [`codec`]: crate::codec
|
||||
#[derive(Debug)]
|
||||
pub struct SinkWriter<S> {
|
||||
#[pin]
|
||||
@@ -115,3 +116,20 @@ where
|
||||
self.project().inner.poll_close(cx).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for SinkWriter<S> {
|
||||
type Item = S::Item;
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.project().inner.poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsyncRead> AsyncRead for SinkWriter<S> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
self.project().inner.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use bytes::Buf;
|
||||
use futures_core::stream::Stream;
|
||||
use futures_sink::Sink;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -165,7 +166,7 @@ where
|
||||
B: Buf,
|
||||
E: Into<std::io::Error>,
|
||||
{
|
||||
/// Convert a stream of byte chunks into an [`AsyncRead`](tokio::io::AsyncRead).
|
||||
/// Convert a stream of byte chunks into an [`AsyncRead`].
|
||||
///
|
||||
/// The item should be a [`Result`] with the ok variant being something that
|
||||
/// implements the [`Buf`] trait (e.g. `Vec<u8>` or `Bytes`). The error
|
||||
@@ -301,7 +302,7 @@ where
|
||||
}
|
||||
|
||||
// The code below is a manual expansion of the code that pin-project-lite would
|
||||
// generate. This is done because pin-project-lite fails by hitting the recusion
|
||||
// generate. This is done because pin-project-lite fails by hitting the recursion
|
||||
// limit on this struct. (Every line of documentation is handled recursively by
|
||||
// the macro.)
|
||||
|
||||
@@ -324,3 +325,22 @@ impl<S, B> StreamReader<S, B> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Sink<T, Error = E>, E, T> Sink<T> for StreamReader<S, E> {
|
||||
type Error = E;
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
|
||||
self.project().inner.start_send(item)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::io::{BufRead, Read, Write};
|
||||
use std::io::{BufRead, Read, Seek, Write};
|
||||
use tokio::io::{
|
||||
AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt,
|
||||
AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite,
|
||||
AsyncWriteExt,
|
||||
};
|
||||
|
||||
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
|
||||
@@ -79,6 +80,13 @@ impl<T: AsyncWrite + Unpin> Write for SyncIoBridge<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncSeek + Unpin> Seek for SyncIoBridge<T> {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(AsyncSeekExt::seek(src, pos))
|
||||
}
|
||||
}
|
||||
|
||||
// Because https://doc.rust-lang.org/std/io/trait.Write.html#method.is_write_vectored is at the time
|
||||
// of this writing still unstable, we expose this as part of a standalone method.
|
||||
impl<T: AsyncWrite> SyncIoBridge<T> {
|
||||
@@ -140,4 +148,9 @@ impl<T: Unpin> SyncIoBridge<T> {
|
||||
pub fn new_with_handle(src: T, rt: tokio::runtime::Handle) -> Self {
|
||||
Self { src, rt }
|
||||
}
|
||||
|
||||
/// Consume this bridge, returning the underlying stream.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.src
|
||||
}
|
||||
}
|
||||
|
||||
+2
-147
@@ -55,151 +55,6 @@ pub mod sync;
|
||||
|
||||
pub mod either;
|
||||
|
||||
#[cfg(any(feature = "io", feature = "codec"))]
|
||||
mod util {
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
pub use bytes;
|
||||
|
||||
use bytes::{Buf, BufMut};
|
||||
use futures_core::ready;
|
||||
use std::io::{self, IoSlice};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Try to read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
|
||||
///
|
||||
/// [`BufMut`]: bytes::Buf
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::{Bytes, BytesMut};
|
||||
/// use tokio_stream as stream;
|
||||
/// use tokio::io::Result;
|
||||
/// use tokio_util::io::{StreamReader, poll_read_buf};
|
||||
/// use futures::future::poll_fn;
|
||||
/// use std::pin::Pin;
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a reader from an iterator. This particular reader will always be
|
||||
/// // ready.
|
||||
/// let mut read = StreamReader::new(stream::iter(vec![Result::Ok(Bytes::from_static(&[0, 1, 2, 3]))]));
|
||||
///
|
||||
/// let mut buf = BytesMut::new();
|
||||
/// let mut reads = 0;
|
||||
///
|
||||
/// loop {
|
||||
/// reads += 1;
|
||||
/// let n = poll_fn(|cx| poll_read_buf(Pin::new(&mut read), cx, &mut buf)).await?;
|
||||
///
|
||||
/// if n == 0 {
|
||||
/// break;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // one or more reads might be necessary.
|
||||
/// assert!(reads >= 1);
|
||||
/// assert_eq!(&buf[..], &[0, 1, 2, 3]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
|
||||
pub fn poll_read_buf<T: AsyncRead, B: BufMut>(
|
||||
io: Pin<&mut T>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if !buf.has_remaining_mut() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let n = {
|
||||
let dst = buf.chunk_mut();
|
||||
|
||||
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
|
||||
// transparent wrapper around `[MaybeUninit<u8>]`.
|
||||
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
|
||||
let mut buf = ReadBuf::uninit(dst);
|
||||
let ptr = buf.filled().as_ptr();
|
||||
ready!(io.poll_read(cx, &mut buf)?);
|
||||
|
||||
// Ensure the pointer does not change from under us
|
||||
assert_eq!(ptr, buf.filled().as_ptr());
|
||||
buf.filled().len()
|
||||
};
|
||||
|
||||
// Safety: This is guaranteed to be the number of initialized (and read)
|
||||
// bytes due to the invariants provided by `ReadBuf::filled`.
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
|
||||
/// Try to write data from an implementer of the [`Buf`] trait to an
|
||||
/// [`AsyncWrite`], advancing the buffer's internal cursor.
|
||||
///
|
||||
/// This function will use [vectored writes] when the [`AsyncWrite`] supports
|
||||
/// vectored writes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`] implements [`AsyncWrite`] and [`Cursor<&[u8]>`] implements
|
||||
/// [`Buf`]:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio_util::io::poll_write_buf;
|
||||
/// use tokio::io;
|
||||
/// use tokio::fs::File;
|
||||
///
|
||||
/// use bytes::Buf;
|
||||
/// use std::io::Cursor;
|
||||
/// use std::pin::Pin;
|
||||
/// use futures::future::poll_fn;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut file = File::create("foo.txt").await?;
|
||||
/// let mut buf = Cursor::new(b"data to write");
|
||||
///
|
||||
/// // Loop until the entire contents of the buffer are written to
|
||||
/// // the file.
|
||||
/// while buf.has_remaining() {
|
||||
/// poll_fn(|cx| poll_write_buf(Pin::new(&mut file), cx, &mut buf)).await?;
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Buf`]: bytes::Buf
|
||||
/// [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
/// [`File`]: tokio::fs::File
|
||||
/// [vectored writes]: tokio::io::AsyncWrite::poll_write_vectored
|
||||
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
|
||||
pub fn poll_write_buf<T: AsyncWrite, B: Buf>(
|
||||
io: Pin<&mut T>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
const MAX_BUFS: usize = 64;
|
||||
|
||||
if !buf.has_remaining() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let n = if io.is_write_vectored() {
|
||||
let mut slices = [IoSlice::new(&[]); MAX_BUFS];
|
||||
let cnt = buf.chunks_vectored(&mut slices);
|
||||
ready!(io.poll_write_vectored(cx, &slices[..cnt]))?
|
||||
} else {
|
||||
ready!(io.poll_write(cx, buf.chunk()))?
|
||||
};
|
||||
|
||||
buf.advance(n);
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
}
|
||||
mod util;
|
||||
|
||||
@@ -4,6 +4,7 @@ pub(crate) mod guard;
|
||||
mod tree_node;
|
||||
|
||||
use crate::loom::sync::Arc;
|
||||
use crate::util::MaybeDangling;
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
@@ -77,11 +78,23 @@ pin_project! {
|
||||
/// [`CancellationToken`] by value instead of using a reference.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct WaitForCancellationFutureOwned {
|
||||
// Since `future` is the first field, it is dropped before the
|
||||
// cancellation_token field. This ensures that the reference inside the
|
||||
// `Notified` remains valid.
|
||||
// This field internally has a reference to the cancellation token, but camouflages
|
||||
// the relationship with `'static`. To avoid Undefined Behavior, we must ensure
|
||||
// that the reference is only used while the cancellation token is still alive. To
|
||||
// do that, we ensure that the future is the first field, so that it is dropped
|
||||
// before the cancellation token.
|
||||
//
|
||||
// We use `MaybeDanglingFuture` here because without it, the compiler could assert
|
||||
// the reference inside `future` to be valid even after the destructor of that
|
||||
// field runs. (Specifically, when the `WaitForCancellationFutureOwned` is passed
|
||||
// as an argument to a function, the reference can be asserted to be valid for the
|
||||
// rest of that function.) To avoid that, we use `MaybeDangling` which tells the
|
||||
// compiler that the reference stored inside it might not be valid.
|
||||
//
|
||||
// See <https://users.rust-lang.org/t/unsafe-code-review-semi-owning-weak-rwlock-t-guard/95706>
|
||||
// for more info.
|
||||
#[pin]
|
||||
future: tokio::sync::futures::Notified<'static>,
|
||||
future: MaybeDangling<tokio::sync::futures::Notified<'static>>,
|
||||
cancellation_token: CancellationToken,
|
||||
}
|
||||
}
|
||||
@@ -120,7 +133,7 @@ impl Default for CancellationToken {
|
||||
}
|
||||
|
||||
impl CancellationToken {
|
||||
/// Creates a new CancellationToken in the non-cancelled state.
|
||||
/// Creates a new `CancellationToken` in the non-cancelled state.
|
||||
pub fn new() -> CancellationToken {
|
||||
CancellationToken {
|
||||
inner: Arc::new(tree_node::TreeNode::new()),
|
||||
@@ -279,7 +292,7 @@ impl WaitForCancellationFutureOwned {
|
||||
// # Safety
|
||||
//
|
||||
// cancellation_token is dropped after future due to the field ordering.
|
||||
future: unsafe { Self::new_future(&cancellation_token) },
|
||||
future: MaybeDangling::new(unsafe { Self::new_future(&cancellation_token) }),
|
||||
cancellation_token,
|
||||
}
|
||||
}
|
||||
@@ -320,8 +333,9 @@ impl Future for WaitForCancellationFutureOwned {
|
||||
// # Safety
|
||||
//
|
||||
// cancellation_token is dropped after future due to the field ordering.
|
||||
this.future
|
||||
.set(unsafe { Self::new_future(this.cancellation_token) });
|
||||
this.future.set(MaybeDangling::new(unsafe {
|
||||
Self::new_future(this.cancellation_token)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! This mod provides the logic for the inner tree structure of the CancellationToken.
|
||||
//!
|
||||
//! CancellationTokens are only light handles with references to TreeNode.
|
||||
//! All the logic is actually implemented in the TreeNode.
|
||||
//! CancellationTokens are only light handles with references to [`TreeNode`].
|
||||
//! All the logic is actually implemented in the [`TreeNode`].
|
||||
//!
|
||||
//! A TreeNode is part of the cancellation tree and may have one parent and an arbitrary number of
|
||||
//! A [`TreeNode`] is part of the cancellation tree and may have one parent and an arbitrary number of
|
||||
//! children.
|
||||
//!
|
||||
//! A TreeNode can receive the request to perform a cancellation through a CancellationToken.
|
||||
//! A [`TreeNode`] can receive the request to perform a cancellation through a CancellationToken.
|
||||
//! This cancellation request will cancel the node and all of its descendants.
|
||||
//!
|
||||
//! As soon as a node cannot get cancelled any more (because it was already cancelled or it has no
|
||||
|
||||
@@ -29,7 +29,7 @@ impl PollSemaphore {
|
||||
|
||||
/// Closes the semaphore.
|
||||
pub fn close(&self) {
|
||||
self.semaphore.close()
|
||||
self.semaphore.close();
|
||||
}
|
||||
|
||||
/// Obtain a clone of the inner semaphore.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::alloc::Layout;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::future::{self, Future};
|
||||
use std::mem::{self, ManuallyDrop};
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
@@ -61,7 +60,7 @@ impl<'a, T> ReusableBoxFuture<'a, T> {
|
||||
F: Future + Send + 'a,
|
||||
{
|
||||
// future::Pending<T> is a ZST so this never allocates.
|
||||
let boxed = mem::replace(&mut this.boxed, Box::pin(Pending(PhantomData)));
|
||||
let boxed = mem::replace(&mut this.boxed, Box::pin(future::pending()));
|
||||
reuse_pin_box(boxed, future, |boxed| this.boxed = Pin::from(boxed))
|
||||
}
|
||||
|
||||
@@ -156,16 +155,3 @@ impl<O, F: FnOnce() -> O> Drop for CallOnDrop<O, F> {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as `std::future::Pending<T>`; we can't use that type directly because on rustc
|
||||
/// versions <1.60 it didn't unconditionally implement `Send`.
|
||||
// FIXME: use `std::future::Pending<T>` once the MSRV is >=1.60
|
||||
struct Pending<T>(PhantomData<fn() -> T>);
|
||||
|
||||
impl<T> Future for Pending<T> {
|
||||
type Output = T;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::collections::hash_map::RandomState;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::{AbortHandle, Id, JoinError, JoinSet, LocalSet};
|
||||
|
||||
@@ -316,6 +317,60 @@ where
|
||||
self.insert(key, task);
|
||||
}
|
||||
|
||||
/// Spawn the blocking code on the blocking threadpool and store it in this `JoinMap` with the provided
|
||||
/// key.
|
||||
///
|
||||
/// If a task previously existed in the `JoinMap` for this key, that task
|
||||
/// will be cancelled and replaced with the new one. The previous task will
|
||||
/// be removed from the `JoinMap`; a subsequent call to [`join_next`] will
|
||||
/// *not* return a cancelled [`JoinError`] for that task.
|
||||
///
|
||||
/// Note that blocking tasks cannot be cancelled after execution starts.
|
||||
/// Replaced blocking tasks will still run to completion if the task has begun
|
||||
/// to execute when it is replaced. A blocking task which is replaced before
|
||||
/// it has been scheduled on a blocking worker thread will be cancelled.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if called outside of a Tokio runtime.
|
||||
///
|
||||
/// [`join_next`]: Self::join_next
|
||||
#[track_caller]
|
||||
pub fn spawn_blocking<F>(&mut self, key: K, f: F)
|
||||
where
|
||||
F: FnOnce() -> V,
|
||||
F: Send + 'static,
|
||||
V: Send,
|
||||
{
|
||||
let task = self.tasks.spawn_blocking(f);
|
||||
self.insert(key, task)
|
||||
}
|
||||
|
||||
/// Spawn the blocking code on the blocking threadpool of the provided runtime and store it in this
|
||||
/// `JoinMap` with the provided key.
|
||||
///
|
||||
/// If a task previously existed in the `JoinMap` for this key, that task
|
||||
/// will be cancelled and replaced with the new one. The previous task will
|
||||
/// be removed from the `JoinMap`; a subsequent call to [`join_next`] will
|
||||
/// *not* return a cancelled [`JoinError`] for that task.
|
||||
///
|
||||
/// Note that blocking tasks cannot be cancelled after execution starts.
|
||||
/// Replaced blocking tasks will still run to completion if the task has begun
|
||||
/// to execute when it is replaced. A blocking task which is replaced before
|
||||
/// it has been scheduled on a blocking worker thread will be cancelled.
|
||||
///
|
||||
/// [`join_next`]: Self::join_next
|
||||
#[track_caller]
|
||||
pub fn spawn_blocking_on<F>(&mut self, key: K, f: F, handle: &Handle)
|
||||
where
|
||||
F: FnOnce() -> V,
|
||||
F: Send + 'static,
|
||||
V: Send,
|
||||
{
|
||||
let task = self.tasks.spawn_blocking_on(f, handle);
|
||||
self.insert(key, task);
|
||||
}
|
||||
|
||||
/// Spawn the provided task on the current [`LocalSet`] and store it in this
|
||||
/// `JoinMap` with the provided key.
|
||||
///
|
||||
@@ -572,6 +627,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator visiting all keys in this `JoinMap` in arbitrary order.
|
||||
///
|
||||
/// If a task has completed, but its output hasn't yet been consumed by a
|
||||
/// call to [`join_next`], this method will still return its key.
|
||||
///
|
||||
/// [`join_next`]: fn@Self::join_next
|
||||
pub fn keys(&self) -> JoinMapKeys<'_, K, V> {
|
||||
JoinMapKeys {
|
||||
iter: self.tasks_by_key.keys(),
|
||||
_value: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this `JoinMap` contains a task for the provided key.
|
||||
///
|
||||
/// If the task has completed, but its output hasn't yet been consumed by a
|
||||
@@ -805,3 +873,32 @@ impl<K: PartialEq> PartialEq for Key<K> {
|
||||
}
|
||||
|
||||
impl<K: Eq> Eq for Key<K> {}
|
||||
|
||||
/// An iterator over the keys of a [`JoinMap`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JoinMapKeys<'a, K, V> {
|
||||
iter: hashbrown::hash_map::Keys<'a, Key<K>, AbortHandle>,
|
||||
/// To make it easier to change JoinMap in the future, keep V as a generic
|
||||
/// parameter.
|
||||
_value: PhantomData<&'a V>,
|
||||
}
|
||||
|
||||
impl<'a, K, V> Iterator for JoinMapKeys<'a, K, V> {
|
||||
type Item = &'a K;
|
||||
|
||||
fn next(&mut self) -> Option<&'a K> {
|
||||
self.iter.next().map(|key| &key.key)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> ExactSizeIterator for JoinMapKeys<'a, K, V> {
|
||||
fn len(&self) -> usize {
|
||||
self.iter.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> std::iter::FusedIterator for JoinMapKeys<'a, K, V> {}
|
||||
|
||||
@@ -9,4 +9,7 @@ pub use spawn_pinned::LocalPoolHandle;
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt"))))]
|
||||
pub use join_map::JoinMap;
|
||||
pub use join_map::{JoinMap, JoinMapKeys};
|
||||
|
||||
pub mod task_tracker;
|
||||
pub use task_tracker::TaskTracker;
|
||||
|
||||
@@ -0,0 +1,719 @@
|
||||
//! Types related to the [`TaskTracker`] collection.
|
||||
//!
|
||||
//! See the documentation of [`TaskTracker`] for more information.
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::{futures::Notified, Notify};
|
||||
|
||||
#[cfg(feature = "rt")]
|
||||
use tokio::{
|
||||
runtime::Handle,
|
||||
task::{JoinHandle, LocalSet},
|
||||
};
|
||||
|
||||
/// A task tracker used for waiting until tasks exit.
|
||||
///
|
||||
/// This is usually used together with [`CancellationToken`] to implement [graceful shutdown]. The
|
||||
/// `CancellationToken` is used to signal to tasks that they should shut down, and the
|
||||
/// `TaskTracker` is used to wait for them to finish shutting down.
|
||||
///
|
||||
/// The `TaskTracker` will also keep track of a `closed` boolean. This is used to handle the case
|
||||
/// where the `TaskTracker` is empty, but we don't want to shut down yet. This means that the
|
||||
/// [`wait`] method will wait until *both* of the following happen at the same time:
|
||||
///
|
||||
/// * The `TaskTracker` must be closed using the [`close`] method.
|
||||
/// * The `TaskTracker` must be empty, that is, all tasks that it is tracking must have exited.
|
||||
///
|
||||
/// When a call to [`wait`] returns, it is guaranteed that all tracked tasks have exited and that
|
||||
/// the destructor of the future has finished running. However, there might be a short amount of
|
||||
/// time where [`JoinHandle::is_finished`] returns false.
|
||||
///
|
||||
/// # Comparison to `JoinSet`
|
||||
///
|
||||
/// The main Tokio crate has a similar collection known as [`JoinSet`]. The `JoinSet` type has a
|
||||
/// lot more features than `TaskTracker`, so `TaskTracker` should only be used when one of its
|
||||
/// unique features is required:
|
||||
///
|
||||
/// 1. When tasks exit, a `TaskTracker` will allow the task to immediately free its memory.
|
||||
/// 2. By not closing the `TaskTracker`, [`wait`] will be prevented from from returning even if
|
||||
/// the `TaskTracker` is empty.
|
||||
/// 3. A `TaskTracker` does not require mutable access to insert tasks.
|
||||
/// 4. A `TaskTracker` can be cloned to share it with many tasks.
|
||||
///
|
||||
/// The first point is the most important one. A [`JoinSet`] keeps track of the return value of
|
||||
/// every inserted task. This means that if the caller keeps inserting tasks and never calls
|
||||
/// [`join_next`], then their return values will keep building up and consuming memory, _even if_
|
||||
/// most of the tasks have already exited. This can cause the process to run out of memory. With a
|
||||
/// `TaskTracker`, this does not happen. Once tasks exit, they are immediately removed from the
|
||||
/// `TaskTracker`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// For more examples, please see the topic page on [graceful shutdown].
|
||||
///
|
||||
/// ## Spawn tasks and wait for them to exit
|
||||
///
|
||||
/// This is a simple example. For this case, [`JoinSet`] should probably be used instead.
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
///
|
||||
/// for i in 0..10 {
|
||||
/// tracker.spawn(async move {
|
||||
/// println!("Task {} is running!", i);
|
||||
/// });
|
||||
/// }
|
||||
/// // Once we spawned everything, we close the tracker.
|
||||
/// tracker.close();
|
||||
///
|
||||
/// // Wait for everything to finish.
|
||||
/// tracker.wait().await;
|
||||
///
|
||||
/// println!("This is printed after all of the tasks.");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Wait for tasks to exit
|
||||
///
|
||||
/// This example shows the intended use-case of `TaskTracker`. It is used together with
|
||||
/// [`CancellationToken`] to implement graceful shutdown.
|
||||
/// ```
|
||||
/// use tokio_util::sync::CancellationToken;
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
/// use tokio::time::{self, Duration};
|
||||
///
|
||||
/// async fn background_task(num: u64) {
|
||||
/// for i in 0..10 {
|
||||
/// time::sleep(Duration::from_millis(100*num)).await;
|
||||
/// println!("Background task {} in iteration {}.", num, i);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// # async fn _hidden() {}
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
/// let token = CancellationToken::new();
|
||||
///
|
||||
/// for i in 0..10 {
|
||||
/// let token = token.clone();
|
||||
/// tracker.spawn(async move {
|
||||
/// // Use a `tokio::select!` to kill the background task if the token is
|
||||
/// // cancelled.
|
||||
/// tokio::select! {
|
||||
/// () = background_task(i) => {
|
||||
/// println!("Task {} exiting normally.", i);
|
||||
/// },
|
||||
/// () = token.cancelled() => {
|
||||
/// // Do some cleanup before we really exit.
|
||||
/// time::sleep(Duration::from_millis(50)).await;
|
||||
/// println!("Task {} finished cleanup.", i);
|
||||
/// },
|
||||
/// }
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// // Spawn a background task that will send the shutdown signal.
|
||||
/// {
|
||||
/// let tracker = tracker.clone();
|
||||
/// tokio::spawn(async move {
|
||||
/// // Normally you would use something like ctrl-c instead of
|
||||
/// // sleeping.
|
||||
/// time::sleep(Duration::from_secs(2)).await;
|
||||
/// tracker.close();
|
||||
/// token.cancel();
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// // Wait for all tasks to exit.
|
||||
/// tracker.wait().await;
|
||||
///
|
||||
/// println!("All tasks have exited now.");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`CancellationToken`]: crate::sync::CancellationToken
|
||||
/// [`JoinHandle::is_finished`]: tokio::task::JoinHandle::is_finished
|
||||
/// [`JoinSet`]: tokio::task::JoinSet
|
||||
/// [`close`]: Self::close
|
||||
/// [`join_next`]: tokio::task::JoinSet::join_next
|
||||
/// [`wait`]: Self::wait
|
||||
/// [graceful shutdown]: https://tokio.rs/tokio/topics/shutdown
|
||||
pub struct TaskTracker {
|
||||
inner: Arc<TaskTrackerInner>,
|
||||
}
|
||||
|
||||
/// Represents a task tracked by a [`TaskTracker`].
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct TaskTrackerToken {
|
||||
task_tracker: TaskTracker,
|
||||
}
|
||||
|
||||
struct TaskTrackerInner {
|
||||
/// Keeps track of the state.
|
||||
///
|
||||
/// The lowest bit is whether the task tracker is closed.
|
||||
///
|
||||
/// The rest of the bits count the number of tracked tasks.
|
||||
state: AtomicUsize,
|
||||
/// Used to notify when the last task exits.
|
||||
on_last_exit: Notify,
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A future that is tracked as a task by a [`TaskTracker`].
|
||||
///
|
||||
/// The associated [`TaskTracker`] cannot complete until this future is dropped.
|
||||
///
|
||||
/// This future is returned by [`TaskTracker::track_future`].
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct TrackedFuture<F> {
|
||||
#[pin]
|
||||
future: F,
|
||||
token: TaskTrackerToken,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A future that completes when the [`TaskTracker`] is empty and closed.
|
||||
///
|
||||
/// This future is returned by [`TaskTracker::wait`].
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct TaskTrackerWaitFuture<'a> {
|
||||
#[pin]
|
||||
future: Notified<'a>,
|
||||
inner: Option<&'a TaskTrackerInner>,
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTrackerInner {
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
state: AtomicUsize::new(0),
|
||||
on_last_exit: Notify::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_closed_and_empty(&self) -> bool {
|
||||
// If empty and closed bit set, then we are done.
|
||||
//
|
||||
// The acquire load will synchronize with the release store of any previous call to
|
||||
// `set_closed` and `drop_task`.
|
||||
self.state.load(Ordering::Acquire) == 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_closed(&self) -> bool {
|
||||
// The AcqRel ordering makes the closed bit behave like a `Mutex<bool>` for synchronization
|
||||
// purposes. We do this because it makes the return value of `TaskTracker::{close,reopen}`
|
||||
// more meaningful for the user. Without these orderings, this assert could fail:
|
||||
// ```
|
||||
// // thread 1
|
||||
// some_other_atomic.store(true, Relaxed);
|
||||
// tracker.close();
|
||||
//
|
||||
// // thread 2
|
||||
// if tracker.reopen() {
|
||||
// assert!(some_other_atomic.load(Relaxed));
|
||||
// }
|
||||
// ```
|
||||
// However, with the AcqRel ordering, we establish a happens-before relationship from the
|
||||
// call to `close` and the later call to `reopen` that returned true.
|
||||
let state = self.state.fetch_or(1, Ordering::AcqRel);
|
||||
|
||||
// If there are no tasks, and if it was not already closed:
|
||||
if state == 0 {
|
||||
self.notify_now();
|
||||
}
|
||||
|
||||
(state & 1) == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_open(&self) -> bool {
|
||||
// See `set_closed` regarding the AcqRel ordering.
|
||||
let state = self.state.fetch_and(!1, Ordering::AcqRel);
|
||||
(state & 1) == 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_task(&self) {
|
||||
self.state.fetch_add(2, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn drop_task(&self) {
|
||||
let state = self.state.fetch_sub(2, Ordering::Release);
|
||||
|
||||
// If this was the last task and we are closed:
|
||||
if state == 3 {
|
||||
self.notify_now();
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn notify_now(&self) {
|
||||
// Insert an acquire fence. This matters for `drop_task` but doesn't matter for
|
||||
// `set_closed` since it already uses AcqRel.
|
||||
//
|
||||
// This synchronizes with the release store of any other call to `drop_task`, and with the
|
||||
// release store in the call to `set_closed`. That ensures that everything that happened
|
||||
// before those other calls to `drop_task` or `set_closed` will be visible after this load,
|
||||
// and those things will also be visible to anything woken by the call to `notify_waiters`.
|
||||
self.state.load(Ordering::Acquire);
|
||||
|
||||
self.on_last_exit.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTracker {
|
||||
/// Creates a new `TaskTracker`.
|
||||
///
|
||||
/// The `TaskTracker` will start out as open.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(TaskTrackerInner::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until this `TaskTracker` is both closed and empty.
|
||||
///
|
||||
/// If the `TaskTracker` is already closed and empty when this method is called, then it
|
||||
/// returns immediately.
|
||||
///
|
||||
/// The `wait` future is resistant against [ABA problems][aba]. That is, if the `TaskTracker`
|
||||
/// becomes both closed and empty for a short amount of time, then it is guarantee that all
|
||||
/// `wait` futures that were created before the short time interval will trigger, even if they
|
||||
/// are not polled during that short time interval.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe.
|
||||
///
|
||||
/// However, the resistance against [ABA problems][aba] is lost when using `wait` as the
|
||||
/// condition in a `tokio::select!` loop.
|
||||
///
|
||||
/// [aba]: https://en.wikipedia.org/wiki/ABA_problem
|
||||
#[inline]
|
||||
pub fn wait(&self) -> TaskTrackerWaitFuture<'_> {
|
||||
TaskTrackerWaitFuture {
|
||||
future: self.inner.on_last_exit.notified(),
|
||||
inner: if self.inner.is_closed_and_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&self.inner)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Close this `TaskTracker`.
|
||||
///
|
||||
/// This allows [`wait`] futures to complete. It does not prevent you from spawning new tasks.
|
||||
///
|
||||
/// Returns `true` if this closed the `TaskTracker`, or `false` if it was already closed.
|
||||
///
|
||||
/// [`wait`]: Self::wait
|
||||
#[inline]
|
||||
pub fn close(&self) -> bool {
|
||||
self.inner.set_closed()
|
||||
}
|
||||
|
||||
/// Reopen this `TaskTracker`.
|
||||
///
|
||||
/// This prevents [`wait`] futures from completing even if the `TaskTracker` is empty.
|
||||
///
|
||||
/// Returns `true` if this reopened the `TaskTracker`, or `false` if it was already open.
|
||||
///
|
||||
/// [`wait`]: Self::wait
|
||||
#[inline]
|
||||
pub fn reopen(&self) -> bool {
|
||||
self.inner.set_open()
|
||||
}
|
||||
|
||||
/// Returns `true` if this `TaskTracker` is [closed](Self::close).
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_closed(&self) -> bool {
|
||||
(self.inner.state.load(Ordering::Acquire) & 1) != 0
|
||||
}
|
||||
|
||||
/// Returns the number of tasks tracked by this `TaskTracker`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.state.load(Ordering::Acquire) >> 1
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no tasks in this `TaskTracker`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.state.load(Ordering::Acquire) <= 1
|
||||
}
|
||||
|
||||
/// Spawn the provided future on the current Tokio runtime, and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `tokio::spawn(tracker.track_future(task))`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn<F>(&self, task: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
tokio::task::spawn(self.track_future(task))
|
||||
}
|
||||
|
||||
/// Spawn the provided future on the provided Tokio runtime, and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `handle.spawn(tracker.track_future(task))`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_on<F>(&self, task: F, handle: &Handle) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
handle.spawn(self.track_future(task))
|
||||
}
|
||||
|
||||
/// Spawn the provided future on the current [`LocalSet`], and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `tokio::task::spawn_local(tracker.track_future(task))`.
|
||||
///
|
||||
/// [`LocalSet`]: tokio::task::LocalSet
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_local<F>(&self, task: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + 'static,
|
||||
F::Output: 'static,
|
||||
{
|
||||
tokio::task::spawn_local(self.track_future(task))
|
||||
}
|
||||
|
||||
/// Spawn the provided future on the provided [`LocalSet`], and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `local_set.spawn_local(tracker.track_future(task))`.
|
||||
///
|
||||
/// [`LocalSet`]: tokio::task::LocalSet
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_local_on<F>(&self, task: F, local_set: &LocalSet) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + 'static,
|
||||
F::Output: 'static,
|
||||
{
|
||||
local_set.spawn_local(self.track_future(task))
|
||||
}
|
||||
|
||||
/// Spawn the provided blocking task on the current Tokio runtime, and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `tokio::task::spawn_blocking(tracker.track_future(task))`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_blocking<F, T>(&self, task: F) -> JoinHandle<T>
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
F: Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let token = self.token();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let res = task();
|
||||
drop(token);
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn the provided blocking task on the provided Tokio runtime, and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `handle.spawn_blocking(tracker.track_future(task))`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_blocking_on<F, T>(&self, task: F, handle: &Handle) -> JoinHandle<T>
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
F: Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let token = self.token();
|
||||
handle.spawn_blocking(move || {
|
||||
let res = task();
|
||||
drop(token);
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Track the provided future.
|
||||
///
|
||||
/// The returned [`TrackedFuture`] will count as a task tracked by this collection, and will
|
||||
/// prevent calls to [`wait`] from returning until the task is dropped.
|
||||
///
|
||||
/// The task is removed from the collection when it is dropped, not when [`poll`] returns
|
||||
/// [`Poll::Ready`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Track a future spawned with [`tokio::spawn`].
|
||||
///
|
||||
/// ```
|
||||
/// # async fn my_async_fn() {}
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
///
|
||||
/// tokio::spawn(tracker.track_future(my_async_fn()));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Track a future spawned on a [`JoinSet`].
|
||||
/// ```
|
||||
/// # async fn my_async_fn() {}
|
||||
/// use tokio::task::JoinSet;
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
/// let mut join_set = JoinSet::new();
|
||||
///
|
||||
/// join_set.spawn(tracker.track_future(my_async_fn()));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`JoinSet`]: tokio::task::JoinSet
|
||||
/// [`Poll::Pending`]: std::task::Poll::Pending
|
||||
/// [`poll`]: std::future::Future::poll
|
||||
/// [`wait`]: Self::wait
|
||||
#[inline]
|
||||
pub fn track_future<F: Future>(&self, future: F) -> TrackedFuture<F> {
|
||||
TrackedFuture {
|
||||
future,
|
||||
token: self.token(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`TaskTrackerToken`] representing a task tracked by this `TaskTracker`.
|
||||
///
|
||||
/// This token is a lower-level utility than the spawn methods. Each token is considered to
|
||||
/// correspond to a task. As long as the token exists, the `TaskTracker` cannot complete.
|
||||
/// Furthermore, the count returned by the [`len`] method will include the tokens in the count.
|
||||
///
|
||||
/// Dropping the token indicates to the `TaskTracker` that the task has exited.
|
||||
///
|
||||
/// [`len`]: TaskTracker::len
|
||||
#[inline]
|
||||
pub fn token(&self) -> TaskTrackerToken {
|
||||
self.inner.add_task();
|
||||
TaskTrackerToken {
|
||||
task_tracker: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if both task trackers correspond to the same set of tasks.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// let tracker_1 = TaskTracker::new();
|
||||
/// let tracker_2 = TaskTracker::new();
|
||||
/// let tracker_1_clone = tracker_1.clone();
|
||||
///
|
||||
/// assert!(TaskTracker::ptr_eq(&tracker_1, &tracker_1_clone));
|
||||
/// assert!(!TaskTracker::ptr_eq(&tracker_1, &tracker_2));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn ptr_eq(left: &TaskTracker, right: &TaskTracker) -> bool {
|
||||
Arc::ptr_eq(&left.inner, &right.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TaskTracker {
|
||||
/// Creates a new `TaskTracker`.
|
||||
///
|
||||
/// The `TaskTracker` will start out as open.
|
||||
#[inline]
|
||||
fn default() -> TaskTracker {
|
||||
TaskTracker::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TaskTracker {
|
||||
/// Returns a new `TaskTracker` that tracks the same set of tasks.
|
||||
///
|
||||
/// Since the new `TaskTracker` shares the same set of tasks, changes to one set are visible in
|
||||
/// all other clones.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// # async fn _hidden() {}
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
/// let cloned = tracker.clone();
|
||||
///
|
||||
/// // Spawns on `tracker` are visible in `cloned`.
|
||||
/// tracker.spawn(std::future::pending::<()>());
|
||||
/// assert_eq!(cloned.len(), 1);
|
||||
///
|
||||
/// // Spawns on `cloned` are visible in `tracker`.
|
||||
/// cloned.spawn(std::future::pending::<()>());
|
||||
/// assert_eq!(tracker.len(), 2);
|
||||
///
|
||||
/// // Calling `close` is visible to `cloned`.
|
||||
/// tracker.close();
|
||||
/// assert!(cloned.is_closed());
|
||||
///
|
||||
/// // Calling `reopen` is visible to `tracker`.
|
||||
/// cloned.reopen();
|
||||
/// assert!(!tracker.is_closed());
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
fn clone(&self) -> TaskTracker {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_inner(inner: &TaskTrackerInner, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let state = inner.state.load(Ordering::Acquire);
|
||||
let is_closed = (state & 1) != 0;
|
||||
let len = state >> 1;
|
||||
|
||||
f.debug_struct("TaskTracker")
|
||||
.field("len", &len)
|
||||
.field("is_closed", &is_closed)
|
||||
.field("inner", &(inner as *const TaskTrackerInner))
|
||||
.finish()
|
||||
}
|
||||
|
||||
impl fmt::Debug for TaskTracker {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
debug_inner(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTrackerToken {
|
||||
/// Returns the [`TaskTracker`] that this token is associated with.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn task_tracker(&self) -> &TaskTracker {
|
||||
&self.task_tracker
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TaskTrackerToken {
|
||||
/// Returns a new `TaskTrackerToken` associated with the same [`TaskTracker`].
|
||||
///
|
||||
/// This is equivalent to `token.task_tracker().token()`.
|
||||
#[inline]
|
||||
fn clone(&self) -> TaskTrackerToken {
|
||||
self.task_tracker.token()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TaskTrackerToken {
|
||||
/// Dropping the token indicates to the [`TaskTracker`] that the task has exited.
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
self.task_tracker.inner.drop_task();
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for TrackedFuture<F> {
|
||||
type Output = F::Output;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
|
||||
self.project().future.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: fmt::Debug> fmt::Debug for TrackedFuture<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TrackedFuture")
|
||||
.field("future", &self.future)
|
||||
.field("task_tracker", self.token.task_tracker())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Future for TaskTrackerWaitFuture<'a> {
|
||||
type Output = ();
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||
let me = self.project();
|
||||
|
||||
let inner = match me.inner.as_ref() {
|
||||
None => return Poll::Ready(()),
|
||||
Some(inner) => inner,
|
||||
};
|
||||
|
||||
let ready = inner.is_closed_and_empty() || me.future.poll(cx).is_ready();
|
||||
if ready {
|
||||
*me.inner = None;
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for TaskTrackerWaitFuture<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
struct Helper<'a>(&'a TaskTrackerInner);
|
||||
|
||||
impl fmt::Debug for Helper<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
debug_inner(self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
f.debug_struct("TaskTrackerWaitFuture")
|
||||
.field("future", &self.future)
|
||||
.field("task_tracker", &self.inner.map(Helper))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -671,6 +671,39 @@ impl<T> DelayQueue<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the deadline of the item associated with `key`.
|
||||
///
|
||||
/// Since the queue operates at millisecond granularity, the returned
|
||||
/// deadline may not exactly match the value that was given when initially
|
||||
/// inserting the item into the queue.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `key` is not contained by the queue.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
///
|
||||
/// let key1 = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
/// let key2 = delay_queue.insert("bar", Duration::from_secs(10));
|
||||
///
|
||||
/// assert!(delay_queue.deadline(&key1) < delay_queue.deadline(&key2));
|
||||
/// # }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn deadline(&self, key: &Key) -> Instant {
|
||||
self.start + Duration::from_millis(self.slab[*key].when)
|
||||
}
|
||||
|
||||
/// Removes the key from the expired queue or the timer wheel
|
||||
/// depending on its expiration status.
|
||||
///
|
||||
@@ -909,8 +942,10 @@ impl<T> DelayQueue<T> {
|
||||
self.expired.peek().or_else(|| self.wheel.peek())
|
||||
}
|
||||
|
||||
/// Returns the next time to poll as determined by the wheel
|
||||
fn next_deadline(&mut self) -> Option<Instant> {
|
||||
/// Returns the next time to poll as determined by the wheel.
|
||||
///
|
||||
/// Note that this does not include deadlines in the `expired` queue.
|
||||
fn next_deadline(&self) -> Option<Instant> {
|
||||
self.wheel
|
||||
.poll_at()
|
||||
.map(|poll_at| self.start + Duration::from_millis(poll_at))
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
use core::future::Future;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
/// A wrapper type that tells the compiler that the contents might not be valid.
|
||||
///
|
||||
/// This is necessary mainly when `T` contains a reference. In that case, the
|
||||
/// compiler will sometimes assume that the reference is always valid; in some
|
||||
/// cases it will assume this even after the destructor of `T` runs. For
|
||||
/// example, when a reference is used as a function argument, then the compiler
|
||||
/// will assume that the reference is valid until the function returns, even if
|
||||
/// the reference is destroyed during the function. When the reference is used
|
||||
/// as part of a self-referential struct, that assumption can be false. Wrapping
|
||||
/// the reference in this type prevents the compiler from making that
|
||||
/// assumption.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// The `MaybeUninit` will always contain a valid value until the destructor runs.
|
||||
//
|
||||
// Reference
|
||||
// See <https://users.rust-lang.org/t/unsafe-code-review-semi-owning-weak-rwlock-t-guard/95706>
|
||||
//
|
||||
// TODO: replace this with an official solution once RFC #3336 or similar is available.
|
||||
// <https://github.com/rust-lang/rfcs/pull/3336>
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct MaybeDangling<T>(MaybeUninit<T>);
|
||||
|
||||
impl<T> Drop for MaybeDangling<T> {
|
||||
fn drop(&mut self) {
|
||||
// Safety: `0` is always initialized.
|
||||
unsafe { core::ptr::drop_in_place(self.0.as_mut_ptr()) };
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MaybeDangling<T> {
|
||||
pub(crate) fn new(inner: T) -> Self {
|
||||
Self(MaybeUninit::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for MaybeDangling<F> {
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// Safety: `0` is always initialized.
|
||||
let fut = unsafe { self.map_unchecked_mut(|this| this.0.assume_init_mut()) };
|
||||
fut.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybedangling_runs_drop() {
|
||||
struct SetOnDrop<'a>(&'a mut bool);
|
||||
|
||||
impl Drop for SetOnDrop<'_> {
|
||||
fn drop(&mut self) {
|
||||
*self.0 = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut success = false;
|
||||
|
||||
drop(MaybeDangling::new(SetOnDrop(&mut success)));
|
||||
assert!(success);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
mod maybe_dangling;
|
||||
#[cfg(any(feature = "io", feature = "codec"))]
|
||||
mod poll_buf;
|
||||
|
||||
pub(crate) use maybe_dangling::MaybeDangling;
|
||||
#[cfg(any(feature = "io", feature = "codec"))]
|
||||
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
|
||||
pub use poll_buf::{poll_read_buf, poll_write_buf};
|
||||
@@ -0,0 +1,145 @@
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
use bytes::{Buf, BufMut};
|
||||
use futures_core::ready;
|
||||
use std::io::{self, IoSlice};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Try to read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
|
||||
///
|
||||
/// [`BufMut`]: bytes::Buf
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::{Bytes, BytesMut};
|
||||
/// use tokio_stream as stream;
|
||||
/// use tokio::io::Result;
|
||||
/// use tokio_util::io::{StreamReader, poll_read_buf};
|
||||
/// use futures::future::poll_fn;
|
||||
/// use std::pin::Pin;
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a reader from an iterator. This particular reader will always be
|
||||
/// // ready.
|
||||
/// let mut read = StreamReader::new(stream::iter(vec![Result::Ok(Bytes::from_static(&[0, 1, 2, 3]))]));
|
||||
///
|
||||
/// let mut buf = BytesMut::new();
|
||||
/// let mut reads = 0;
|
||||
///
|
||||
/// loop {
|
||||
/// reads += 1;
|
||||
/// let n = poll_fn(|cx| poll_read_buf(Pin::new(&mut read), cx, &mut buf)).await?;
|
||||
///
|
||||
/// if n == 0 {
|
||||
/// break;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // one or more reads might be necessary.
|
||||
/// assert!(reads >= 1);
|
||||
/// assert_eq!(&buf[..], &[0, 1, 2, 3]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
|
||||
pub fn poll_read_buf<T: AsyncRead, B: BufMut>(
|
||||
io: Pin<&mut T>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if !buf.has_remaining_mut() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let n = {
|
||||
let dst = buf.chunk_mut();
|
||||
|
||||
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
|
||||
// transparent wrapper around `[MaybeUninit<u8>]`.
|
||||
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
|
||||
let mut buf = ReadBuf::uninit(dst);
|
||||
let ptr = buf.filled().as_ptr();
|
||||
ready!(io.poll_read(cx, &mut buf)?);
|
||||
|
||||
// Ensure the pointer does not change from under us
|
||||
assert_eq!(ptr, buf.filled().as_ptr());
|
||||
buf.filled().len()
|
||||
};
|
||||
|
||||
// Safety: This is guaranteed to be the number of initialized (and read)
|
||||
// bytes due to the invariants provided by `ReadBuf::filled`.
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
|
||||
/// Try to write data from an implementer of the [`Buf`] trait to an
|
||||
/// [`AsyncWrite`], advancing the buffer's internal cursor.
|
||||
///
|
||||
/// This function will use [vectored writes] when the [`AsyncWrite`] supports
|
||||
/// vectored writes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`] implements [`AsyncWrite`] and [`Cursor<&[u8]>`] implements
|
||||
/// [`Buf`]:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio_util::io::poll_write_buf;
|
||||
/// use tokio::io;
|
||||
/// use tokio::fs::File;
|
||||
///
|
||||
/// use bytes::Buf;
|
||||
/// use std::io::Cursor;
|
||||
/// use std::pin::Pin;
|
||||
/// use futures::future::poll_fn;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut file = File::create("foo.txt").await?;
|
||||
/// let mut buf = Cursor::new(b"data to write");
|
||||
///
|
||||
/// // Loop until the entire contents of the buffer are written to
|
||||
/// // the file.
|
||||
/// while buf.has_remaining() {
|
||||
/// poll_fn(|cx| poll_write_buf(Pin::new(&mut file), cx, &mut buf)).await?;
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Buf`]: bytes::Buf
|
||||
/// [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
/// [`File`]: tokio::fs::File
|
||||
/// [vectored writes]: tokio::io::AsyncWrite::poll_write_vectored
|
||||
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
|
||||
pub fn poll_write_buf<T: AsyncWrite, B: Buf>(
|
||||
io: Pin<&mut T>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
const MAX_BUFS: usize = 64;
|
||||
|
||||
if !buf.has_remaining() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let n = if io.is_write_vectored() {
|
||||
let mut slices = [IoSlice::new(&[]); MAX_BUFS];
|
||||
let cnt = buf.chunks_vectored(&mut slices);
|
||||
ready!(io.poll_write_vectored(cx, &slices[..cnt]))?
|
||||
} else {
|
||||
ready!(io.poll_write(cx, buf.chunk()))?
|
||||
};
|
||||
|
||||
buf.advance(n);
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#![cfg(all(feature = "compat"))]
|
||||
#![cfg(not(target_os = "wasi"))] // WASI does not support all fs operations
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures_io::SeekFrom;
|
||||
use futures_util::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::fs::OpenOptions;
|
||||
use tokio_util::compat::TokioAsyncWriteCompatExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn compat_file_seek() -> futures_util::io::Result<()> {
|
||||
let temp_file = NamedTempFile::new()?;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(temp_file)
|
||||
.await?
|
||||
.compat_write();
|
||||
|
||||
file.write_all(&[0, 1, 2, 3, 4, 5]).await?;
|
||||
file.write_all(&[6, 7]).await?;
|
||||
|
||||
assert_eq!(file.stream_position().await?, 8);
|
||||
|
||||
// Modify elements at position 2.
|
||||
assert_eq!(file.seek(SeekFrom::Start(2)).await?, 2);
|
||||
file.write_all(&[8, 9]).await?;
|
||||
|
||||
file.flush().await?;
|
||||
|
||||
// Verify we still have 8 elements.
|
||||
assert_eq!(file.seek(SeekFrom::End(0)).await?, 8);
|
||||
// Seek back to the start of the file to read and verify contents.
|
||||
file.seek(SeekFrom::Start(0)).await?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let num_bytes = file.read_to_end(&mut buf).await?;
|
||||
assert_eq!(&buf[..num_bytes], &[0, 1, 8, 9, 4, 5, 6, 7]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -43,6 +43,18 @@ async fn test_async_write_to_sync() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_into_inner() -> Result<(), Box<dyn Error>> {
|
||||
let mut buf = Vec::new();
|
||||
SyncIoBridge::new(tokio::io::empty())
|
||||
.into_inner()
|
||||
.read_to_end(&mut buf)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(buf.len(), 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_shutdown() -> Result<(), Box<dyn Error>> {
|
||||
let (s1, mut s2) = tokio::io::duplex(1024);
|
||||
|
||||
@@ -12,7 +12,6 @@ use futures::{pin_mut, Sink, Stream};
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll::*;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
macro_rules! mock {
|
||||
@@ -39,10 +38,10 @@ macro_rules! assert_next_eq {
|
||||
macro_rules! assert_next_pending {
|
||||
($io:ident) => {{
|
||||
task::spawn(()).enter(|cx, _| match $io.as_mut().poll_next(cx) {
|
||||
Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Ready(Some(Err(e))) => panic!("error = {:?}", e),
|
||||
Ready(None) => panic!("done"),
|
||||
Pending => {}
|
||||
Poll::Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Poll::Ready(Some(Err(e))) => panic!("error = {:?}", e),
|
||||
Poll::Ready(None) => panic!("done"),
|
||||
Poll::Pending => {}
|
||||
});
|
||||
}};
|
||||
}
|
||||
@@ -50,10 +49,10 @@ macro_rules! assert_next_pending {
|
||||
macro_rules! assert_next_err {
|
||||
($io:ident) => {{
|
||||
task::spawn(()).enter(|cx, _| match $io.as_mut().poll_next(cx) {
|
||||
Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Ready(Some(Err(_))) => {}
|
||||
Ready(None) => panic!("done"),
|
||||
Pending => panic!("pending"),
|
||||
Poll::Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Poll::Ready(Some(Err(_))) => {}
|
||||
Poll::Ready(None) => panic!("done"),
|
||||
Poll::Pending => panic!("pending"),
|
||||
});
|
||||
}};
|
||||
}
|
||||
@@ -186,11 +185,11 @@ fn read_single_frame_multi_packet_wait() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x09abc"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"defghi"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
@@ -208,15 +207,15 @@ fn read_multi_frame_multi_packet_wait() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x09abc"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"defghi"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x00\x00\x0312"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"3\x00\x00\x00\x0bhello world"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
@@ -250,9 +249,9 @@ fn read_incomplete_head() {
|
||||
fn read_incomplete_head_multi() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
@@ -268,9 +267,9 @@ fn read_incomplete_payload() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00\x00\x09ab"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"cd"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
@@ -310,7 +309,7 @@ fn read_update_max_frame_len_at_rest() {
|
||||
fn read_update_max_frame_len_in_flight() {
|
||||
let io = length_delimited::Builder::new().new_read(mock! {
|
||||
data(b"\x00\x00\x00\x09abcd"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"efghi"),
|
||||
data(b"\x00\x00\x00\x09abcdefghi"),
|
||||
});
|
||||
@@ -533,9 +532,9 @@ fn write_single_multi_frame_multi_packet() {
|
||||
fn write_single_frame_would_block() {
|
||||
let io = FramedWrite::new(
|
||||
mock! {
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x09"),
|
||||
data(b"abcdefghi"),
|
||||
flush(),
|
||||
@@ -640,7 +639,7 @@ fn write_update_max_frame_len_in_flight() {
|
||||
let io = length_delimited::Builder::new().new_write(mock! {
|
||||
data(b"\x00\x00\x00\x06"),
|
||||
data(b"ab"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"cdef"),
|
||||
flush(),
|
||||
});
|
||||
@@ -701,8 +700,6 @@ enum Op {
|
||||
Flush,
|
||||
}
|
||||
|
||||
use self::Op::*;
|
||||
|
||||
impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
@@ -710,15 +707,15 @@ impl AsyncRead for Mock {
|
||||
dst: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Data(data)))) => {
|
||||
Some(Poll::Ready(Ok(Op::Data(data)))) => {
|
||||
debug_assert!(dst.remaining() >= data.len());
|
||||
dst.put_slice(&data);
|
||||
Ready(Ok(()))
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(())),
|
||||
Some(Poll::Ready(Ok(_))) => panic!(),
|
||||
Some(Poll::Ready(Err(e))) => Poll::Ready(Err(e)),
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,31 +727,31 @@ impl AsyncWrite for Mock {
|
||||
src: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Data(data)))) => {
|
||||
Some(Poll::Ready(Ok(Op::Data(data)))) => {
|
||||
let len = data.len();
|
||||
assert!(src.len() >= len, "expect={:?}; actual={:?}", data, src);
|
||||
assert_eq!(&data[..], &src[..len]);
|
||||
Ready(Ok(len))
|
||||
Poll::Ready(Ok(len))
|
||||
}
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(0)),
|
||||
Some(Poll::Ready(Ok(_))) => panic!(),
|
||||
Some(Poll::Ready(Err(e))) => Poll::Ready(Err(e)),
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(Ok(0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Flush))) => Ready(Ok(())),
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(())),
|
||||
Some(Poll::Ready(Ok(Op::Flush))) => Poll::Ready(Ok(())),
|
||||
Some(Poll::Ready(Ok(_))) => panic!(),
|
||||
Some(Poll::Ready(Err(e))) => Poll::Ready(Err(e)),
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Ready(Ok(()))
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,9 +768,9 @@ impl From<Vec<u8>> for Op {
|
||||
}
|
||||
|
||||
fn data(bytes: &[u8]) -> Poll<io::Result<Op>> {
|
||||
Ready(Ok(bytes.into()))
|
||||
Poll::Ready(Ok(bytes.into()))
|
||||
}
|
||||
|
||||
fn flush() -> Poll<io::Result<Op>> {
|
||||
Ready(Ok(Flush))
|
||||
Poll::Ready(Ok(Op::Flush))
|
||||
}
|
||||
|
||||
@@ -109,6 +109,30 @@ async fn alternating() {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_keys() {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let mut map = JoinMap::new();
|
||||
|
||||
assert_eq!(map.len(), 0);
|
||||
map.spawn(1, async {});
|
||||
assert_eq!(map.len(), 1);
|
||||
map.spawn(2, async {});
|
||||
assert_eq!(map.len(), 2);
|
||||
|
||||
let keys = map.keys().collect::<HashSet<&u32>>();
|
||||
assert!(keys.contains(&1));
|
||||
assert!(keys.contains(&2));
|
||||
|
||||
let _ = map.join_next().await.unwrap();
|
||||
let _ = map.join_next().await.unwrap();
|
||||
|
||||
assert_eq!(map.len(), 0);
|
||||
let keys = map.keys().collect::<HashSet<&u32>>();
|
||||
assert!(keys.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn abort_by_key() {
|
||||
let mut map = JoinMap::new();
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio_test::{assert_pending, assert_ready, task};
|
||||
use tokio_util::task::TaskTracker;
|
||||
|
||||
#[test]
|
||||
fn open_close() {
|
||||
let tracker = TaskTracker::new();
|
||||
assert!(!tracker.is_closed());
|
||||
assert!(tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 0);
|
||||
|
||||
tracker.close();
|
||||
assert!(tracker.is_closed());
|
||||
assert!(tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 0);
|
||||
|
||||
tracker.reopen();
|
||||
assert!(!tracker.is_closed());
|
||||
tracker.reopen();
|
||||
assert!(!tracker.is_closed());
|
||||
|
||||
assert!(tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 0);
|
||||
|
||||
tracker.close();
|
||||
assert!(tracker.is_closed());
|
||||
tracker.close();
|
||||
assert!(tracker.is_closed());
|
||||
|
||||
assert!(tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_len() {
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
let mut tokens = Vec::new();
|
||||
for i in 0..10 {
|
||||
assert_eq!(tracker.len(), i);
|
||||
tokens.push(tracker.token());
|
||||
}
|
||||
|
||||
assert!(!tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 10);
|
||||
|
||||
for (i, token) in tokens.into_iter().enumerate() {
|
||||
drop(token);
|
||||
assert_eq!(tracker.len(), 9 - i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_immediately() {
|
||||
let tracker = TaskTracker::new();
|
||||
tracker.close();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_immediately_on_reopen() {
|
||||
let tracker = TaskTracker::new();
|
||||
tracker.close();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
tracker.reopen();
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_on_close() {
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
|
||||
assert_pending!(wait.poll());
|
||||
tracker.close();
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_on_close_reopen() {
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
|
||||
assert_pending!(wait.poll());
|
||||
tracker.close();
|
||||
tracker.reopen();
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_on_last_task() {
|
||||
let tracker = TaskTracker::new();
|
||||
tracker.close();
|
||||
let token = tracker.token();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
assert_pending!(wait.poll());
|
||||
drop(token);
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_on_last_task_respawn() {
|
||||
let tracker = TaskTracker::new();
|
||||
tracker.close();
|
||||
let token = tracker.token();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
assert_pending!(wait.poll());
|
||||
drop(token);
|
||||
let token2 = tracker.token();
|
||||
assert_ready!(wait.poll());
|
||||
drop(token2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_notify_on_respawn_if_open() {
|
||||
let tracker = TaskTracker::new();
|
||||
let token = tracker.token();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
assert_pending!(wait.poll());
|
||||
drop(token);
|
||||
let token2 = tracker.token();
|
||||
assert_pending!(wait.poll());
|
||||
drop(token2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_during_exit() {
|
||||
const ITERS: usize = 5;
|
||||
|
||||
for close_spot in 0..=ITERS {
|
||||
let tracker = TaskTracker::new();
|
||||
let tokens: Vec<_> = (0..ITERS).map(|_| tracker.token()).collect();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
|
||||
for (i, token) in tokens.into_iter().enumerate() {
|
||||
assert_pending!(wait.poll());
|
||||
if i == close_spot {
|
||||
tracker.close();
|
||||
assert_pending!(wait.poll());
|
||||
}
|
||||
drop(token);
|
||||
}
|
||||
|
||||
if close_spot == ITERS {
|
||||
assert_pending!(wait.poll());
|
||||
tracker.close();
|
||||
}
|
||||
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_many() {
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
let mut waits: Vec<_> = (0..10).map(|_| task::spawn(tracker.wait())).collect();
|
||||
|
||||
for wait in &mut waits {
|
||||
assert_pending!(wait.poll());
|
||||
}
|
||||
|
||||
tracker.close();
|
||||
|
||||
for wait in &mut waits {
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -13,7 +13,15 @@ use futures::sink::SinkExt;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg_attr(any(target_os = "macos", target_os = "ios"), allow(unused_assignments))]
|
||||
#[cfg_attr(
|
||||
any(
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos"
|
||||
),
|
||||
allow(unused_assignments)
|
||||
)]
|
||||
#[tokio::test]
|
||||
async fn send_framed_byte_codec() -> std::io::Result<()> {
|
||||
let mut a_soc = UdpSocket::bind("127.0.0.1:0").await?;
|
||||
@@ -41,7 +49,12 @@ async fn send_framed_byte_codec() -> std::io::Result<()> {
|
||||
b_soc = b.into_inner();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos"
|
||||
)))]
|
||||
// test sending & receiving an empty message
|
||||
{
|
||||
let mut a = UdpFramed::new(a_soc, ByteCodec);
|
||||
|
||||
+409
-1
@@ -1,3 +1,354 @@
|
||||
# 1.35.1 (December 19, 2023)
|
||||
|
||||
This is a forward part of a change that was backported to 1.25.3.
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: add budgeting to `tokio::runtime::io::registration::async_io` ([#6221])
|
||||
|
||||
[#6221]: https://github.com/tokio-rs/tokio/pull/6221
|
||||
|
||||
# 1.35.0 (December 8th, 2023)
|
||||
|
||||
### Added
|
||||
|
||||
- net: add Apple watchOS support ([#6176])
|
||||
|
||||
### Changed
|
||||
|
||||
- io: drop the `Sized` requirements from `AsyncReadExt.read_buf` ([#6169])
|
||||
- runtime: make `Runtime` unwind safe ([#6189])
|
||||
- runtime: reduce the lock contention in task spawn ([#6001])
|
||||
- tokio: update nix dependency to 0.27.1 ([#6190])
|
||||
|
||||
### Fixed
|
||||
|
||||
- chore: make `--cfg docsrs` work without net feature ([#6166])
|
||||
- chore: use relaxed load for `unsync_load` on miri ([#6179])
|
||||
- runtime: handle missing context on wake ([#6148])
|
||||
- taskdump: fix taskdump cargo config example ([#6150])
|
||||
- taskdump: skip notified tasks during taskdumps ([#6194])
|
||||
- tracing: avoid creating resource spans with current parent, use a None parent instead ([#6107])
|
||||
- tracing: make task span explicit root ([#6158])
|
||||
|
||||
### Documented
|
||||
|
||||
- io: flush in `AsyncWriteExt` examples ([#6149])
|
||||
- runtime: document fairness guarantees and current behavior ([#6145])
|
||||
- task: document cancel safety of `LocalSet::run_until` ([#6147])
|
||||
|
||||
[#6001]: https://github.com/tokio-rs/tokio/pull/6001
|
||||
[#6107]: https://github.com/tokio-rs/tokio/pull/6107
|
||||
[#6144]: https://github.com/tokio-rs/tokio/pull/6144
|
||||
[#6145]: https://github.com/tokio-rs/tokio/pull/6145
|
||||
[#6147]: https://github.com/tokio-rs/tokio/pull/6147
|
||||
[#6148]: https://github.com/tokio-rs/tokio/pull/6148
|
||||
[#6149]: https://github.com/tokio-rs/tokio/pull/6149
|
||||
[#6150]: https://github.com/tokio-rs/tokio/pull/6150
|
||||
[#6158]: https://github.com/tokio-rs/tokio/pull/6158
|
||||
[#6166]: https://github.com/tokio-rs/tokio/pull/6166
|
||||
[#6169]: https://github.com/tokio-rs/tokio/pull/6169
|
||||
[#6176]: https://github.com/tokio-rs/tokio/pull/6176
|
||||
[#6179]: https://github.com/tokio-rs/tokio/pull/6179
|
||||
[#6189]: https://github.com/tokio-rs/tokio/pull/6189
|
||||
[#6190]: https://github.com/tokio-rs/tokio/pull/6190
|
||||
[#6194]: https://github.com/tokio-rs/tokio/pull/6194
|
||||
|
||||
# 1.34.0 (November 19th, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: allow `clear_readiness` after io driver shutdown ([#6067])
|
||||
- io: fix integer overflow in `take` ([#6080])
|
||||
- io: fix I/O resource hang ([#6134])
|
||||
- sync: fix `broadcast::channel` link ([#6100])
|
||||
|
||||
### Changed
|
||||
|
||||
- macros: use `::core` qualified imports instead of `::std` inside `tokio::test` macro ([#5973])
|
||||
|
||||
### Added
|
||||
|
||||
- fs: update cfg attr in `fs::read_dir` to include `aix` ([#6075])
|
||||
- sync: add `mpsc::Receiver::recv_many` ([#6010])
|
||||
- tokio: added vita target support ([#6094])
|
||||
|
||||
[#5973]: https://github.com/tokio-rs/tokio/pull/5973
|
||||
[#6067]: https://github.com/tokio-rs/tokio/pull/6067
|
||||
[#6080]: https://github.com/tokio-rs/tokio/pull/6080
|
||||
[#6134]: https://github.com/tokio-rs/tokio/pull/6134
|
||||
[#6100]: https://github.com/tokio-rs/tokio/pull/6100
|
||||
[#6075]: https://github.com/tokio-rs/tokio/pull/6075
|
||||
[#6010]: https://github.com/tokio-rs/tokio/pull/6010
|
||||
[#6094]: https://github.com/tokio-rs/tokio/pull/6094
|
||||
|
||||
# 1.33.0 (October 9, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: mark `Interest::add` with `#[must_use]` ([#6037])
|
||||
- runtime: fix cache line size for RISC-V ([#5994])
|
||||
- sync: prevent lock poisoning in `watch::Receiver::wait_for` ([#6021])
|
||||
- task: fix `spawn_local` source location ([#5984])
|
||||
|
||||
### Changed
|
||||
|
||||
- sync: use Acquire/Release orderings instead of SeqCst in `watch` ([#6018])
|
||||
|
||||
### Added
|
||||
|
||||
- fs: add vectored writes to `tokio::fs::File` ([#5958])
|
||||
- io: add `Interest::remove` method ([#5906])
|
||||
- io: add vectored writes to `DuplexStream` ([#5985])
|
||||
- net: add Apple tvOS support ([#6045])
|
||||
- sync: add `?Sized` bound to `{MutexGuard,OwnedMutexGuard}::map` ([#5997])
|
||||
- sync: add `watch::Receiver::mark_unseen` ([#5962], [#6014], [#6017])
|
||||
- sync: add `watch::Sender::new` ([#5998])
|
||||
- sync: add const fn `OnceCell::from_value` ([#5903])
|
||||
|
||||
### Removed
|
||||
|
||||
- remove unused `stats` feature ([#5952])
|
||||
|
||||
### Documented
|
||||
|
||||
- add missing backticks in code examples ([#5938], [#6056])
|
||||
- fix typos ([#5988], [#6030])
|
||||
- process: document that `Child::wait` is cancel safe ([#5977])
|
||||
- sync: add examples for `Semaphore` ([#5939], [#5956], [#5978], [#6031], [#6032], [#6050])
|
||||
- sync: document that `broadcast` capacity is a lower bound ([#6042])
|
||||
- sync: document that `const_new` is not instrumented ([#6002])
|
||||
- sync: improve cancel-safety documentation for `mpsc::Sender::send` ([#5947])
|
||||
- sync: improve docs for `watch` channel ([#5954])
|
||||
- taskdump: render taskdump documentation on docs.rs ([#5972])
|
||||
|
||||
### Unstable
|
||||
|
||||
- taskdump: fix potential deadlock ([#6036])
|
||||
|
||||
[#5903]: https://github.com/tokio-rs/tokio/pull/5903
|
||||
[#5906]: https://github.com/tokio-rs/tokio/pull/5906
|
||||
[#5938]: https://github.com/tokio-rs/tokio/pull/5938
|
||||
[#5939]: https://github.com/tokio-rs/tokio/pull/5939
|
||||
[#5947]: https://github.com/tokio-rs/tokio/pull/5947
|
||||
[#5952]: https://github.com/tokio-rs/tokio/pull/5952
|
||||
[#5954]: https://github.com/tokio-rs/tokio/pull/5954
|
||||
[#5956]: https://github.com/tokio-rs/tokio/pull/5956
|
||||
[#5958]: https://github.com/tokio-rs/tokio/pull/5958
|
||||
[#5960]: https://github.com/tokio-rs/tokio/pull/5960
|
||||
[#5962]: https://github.com/tokio-rs/tokio/pull/5962
|
||||
[#5971]: https://github.com/tokio-rs/tokio/pull/5971
|
||||
[#5972]: https://github.com/tokio-rs/tokio/pull/5972
|
||||
[#5977]: https://github.com/tokio-rs/tokio/pull/5977
|
||||
[#5978]: https://github.com/tokio-rs/tokio/pull/5978
|
||||
[#5984]: https://github.com/tokio-rs/tokio/pull/5984
|
||||
[#5985]: https://github.com/tokio-rs/tokio/pull/5985
|
||||
[#5988]: https://github.com/tokio-rs/tokio/pull/5988
|
||||
[#5994]: https://github.com/tokio-rs/tokio/pull/5994
|
||||
[#5997]: https://github.com/tokio-rs/tokio/pull/5997
|
||||
[#5998]: https://github.com/tokio-rs/tokio/pull/5998
|
||||
[#6002]: https://github.com/tokio-rs/tokio/pull/6002
|
||||
[#6014]: https://github.com/tokio-rs/tokio/pull/6014
|
||||
[#6017]: https://github.com/tokio-rs/tokio/pull/6017
|
||||
[#6018]: https://github.com/tokio-rs/tokio/pull/6018
|
||||
[#6021]: https://github.com/tokio-rs/tokio/pull/6021
|
||||
[#6030]: https://github.com/tokio-rs/tokio/pull/6030
|
||||
[#6031]: https://github.com/tokio-rs/tokio/pull/6031
|
||||
[#6032]: https://github.com/tokio-rs/tokio/pull/6032
|
||||
[#6036]: https://github.com/tokio-rs/tokio/pull/6036
|
||||
[#6037]: https://github.com/tokio-rs/tokio/pull/6037
|
||||
[#6042]: https://github.com/tokio-rs/tokio/pull/6042
|
||||
[#6045]: https://github.com/tokio-rs/tokio/pull/6045
|
||||
[#6050]: https://github.com/tokio-rs/tokio/pull/6050
|
||||
[#6056]: https://github.com/tokio-rs/tokio/pull/6056
|
||||
[#6058]: https://github.com/tokio-rs/tokio/pull/6058
|
||||
|
||||
# 1.32.1 (December 19, 2023)
|
||||
|
||||
This is a forward part of a change that was backported to 1.25.3.
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: add budgeting to `tokio::runtime::io::registration::async_io` ([#6221])
|
||||
|
||||
[#6221]: https://github.com/tokio-rs/tokio/pull/6221
|
||||
|
||||
# 1.32.0 (August 16, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: fix potential quadratic behavior in `broadcast::Receiver` ([#5925])
|
||||
|
||||
### Added
|
||||
|
||||
- process: stabilize `Command::raw_arg` ([#5930])
|
||||
- io: enable awaiting error readiness ([#5781])
|
||||
|
||||
### Unstable
|
||||
|
||||
- rt(alt): improve scalability of alt runtime as the number of cores grows ([#5935])
|
||||
|
||||
[#5925]: https://github.com/tokio-rs/tokio/pull/5925
|
||||
[#5930]: https://github.com/tokio-rs/tokio/pull/5930
|
||||
[#5781]: https://github.com/tokio-rs/tokio/pull/5781
|
||||
[#5935]: https://github.com/tokio-rs/tokio/pull/5935
|
||||
|
||||
# 1.31.0 (August 10, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
* io: delegate `WriteHalf::poll_write_vectored` ([#5914])
|
||||
|
||||
### Unstable
|
||||
|
||||
* rt(alt): fix memory leak in unstable next-gen scheduler prototype ([#5911])
|
||||
* rt: expose mean task poll time metric ([#5927])
|
||||
|
||||
[#5914]: https://github.com/tokio-rs/tokio/pull/5914
|
||||
[#5911]: https://github.com/tokio-rs/tokio/pull/5911
|
||||
[#5927]: https://github.com/tokio-rs/tokio/pull/5927
|
||||
|
||||
# 1.30.0 (August 9, 2023)
|
||||
|
||||
This release bumps the MSRV of Tokio to 1.63. ([#5887])
|
||||
|
||||
### Changed
|
||||
|
||||
- tokio: reduce LLVM code generation ([#5859])
|
||||
- io: support `--cfg mio_unsupported_force_poll_poll` flag ([#5881])
|
||||
- sync: make `const_new` methods always available ([#5885])
|
||||
- sync: avoid false sharing in mpsc channel ([#5829])
|
||||
- rt: pop at least one task from inject queue ([#5908])
|
||||
|
||||
### Added
|
||||
|
||||
- sync: add `broadcast::Sender::new` ([#5824])
|
||||
- net: implement `UCred` for espidf ([#5868])
|
||||
- fs: add `File::options()` ([#5869])
|
||||
- time: implement extra reset variants for `Interval` ([#5878])
|
||||
- process: add `{ChildStd*}::into_owned_{fd, handle}` ([#5899])
|
||||
|
||||
### Removed
|
||||
|
||||
- tokio: removed unused `tokio_*` cfgs ([#5890])
|
||||
- remove build script to speed up compilation ([#5887])
|
||||
|
||||
### Documented
|
||||
|
||||
- sync: mention lagging in docs for `broadcast::send` ([#5820])
|
||||
- runtime: expand on sharing runtime docs ([#5858])
|
||||
- io: use vec in example for `AsyncReadExt::read_exact` ([#5863])
|
||||
- time: mark `Sleep` as `!Unpin` in docs ([#5916])
|
||||
- process: fix `raw_arg` not showing up in docs ([#5865])
|
||||
|
||||
### Unstable
|
||||
|
||||
- rt: add runtime ID ([#5864])
|
||||
- rt: initial implementation of new threaded runtime ([#5823])
|
||||
|
||||
[#5820]: https://github.com/tokio-rs/tokio/pull/5820
|
||||
[#5823]: https://github.com/tokio-rs/tokio/pull/5823
|
||||
[#5824]: https://github.com/tokio-rs/tokio/pull/5824
|
||||
[#5829]: https://github.com/tokio-rs/tokio/pull/5829
|
||||
[#5858]: https://github.com/tokio-rs/tokio/pull/5858
|
||||
[#5859]: https://github.com/tokio-rs/tokio/pull/5859
|
||||
[#5863]: https://github.com/tokio-rs/tokio/pull/5863
|
||||
[#5864]: https://github.com/tokio-rs/tokio/pull/5864
|
||||
[#5865]: https://github.com/tokio-rs/tokio/pull/5865
|
||||
[#5868]: https://github.com/tokio-rs/tokio/pull/5868
|
||||
[#5869]: https://github.com/tokio-rs/tokio/pull/5869
|
||||
[#5878]: https://github.com/tokio-rs/tokio/pull/5878
|
||||
[#5881]: https://github.com/tokio-rs/tokio/pull/5881
|
||||
[#5885]: https://github.com/tokio-rs/tokio/pull/5885
|
||||
[#5887]: https://github.com/tokio-rs/tokio/pull/5887
|
||||
[#5890]: https://github.com/tokio-rs/tokio/pull/5890
|
||||
[#5899]: https://github.com/tokio-rs/tokio/pull/5899
|
||||
[#5908]: https://github.com/tokio-rs/tokio/pull/5908
|
||||
[#5916]: https://github.com/tokio-rs/tokio/pull/5916
|
||||
|
||||
# 1.29.1 (June 29, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- rt: fix nesting two `block_in_place` with a `block_on` between ([#5837])
|
||||
|
||||
[#5837]: https://github.com/tokio-rs/tokio/pull/5837
|
||||
|
||||
# 1.29.0 (June 27, 2023)
|
||||
|
||||
Technically a breaking change, the `Send` implementation is removed from
|
||||
`runtime::EnterGuard`. This change fixes a bug and should not impact most users.
|
||||
|
||||
### Breaking
|
||||
|
||||
- rt: `EnterGuard` should not be `Send` ([#5766])
|
||||
|
||||
### Fixed
|
||||
|
||||
- fs: reduce blocking ops in `fs::read_dir` ([#5653])
|
||||
- rt: fix possible starvation ([#5686], [#5712])
|
||||
- rt: fix stacked borrows issue in `JoinSet` ([#5693])
|
||||
- rt: panic if `EnterGuard` dropped incorrect order ([#5772])
|
||||
- time: do not overflow to signal value ([#5710])
|
||||
- fs: wait for in-flight ops before cloning `File` ([#5803])
|
||||
|
||||
### Changed
|
||||
|
||||
- rt: reduce time to poll tasks scheduled from outside the runtime ([#5705], [#5720])
|
||||
|
||||
### Added
|
||||
|
||||
- net: add uds doc alias for unix sockets ([#5659])
|
||||
- rt: add metric for number of tasks ([#5628])
|
||||
- sync: implement more traits for channel errors ([#5666])
|
||||
- net: add nodelay methods on TcpSocket ([#5672])
|
||||
- sync: add `broadcast::Receiver::blocking_recv` ([#5690])
|
||||
- process: add `raw_arg` method to `Command` ([#5704])
|
||||
- io: support PRIORITY epoll events ([#5566])
|
||||
- task: add `JoinSet::poll_join_next` ([#5721])
|
||||
- net: add support for Redox OS ([#5790])
|
||||
|
||||
|
||||
### Unstable
|
||||
|
||||
- rt: add the ability to dump task backtraces ([#5608], [#5676], [#5708], [#5717])
|
||||
- rt: instrument task poll times with a histogram ([#5685])
|
||||
|
||||
[#5766]: https://github.com/tokio-rs/tokio/pull/5766
|
||||
[#5653]: https://github.com/tokio-rs/tokio/pull/5653
|
||||
[#5686]: https://github.com/tokio-rs/tokio/pull/5686
|
||||
[#5712]: https://github.com/tokio-rs/tokio/pull/5712
|
||||
[#5693]: https://github.com/tokio-rs/tokio/pull/5693
|
||||
[#5772]: https://github.com/tokio-rs/tokio/pull/5772
|
||||
[#5710]: https://github.com/tokio-rs/tokio/pull/5710
|
||||
[#5803]: https://github.com/tokio-rs/tokio/pull/5803
|
||||
[#5705]: https://github.com/tokio-rs/tokio/pull/5705
|
||||
[#5720]: https://github.com/tokio-rs/tokio/pull/5720
|
||||
[#5659]: https://github.com/tokio-rs/tokio/pull/5659
|
||||
[#5628]: https://github.com/tokio-rs/tokio/pull/5628
|
||||
[#5666]: https://github.com/tokio-rs/tokio/pull/5666
|
||||
[#5672]: https://github.com/tokio-rs/tokio/pull/5672
|
||||
[#5690]: https://github.com/tokio-rs/tokio/pull/5690
|
||||
[#5704]: https://github.com/tokio-rs/tokio/pull/5704
|
||||
[#5566]: https://github.com/tokio-rs/tokio/pull/5566
|
||||
[#5721]: https://github.com/tokio-rs/tokio/pull/5721
|
||||
[#5790]: https://github.com/tokio-rs/tokio/pull/5790
|
||||
[#5608]: https://github.com/tokio-rs/tokio/pull/5608
|
||||
[#5676]: https://github.com/tokio-rs/tokio/pull/5676
|
||||
[#5708]: https://github.com/tokio-rs/tokio/pull/5708
|
||||
[#5717]: https://github.com/tokio-rs/tokio/pull/5717
|
||||
[#5685]: https://github.com/tokio-rs/tokio/pull/5685
|
||||
|
||||
# 1.28.2 (May 28, 2023)
|
||||
|
||||
Forward ports 1.18.6 changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: disable default features for mio ([#5728])
|
||||
|
||||
[#5728]: https://github.com/tokio-rs/tokio/pull/5728
|
||||
|
||||
# 1.28.1 (May 10th, 2023)
|
||||
|
||||
This release fixes a mistake in the build script that makes `AsFd`
|
||||
@@ -184,6 +535,33 @@ This release bumps the MSRV of Tokio to 1.56. ([#5559])
|
||||
[#5513]: https://github.com/tokio-rs/tokio/pull/5513
|
||||
[#5517]: https://github.com/tokio-rs/tokio/pull/5517
|
||||
|
||||
# 1.25.3 (December 17th, 2023)
|
||||
|
||||
### Fixed
|
||||
- io: add budgeting to `tokio::runtime::io::registration::async_io` ([#6221])
|
||||
|
||||
[#6221]: https://github.com/tokio-rs/tokio/pull/6221
|
||||
|
||||
# 1.25.2 (September 22, 2023)
|
||||
|
||||
Forward ports 1.20.6 changes.
|
||||
|
||||
### Changed
|
||||
|
||||
- io: use `memchr` from `libc` ([#5960])
|
||||
|
||||
[#5960]: https://github.com/tokio-rs/tokio/pull/5960
|
||||
|
||||
# 1.25.1 (May 28, 2023)
|
||||
|
||||
Forward ports 1.18.6 changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: disable default features for mio ([#5728])
|
||||
|
||||
[#5728]: https://github.com/tokio-rs/tokio/pull/5728
|
||||
|
||||
# 1.25.0 (January 28, 2023)
|
||||
|
||||
### Fixed
|
||||
@@ -520,6 +898,26 @@ wasm32-wasi target is given unstable support for the `net` feature.
|
||||
[#4956]: https://github.com/tokio-rs/tokio/pull/4956
|
||||
[#4959]: https://github.com/tokio-rs/tokio/pull/4959
|
||||
|
||||
# 1.20.6 (September 22, 2023)
|
||||
|
||||
This is a backport of a change from 1.27.0.
|
||||
|
||||
### Changed
|
||||
|
||||
- io: use `memchr` from `libc` ([#5960])
|
||||
|
||||
[#5960]: https://github.com/tokio-rs/tokio/pull/5960
|
||||
|
||||
# 1.20.5 (May 28, 2023)
|
||||
|
||||
Forward ports 1.18.6 changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: disable default features for mio ([#5728])
|
||||
|
||||
[#5728]: https://github.com/tokio-rs/tokio/pull/5728
|
||||
|
||||
# 1.20.4 (January 17, 2023)
|
||||
|
||||
Forward ports 1.18.5 changes.
|
||||
@@ -666,6 +1064,14 @@ This release fixes a bug in `Notified::enable`. ([#4747])
|
||||
[#4729]: https://github.com/tokio-rs/tokio/pull/4729
|
||||
[#4739]: https://github.com/tokio-rs/tokio/pull/4739
|
||||
|
||||
# 1.18.6 (May 28, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: disable default features for mio ([#5728])
|
||||
|
||||
[#5728]: https://github.com/tokio-rs/tokio/pull/5728
|
||||
|
||||
# 1.18.5 (January 17, 2023)
|
||||
|
||||
### Fixed
|
||||
@@ -1214,7 +1620,9 @@ Fixes a missed edge case from 1.8.1.
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: drop canceled future on next poll (#3965)
|
||||
- runtime: drop canceled future on next poll ([#3965])
|
||||
|
||||
[#3965]: https://github.com/tokio-rs/tokio/pull/3965
|
||||
|
||||
# 1.8.1 (July 6, 2021)
|
||||
|
||||
|
||||
+20
-28
@@ -6,9 +6,9 @@ name = "tokio"
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.x.y" git tag.
|
||||
version = "1.28.1"
|
||||
version = "1.35.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.63"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
@@ -88,27 +88,19 @@ sync = []
|
||||
test-util = ["rt", "sync", "time"]
|
||||
time = []
|
||||
|
||||
# Technically, removing this is a breaking change even though it only ever did
|
||||
# anything with the unstable flag on. It is probably safe to get rid of it after
|
||||
# a few releases.
|
||||
stats = []
|
||||
|
||||
[build-dependencies]
|
||||
autocfg = "1.1"
|
||||
|
||||
[dependencies]
|
||||
tokio-macros = { version = "~2.1.0", path = "../tokio-macros", optional = true }
|
||||
tokio-macros = { version = "~2.2.0", path = "../tokio-macros", optional = true }
|
||||
|
||||
pin-project-lite = "0.2.0"
|
||||
pin-project-lite = "0.2.11"
|
||||
|
||||
# Everything else is optional...
|
||||
bytes = { version = "1.0.0", optional = true }
|
||||
mio = { version = "0.8.4", optional = true }
|
||||
mio = { version = "0.8.9", optional = true, default-features = false }
|
||||
num_cpus = { version = "1.8.0", optional = true }
|
||||
parking_lot = { version = "0.12.0", optional = true }
|
||||
|
||||
[target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dependencies]
|
||||
socket2 = { version = "0.4.9", optional = true, features = [ "all" ] }
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
socket2 = { version = "0.5.5", optional = true, features = [ "all" ] }
|
||||
|
||||
# Currently unstable. The API exposed by these features may be broken at any time.
|
||||
# Requires `--cfg tokio_unstable` to enable.
|
||||
@@ -121,18 +113,18 @@ tracing = { version = "0.1.25", default-features = false, features = ["std"], op
|
||||
backtrace = { version = "0.3.58" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = { version = "0.2.42", optional = true }
|
||||
libc = { version = "0.2.149", optional = true }
|
||||
signal-hook-registry = { version = "1.1.1", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
libc = { version = "0.2.42" }
|
||||
nix = { version = "0.26", default-features = false, features = ["fs", "socket"] }
|
||||
libc = { version = "0.2.149" }
|
||||
nix = { version = "0.27.1", default-features = false, features = ["fs", "socket"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(docsrs)'.dependencies.windows-sys]
|
||||
[target.'cfg(windows)'.dev-dependencies.windows-sys]
|
||||
version = "0.48"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
@@ -146,29 +138,29 @@ futures = { version = "0.3.0", features = ["async-await"] }
|
||||
mockall = "0.11.1"
|
||||
async-stream = "0.3"
|
||||
|
||||
[target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dev-dependencies]
|
||||
socket2 = "0.4.9"
|
||||
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
|
||||
socket2 = "0.5.5"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[target.'cfg(not(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown")))'.dev-dependencies]
|
||||
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dev-dependencies]
|
||||
rand = "0.8.0"
|
||||
|
||||
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), not(target_os = "wasi")))'.dev-dependencies]
|
||||
[target.'cfg(all(target_family = "wasm", not(target_os = "wasi")))'.dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
|
||||
mio-aio = { version = "0.7.0", features = ["tokio"] }
|
||||
|
||||
[target.'cfg(loom)'.dev-dependencies]
|
||||
loom = { version = "0.5.2", features = ["futures", "checkpoint"] }
|
||||
loom = { version = "0.7", features = ["futures", "checkpoint"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# enable unstable features in the documentation
|
||||
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"]
|
||||
# it's necessary to _also_ pass `--cfg tokio_unstable` to rustc, or else
|
||||
# dependencies will not be enabled, and the docs build will fail.
|
||||
rustc-args = ["--cfg", "tokio_unstable"]
|
||||
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable", "--cfg", "tokio_taskdump"]
|
||||
# it's necessary to _also_ pass `--cfg tokio_unstable` and `--cfg tokio_taskdump`
|
||||
# to rustc, or else dependencies will not be enabled, and the docs build will fail.
|
||||
rustc-args = ["--cfg", "tokio_unstable", "--cfg", "tokio_taskdump"]
|
||||
|
||||
[package.metadata.playground]
|
||||
features = ["full", "test-util"]
|
||||
|
||||
+19
-13
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
tokio = { version = "1.35.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -132,6 +132,8 @@ project.
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`axum`]: A web application framework that focuses on ergonomics and modularity.
|
||||
|
||||
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
|
||||
|
||||
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
|
||||
@@ -142,21 +144,18 @@ several other libraries, including:
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers `tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
* [`loom`]: A testing tool for concurrent Rust code
|
||||
* [`loom`]: A testing tool for concurrent Rust code.
|
||||
|
||||
[`axum`]: https://github.com/tokio-rs/axum
|
||||
[`warp`]: https://github.com/seanmonstar/warp
|
||||
[`hyper`]: https://github.com/hyperium/hyper
|
||||
[`tonic`]: https://github.com/hyperium/tonic
|
||||
[`tower`]: https://github.com/tower-rs/tower
|
||||
[`loom`]: https://github.com/tokio-rs/loom
|
||||
[`rdbc`]: https://github.com/tokio-rs/rdbc
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
@@ -187,12 +186,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.56.0.
|
||||
released at least six months ago. The current MSRV is 1.63.
|
||||
|
||||
Note that the MSRV is not increased automatically, and only as part of a minor
|
||||
release. The MSRV history for past minor releases can be found below:
|
||||
|
||||
* 1.27 to now - Rust 1.56
|
||||
* 1.30 to now - Rust 1.63
|
||||
* 1.27 to 1.29 - Rust 1.56
|
||||
* 1.17 to 1.26 - Rust 1.49
|
||||
* 1.15 to 1.16 - Rust 1.46
|
||||
* 1.0 to 1.14 - Rust 1.45
|
||||
@@ -215,21 +215,27 @@ 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.18.x` - LTS release until June 2023. (MSRV 1.49)
|
||||
* `1.20.x` - LTS release until September 2023. (MSRV 1.49)
|
||||
* `1.25.x` - LTS release until March 2024. (MSRV 1.49)
|
||||
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
|
||||
|
||||
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.18.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.25.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.18", features = [...] }
|
||||
tokio = { version = "~1.25", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
|
||||
* `1.8.x` - LTS release until February 2022.
|
||||
* `1.14.x` - LTS release until June 2022.
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
-192
@@ -1,192 +0,0 @@
|
||||
use autocfg::AutoCfg;
|
||||
|
||||
const CONST_THREAD_LOCAL_PROBE: &str = r#"
|
||||
{
|
||||
thread_local! {
|
||||
static MY_PROBE: usize = const { 10 };
|
||||
}
|
||||
|
||||
MY_PROBE.with(|val| *val)
|
||||
}
|
||||
"#;
|
||||
|
||||
const CONST_MUTEX_NEW_PROBE: &str = r#"
|
||||
{
|
||||
static MY_MUTEX: ::std::sync::Mutex<i32> = ::std::sync::Mutex::new(1);
|
||||
*MY_MUTEX.lock().unwrap()
|
||||
}
|
||||
"#;
|
||||
|
||||
const AS_FD_PROBE: &str = r#"
|
||||
{
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::prelude::AsFd as _;
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::prelude::AsSocket as _;
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(target_os = "wasi")]
|
||||
use std::os::wasi::prelude::AsFd as _;
|
||||
}
|
||||
"#;
|
||||
|
||||
const TARGET_HAS_ATOMIC_PROBE: &str = r#"
|
||||
{
|
||||
#[cfg(target_has_atomic = "ptr")]
|
||||
let _ = ();
|
||||
}
|
||||
"#;
|
||||
|
||||
const TARGET_ATOMIC_U64_PROBE: &str = r#"
|
||||
{
|
||||
#[allow(unused_imports)]
|
||||
use std::sync::atomic::AtomicU64 as _;
|
||||
}
|
||||
"#;
|
||||
|
||||
fn main() {
|
||||
let mut enable_const_thread_local = false;
|
||||
let mut enable_target_has_atomic = false;
|
||||
let mut enable_const_mutex_new = false;
|
||||
let mut enable_as_fd = false;
|
||||
let mut target_needs_atomic_u64_fallback = false;
|
||||
|
||||
match AutoCfg::new() {
|
||||
Ok(ac) => {
|
||||
// These checks prefer to call only `probe_rustc_version` if that is
|
||||
// enough to determine whether the feature is supported. This is
|
||||
// because the `probe_expression` call involves a call to rustc,
|
||||
// which the `probe_rustc_version` call avoids.
|
||||
|
||||
// Const-initialized thread locals were stabilized in 1.59.
|
||||
if ac.probe_rustc_version(1, 60) {
|
||||
enable_const_thread_local = true;
|
||||
} else if ac.probe_rustc_version(1, 59) {
|
||||
// This compiler claims to be 1.59, but there are some nightly
|
||||
// compilers that claim to be 1.59 without supporting the
|
||||
// feature. Explicitly probe to check if code using them
|
||||
// compiles.
|
||||
//
|
||||
// The oldest nightly that supports the feature is 2021-12-06.
|
||||
if ac.probe_expression(CONST_THREAD_LOCAL_PROBE) {
|
||||
enable_const_thread_local = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The `target_has_atomic` cfg was stabilized in 1.60.
|
||||
if ac.probe_rustc_version(1, 61) {
|
||||
enable_target_has_atomic = true;
|
||||
} else if ac.probe_rustc_version(1, 60) {
|
||||
// This compiler claims to be 1.60, but there are some nightly
|
||||
// compilers that claim to be 1.60 without supporting the
|
||||
// feature. Explicitly probe to check if code using them
|
||||
// compiles.
|
||||
//
|
||||
// The oldest nightly that supports the feature is 2022-02-11.
|
||||
if ac.probe_expression(TARGET_HAS_ATOMIC_PROBE) {
|
||||
enable_target_has_atomic = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't tell using `target_has_atomic`, tell if the target
|
||||
// has `AtomicU64` by trying to use it.
|
||||
if !enable_target_has_atomic && !ac.probe_expression(TARGET_ATOMIC_U64_PROBE) {
|
||||
target_needs_atomic_u64_fallback = true;
|
||||
}
|
||||
|
||||
// The `Mutex::new` method was made const in 1.63.
|
||||
if ac.probe_rustc_version(1, 64) {
|
||||
enable_const_mutex_new = true;
|
||||
} else if ac.probe_rustc_version(1, 63) {
|
||||
// This compiler claims to be 1.63, but there are some nightly
|
||||
// compilers that claim to be 1.63 without supporting the
|
||||
// feature. Explicitly probe to check if code using them
|
||||
// compiles.
|
||||
//
|
||||
// The oldest nightly that supports the feature is 2022-06-20.
|
||||
if ac.probe_expression(CONST_MUTEX_NEW_PROBE) {
|
||||
enable_const_mutex_new = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The `AsFd` family of traits were made stable in 1.63.
|
||||
if ac.probe_rustc_version(1, 64) {
|
||||
enable_as_fd = true;
|
||||
} else if ac.probe_rustc_version(1, 63) {
|
||||
// This compiler claims to be 1.63, but there are some nightly
|
||||
// compilers that claim to be 1.63 without supporting the
|
||||
// feature. Explicitly probe to check if code using them
|
||||
// compiles.
|
||||
//
|
||||
// The oldest nightly that supports the feature is 2022-06-16.
|
||||
if ac.probe_expression(AS_FD_PROBE) {
|
||||
enable_as_fd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
// If we couldn't detect the compiler version and features, just
|
||||
// print a warning. This isn't a fatal error: we can still build
|
||||
// Tokio, we just can't enable cfgs automatically.
|
||||
println!(
|
||||
"cargo:warning=tokio: failed to detect compiler features: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !enable_const_thread_local {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
//
|
||||
// RUSTFLAGS="--cfg tokio_no_const_thread_local"
|
||||
autocfg::emit("tokio_no_const_thread_local")
|
||||
}
|
||||
|
||||
if !enable_target_has_atomic {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
//
|
||||
// RUSTFLAGS="--cfg tokio_no_target_has_atomic"
|
||||
autocfg::emit("tokio_no_target_has_atomic")
|
||||
}
|
||||
|
||||
if !enable_const_mutex_new {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
//
|
||||
// RUSTFLAGS="--cfg tokio_no_const_mutex_new"
|
||||
autocfg::emit("tokio_no_const_mutex_new")
|
||||
}
|
||||
|
||||
if !enable_as_fd {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
//
|
||||
// RUSTFLAGS="--cfg tokio_no_as_fd"
|
||||
autocfg::emit("tokio_no_as_fd");
|
||||
}
|
||||
|
||||
if target_needs_atomic_u64_fallback {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
//
|
||||
// RUSTFLAGS="--cfg tokio_no_atomic_u64"
|
||||
autocfg::emit("tokio_no_atomic_u64")
|
||||
}
|
||||
|
||||
let target = ::std::env::var("TARGET").unwrap_or_default();
|
||||
|
||||
// We emit cfgs instead of using `target_family = "wasm"` that requires Rust 1.54.
|
||||
// Note that these cfgs are unavailable in `Cargo.toml`.
|
||||
if target.starts_with("wasm") {
|
||||
autocfg::emit("tokio_wasm");
|
||||
if target.contains("wasi") {
|
||||
autocfg::emit("tokio_wasi");
|
||||
} else {
|
||||
autocfg::emit("tokio_wasm_not_wasi");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tokio-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
@@ -20,4 +20,28 @@
|
||||
#[derive(Debug)]
|
||||
pub enum NotDefinedHere {}
|
||||
|
||||
#[cfg(feature = "net")]
|
||||
impl mio::event::Source for NotDefinedHere {
|
||||
fn register(
|
||||
&mut self,
|
||||
registry: &mio::Registry,
|
||||
token: mio::Token,
|
||||
interests: mio::Interest,
|
||||
) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn reregister(
|
||||
&mut self,
|
||||
registry: &mio::Registry,
|
||||
token: mio::Token,
|
||||
interests: mio::Interest,
|
||||
) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn deregister(&mut self, registry: &mio::Registry) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "net")]
|
||||
pub mod os;
|
||||
|
||||
@@ -11,6 +11,9 @@ pub mod windows {
|
||||
/// See [std::os::windows::io::RawHandle](https://doc.rust-lang.org/std/os/windows/io/type.RawHandle.html)
|
||||
pub type RawHandle = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [std::os::windows::io::OwnedHandle](https://doc.rust-lang.org/std/os/windows/io/struct.OwnedHandle.html)
|
||||
pub type OwnedHandle = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [std::os::windows::io::AsRawHandle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html)
|
||||
pub trait AsRawHandle {
|
||||
/// See [std::os::windows::io::AsRawHandle::as_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html#tymethod.as_raw_handle)
|
||||
|
||||
@@ -6,9 +6,7 @@ use std::path::{Path, PathBuf};
|
||||
/// Returns the canonical, absolute form of a path with all intermediate
|
||||
/// components normalized and symbolic links resolved.
|
||||
///
|
||||
/// This is an async version of [`std::fs::canonicalize`][std]
|
||||
///
|
||||
/// [std]: std::fs::canonicalize
|
||||
/// This is an async version of [`std::fs::canonicalize`].
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::path::Path;
|
||||
/// of the original file to the destination file.
|
||||
/// This function will overwrite the contents of to.
|
||||
///
|
||||
/// This is the async equivalent of [`std::fs::copy`][std].
|
||||
///
|
||||
/// [std]: fn@std::fs::copy
|
||||
/// This is the async equivalent of [`std::fs::copy`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::path::Path;
|
||||
|
||||
/// Creates a new, empty directory at the provided path.
|
||||
///
|
||||
/// This is an async version of [`std::fs::create_dir`][std]
|
||||
///
|
||||
/// [std]: std::fs::create_dir
|
||||
/// This is an async version of [`std::fs::create_dir`].
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
|
||||
@@ -6,9 +6,7 @@ use std::path::Path;
|
||||
/// Recursively creates a directory and all of its parent components if they
|
||||
/// are missing.
|
||||
///
|
||||
/// This is an async version of [`std::fs::create_dir_all`][std]
|
||||
///
|
||||
/// [std]: std::fs::create_dir_all
|
||||
/// This is an async version of [`std::fs::create_dir_all`].
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
|
||||
@@ -7,8 +7,6 @@ use std::path::Path;
|
||||
///
|
||||
/// This is a specialized version of [`std::fs::DirBuilder`] for usage on
|
||||
/// the Tokio runtime.
|
||||
///
|
||||
/// [std::fs::DirBuilder]: std::fs::DirBuilder
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DirBuilder {
|
||||
/// Indicates whether to create parent directories if they are missing.
|
||||
@@ -23,9 +21,7 @@ impl DirBuilder {
|
||||
/// Creates a new set of options with default mode/security settings for all
|
||||
/// platforms and also non-recursive.
|
||||
///
|
||||
/// This is an async version of [`std::fs::DirBuilder::new`][std]
|
||||
///
|
||||
/// [std]: std::fs::DirBuilder::new
|
||||
/// This is an async version of [`std::fs::DirBuilder::new`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -35,7 +31,7 @@ impl DirBuilder {
|
||||
/// let builder = DirBuilder::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
DirBuilder::default()
|
||||
}
|
||||
|
||||
/// Indicates whether to create directories recursively (including all parent directories).
|
||||
@@ -43,9 +39,7 @@ impl DirBuilder {
|
||||
///
|
||||
/// This option defaults to `false`.
|
||||
///
|
||||
/// This is an async version of [`std::fs::DirBuilder::recursive`][std]
|
||||
///
|
||||
/// [std]: std::fs::DirBuilder::recursive
|
||||
/// This is an async version of [`std::fs::DirBuilder::recursive`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -65,9 +59,7 @@ impl DirBuilder {
|
||||
/// It is considered an error if the directory already exists unless
|
||||
/// recursive mode is enabled.
|
||||
///
|
||||
/// This is an async version of [`std::fs::DirBuilder::create`][std]
|
||||
///
|
||||
/// [std]: std::fs::DirBuilder::create
|
||||
/// This is an async version of [`std::fs::DirBuilder::create`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
||||
+153
-63
@@ -2,8 +2,7 @@
|
||||
//!
|
||||
//! [`File`]: File
|
||||
|
||||
use self::State::*;
|
||||
use crate::fs::asyncify;
|
||||
use crate::fs::{asyncify, OpenOptions};
|
||||
use crate::io::blocking::Buf;
|
||||
use crate::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
||||
use crate::sync::Mutex;
|
||||
@@ -17,7 +16,6 @@ use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use std::task::Poll::*;
|
||||
|
||||
#[cfg(test)]
|
||||
use super::mocks::JoinHandle;
|
||||
@@ -34,7 +32,7 @@ use std::fs::File as StdFile;
|
||||
|
||||
/// A reference to an open file on the filesystem.
|
||||
///
|
||||
/// This is a specialized version of [`std::fs::File`][std] for usage from the
|
||||
/// This is a specialized version of [`std::fs::File`] for usage from the
|
||||
/// Tokio runtime.
|
||||
///
|
||||
/// An instance of a `File` can be read and/or written depending on what options
|
||||
@@ -52,7 +50,6 @@ use std::fs::File as StdFile;
|
||||
/// Reading and writing to a `File` is usually done using the convenience
|
||||
/// methods found on the [`AsyncReadExt`] and [`AsyncWriteExt`] traits.
|
||||
///
|
||||
/// [std]: struct@std::fs::File
|
||||
/// [`AsyncSeek`]: trait@crate::io::AsyncSeek
|
||||
/// [`flush`]: fn@crate::io::AsyncWriteExt::flush
|
||||
/// [`sync_all`]: fn@crate::fs::File::sync_all
|
||||
@@ -124,13 +121,11 @@ impl File {
|
||||
///
|
||||
/// See [`OpenOptions`] for more details.
|
||||
///
|
||||
/// [`OpenOptions`]: super::OpenOptions
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if called from outside of the Tokio
|
||||
/// runtime or if path does not already exist. Other errors may also be
|
||||
/// returned according to OpenOptions::open.
|
||||
/// returned according to `OpenOptions::open`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -167,8 +162,6 @@ impl File {
|
||||
///
|
||||
/// See [`OpenOptions`] for more details.
|
||||
///
|
||||
/// [`OpenOptions`]: super::OpenOptions
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Results in an error if called from outside of the Tokio runtime or if
|
||||
@@ -199,10 +192,38 @@ impl File {
|
||||
Ok(File::from_std(std_file))
|
||||
}
|
||||
|
||||
/// Converts a [`std::fs::File`][std] to a [`tokio::fs::File`][file].
|
||||
/// Returns a new [`OpenOptions`] object.
|
||||
///
|
||||
/// [std]: std::fs::File
|
||||
/// [file]: File
|
||||
/// This function returns a new `OpenOptions` object that you can use to
|
||||
/// open or create a file with specific options if `open()` or `create()`
|
||||
/// are not appropriate.
|
||||
///
|
||||
/// It is equivalent to `OpenOptions::new()`, but allows you to write more
|
||||
/// readable code. Instead of
|
||||
/// `OpenOptions::new().append(true).open("example.log")`,
|
||||
/// you can write `File::options().append(true).open("example.log")`. This
|
||||
/// also avoids the need to import `OpenOptions`.
|
||||
///
|
||||
/// See the [`OpenOptions::new`] function for more details.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::File;
|
||||
/// use tokio::io::AsyncWriteExt;
|
||||
///
|
||||
/// # async fn dox() -> std::io::Result<()> {
|
||||
/// let mut f = File::options().append(true).open("example.log").await?;
|
||||
/// f.write_all(b"new line\n").await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn options() -> OpenOptions {
|
||||
OpenOptions::new()
|
||||
}
|
||||
|
||||
/// Converts a [`std::fs::File`] to a [`tokio::fs::File`](File).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -324,7 +345,7 @@ impl File {
|
||||
inner.complete_inflight().await;
|
||||
|
||||
let mut buf = match inner.state {
|
||||
Idle(ref mut buf_cell) => buf_cell.take().unwrap(),
|
||||
State::Idle(ref mut buf_cell) => buf_cell.take().unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@@ -336,24 +357,24 @@ impl File {
|
||||
|
||||
let std = self.std.clone();
|
||||
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
inner.state = State::Busy(spawn_blocking(move || {
|
||||
let res = if let Some(seek) = seek {
|
||||
(&*std).seek(seek).and_then(|_| std.set_len(size))
|
||||
} else {
|
||||
std.set_len(size)
|
||||
}
|
||||
.map(|_| 0); // the value is discarded later
|
||||
.map(|()| 0); // the value is discarded later
|
||||
|
||||
// Return the result as a seek
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
|
||||
let (op, buf) = match inner.state {
|
||||
Idle(_) => unreachable!(),
|
||||
Busy(ref mut rx) => rx.await?,
|
||||
State::Idle(_) => unreachable!(),
|
||||
State::Busy(ref mut rx) => rx.await?,
|
||||
};
|
||||
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Seek(res) => res.map(|pos| {
|
||||
@@ -399,18 +420,17 @@ impl File {
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn try_clone(&self) -> io::Result<File> {
|
||||
self.inner.lock().await.complete_inflight().await;
|
||||
let std = self.std.clone();
|
||||
let std_file = asyncify(move || std.try_clone()).await?;
|
||||
Ok(File::from_std(std_file))
|
||||
}
|
||||
|
||||
/// Destructures `File` into a [`std::fs::File`][std]. This function is
|
||||
/// Destructures `File` into a [`std::fs::File`]. This function is
|
||||
/// async to allow any in-flight operations to complete.
|
||||
///
|
||||
/// Use `File::try_into_std` to attempt conversion immediately.
|
||||
///
|
||||
/// [std]: std::fs::File
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -427,9 +447,7 @@ impl File {
|
||||
Arc::try_unwrap(self.std).expect("Arc::try_unwrap failed")
|
||||
}
|
||||
|
||||
/// Tries to immediately destructure `File` into a [`std::fs::File`][std].
|
||||
///
|
||||
/// [std]: std::fs::File
|
||||
/// Tries to immediately destructure `File` into a [`std::fs::File`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -504,51 +522,51 @@ impl AsyncRead for File {
|
||||
|
||||
loop {
|
||||
match inner.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
if !buf.is_empty() {
|
||||
buf.copy_to(dst);
|
||||
*buf_cell = Some(buf);
|
||||
return Ready(Ok(()));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
buf.ensure_capacity_for(dst);
|
||||
let std = me.std.clone();
|
||||
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
inner.state = State::Busy(spawn_blocking(move || {
|
||||
let res = buf.read_from(&mut &*std);
|
||||
(Operation::Read(res), buf)
|
||||
}));
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, mut buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
|
||||
match op {
|
||||
Operation::Read(Ok(_)) => {
|
||||
buf.copy_to(dst);
|
||||
inner.state = Idle(Some(buf));
|
||||
return Ready(Ok(()));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
Operation::Read(Err(e)) => {
|
||||
assert!(buf.is_empty());
|
||||
|
||||
inner.state = Idle(Some(buf));
|
||||
return Ready(Err(e));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
Operation::Write(Ok(_)) => {
|
||||
Operation::Write(Ok(())) => {
|
||||
assert!(buf.is_empty());
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
continue;
|
||||
}
|
||||
Operation::Write(Err(e)) => {
|
||||
assert!(inner.last_write_err.is_none());
|
||||
inner.last_write_err = Some(e.kind());
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
}
|
||||
Operation::Seek(result) => {
|
||||
assert!(buf.is_empty());
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
if let Ok(pos) = result {
|
||||
inner.pos = pos;
|
||||
}
|
||||
@@ -567,11 +585,11 @@ impl AsyncSeek for File {
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
match inner.state {
|
||||
Busy(_) => Err(io::Error::new(
|
||||
State::Busy(_) => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"other file operation is pending, call poll_complete before start_seek",
|
||||
)),
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
// Factor in any unread data from the buf
|
||||
@@ -585,7 +603,7 @@ impl AsyncSeek for File {
|
||||
|
||||
let std = me.std.clone();
|
||||
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
inner.state = State::Busy(spawn_blocking(move || {
|
||||
let res = (&*std).seek(pos);
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
@@ -600,10 +618,10 @@ impl AsyncSeek for File {
|
||||
|
||||
loop {
|
||||
match inner.state {
|
||||
Idle(_) => return Poll::Ready(Ok(inner.pos)),
|
||||
Busy(ref mut rx) => {
|
||||
State::Idle(_) => return Poll::Ready(Ok(inner.pos)),
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {}
|
||||
@@ -616,7 +634,7 @@ impl AsyncSeek for File {
|
||||
if let Ok(pos) = res {
|
||||
inner.pos = pos;
|
||||
}
|
||||
return Ready(res);
|
||||
return Poll::Ready(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,12 +654,12 @@ impl AsyncWrite for File {
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
if let Some(e) = inner.last_write_err.take() {
|
||||
return Ready(Err(e.into()));
|
||||
return Poll::Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
loop {
|
||||
match inner.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
let seek = if !buf.is_empty() {
|
||||
@@ -666,13 +684,13 @@ impl AsyncWrite for File {
|
||||
io::Error::new(io::ErrorKind::Other, "background task failed")
|
||||
})?;
|
||||
|
||||
inner.state = Busy(blocking_task_join_handle);
|
||||
inner.state = State::Busy(blocking_task_join_handle);
|
||||
|
||||
return Ready(Ok(n));
|
||||
return Poll::Ready(Ok(n));
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {
|
||||
@@ -697,6 +715,81 @@ impl AsyncWrite for File {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let me = self.get_mut();
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
if let Some(e) = inner.last_write_err.take() {
|
||||
return Poll::Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
loop {
|
||||
match inner.state {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
let seek = if !buf.is_empty() {
|
||||
Some(SeekFrom::Current(buf.discard_read()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let n = buf.copy_from_bufs(bufs);
|
||||
let std = me.std.clone();
|
||||
|
||||
let blocking_task_join_handle = spawn_mandatory_blocking(move || {
|
||||
let res = if let Some(seek) = seek {
|
||||
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
|
||||
} else {
|
||||
buf.write_to(&mut &*std)
|
||||
};
|
||||
|
||||
(Operation::Write(res), buf)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "background task failed")
|
||||
})?;
|
||||
|
||||
inner.state = State::Busy(blocking_task_join_handle);
|
||||
|
||||
return Poll::Ready(Ok(n));
|
||||
}
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
inner.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {
|
||||
// We don't care about the result here. The fact
|
||||
// that the cursor has advanced will be reflected in
|
||||
// the next iteration of the loop
|
||||
continue;
|
||||
}
|
||||
Operation::Write(res) => {
|
||||
// If the previous write was successful, continue.
|
||||
// Otherwise, error.
|
||||
res?;
|
||||
continue;
|
||||
}
|
||||
Operation::Seek(_) => {
|
||||
// Ignore the seek
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let inner = self.inner.get_mut();
|
||||
@@ -730,7 +823,7 @@ impl std::os::unix::io::AsRawFd for File {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(tokio_no_as_fd)))]
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::AsFd for File {
|
||||
fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> {
|
||||
unsafe {
|
||||
@@ -747,9 +840,7 @@ impl std::os::unix::io::FromRawFd for File {
|
||||
}
|
||||
|
||||
cfg_windows! {
|
||||
use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsHandle, BorrowedHandle};
|
||||
use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle, AsHandle, BorrowedHandle};
|
||||
|
||||
impl AsRawHandle for File {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
@@ -757,7 +848,6 @@ cfg_windows! {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for File {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe {
|
||||
@@ -779,7 +869,7 @@ impl Inner {
|
||||
async fn complete_inflight(&mut self) {
|
||||
use crate::future::poll_fn;
|
||||
|
||||
poll_fn(|cx| self.poll_complete_inflight(cx)).await
|
||||
poll_fn(|cx| self.poll_complete_inflight(cx)).await;
|
||||
}
|
||||
|
||||
fn poll_complete_inflight(&mut self, cx: &mut Context<'_>) -> Poll<()> {
|
||||
@@ -796,21 +886,21 @@ impl Inner {
|
||||
|
||||
fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
if let Some(e) = self.last_write_err.take() {
|
||||
return Ready(Err(e.into()));
|
||||
return Poll::Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
let (op, buf) = match self.state {
|
||||
Idle(_) => return Ready(Ok(())),
|
||||
Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx))?,
|
||||
State::Idle(_) => return Poll::Ready(Ok(())),
|
||||
State::Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx))?,
|
||||
};
|
||||
|
||||
// The buffer is not used here
|
||||
self.state = Idle(Some(buf));
|
||||
self.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => Ready(Ok(())),
|
||||
Operation::Write(res) => Ready(res),
|
||||
Operation::Seek(_) => Ready(Ok(())),
|
||||
Operation::Read(_) => Poll::Ready(Ok(())),
|
||||
Operation::Write(res) => Poll::Ready(res),
|
||||
Operation::Seek(_) => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user