mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
217
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c337ca1a3 | ||
|
|
e542501439 | ||
|
|
4601c84718 | ||
|
|
deff2524c3 | ||
|
|
4565b81097 | ||
|
|
3ce4720a45 | ||
|
|
8342e4b524 | ||
|
|
4c453e9790 | ||
|
|
1846483f19 | ||
|
|
baad270b98 | ||
|
|
f9d78fbe92 | ||
|
|
bb25a06f34 | ||
|
|
d51f16855b | ||
|
|
5baa8d58ec | ||
|
|
bd51feac47 | ||
|
|
b2896feb5b | ||
|
|
3d0d0fd2af | ||
|
|
7cfb100796 | ||
|
|
e37bd63854 | ||
|
|
c9e75785c8 | ||
|
|
3141ed6228 | ||
|
|
ea1cfbdb97 | ||
|
|
fb2dc97468 | ||
|
|
a3d2548789 | ||
|
|
b4ab6472de | ||
|
|
3133af42e1 | ||
|
|
1f924f95f1 | ||
|
|
f6d061919f | ||
|
|
f5ca423bf1 | ||
|
|
e0d33c4a20 | ||
|
|
3125279697 | ||
|
|
5658d7c503 | ||
|
|
a2096049ee | ||
|
|
94db07b379 | ||
|
|
099ee23b65 | ||
|
|
b32826bc93 | ||
|
|
2ce1cee0f9 | ||
|
|
84e41d4aff | ||
|
|
db6929ad62 | ||
|
|
e25d661e08 | ||
|
|
e392c4ff1e | ||
|
|
0fbde0e94b | ||
|
|
10c9eeb6c2 | ||
|
|
47a5fe3a12 | ||
|
|
fbdf539ac2 | ||
|
|
63caced26f | ||
|
|
0b31b2a195 | ||
|
|
cb197def68 | ||
|
|
eaf81ed324 | ||
|
|
53f9e5a357 | ||
|
|
9077762545 | ||
|
|
131e7b4e49 | ||
|
|
e53b92a993 | ||
|
|
7536132065 | ||
|
|
b6d0c9091d | ||
|
|
4846959e8a | ||
|
|
ec3038357f | ||
|
|
f80bbec28f | ||
|
|
eab26a662c | ||
|
|
58edfc61ab | ||
|
|
bfd7b08067 | ||
|
|
12ce924fb9 | ||
|
|
e4f9bcb577 | ||
|
|
8463af92af | ||
|
|
84c5674c60 | ||
|
|
3275cfb638 | ||
|
|
9780bf491f | ||
|
|
d6ba535ceb | ||
|
|
e929d0e8b9 | ||
|
|
2f730d4d5a | ||
|
|
c7e7f203ee | ||
|
|
7c606ab44a | ||
|
|
2d2faf6014 | ||
|
|
c029771247 | ||
|
|
7341004535 | ||
|
|
02b779e315 | ||
|
|
48345d6e48 | ||
|
|
581cd41d79 | ||
|
|
5f7fe8fd0d | ||
|
|
52f28dcb4f | ||
|
|
d51308b035 | ||
|
|
7cae89af47 | ||
|
|
53ad102cf0 | ||
|
|
e7214e3670 | ||
|
|
433545782e | ||
|
|
bd15bace35 | ||
|
|
410660fd54 | ||
|
|
46ff36386d | ||
|
|
0a1695ebfb | ||
|
|
c4f0178b74 | ||
|
|
22b3a65934 | ||
|
|
0d362339d5 | ||
|
|
ab7313ff6b | ||
|
|
4aa7bbff4c | ||
|
|
c06a55088b | ||
|
|
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 |
@@ -1,8 +0,0 @@
|
||||
# See https://github.com/rustsec/rustsec/blob/59e1d2ad0b9cbc6892c26de233d4925074b4b97b/cargo-audit/audit.toml.example for example.
|
||||
|
||||
[advisories]
|
||||
ignore = [
|
||||
# We depend on nix 0.22 only via mio-aio, a dev-dependency.
|
||||
# https://github.com/tokio-rs/tokio/pull/4255#issuecomment-974786349
|
||||
"RUSTSEC-2021-0119",
|
||||
]
|
||||
@@ -2,7 +2,7 @@ version: 2.1
|
||||
jobs:
|
||||
test-arm:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
image: default
|
||||
resource_class: arm.medium
|
||||
environment:
|
||||
# Change to pin rust version
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-1
|
||||
image_family: freebsd-14-0
|
||||
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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
msrv = "1.56"
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Audit Check
|
||||
# https://github.com/rustsec/audit-check/issues/2
|
||||
|
||||
+157
-41
@@ -15,8 +15,8 @@ env:
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2023-05-18
|
||||
rust_clippy: 1.65.0
|
||||
rust_nightly: nightly-2023-10-21
|
||||
rust_clippy: '1.76'
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
# - tokio/README.md
|
||||
@@ -25,7 +25,7 @@ env:
|
||||
# - tokio-util/Cargo.toml
|
||||
# - tokio-test/Cargo.toml
|
||||
# - tokio-stream/Cargo.toml
|
||||
rust_min: 1.63.0
|
||||
rust_min: '1.63'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -44,11 +44,13 @@ jobs:
|
||||
- test-workspace-all-features
|
||||
- test-integration-tests-per-feature
|
||||
- test-parking_lot
|
||||
- test-tracing-instrumentation
|
||||
- valgrind
|
||||
- test-unstable
|
||||
- miri
|
||||
- asan
|
||||
- cross-check
|
||||
- cross-check-tier3
|
||||
- cross-test-with-parking_lot
|
||||
- cross-test-without-parking_lot
|
||||
- no-atomic-u64-test
|
||||
@@ -69,6 +71,7 @@ jobs:
|
||||
- check-external-types
|
||||
- check-fuzzing
|
||||
- check-unstable-mt-counters
|
||||
- check-spelling
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
@@ -95,7 +98,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -127,7 +130,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -146,6 +149,34 @@ jobs:
|
||||
cargo nextest run --workspace --all-features
|
||||
cargo test --doc --workspace --all-features
|
||||
|
||||
test-workspace-all-features-panic-abort:
|
||||
needs: basics
|
||||
name: test all crates in the workspace with all features and panic=abort
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: test all --all-features panic=abort
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run --workspace --exclude tokio-macros --exclude tests-build --all-features --tests
|
||||
|
||||
test-integration-tests-per-feature:
|
||||
needs: basics
|
||||
name: Run integration tests for each feature
|
||||
@@ -157,7 +188,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -179,11 +210,10 @@ jobs:
|
||||
run: cargo hack test --each-feature
|
||||
working-directory: tests-build
|
||||
|
||||
# Check benchmarks. Run of benchmarks is done by bench.yml workflow.
|
||||
# Check benchmarks.
|
||||
- name: Check benches
|
||||
run: cargo check --benches
|
||||
working-directory: benches
|
||||
# bench.yml workflow runs benchmarks only on linux.
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
|
||||
test-parking_lot:
|
||||
@@ -198,7 +228,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -212,12 +242,40 @@ jobs:
|
||||
- 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@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: test tracing-instrumentation
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run
|
||||
working-directory: tokio/tests/tracing-instrumentation
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -256,7 +314,7 @@ jobs:
|
||||
- os: ubuntu-latest
|
||||
- os: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -290,7 +348,7 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -324,7 +382,7 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -352,7 +410,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -373,7 +431,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install llvm
|
||||
# Required to resolve symbols in sanitizer output
|
||||
run: sudo apt-get install -y llvm
|
||||
@@ -395,7 +453,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check semver
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
@@ -411,11 +469,9 @@ 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
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -427,6 +483,31 @@ jobs:
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
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@v4
|
||||
- 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
|
||||
@@ -440,7 +521,7 @@ jobs:
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -455,6 +536,7 @@ jobs:
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
qemu: '7.2'
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Tests run with all features (including parking_lot)
|
||||
@@ -479,7 +561,7 @@ jobs:
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -494,6 +576,7 @@ jobs:
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
qemu: '7.2'
|
||||
|
||||
- name: Remove `parking_lot` from `full` feature
|
||||
run: sed -i '0,/parking_lot/{/parking_lot/d;}' tokio/Cargo.toml
|
||||
@@ -515,7 +598,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -530,6 +613,7 @@ jobs:
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: i686-unknown-linux-gnu
|
||||
qemu: '7.2'
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: test tokio --all-features
|
||||
@@ -545,7 +629,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -578,7 +662,7 @@ jobs:
|
||||
# Try with unstable and taskdump feature flags
|
||||
- { name: "--unstable --taskdump", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump" }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -597,7 +681,7 @@ jobs:
|
||||
name: minrust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_min }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -613,7 +697,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -645,7 +729,7 @@ jobs:
|
||||
name: fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -665,7 +749,7 @@ jobs:
|
||||
name: clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_clippy }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -680,7 +764,7 @@ jobs:
|
||||
name: docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -698,7 +782,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -714,7 +798,7 @@ jobs:
|
||||
name: Check README
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Verify that both READMEs are identical
|
||||
run: diff README.md tokio/README.md
|
||||
|
||||
@@ -733,7 +817,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -779,7 +863,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -796,7 +880,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -811,7 +895,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -829,7 +913,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -878,9 +962,9 @@ jobs:
|
||||
rust:
|
||||
# `check-external-types` requires a specific Rust nightly version. See
|
||||
# the README for details: https://github.com/awslabs/cargo-check-external-types
|
||||
- nightly-2023-05-31
|
||||
- nightly-2023-10-21
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -889,9 +973,9 @@ jobs:
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v1
|
||||
with:
|
||||
tool: [email protected].7
|
||||
tool: [email protected].10
|
||||
- name: check-external-types
|
||||
run: cargo check-external-types --all-features --config external-types.toml
|
||||
run: cargo check-external-types --all-features
|
||||
working-directory: tokio
|
||||
|
||||
check-fuzzing:
|
||||
@@ -899,7 +983,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -913,3 +997,35 @@ jobs:
|
||||
- name: Check /tokio-stream/
|
||||
run: cargo fuzz check --all-features
|
||||
working-directory: tokio-stream
|
||||
|
||||
check-spelling:
|
||||
name: check-spelling
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-spellcheck
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-spellcheck
|
||||
- uses: actions/checkout@v4
|
||||
- name: Make sure dictionary words are sorted and unique
|
||||
run: |
|
||||
# `sed` removes the first line (number of words) and
|
||||
# the last line (new line).
|
||||
#
|
||||
# `sort` makes sure everything in between is sorted
|
||||
# and contains no duplicates.
|
||||
#
|
||||
# Since `sort` is sensitive to locale, we set it
|
||||
# using LC_ALL to en_US.UTF8 to be consistent in different
|
||||
# environments.
|
||||
|
||||
sed '1d; $d' spellcheck.dic | LC_ALL=en_US.UTF8 sort -uc
|
||||
- name: Run cargo-spellcheck
|
||||
run: cargo spellcheck --code 1
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
- scope: loom_multi_thread::group_c
|
||||
- scope: loom_multi_thread::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
- scope: loom_multi_thread_alt::group_c
|
||||
- scope: loom_multi_thread_alt::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
stress-test:
|
||||
- simple_echo_tcp
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
+53
-8
@@ -149,7 +149,7 @@ When updating this, also update:
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.65.0 clippy --all --tests --all-features
|
||||
cargo +1.76 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation normally, the markers that list the features
|
||||
@@ -184,9 +184,11 @@ it, `rustfmt` will update your files locally instead.
|
||||
You can run loom tests with
|
||||
```
|
||||
cd tokio # tokio crate in workspace
|
||||
LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \
|
||||
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
|
||||
run loom tests that test unstable features.
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
@@ -194,6 +196,24 @@ MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" \
|
||||
cargo +nightly miri test --features full --lib
|
||||
```
|
||||
|
||||
### Performing spellcheck on tokio codebase
|
||||
|
||||
You can perform spell-check on tokio codebase. For details of how to use the spellcheck tool, feel free to visit
|
||||
https://github.com/drahnr/cargo-spellcheck
|
||||
```
|
||||
# First install the spell-check plugin
|
||||
cargo install --locked cargo-spellcheck
|
||||
|
||||
# Then run the cargo spell check command
|
||||
cargo spellcheck check
|
||||
```
|
||||
|
||||
if the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
|
||||
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
|
||||
the total number of words included in the file
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
@@ -327,6 +347,29 @@ example would explicitly use `Timeout::new`. For example:
|
||||
/// # }
|
||||
```
|
||||
|
||||
### Benchmarks
|
||||
|
||||
You can run benchmarks locally for the changes you've made to the tokio codebase.
|
||||
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
|
||||
against the changes you have made, for example, you can run;
|
||||
|
||||
```bash
|
||||
cd benches
|
||||
|
||||
# Run all benchmarks.
|
||||
cargo bench
|
||||
|
||||
# Run all tests in the `benches/fs.rs` file
|
||||
cargo bench --bench fs
|
||||
|
||||
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
|
||||
cargo bench async_read_buf
|
||||
|
||||
# After running benches, you can check the statistics under `tokio/target/criterion/`
|
||||
```
|
||||
|
||||
You can also refer to Criterion docs for additional options and details.
|
||||
|
||||
### Commits
|
||||
|
||||
It is a recommended best practice to keep your changes as logically grouped as
|
||||
@@ -350,14 +393,16 @@ A good commit message should describe what changed and why.
|
||||
and no more than 72 characters)
|
||||
* be entirely in lowercase with the exception of proper nouns, acronyms, and
|
||||
the words that refer to code, like function/variable names
|
||||
* be prefixed with the name of the sub crate being changed (without the `tokio-`
|
||||
prefix) and start with an imperative verb. If modifying `tokio` proper,
|
||||
omit the crate prefix.
|
||||
* start with an imperative verb
|
||||
* not have a period at the end
|
||||
* be prefixed with the name of the module being changed; usually this is the
|
||||
same as the M-* label on the PR
|
||||
|
||||
Examples:
|
||||
|
||||
* timer: introduce `Timeout` and deprecate `Deadline`
|
||||
* export `Encoder`, `Decoder`, `Framed*` from tokio_codec
|
||||
* time: introduce `Timeout` and deprecate `Deadline`
|
||||
* codec: export `Encoder`, `Decoder`, `Framed*`
|
||||
* ci: fix the FreeBSD ci configuration
|
||||
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns (except for long URLs).
|
||||
@@ -374,7 +419,7 @@ A good commit message should describe what changed and why.
|
||||
Sample complete commit message:
|
||||
|
||||
```txt
|
||||
subcrate: explain the commit in one line
|
||||
module: explain the commit in one line
|
||||
|
||||
Body of commit message is a few lines of text, explaining things
|
||||
in more detail, possibly giving some background about the issue
|
||||
|
||||
@@ -14,3 +14,7 @@ members = [
|
||||
"tests-build",
|
||||
"tests-integration",
|
||||
]
|
||||
|
||||
[workspace.metadata.spellcheck]
|
||||
config = "spellcheck.toml"
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
tokio = { version = "1.37.0", 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
|
||||
@@ -216,18 +215,18 @@ warrants a patch release with a fix for the bug, it will be backported and
|
||||
released as a new patch release for each LTS minor version. Our current LTS
|
||||
releases are:
|
||||
|
||||
* `1.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)
|
||||
* `1.36.x` - LTS release until March 2025. (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.32.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.18", features = [...] }
|
||||
tokio = { version = "~1.32", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -235,6 +234,8 @@ tokio = { version = "~1.18", features = [...] }
|
||||
* `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.
|
||||
* `1.25.x` - LTS release until March 2024.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+7
-1
@@ -9,9 +9,10 @@ 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"] }
|
||||
@@ -25,6 +26,11 @@ name = "spawn"
|
||||
path = "spawn.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_broadcast"
|
||||
path = "sync_broadcast.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_mpsc"
|
||||
path = "sync_mpsc.rs"
|
||||
|
||||
+54
-42
@@ -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;
|
||||
@@ -29,7 +29,7 @@ const WRITE_SERVICE_PERIOD: Duration = Duration::from_millis(20);
|
||||
// because another writer claimed the buffer space
|
||||
const PROBABILITY_FLUSH_WAIT: f64 = 0.1;
|
||||
|
||||
/// A slow writer that aims to simulate HDD behaviour under heavy load.
|
||||
/// A slow writer that aims to simulate HDD behavior under heavy load.
|
||||
///
|
||||
/// There is a limited buffer, which is fully drained on the next write after
|
||||
/// a time limit is reached. Flush waits for the time limit to be reached
|
||||
@@ -64,7 +64,7 @@ impl SlowHddWriter {
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
// If we hit a service interval, the buffer can be cleared
|
||||
let res = self.service_intervals.poll_tick(cx).map(|_| Ok(()));
|
||||
if let Poll::Ready(_) = res {
|
||||
if res.is_ready() {
|
||||
self.buffer_used = 0;
|
||||
}
|
||||
res
|
||||
@@ -123,7 +123,7 @@ impl AsyncWrite for SlowHddWriter {
|
||||
cx: &mut std::task::Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
let writeable = bufs.into_iter().fold(0, |acc, buf| acc + buf.len());
|
||||
let writeable = bufs.iter().fold(0, |acc, buf| acc + buf.len());
|
||||
self.write_bytes(cx, writeable)
|
||||
}
|
||||
|
||||
@@ -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 rt_curr_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 rt_curr_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 rt_curr_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!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy
|
||||
criterion_group!(
|
||||
rt_curr_scheduler,
|
||||
rt_curr_spawn_many_local,
|
||||
rt_curr_spawn_many_remote_idle,
|
||||
rt_curr_spawn_many_remote_busy
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
criterion_main!(rt_curr_scheduler);
|
||||
|
||||
+125
-112
@@ -5,63 +5,68 @@
|
||||
use tokio::runtime::{self, Runtime};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
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 rt_multi_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();
|
||||
});
|
||||
rx.recv().unwrap();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
fn rt_multi_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();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and yield. Yielding is a
|
||||
// lower notification priority than spawning / regular notification.
|
||||
fn spawn_many_remote_busy1(b: &mut Bencher) {
|
||||
fn rt_multi_spawn_many_remote_busy1(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
@@ -78,16 +83,18 @@ fn spawn_many_remote_busy1(b: &mut Bencher) {
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -95,7 +102,7 @@ fn spawn_many_remote_busy1(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(b: &mut Bencher) {
|
||||
fn rt_multi_spawn_many_remote_busy2(c: &mut Criterion) {
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
@@ -119,49 +126,52 @@ fn spawn_many_remote_busy2(b: &mut Bencher) {
|
||||
});
|
||||
}
|
||||
|
||||
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_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(b: &mut Bencher) {
|
||||
fn rt_multi_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 {
|
||||
rx.recv().unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn ping_pong(b: &mut Bencher) {
|
||||
fn rt_multi_ping_pong(c: &mut Criterion) {
|
||||
const NUM_PINGS: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
@@ -169,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 rt_multi_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();
|
||||
@@ -219,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();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -249,15 +262,15 @@ fn stall() {
|
||||
}
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy1,
|
||||
spawn_many_remote_busy2,
|
||||
ping_pong,
|
||||
yield_many,
|
||||
chained_spawn,
|
||||
criterion_group!(
|
||||
rt_multi_scheduler,
|
||||
rt_multi_spawn_many_local,
|
||||
rt_multi_spawn_many_remote_idle,
|
||||
rt_multi_spawn_many_remote_busy1,
|
||||
rt_multi_spawn_many_remote_busy2,
|
||||
rt_multi_ping_pong,
|
||||
rt_multi_yield_many,
|
||||
rt_multi_chained_spawn,
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
criterion_main!(rt_multi_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);
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
use rand::{Rng, RngCore, SeedableRng};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, 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)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.map(|&c| c as u32)
|
||||
.fold(0, u32::wrapping_add)
|
||||
}
|
||||
|
||||
fn contention_impl<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
let (tx, _rx) = broadcast::channel::<usize>(1000);
|
||||
let wg = Arc::new((AtomicUsize::new(0), Notify::new()));
|
||||
|
||||
for n in 0..N_TASKS {
|
||||
let wg = wg.clone();
|
||||
let mut rx = tx.subscribe();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
|
||||
rt.spawn(async move {
|
||||
while (rx.recv().await).is_ok() {
|
||||
let r = do_work(&mut rng);
|
||||
let _ = black_box(r);
|
||||
if wg.0.fetch_sub(1, Ordering::Relaxed) == 1 {
|
||||
wg.1.notify_one();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 100;
|
||||
|
||||
g.bench_function(N_TASKS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on({
|
||||
let wg = wg.clone();
|
||||
let tx = tx.clone();
|
||||
async move {
|
||||
for i in 0..N_ITERS {
|
||||
assert_eq!(wg.0.fetch_add(N_TASKS, Ordering::Relaxed), 0);
|
||||
tx.send(i).unwrap();
|
||||
while wg.0.load(Ordering::Relaxed) > 0 {
|
||||
wg.1.notified().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
contention_impl::<10>(&mut group);
|
||||
contention_impl::<100>(&mut group);
|
||||
contention_impl::<500>(&mut group);
|
||||
contention_impl::<1000>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention);
|
||||
|
||||
criterion_main!(contention);
|
||||
+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(#[allow(dead_code)] [usize; 64]);
|
||||
impl Default for Medium {
|
||||
fn default() -> Self {
|
||||
Medium([0; 64])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Large(#[allow(dead_code)] [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
|
||||
|
||||
@@ -23,7 +23,7 @@ fn main() {
|
||||
|
||||
// Without the `HandleExt.wrap()` there would be a panic because there is
|
||||
// no timer running, since it would be referencing runtime r1.
|
||||
let _ = rt1.block_on(rt2.wrap(async move {
|
||||
rt1.block_on(rt2.wrap(async move {
|
||||
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
|
||||
println!("addr: {:?}", listener.local_addr());
|
||||
tx.send(()).unwrap();
|
||||
|
||||
+3
-2
@@ -47,9 +47,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// 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() {
|
||||
for task in dump.tasks().iter() {
|
||||
let id = task.id();
|
||||
let trace = task.trace();
|
||||
println!("TASK {i}:");
|
||||
println!("TASK {id}:");
|
||||
println!("{trace}\n");
|
||||
}
|
||||
} else {
|
||||
|
||||
+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 = "/"
|
||||
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
283
|
||||
&
|
||||
+
|
||||
<
|
||||
=
|
||||
>
|
||||
\
|
||||
~
|
||||
—
|
||||
0o777
|
||||
0s
|
||||
0xA
|
||||
0xD
|
||||
100ms
|
||||
10ms
|
||||
~12
|
||||
±1m
|
||||
±1ms
|
||||
1ms
|
||||
1s
|
||||
250ms
|
||||
2x
|
||||
~4
|
||||
443
|
||||
450ms
|
||||
50ms
|
||||
8MB
|
||||
adaptor
|
||||
adaptors
|
||||
Adaptors
|
||||
AIO
|
||||
ambiant
|
||||
amongst
|
||||
api
|
||||
APIs
|
||||
async
|
||||
awaitable
|
||||
backend
|
||||
backpressure
|
||||
backtrace
|
||||
backtraces
|
||||
backtracing
|
||||
binded
|
||||
bitfield
|
||||
bitfields
|
||||
Blockingly
|
||||
boolean
|
||||
broadcasted
|
||||
cancelled
|
||||
cancelling
|
||||
Cancelling
|
||||
CLI
|
||||
cloneable
|
||||
codebase
|
||||
codec
|
||||
codecs
|
||||
combinator
|
||||
combinators
|
||||
config
|
||||
Config
|
||||
connectionless
|
||||
coroutines
|
||||
cpu
|
||||
cpus
|
||||
Customizable
|
||||
datagram
|
||||
Datagram
|
||||
datagrams
|
||||
deallocate
|
||||
deallocated
|
||||
Deallocates
|
||||
decrementing
|
||||
dequeued
|
||||
deregister
|
||||
deregistered
|
||||
deregistering
|
||||
deregisters
|
||||
Deregisters
|
||||
deregistration
|
||||
descriptor's
|
||||
destructor
|
||||
destructors
|
||||
destructure
|
||||
Destructures
|
||||
Dev
|
||||
dns
|
||||
DNS
|
||||
DoS
|
||||
dwOpenMode
|
||||
endian
|
||||
enqueue
|
||||
enqueued
|
||||
EntryInner
|
||||
enum
|
||||
eof
|
||||
Erlang's
|
||||
errored
|
||||
EWMA
|
||||
expirations
|
||||
fcntl
|
||||
fd's
|
||||
FIFOs
|
||||
filename
|
||||
filesystem
|
||||
filesystems
|
||||
fn
|
||||
fns
|
||||
FreeBSD
|
||||
frontend
|
||||
fs
|
||||
functionalities
|
||||
getters
|
||||
GID
|
||||
goroutines
|
||||
Growable
|
||||
gzip
|
||||
hashmaps
|
||||
HashMaps
|
||||
hashsets
|
||||
ie
|
||||
Illumos
|
||||
impl
|
||||
implementers
|
||||
implementor
|
||||
implementors
|
||||
incrementing
|
||||
interoperate
|
||||
invariants
|
||||
Invariants
|
||||
io
|
||||
IOCP
|
||||
iOS
|
||||
IOs
|
||||
IP
|
||||
IPv4
|
||||
IPv6
|
||||
iteratively
|
||||
Kotlin's
|
||||
latencies
|
||||
Lauck
|
||||
libc
|
||||
lifecycle
|
||||
lifo
|
||||
lookups
|
||||
macOS
|
||||
MacOS
|
||||
Marsaglia's
|
||||
metadata
|
||||
mio
|
||||
Mio
|
||||
mio's
|
||||
misconfigured
|
||||
mock's
|
||||
mpmc
|
||||
mpsc
|
||||
multi
|
||||
Multi
|
||||
multicast
|
||||
Multithreaded
|
||||
mut
|
||||
mutex
|
||||
Mutex
|
||||
Nagle
|
||||
nonblocking
|
||||
nondecreasing
|
||||
noop
|
||||
ntasks
|
||||
ok
|
||||
oneshot
|
||||
ORed
|
||||
os
|
||||
overweighing
|
||||
parker
|
||||
parsers
|
||||
peekable
|
||||
PGID
|
||||
PID
|
||||
plaintext
|
||||
poller
|
||||
POSIX
|
||||
proxied
|
||||
qos
|
||||
RAII
|
||||
reallocations
|
||||
recv's
|
||||
refactors
|
||||
refcount
|
||||
refcounting
|
||||
repo
|
||||
repr
|
||||
representable
|
||||
reqwest
|
||||
reregistering
|
||||
resize
|
||||
resized
|
||||
RMW
|
||||
runtime
|
||||
runtime's
|
||||
runtimes
|
||||
rwlock
|
||||
rx
|
||||
scalability
|
||||
scheduler's
|
||||
semver
|
||||
setpgid
|
||||
sharded
|
||||
signalled
|
||||
signalling
|
||||
SmallCrush
|
||||
Solaris
|
||||
spawner
|
||||
Splitter
|
||||
spmc
|
||||
spsc
|
||||
src
|
||||
stabilised
|
||||
startup
|
||||
stateful
|
||||
stderr
|
||||
stdin
|
||||
stdout
|
||||
stealable
|
||||
stealer
|
||||
stealers
|
||||
struct
|
||||
structs
|
||||
subfield
|
||||
suboptimal
|
||||
subprocess
|
||||
superset
|
||||
symlink
|
||||
symlinks
|
||||
sys
|
||||
syscall
|
||||
syscalls
|
||||
tcp
|
||||
TCP
|
||||
TestU01
|
||||
threadpool
|
||||
timestamp
|
||||
timestamps
|
||||
TLS
|
||||
TOCTOU
|
||||
TODO
|
||||
tokio
|
||||
Tokio
|
||||
tokio's
|
||||
Tokio's
|
||||
tuple
|
||||
Tuple
|
||||
tx
|
||||
udp
|
||||
UDP
|
||||
UID
|
||||
unhandled
|
||||
unix
|
||||
unlink
|
||||
unpark
|
||||
Unpark
|
||||
unparked
|
||||
unparking
|
||||
unparks
|
||||
Unparks
|
||||
unreceived
|
||||
unsafety
|
||||
Unsets
|
||||
unsynchronized
|
||||
untrusted
|
||||
usecases
|
||||
Valgrind
|
||||
Varghese
|
||||
vec
|
||||
versa
|
||||
versioned
|
||||
versioning
|
||||
vtable
|
||||
waker
|
||||
wakers
|
||||
Wakers
|
||||
wakeup
|
||||
wakeups
|
||||
workstealing
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
dev_comments = false
|
||||
skip_readme = false
|
||||
|
||||
[Hunspell]
|
||||
lang = "en_US"
|
||||
search_dirs = ["."]
|
||||
extra_dictionaries = ["spellcheck.dic"]
|
||||
skip_os_lookups = true
|
||||
use_builtin = true
|
||||
|
||||
[Hunspell.quirks]
|
||||
allow_concatenation = true
|
||||
|
||||
@@ -42,7 +42,7 @@ fn main() {
|
||||
.write_all(one_mega_random_bytes.as_slice())
|
||||
.await
|
||||
.unwrap();
|
||||
stream.read(&mut buff).await.unwrap();
|
||||
let _ = stream.read(&mut buff).await.unwrap();
|
||||
}
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:5:5
|
||||
|
|
||||
4 | async fn missing_semicolon_or_return_type() {
|
||||
| - help: a return type might be missing here: `-> _`
|
||||
5 | Ok(())
|
||||
| ^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
help: a return type might be missing here
|
||||
|
|
||||
4 | async fn missing_semicolon_or_return_type() -> _ {
|
||||
| ++++
|
||||
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
||||
|
|
||||
5 | Ok(()).expect("REASON")
|
||||
| +++++++++++++++++
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:10:5
|
||||
|
|
||||
9 | async fn missing_return_type() {
|
||||
| - help: a return type might be missing here: `-> _`
|
||||
10 | return Ok(());
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
help: a return type might be missing here
|
||||
|
|
||||
9 | async fn missing_return_type() -> _ {
|
||||
| ++++
|
||||
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
||||
|
|
||||
10 | return Ok(());.expect("REASON")
|
||||
| +++++++++++++++++
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:23:5
|
||||
@@ -41,7 +53,7 @@ error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:32:5
|
||||
|
|
||||
30 | async fn issue_4635() {
|
||||
| - help: try adding a return type: `-> i32`
|
||||
| - help: try adding a return type: `-> i32`
|
||||
31 | return 1;
|
||||
32 | ;
|
||||
| ^ expected `()`, found integer
|
||||
|
||||
@@ -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"
|
||||
version = "2.2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
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"] }
|
||||
|
||||
|
||||
+11
-11
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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! {
|
||||
@@ -507,7 +507,7 @@ impl ItemFn {
|
||||
|
||||
// Inner attributes require extra care, since they're not supported on
|
||||
// blocks (which is what we're expanded into) we instead lift them
|
||||
// outside of the function. This matches the behaviour of `syn`.
|
||||
// outside of the function. This matches the behavior of `syn`.
|
||||
for mut attr in self.inner_attrs {
|
||||
attr.style = syn::AttrStyle::Outer;
|
||||
attr.to_tokens(&mut tokens);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# 0.1.15 (March 14th, 2024)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.63.
|
||||
|
||||
- docs: fix typo in argument name ([#6389])
|
||||
- docs: fix typo in peekable docs ([#6130])
|
||||
- docs: link to latest version of tokio-util docs ([#5694])
|
||||
- docs: typographic improvements ([#6262])
|
||||
- stream: add `StreamExt::peekable` ([#6095])
|
||||
|
||||
[#5694]: https://github.com/tokio-rs/tokio/pull/5694
|
||||
[#6095]: https://github.com/tokio-rs/tokio/pull/6095
|
||||
[#6130]: https://github.com/tokio-rs/tokio/pull/6130
|
||||
[#6262]: https://github.com/tokio-rs/tokio/pull/6262
|
||||
[#6389]: https://github.com/tokio-rs/tokio/pull/6389
|
||||
|
||||
# 0.1.14 (April 26th, 2023)
|
||||
|
||||
This bugfix release bumps the minimum version of Tokio to 1.15, which is
|
||||
|
||||
@@ -4,9 +4,9 @@ name = "tokio-stream"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.63"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
//!
|
||||
//! [async-stream]: https://docs.rs/async-stream
|
||||
//!
|
||||
//! # Conversion to and from AsyncRead/AsyncWrite
|
||||
//! # Conversion to and from `AsyncRead`/`AsyncWrite`
|
||||
//!
|
||||
//! It is often desirable to convert a `Stream` into an [`AsyncRead`],
|
||||
//! especially when dealing with plaintext formats streamed over the network.
|
||||
@@ -73,6 +73,9 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod poll_fn;
|
||||
pub(crate) use poll_fn::poll_fn;
|
||||
|
||||
pub mod wrappers;
|
||||
|
||||
mod stream_ext;
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub(crate) struct PollFn<F> {
|
||||
f: F,
|
||||
}
|
||||
|
||||
pub(crate) fn poll_fn<T, F>(f: F) -> PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
PollFn { f }
|
||||
}
|
||||
|
||||
impl<T, F> Future for PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
|
||||
// Safety: We never construct a `Pin<&mut F>` anywhere, so accessing `f`
|
||||
// mutably in an unpinned way is sound.
|
||||
//
|
||||
// This use of unsafe cannot be replaced with the pin-project macro
|
||||
// because:
|
||||
// * If we put `#[pin]` on the field, then it gives us a `Pin<&mut F>`,
|
||||
// which we can't use to call the closure.
|
||||
// * If we don't put `#[pin]` on the field, then it makes `PollFn` be
|
||||
// unconditionally `Unpin`, which we also don't want.
|
||||
let me = unsafe { Pin::into_inner_unchecked(self) };
|
||||
(me.f)(cx)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
///
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
-15
@@ -1,4 +1,4 @@
|
||||
use crate::Stream;
|
||||
use crate::{poll_fn, Stream};
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::hash::Hash;
|
||||
@@ -209,7 +209,7 @@ pub struct StreamMap<K, V> {
|
||||
impl<K, V> StreamMap<K, V> {
|
||||
/// An iterator visiting all key-value pairs in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is &'a (K, V).
|
||||
/// The iterator element type is `&'a (K, V)`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -232,7 +232,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
|
||||
/// An iterator visiting all key-value pairs mutably in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is &'a mut (K, V).
|
||||
/// The iterator element type is `&'a mut (K, V)`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -289,7 +289,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
|
||||
/// Returns an iterator visiting all keys in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is &'a K.
|
||||
/// The iterator element type is `&'a K`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -312,7 +312,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
|
||||
/// An iterator visiting all values in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is &'a V.
|
||||
/// The iterator element type is `&'a V`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -335,7 +335,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
|
||||
/// An iterator visiting all values mutably in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is &'a mut V.
|
||||
/// The iterator element type is `&'a mut V`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -563,6 +561,110 @@ impl<K, V> Default for StreamMap<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> StreamMap<K, V>
|
||||
where
|
||||
K: Clone + Unpin,
|
||||
V: Stream + Unpin,
|
||||
{
|
||||
/// Receives multiple items on this [`StreamMap`], extending the provided `buffer`.
|
||||
///
|
||||
/// This method returns the number of items that is appended to the `buffer`.
|
||||
///
|
||||
/// Note that this method does not guarantee that exactly `limit` items
|
||||
/// are received. Rather, if at least one item is available, it returns
|
||||
/// as many items as it can up to the given limit. This method returns
|
||||
/// zero only if the `StreamMap` is empty (or if `limit` is zero).
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. If `next_many` is used as the event in a
|
||||
/// [`tokio::select!`](tokio::select) statement and some other branch
|
||||
/// completes first, it is guaranteed that no items were received on any of
|
||||
/// the underlying streams.
|
||||
pub async fn next_many(&mut self, buffer: &mut Vec<(K, V::Item)>, limit: usize) -> usize {
|
||||
poll_fn(|cx| self.poll_next_many(cx, buffer, limit)).await
|
||||
}
|
||||
|
||||
/// Polls to receive multiple items on this `StreamMap`, extending the provided `buffer`.
|
||||
///
|
||||
/// This method returns:
|
||||
/// * `Poll::Pending` if no items are available but the `StreamMap` is not empty.
|
||||
/// * `Poll::Ready(count)` where `count` is the number of items successfully received and
|
||||
/// stored in `buffer`. This can be less than, or equal to, `limit`.
|
||||
/// * `Poll::Ready(0)` if `limit` is set to zero or when the `StreamMap` is empty.
|
||||
///
|
||||
/// Note that this method does not guarantee that exactly `limit` items
|
||||
/// are received. Rather, if at least one item is available, it returns
|
||||
/// as many items as it can up to the given limit. This method returns
|
||||
/// zero only if the `StreamMap` is empty (or if `limit` is zero).
|
||||
pub fn poll_next_many(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
buffer: &mut Vec<(K, V::Item)>,
|
||||
limit: usize,
|
||||
) -> Poll<usize> {
|
||||
if limit == 0 || self.entries.is_empty() {
|
||||
return Poll::Ready(0);
|
||||
}
|
||||
|
||||
let mut added = 0;
|
||||
|
||||
let start = self::rand::thread_rng_n(self.entries.len() as u32) as usize;
|
||||
let mut idx = start;
|
||||
|
||||
while added < limit {
|
||||
// Indicates whether at least one stream returned a value when polled or not
|
||||
let mut should_loop = false;
|
||||
|
||||
for _ in 0..self.entries.len() {
|
||||
let (_, stream) = &mut self.entries[idx];
|
||||
|
||||
match Pin::new(stream).poll_next(cx) {
|
||||
Poll::Ready(Some(val)) => {
|
||||
added += 1;
|
||||
|
||||
let key = self.entries[idx].0.clone();
|
||||
buffer.push((key, val));
|
||||
|
||||
should_loop = true;
|
||||
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
// Remove the entry
|
||||
self.entries.swap_remove(idx);
|
||||
|
||||
// Check if this was the last entry, if so the cursor needs
|
||||
// to wrap
|
||||
if idx == self.entries.len() {
|
||||
idx = 0;
|
||||
} else if idx < start && start <= self.entries.len() {
|
||||
// The stream being swapped into the current index has
|
||||
// already been polled, so skip it.
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
Poll::Pending => {
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !should_loop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if added > 0 {
|
||||
Poll::Ready(added)
|
||||
} else if self.entries.is_empty() {
|
||||
Poll::Ready(0)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Stream for StreamMap<K, V>
|
||||
where
|
||||
K: Clone + Unpin,
|
||||
@@ -660,9 +762,9 @@ mod rand {
|
||||
|
||||
/// Fast random number generate
|
||||
///
|
||||
/// Implement xorshift64+: 2 32-bit xorshift sequences added together.
|
||||
/// Implement `xorshift64+`: 2 32-bit `xorshift` sequences added together.
|
||||
/// Shift triplet `[17,7,16]` was calculated as indicated in Marsaglia's
|
||||
/// Xorshift paper: <https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf>
|
||||
/// `Xorshift` paper: <https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf>
|
||||
/// This generator passes the SmallCrush suite, part of TestU01 framework:
|
||||
/// <http://simul.iro.umontreal.ca/testu01/tu01.html>
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -30,7 +30,6 @@ impl<R> LinesStream<R> {
|
||||
}
|
||||
|
||||
/// Obtain a pinned reference to the inner `Lines<R>`.
|
||||
#[allow(clippy::wrong_self_convention)] // https://github.com/rust-lang/rust-clippy/issues/4546
|
||||
pub fn as_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Lines<R>> {
|
||||
self.project().inner
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ pub struct SignalStream {
|
||||
|
||||
impl SignalStream {
|
||||
/// Create a new `SignalStream`.
|
||||
pub fn new(interval: Signal) -> Self {
|
||||
Self { inner: interval }
|
||||
pub fn new(signal: Signal) -> Self {
|
||||
Self { inner: signal }
|
||||
}
|
||||
|
||||
/// Get back the inner `Signal`.
|
||||
|
||||
@@ -30,7 +30,6 @@ impl<R> SplitStream<R> {
|
||||
}
|
||||
|
||||
/// Obtain a pinned reference to the inner `Split<R>`.
|
||||
#[allow(clippy::wrong_self_convention)] // https://github.com/rust-lang/rust-clippy/issues/4546
|
||||
pub fn as_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Split<R>> {
|
||||
self.project().inner
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use tokio::sync::watch::error::RecvError;
|
||||
|
||||
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// This stream will start by yielding the current value when the WatchStream is polled,
|
||||
/// This stream will start by yielding the current value when the `WatchStream` is polled,
|
||||
/// regardless of whether it was the initial value or sent afterwards,
|
||||
/// unless you use [`WatchStream<T>::from_changes`].
|
||||
///
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(all(feature = "time", not(target_os = "wasi")))] // Wasi does not support panic recovery
|
||||
#![cfg(panic = "unwind")]
|
||||
|
||||
use parking_lot::{const_mutex, Mutex};
|
||||
use std::error::Error;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
use futures::stream::iter;
|
||||
use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap};
|
||||
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
|
||||
|
||||
use std::future::{poll_fn, Future};
|
||||
use std::pin::{pin, Pin};
|
||||
use std::task::Poll;
|
||||
|
||||
mod support {
|
||||
pub(crate) mod mpsc;
|
||||
}
|
||||
|
||||
use support::mpsc;
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
macro_rules! assert_ready_some {
|
||||
($($t:tt)*) => {
|
||||
match assert_ready!($($t)*) {
|
||||
@@ -328,3 +331,233 @@ fn one_ready_many_none() {
|
||||
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
|
||||
Box::pin(s)
|
||||
}
|
||||
|
||||
type UsizeStream = Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_zero() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut vec![], 0)).await;
|
||||
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_empty() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut vec![], 1)).await;
|
||||
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_pending() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
|
||||
|
||||
let mut is_pending = false;
|
||||
poll_fn(|cx| {
|
||||
let poll = stream_map.poll_next_many(cx, &mut vec![], 1);
|
||||
|
||||
is_pending = poll.is_pending();
|
||||
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
|
||||
assert!(is_pending);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_not_enough() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 3)).await;
|
||||
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(buffer.len(), 2);
|
||||
assert!(buffer.contains(&(0, 0)));
|
||||
assert!(buffer.contains(&(1, 1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_enough() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 2)).await;
|
||||
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(buffer.len(), 2);
|
||||
assert!(buffer.contains(&(0, 0)));
|
||||
assert!(buffer.contains(&(1, 1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_correctly_loops_around() {
|
||||
for _ in 0..10 {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([0usize, 1].into_iter())) as UsizeStream);
|
||||
stream_map.insert(2, Box::pin(iter([0usize, 1, 2].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 3)).await;
|
||||
assert_eq!(n, 3);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![0, 0, 0]
|
||||
);
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 2)).await;
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![1, 1]
|
||||
);
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 1)).await;
|
||||
assert_eq!(n, 1);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![2]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_zero() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
|
||||
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut vec![], 0)).poll(cx)).await;
|
||||
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_empty() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
let n = stream_map.next_many(&mut vec![], 1).await;
|
||||
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_pending() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
|
||||
|
||||
let mut is_pending = false;
|
||||
poll_fn(|cx| {
|
||||
let poll = pin!(stream_map.next_many(&mut vec![], 1)).poll(cx);
|
||||
|
||||
is_pending = poll.is_pending();
|
||||
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
|
||||
assert!(is_pending);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_not_enough() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 3)).poll(cx)).await;
|
||||
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(buffer.len(), 2);
|
||||
assert!(buffer.contains(&(0, 0)));
|
||||
assert!(buffer.contains(&(1, 1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_enough() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 2)).poll(cx)).await;
|
||||
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(buffer.len(), 2);
|
||||
assert!(buffer.contains(&(0, 0)));
|
||||
assert!(buffer.contains(&(1, 1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_correctly_loops_around() {
|
||||
for _ in 0..10 {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([0usize, 1].into_iter())) as UsizeStream);
|
||||
stream_map.insert(2, Box::pin(iter([0usize, 1, 2].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 3)).poll(cx)).await;
|
||||
assert_eq!(n, 3);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![0, 0, 0]
|
||||
);
|
||||
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 2)).poll(cx)).await;
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![1, 1]
|
||||
);
|
||||
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 1)).poll(cx)).await;
|
||||
assert_eq!(n, 1);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![2]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
# 0.4.4 (March 14, 2024)
|
||||
|
||||
- task: mark `Spawn` as `#[must_use]` ([#6371])
|
||||
- test: increase MSRV to 1.63 ([#6126])
|
||||
- test: update category slug ([#5953])
|
||||
|
||||
[#5953]: https://github.com/tokio-rs/tokio/pull/5953
|
||||
[#6126]: https://github.com/tokio-rs/tokio/pull/6126
|
||||
[#6371]: https://github.com/tokio-rs/tokio/pull/6371
|
||||
|
||||
# 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.4"
|
||||
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"] }
|
||||
|
||||
@@ -74,7 +74,7 @@ struct Inner {
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Return a new, empty `Builder.
|
||||
/// Return a new, empty `Builder`.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
@@ -478,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
-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)+))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//! intervals between items.
|
||||
//!
|
||||
//! # Usage
|
||||
//! To use the `StreamMock`, you need to create a builder using[`StreamMockBuilder`]. The builder
|
||||
//! 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
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
//! assert!(task.poll().is_ready(), "Task was not ready!");
|
||||
//! ```
|
||||
|
||||
#![allow(clippy::mutex_atomic)]
|
||||
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
@@ -51,6 +49,7 @@ pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
/// Future spawned on a mock task that can be used to poll the future or stream
|
||||
/// without needing pinning or context types.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Spawn<T> {
|
||||
task: MockTask,
|
||||
future: Pin<Box<T>>,
|
||||
@@ -127,7 +126,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();
|
||||
|
||||
@@ -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"
|
||||
@@ -45,7 +45,7 @@ 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"] }
|
||||
|
||||
@@ -20,10 +20,21 @@ pin_project! {
|
||||
/// You can create a `Framed` instance by using the [`Decoder::framed`] adapter, or
|
||||
/// by using the `new` function seen below.
|
||||
///
|
||||
/// # Cancellation safety
|
||||
///
|
||||
/// * [`futures_util::sink::SinkExt::send`]: if send is used as the event in a
|
||||
/// `tokio::select!` statement and some other branch completes first, then it is
|
||||
/// guaranteed that the message was not sent, but the message itself is lost.
|
||||
/// * [`tokio_stream::StreamExt::next`]: This method is cancel safe. The returned
|
||||
/// future only holds onto a reference to the underlying stream, so dropping it will
|
||||
/// never lose a value.
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Decoder::framed`]: crate::codec::Decoder::framed()
|
||||
/// [`futures_util::sink::SinkExt::send`]: futures_util::sink::SinkExt::send
|
||||
/// [`tokio_stream::StreamExt::next`]: https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.next
|
||||
pub struct Framed<T, U> {
|
||||
#[pin]
|
||||
inner: FramedImpl<T, U, RWFrames>
|
||||
@@ -130,7 +141,7 @@ impl<T, U> Framed<T, U> {
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
///
|
||||
/// This objects takes a stream and a readbuffer and a writebuffer. These field
|
||||
/// This objects takes a stream and a `readbuffer` and a `writebuffer`. These field
|
||||
/// can be obtained from an existing `Framed` with the [`into_parts`] method.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
|
||||
@@ -218,6 +218,7 @@ where
|
||||
// Make sure we've got room for at least one byte to read to ensure
|
||||
// that we don't get a spurious 0 that looks like EOF.
|
||||
state.buffer.reserve(1);
|
||||
#[allow(clippy::blocks_in_conditions)]
|
||||
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer).map_err(
|
||||
|err| {
|
||||
trace!("Got an error, going to errored state");
|
||||
|
||||
@@ -14,8 +14,18 @@ use std::task::{Context, Poll};
|
||||
pin_project! {
|
||||
/// A [`Stream`] of messages decoded from an [`AsyncRead`].
|
||||
///
|
||||
/// For examples of how to use `FramedRead` with a codec, see the
|
||||
/// examples on the [`codec`] module.
|
||||
///
|
||||
/// # Cancellation safety
|
||||
/// * [`tokio_stream::StreamExt::next`]: This method is cancel safe. The returned
|
||||
/// future only holds onto a reference to the underlying stream, so dropping it will
|
||||
/// never lose a value.
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`codec`]: crate::codec
|
||||
/// [`tokio_stream::StreamExt::next`]: https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.next
|
||||
pub struct FramedRead<T, D> {
|
||||
#[pin]
|
||||
inner: FramedImpl<T, D, ReadFrame>,
|
||||
|
||||
@@ -15,7 +15,18 @@ use std::task::{Context, Poll};
|
||||
pin_project! {
|
||||
/// A [`Sink`] of frames encoded to an `AsyncWrite`.
|
||||
///
|
||||
/// For examples of how to use `FramedWrite` with a codec, see the
|
||||
/// examples on the [`codec`] module.
|
||||
///
|
||||
/// # Cancellation safety
|
||||
///
|
||||
/// * [`futures_util::sink::SinkExt::send`]: if send is used as the event in a
|
||||
/// `tokio::select!` statement and some other branch completes first, then it is
|
||||
/// guaranteed that the message was not sent, but the message itself is lost.
|
||||
///
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`codec`]: crate::codec
|
||||
/// [`futures_util::sink::SinkExt::send`]: futures_util::sink::SinkExt::send
|
||||
pub struct FramedWrite<T, E> {
|
||||
#[pin]
|
||||
inner: FramedImpl<T, E, WriteFrame>,
|
||||
|
||||
@@ -386,6 +386,10 @@ use std::{cmp, fmt, mem};
|
||||
/// `Builder` enables constructing configured length delimited codecs. Note
|
||||
/// that not all configuration settings apply to both encoding and decoding. See
|
||||
/// the documentation for specific methods for more detail.
|
||||
///
|
||||
/// Note that the if the value of [`Builder::max_frame_length`] becomes larger than
|
||||
/// what can actually fit in [`Builder::length_field_length`], it will be clipped to
|
||||
/// the maximum value that can fit.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Builder {
|
||||
// Maximum frame length
|
||||
@@ -935,8 +939,12 @@ impl Builder {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new_codec(&self) -> LengthDelimitedCodec {
|
||||
let mut builder = *self;
|
||||
|
||||
builder.adjust_max_frame_len();
|
||||
|
||||
LengthDelimitedCodec {
|
||||
builder: *self,
|
||||
builder,
|
||||
state: DecodeState::Head,
|
||||
}
|
||||
}
|
||||
@@ -1018,6 +1026,35 @@ impl Builder {
|
||||
self.num_skip
|
||||
.unwrap_or(self.length_field_offset + self.length_field_len)
|
||||
}
|
||||
|
||||
fn adjust_max_frame_len(&mut self) {
|
||||
// This function is basically `std::u64::saturating_add_signed`. Since it
|
||||
// requires MSRV 1.66, its implementation is copied here.
|
||||
//
|
||||
// TODO: use the method from std when MSRV becomes >= 1.66
|
||||
fn saturating_add_signed(num: u64, rhs: i64) -> u64 {
|
||||
let (res, overflow) = num.overflowing_add(rhs as u64);
|
||||
if overflow == (rhs < 0) {
|
||||
res
|
||||
} else if overflow {
|
||||
u64::MAX
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the maximum number that can be represented using `length_field_len` bytes.
|
||||
let max_number = match 1u64.checked_shl((8 * self.length_field_len) as u32) {
|
||||
Some(shl) => shl - 1,
|
||||
None => u64::MAX,
|
||||
};
|
||||
|
||||
let max_allowed_len = saturating_add_signed(max_number, self.length_adjustment as i64);
|
||||
|
||||
if self.max_frame_len as u64 > max_allowed_len {
|
||||
self.max_frame_len = usize::try_from(max_allowed_len).unwrap_or(usize::MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Builder {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Adaptors from AsyncRead/AsyncWrite to Stream/Sink
|
||||
//! Adaptors from `AsyncRead`/`AsyncWrite` to Stream/Sink
|
||||
//!
|
||||
//! Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
//! wants to batch these into meaningful chunks, called "frames".
|
||||
@@ -7,6 +7,71 @@
|
||||
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
|
||||
//! Framed streams are also known as transports.
|
||||
//!
|
||||
//! # Example encoding using `LinesCodec`
|
||||
//!
|
||||
//! The following example demonstrates how to use a codec such as [`LinesCodec`] to
|
||||
//! write framed data. [`FramedWrite`] can be used to achieve this. Data sent to
|
||||
//! [`FramedWrite`] are first framed according to a specific codec, and then sent to
|
||||
//! an implementor of [`AsyncWrite`].
|
||||
//!
|
||||
//! ```
|
||||
//! use futures::sink::SinkExt;
|
||||
//! use tokio_util::codec::LinesCodec;
|
||||
//! use tokio_util::codec::FramedWrite;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let buffer = Vec::new();
|
||||
//! let messages = vec!["Hello", "World"];
|
||||
//! let encoder = LinesCodec::new();
|
||||
//!
|
||||
//! // FramedWrite is a sink which means you can send values into it
|
||||
//! // asynchronously.
|
||||
//! let mut writer = FramedWrite::new(buffer, encoder);
|
||||
//!
|
||||
//! // To be able to send values into a FramedWrite, you need to bring the
|
||||
//! // `SinkExt` trait into scope.
|
||||
//! writer.send(messages[0]).await.unwrap();
|
||||
//! writer.send(messages[1]).await.unwrap();
|
||||
//!
|
||||
//! let buffer = writer.get_ref();
|
||||
//!
|
||||
//! assert_eq!(buffer.as_slice(), "Hello\nWorld\n".as_bytes());
|
||||
//! }
|
||||
//!```
|
||||
//!
|
||||
//! # Example decoding using `LinesCodec`
|
||||
//! The following example demonstrates how to use a codec such as [`LinesCodec`] to
|
||||
//! read a stream of framed data. [`FramedRead`] can be used to achieve this. [`FramedRead`]
|
||||
//! will keep reading from an [`AsyncRead`] implementor until a whole frame, according to a codec,
|
||||
//! can be parsed.
|
||||
//!
|
||||
//!```
|
||||
//! use tokio_stream::StreamExt;
|
||||
//! use tokio_util::codec::LinesCodec;
|
||||
//! use tokio_util::codec::FramedRead;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let message = "Hello\nWorld".as_bytes();
|
||||
//! let decoder = LinesCodec::new();
|
||||
//!
|
||||
//! // FramedRead can be used to read a stream of values that are framed according to
|
||||
//! // a codec. FramedRead will read from its input (here `buffer`) until a whole frame
|
||||
//! // can be parsed.
|
||||
//! let mut reader = FramedRead::new(message, decoder);
|
||||
//!
|
||||
//! // To read values from a FramedRead, you need to bring the
|
||||
//! // `StreamExt` trait into scope.
|
||||
//! let frame1 = reader.next().await.unwrap().unwrap();
|
||||
//! let frame2 = reader.next().await.unwrap().unwrap();
|
||||
//!
|
||||
//! assert!(reader.next().await.is_none());
|
||||
//! assert_eq!(frame1, "Hello");
|
||||
//! assert_eq!(frame2, "World");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # The Decoder trait
|
||||
//!
|
||||
//! A [`Decoder`] is used together with [`FramedRead`] or [`Framed`] to turn an
|
||||
@@ -248,6 +313,7 @@
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
//! [`Stream`]: futures_core::Stream
|
||||
//! [`Sink`]: futures_sink::Sink
|
||||
//! [`SinkExt`]: futures::sink::SinkExt
|
||||
//! [`SinkExt::close`]: https://docs.rs/futures/0.3/futures/sink/trait.SinkExt.html#method.close
|
||||
//! [`FramedRead`]: struct@crate::codec::FramedRead
|
||||
//! [`FramedWrite`]: struct@crate::codec::FramedWrite
|
||||
|
||||
@@ -116,7 +116,7 @@ where
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
delegate_call!(self.consume(amt))
|
||||
delegate_call!(self.consume(amt));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,39 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R, Item, Error> futures_sink::Sink<Item> for Either<L, R>
|
||||
where
|
||||
L: futures_sink::Sink<Item, Error = Error>,
|
||||
R: futures_sink::Sink<Item, Error = Error>,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn poll_ready(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
delegate_call!(self.poll_ready(cx))
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: Item) -> std::result::Result<(), Self::Error> {
|
||||
delegate_call!(self.start_send(item))
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
delegate_call!(self.poll_flush(cx))
|
||||
}
|
||||
|
||||
fn poll_close(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||
delegate_call!(self.poll_close(cx))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pin_project! {
|
||||
}
|
||||
|
||||
impl<R, F> InspectReader<R, F> {
|
||||
/// Create a new InspectReader, wrapping `reader` and calling `f` for the
|
||||
/// Create a new `InspectReader`, wrapping `reader` and calling `f` for the
|
||||
/// new data supplied by each read call.
|
||||
///
|
||||
/// The closure will only be called with an empty slice if the inner reader
|
||||
@@ -100,7 +100,7 @@ pin_project! {
|
||||
}
|
||||
|
||||
impl<W, F> InspectWriter<W, F> {
|
||||
/// Create a new InspectWriter, wrapping `write` and calling `f` for the
|
||||
/// Create a new `InspectWriter`, wrapping `write` and calling `f` for the
|
||||
/// data successfully written by each write call.
|
||||
///
|
||||
/// The closure `f` will never be called with an empty slice. A vectored
|
||||
|
||||
@@ -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`].
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,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()),
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
//! This mod provides the logic for the inner tree structure of the CancellationToken.
|
||||
//! 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
|
||||
//! more CancellationTokens pointing to it any more), it gets removed from the tree, to keep the
|
||||
//! more `CancellationTokens` pointing to it any more), it gets removed from the tree, to keep the
|
||||
//! tree as small as possible.
|
||||
//!
|
||||
//! # Invariants
|
||||
@@ -66,7 +66,7 @@ impl TreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// The data contained inside a TreeNode.
|
||||
/// The data contained inside a `TreeNode`.
|
||||
///
|
||||
/// This struct exists so that the data of the node can be wrapped
|
||||
/// in a Mutex.
|
||||
@@ -178,6 +178,8 @@ where
|
||||
locked_node = node.inner.lock().unwrap();
|
||||
locked_parent
|
||||
}
|
||||
// https://github.com/tokio-rs/tokio/pull/6273#discussion_r1443752911
|
||||
#[allow(clippy::unnecessary_literal_unwrap)]
|
||||
Err(TryLockError::Poisoned(err)) => Err(err).unwrap(),
|
||||
};
|
||||
|
||||
@@ -196,7 +198,7 @@ where
|
||||
/// `parent` MUST have been a parent of the node when they both got locked,
|
||||
/// otherwise there is a potential for a deadlock as invariant #2 would be violated.
|
||||
///
|
||||
/// To acquire the locks for node and parent, use [with_locked_node_and_parent].
|
||||
/// To acquire the locks for node and parent, use [`with_locked_node_and_parent`].
|
||||
fn move_children_to_parent(node: &mut Inner, parent: &mut Inner) {
|
||||
// Pre-allocate in the parent, for performance
|
||||
parent.children.reserve(node.children.len());
|
||||
@@ -214,7 +216,7 @@ fn move_children_to_parent(node: &mut Inner, parent: &mut Inner) {
|
||||
/// Removes a child from the parent.
|
||||
///
|
||||
/// `parent` MUST be the parent of `node`.
|
||||
/// To acquire the locks for node and parent, use [with_locked_node_and_parent].
|
||||
/// To acquire the locks for node and parent, use [`with_locked_node_and_parent`].
|
||||
fn remove_child(parent: &mut Inner, mut node: MutexGuard<'_, Inner>) {
|
||||
// Query the position from where to remove a node
|
||||
let pos = node.parent_idx;
|
||||
|
||||
@@ -303,7 +303,7 @@ impl<T> Clone for PollSender<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> Sink<T> for PollSender<T> {
|
||||
impl<T: Send> Sink<T> for PollSender<T> {
|
||||
type Error = PollSendError<T>;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -461,7 +462,7 @@ where
|
||||
/// * `Some((key, Ok(value)))` if one of the tasks in this `JoinMap` has
|
||||
/// completed. The `value` is the return value of that ask, and `key` is
|
||||
/// the key associated with the task.
|
||||
/// * `Some((key, Err(err))` if one of the tasks in this JoinMap` has
|
||||
/// * `Some((key, Err(err))` if one of the tasks in this `JoinMap` has
|
||||
/// panicked or been aborted. `key` is the key associated with the task
|
||||
/// that panicked or was aborted.
|
||||
/// * `None` if the `JoinMap` is empty.
|
||||
@@ -626,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
|
||||
@@ -859,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))
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
//!
|
||||
//! This type must be used from within the context of the `Runtime`.
|
||||
|
||||
use futures_core::Future;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Timeout;
|
||||
|
||||
mod wheel;
|
||||
|
||||
@@ -17,6 +19,34 @@ pub mod delay_queue;
|
||||
#[doc(inline)]
|
||||
pub use delay_queue::DelayQueue;
|
||||
|
||||
/// A trait which contains a variety of convenient adapters and utilities for `Future`s.
|
||||
pub trait FutureExt: Future {
|
||||
/// A wrapper around [`tokio::time::timeout`], with the advantage that it is easier to write
|
||||
/// fluent call chains.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::{sync::oneshot, time::Duration};
|
||||
/// use tokio_util::time::FutureExt;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let (tx, rx) = oneshot::channel::<()>();
|
||||
///
|
||||
/// let res = rx.timeout(Duration::from_millis(10)).await;
|
||||
/// assert!(res.is_err());
|
||||
/// # }
|
||||
/// ```
|
||||
fn timeout(self, timeout: Duration) -> Timeout<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
tokio::time::timeout(timeout, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Future + ?Sized> FutureExt for T {}
|
||||
|
||||
// ===== Internal utils =====
|
||||
|
||||
enum Round {
|
||||
|
||||
@@ -270,7 +270,7 @@ mod test {
|
||||
for level in 1..5 {
|
||||
for pos in level..64 {
|
||||
let a = pos * 64_usize.pow(level as u32);
|
||||
assert_eq!(pos as usize, slot_for(a as u64, level));
|
||||
assert_eq!(pos, slot_for(a as u64, level));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg(all(feature = "compat"))]
|
||||
#![cfg(feature = "compat")]
|
||||
#![cfg(not(target_os = "wasi"))] // WASI does not support all fs operations
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
@@ -690,6 +689,66 @@ fn encode_overflow() {
|
||||
codec.encode(Bytes::from("hello"), &mut buf).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_does_not_fit() {
|
||||
let codec = LengthDelimitedCodec::builder()
|
||||
.length_field_length(1)
|
||||
.max_frame_length(256)
|
||||
.new_codec();
|
||||
|
||||
assert_eq!(codec.max_frame_length(), 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neg_adjusted_frame_does_not_fit() {
|
||||
let codec = LengthDelimitedCodec::builder()
|
||||
.length_field_length(1)
|
||||
.length_adjustment(-1)
|
||||
.new_codec();
|
||||
|
||||
assert_eq!(codec.max_frame_length(), 254);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pos_adjusted_frame_does_not_fit() {
|
||||
let codec = LengthDelimitedCodec::builder()
|
||||
.length_field_length(1)
|
||||
.length_adjustment(1)
|
||||
.new_codec();
|
||||
|
||||
assert_eq!(codec.max_frame_length(), 256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_allowed_frame_fits() {
|
||||
let codec = LengthDelimitedCodec::builder()
|
||||
.length_field_length(std::mem::size_of::<usize>())
|
||||
.max_frame_length(usize::MAX)
|
||||
.new_codec();
|
||||
|
||||
assert_eq!(codec.max_frame_length(), usize::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smaller_frame_len_not_adjusted() {
|
||||
let codec = LengthDelimitedCodec::builder()
|
||||
.max_frame_length(10)
|
||||
.length_field_length(std::mem::size_of::<usize>())
|
||||
.new_codec();
|
||||
|
||||
assert_eq!(codec.max_frame_length(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_allowed_length_field() {
|
||||
let codec = LengthDelimitedCodec::builder()
|
||||
.length_field_length(8)
|
||||
.max_frame_length(usize::MAX)
|
||||
.new_codec();
|
||||
|
||||
assert_eq!(codec.max_frame_length(), usize::MAX);
|
||||
}
|
||||
|
||||
// ===== Test utils =====
|
||||
|
||||
struct Mock {
|
||||
@@ -701,8 +760,6 @@ enum Op {
|
||||
Flush,
|
||||
}
|
||||
|
||||
use self::Op::*;
|
||||
|
||||
impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
@@ -710,15 +767,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 +787,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 +828,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))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use futures::future::poll_fn;
|
||||
use futures::sink::SinkExt;
|
||||
use tokio::sync::mpsc::channel;
|
||||
use tokio_test::task::spawn;
|
||||
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok};
|
||||
use tokio_test::{
|
||||
assert_ok, assert_pending, assert_ready, assert_ready_eq, assert_ready_err, assert_ready_ok,
|
||||
};
|
||||
use tokio_util::sync::PollSender;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -29,7 +32,7 @@ async fn simple() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn simple_ref() {
|
||||
let v = vec![1, 2, 3i32];
|
||||
let v = [1, 2, 3i32];
|
||||
|
||||
let (send, mut recv) = channel(3);
|
||||
let mut send = PollSender::new(send);
|
||||
@@ -260,3 +263,87 @@ fn start_send_panics_when_acquiring() {
|
||||
assert_pending!(reserve.poll());
|
||||
send.send_item(2).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sink_send_then_flush() {
|
||||
let (send, mut recv) = channel(1);
|
||||
let mut send = PollSender::new(send);
|
||||
|
||||
let mut recv_task = spawn(recv.recv());
|
||||
assert_pending!(recv_task.poll());
|
||||
|
||||
let mut ready = spawn(poll_fn(|cx| send.poll_ready_unpin(cx)));
|
||||
assert_ready_ok!(ready.poll());
|
||||
assert_ok!(send.start_send_unpin(()));
|
||||
|
||||
let mut ready = spawn(poll_fn(|cx| send.poll_ready_unpin(cx)));
|
||||
assert_pending!(ready.poll());
|
||||
|
||||
let mut flush = spawn(poll_fn(|cx| send.poll_flush_unpin(cx)));
|
||||
assert_ready_ok!(flush.poll());
|
||||
|
||||
// Flushing does not mean that the sender becomes ready.
|
||||
let mut ready = spawn(poll_fn(|cx| send.poll_ready_unpin(cx)));
|
||||
assert_pending!(ready.poll());
|
||||
|
||||
assert_ready_eq!(recv_task.poll(), Some(()));
|
||||
assert!(ready.is_woken());
|
||||
assert_ready_ok!(ready.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sink_send_then_close() {
|
||||
let (send, mut recv) = channel(1);
|
||||
let mut send = PollSender::new(send);
|
||||
|
||||
let mut recv_task = spawn(recv.recv());
|
||||
assert_pending!(recv_task.poll());
|
||||
|
||||
let mut ready = spawn(poll_fn(|cx| send.poll_ready_unpin(cx)));
|
||||
assert_ready_ok!(ready.poll());
|
||||
assert_ok!(send.start_send_unpin(1));
|
||||
|
||||
let mut ready = spawn(poll_fn(|cx| send.poll_ready_unpin(cx)));
|
||||
assert_pending!(ready.poll());
|
||||
|
||||
assert!(recv_task.is_woken());
|
||||
assert_ready_eq!(recv_task.poll(), Some(1));
|
||||
|
||||
assert!(ready.is_woken());
|
||||
assert_ready_ok!(ready.poll());
|
||||
|
||||
drop(recv_task);
|
||||
let mut recv_task = spawn(recv.recv());
|
||||
assert_pending!(recv_task.poll());
|
||||
assert_ok!(send.start_send_unpin(2));
|
||||
|
||||
let mut close = spawn(poll_fn(|cx| send.poll_close_unpin(cx)));
|
||||
assert_ready_ok!(close.poll());
|
||||
|
||||
assert!(recv_task.is_woken());
|
||||
assert_ready_eq!(recv_task.poll(), Some(2));
|
||||
|
||||
drop(recv_task);
|
||||
let mut recv_task = spawn(recv.recv());
|
||||
assert_ready_eq!(recv_task.poll(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sink_send_ref() {
|
||||
let data = "data".to_owned();
|
||||
let (send, mut recv) = channel(1);
|
||||
let mut send = PollSender::new(send);
|
||||
|
||||
let mut recv_task = spawn(recv.recv());
|
||||
assert_pending!(recv_task.poll());
|
||||
|
||||
let mut ready = spawn(poll_fn(|cx| send.poll_ready_unpin(cx)));
|
||||
assert_ready_ok!(ready.poll());
|
||||
|
||||
assert_ok!(send.start_send_unpin(data.as_str()));
|
||||
|
||||
let mut flush = spawn(poll_fn(|cx| send.poll_flush_unpin(cx)));
|
||||
assert_ready_ok!(flush.poll());
|
||||
|
||||
assert_ready_eq!(recv_task.poll(), Some("data"));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support panic recovery
|
||||
#![cfg(panic = "unwind")]
|
||||
|
||||
use parking_lot::{const_mutex, Mutex};
|
||||
use std::error::Error;
|
||||
@@ -76,7 +77,6 @@ fn poll_sender_send_item_panic_caller() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn local_pool_handle_new_panic_caller() -> Result<(), Box<dyn Error>> {
|
||||
let panic_location_file = test_panic(|| {
|
||||
let _ = LocalPoolHandle::new(0);
|
||||
@@ -89,7 +89,6 @@ fn local_pool_handle_new_panic_caller() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn local_pool_handle_spawn_pinned_by_idx_panic_caller() -> Result<(), Box<dyn Error>> {
|
||||
let panic_location_file = test_panic(|| {
|
||||
let rt = basic();
|
||||
|
||||
@@ -31,7 +31,7 @@ fn can_drop_future_and_still_get_output() {
|
||||
let pool = task::LocalPoolHandle::new(1);
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
|
||||
let _ = pool.spawn_pinned(move || {
|
||||
pool.spawn_pinned(move || {
|
||||
// Rc is !Send + !Sync
|
||||
let local_data = Rc::new("test");
|
||||
|
||||
@@ -71,6 +71,7 @@ async fn can_spawn_multiple_futures() {
|
||||
/// A panic in the spawned task causes the join handle to return an error.
|
||||
/// But, you can continue to spawn tasks.
|
||||
#[tokio::test]
|
||||
#[cfg(panic = "unwind")]
|
||||
async fn task_panic_propagates() {
|
||||
let pool = task::LocalPoolHandle::new(1);
|
||||
|
||||
@@ -95,6 +96,7 @@ async fn task_panic_propagates() {
|
||||
/// A panic during task creation causes the join handle to return an error.
|
||||
/// But, you can continue to spawn tasks.
|
||||
#[tokio::test]
|
||||
#[cfg(panic = "unwind")]
|
||||
async fn callback_panic_does_not_kill_worker() {
|
||||
let pool = task::LocalPoolHandle::new(1);
|
||||
|
||||
@@ -209,7 +211,7 @@ async fn spawn_by_idx() {
|
||||
},
|
||||
0,
|
||||
);
|
||||
let _ = pool.spawn_pinned_by_idx(
|
||||
pool.spawn_pinned_by_idx(
|
||||
|| async move {
|
||||
barrier2.wait().await;
|
||||
std::thread::current().id()
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -805,6 +805,7 @@ async fn item_expiry_greater_than_wheel() {
|
||||
|
||||
#[cfg_attr(target_os = "wasi", ignore = "FIXME: Does not seem to work with WASI")]
|
||||
#[tokio::test(start_paused = true)]
|
||||
#[cfg(panic = "unwind")]
|
||||
async fn remove_after_compact() {
|
||||
let now = Instant::now();
|
||||
let mut queue = DelayQueue::new();
|
||||
@@ -822,6 +823,7 @@ async fn remove_after_compact() {
|
||||
|
||||
#[cfg_attr(target_os = "wasi", ignore = "FIXME: Does not seem to work with WASI")]
|
||||
#[tokio::test(start_paused = true)]
|
||||
#[cfg(panic = "unwind")]
|
||||
async fn remove_after_compact_poll() {
|
||||
let now = Instant::now();
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
|
||||
+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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user