mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
88
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efdba5fcf0 | ||
|
|
b0ba02e755 | ||
|
|
7bcd2d343d | ||
|
|
f84b209126 | ||
|
|
eacb98e189 | ||
|
|
d87569164f | ||
|
|
e1aebb031c | ||
|
|
fd63094ee0 | ||
|
|
8c600d0fd2 | ||
|
|
11bfc1345b | ||
|
|
f085b6211b | ||
|
|
30d25ccb8b | ||
|
|
9fccf5339d | ||
|
|
ebf61b45b5 | ||
|
|
4abe9d732e | ||
|
|
f82bcf3f45 | ||
|
|
7db9bc41f1 | ||
|
|
64834ec701 | ||
|
|
967f5715a7 | ||
|
|
9271e3ed05 | ||
|
|
cd1823f43e | ||
|
|
a97cf12ed9 | ||
|
|
bde3f20b0f | ||
|
|
670a907c55 | ||
|
|
d3565a2923 | ||
|
|
8f81e0814b | ||
|
|
47cc31590f | ||
|
|
dc0f728162 | ||
|
|
89713ad1ec | ||
|
|
6c03e03898 | ||
|
|
16bc4e2e28 | ||
|
|
1afb391350 | ||
|
|
b010b5ddaf | ||
|
|
905c146aed | ||
|
|
56aaa43e91 | ||
|
|
57ff47ab58 | ||
|
|
812de3e134 | ||
|
|
ba82e73c7b | ||
|
|
2e85f9ddf8 | ||
|
|
a7e1cd8ff8 | ||
|
|
5f7be0ac42 | ||
|
|
36d12d2686 | ||
|
|
f943312865 | ||
|
|
5db10f538b | ||
|
|
bbdba7101d | ||
|
|
7cfce54386 | ||
|
|
81370e6202 | ||
|
|
203af02126 | ||
|
|
de230926dc | ||
|
|
432ec3f2b2 | ||
|
|
1604bc3351 | ||
|
|
4c7d8b2a14 | ||
|
|
d7db722bb4 | ||
|
|
fccc28f1d0 | ||
|
|
927df0e9d9 | ||
|
|
98df02d7a4 | ||
|
|
3ea11e2a5f | ||
|
|
c79121391d | ||
|
|
ad8c59add6 | ||
|
|
654d38b132 | ||
|
|
857ba80933 | ||
|
|
95b9342da7 | ||
|
|
0af06b7bab | ||
|
|
01a7f1dfab | ||
|
|
eeb55c733b | ||
|
|
1fc450aefb | ||
|
|
324218f9bb | ||
|
|
aa65d0d0b8 | ||
|
|
bf18ed452d | ||
|
|
43134f1e57 | ||
|
|
b4c3246d33 | ||
|
|
7947fa4bd7 | ||
|
|
9f132172db | ||
|
|
6752f50154 | ||
|
|
6c72168a93 | ||
|
|
ee4de81806 | ||
|
|
a8435c0fc8 | ||
|
|
467d614267 | ||
|
|
66c786540e | ||
|
|
dd1196d369 | ||
|
|
2db0070eb8 | ||
|
|
26de3187e7 | ||
|
|
d3c7f56c10 | ||
|
|
ee04abe797 | ||
|
|
aa1e7e2b9d | ||
|
|
961757f548 | ||
|
|
e67cd87f71 | ||
|
|
a502d1b5c8 |
-71
@@ -1,71 +0,0 @@
|
||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-3
|
||||
env:
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2025-01-25
|
||||
RUSTFLAGS: -D warnings
|
||||
# This excludes unstable features like io_uring, which require '--cfg tokio_unstable'.
|
||||
TOKIO_STABLE_FEATURES: full,test-util
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 64-bit
|
||||
setup_script:
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --workspace --features $TOKIO_STABLE_FEATURES
|
||||
# Free the disk space before the next build,
|
||||
# otherwise cirrus-ci complains about "No space left on device".
|
||||
- cargo clean
|
||||
# Enable all unstable features except `taskdump`, which is Linux-only.
|
||||
- |
|
||||
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable" \
|
||||
RUSTDOCFLAGS="$RUSTDOCFLAGS --cfg tokio_unstable" \
|
||||
cargo test \
|
||||
--features $TOKIO_STABLE_FEATURES,io-uring,tracing
|
||||
|
||||
task:
|
||||
name: FreeBSD docs
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_NIGHTLY
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
# We use `--features $TOKIO_STABLE_FEATURES,io-uring,tracing` instead of
|
||||
# `--all-features` to exclude `taskdump`, which is Linux-only.
|
||||
- cargo doc --lib --no-deps --features $TOKIO_STABLE_FEATURES,io-uring,tracing --document-private-items
|
||||
|
||||
task:
|
||||
name: FreeBSD 32-bit
|
||||
setup_script:
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
|
||||
- . $HOME/.cargo/env
|
||||
- rustup target add i686-unknown-freebsd
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --workspace --features $TOKIO_STABLE_FEATURES --target i686-unknown-freebsd
|
||||
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
+29
-17
@@ -1,28 +1,40 @@
|
||||
R-loom-blocking:
|
||||
- tokio/src/runtime/blocking/*
|
||||
- tokio/src/runtime/blocking/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio/src/runtime/blocking/*
|
||||
- tokio/src/runtime/blocking/**/*
|
||||
|
||||
R-loom-sync:
|
||||
- tokio/src/sync/*
|
||||
- tokio/src/sync/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio/src/sync/*
|
||||
- tokio/src/sync/**/*
|
||||
|
||||
R-loom-time-driver:
|
||||
- tokio/src/runtime/time/*
|
||||
- tokio/src/runtime/time/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio/src/runtime/time/*
|
||||
- tokio/src/runtime/time/**/*
|
||||
|
||||
R-loom-current-thread:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/current_thread/*
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/current_thread/*
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-multi-thread:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-util:
|
||||
- tokio-util/src/*
|
||||
- tokio-util/src/**/*
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- tokio-util/src/*
|
||||
- tokio-util/src/**/*
|
||||
|
||||
@@ -20,5 +20,5 @@ jobs:
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
+144
-44
@@ -18,7 +18,7 @@ env:
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2025-10-12
|
||||
# Pin a specific miri version
|
||||
rust_miri_nightly: nightly-2025-11-13
|
||||
rust_miri_nightly: nightly-2026-04-27
|
||||
rust_clippy: '1.88'
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
cargo nextest run --workspace --features $TOKIO_STABLE_FEATURES
|
||||
# Removing workspace patches to run tests without path dependencies
|
||||
# (if not specified differently in the crate)
|
||||
sed -i.bak '/^\[patch\.crates-io\]$/d; /^tokio = { path = "tokio"\s*}$/d' Cargo.toml && rm -f Cargo.toml.bak
|
||||
perl -0 -i -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
|
||||
cargo nextest run \
|
||||
--workspace \
|
||||
--exclude tokio \
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -208,7 +208,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -226,7 +226,7 @@ jobs:
|
||||
cargo check --workspace --tests --features $TOKIO_STABLE_FEATURES
|
||||
# Removing the tokio workspace patch to run tests without path dependencies
|
||||
# (if not specified differently in the crate)
|
||||
sed -i '/^\[patch\.crates-io\]$/d; /^tokio = { path = "tokio"\s*}$/d' Cargo.toml
|
||||
perl -0 -i -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
|
||||
cargo check --workspace --exclude tokio --tests --features $TOKIO_STABLE_FEATURES
|
||||
|
||||
valgrind:
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -275,7 +275,7 @@ jobs:
|
||||
- { os: ubuntu-latest, extra_features: io-uring }
|
||||
- { os: macos-latest, extra_features: "" }
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -309,7 +309,7 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -343,7 +343,7 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -372,7 +372,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -395,7 +395,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -418,7 +418,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -437,7 +437,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install llvm
|
||||
# Required to resolve symbols in sanitizer output
|
||||
run: sudo apt-get install -y llvm
|
||||
@@ -459,7 +459,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Check `tokio` semver
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
@@ -488,7 +488,7 @@ jobs:
|
||||
- powerpc64-unknown-linux-gnu
|
||||
- arm-linux-androideabi
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -513,7 +513,7 @@ jobs:
|
||||
# - name: armv7-sony-vita-newlibeabihf
|
||||
# exclude_features: "process,signal,rt-process-signal,full,taskdump"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
@@ -547,7 +547,7 @@ jobs:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -597,7 +597,7 @@ jobs:
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -638,7 +638,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -670,7 +670,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -708,7 +708,7 @@ jobs:
|
||||
rustflags: "--cfg tokio_unstable -Dwarnings"
|
||||
exclude_features: ""
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -727,7 +727,7 @@ jobs:
|
||||
name: minrust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_min }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -756,7 +756,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -789,7 +789,7 @@ jobs:
|
||||
name: fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -809,7 +809,7 @@ jobs:
|
||||
name: clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_clippy }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -822,14 +822,17 @@ jobs:
|
||||
cargo clippy --workspace --tests --no-deps --features $TOKIO_STABLE_FEATURES
|
||||
# Removing the tokio workspace patch to check without path dependencies
|
||||
# (if not specified differently in the crate)
|
||||
sed -i '/^\[patch\.crates-io\]$/d; /^tokio = { path = "tokio"\s*}$/d' Cargo.toml
|
||||
perl -0 -i.bak -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
|
||||
cargo clippy --workspace --exclude tokio --tests --no-deps --features $TOKIO_STABLE_FEATURES
|
||||
- name: "clippy --workspace --all-features --unstable"
|
||||
run: |
|
||||
# Forcing the cargo lock regeneration to apply the tokio patch
|
||||
rm Cargo.lock
|
||||
cargo clippy --workspace --tests --no-deps --all-features --config 'patch.crates-io.tokio.path="tokio"'
|
||||
mv Cargo.toml Cargo.toml.nopatch
|
||||
mv Cargo.toml.bak Cargo.toml
|
||||
cargo clippy --workspace --tests --no-deps --all-features
|
||||
# check without path dependencies
|
||||
mv Cargo.toml.nopatch Cargo.toml
|
||||
cargo clippy --workspace --exclude tokio --tests --no-deps --all-features
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
@@ -846,7 +849,7 @@ jobs:
|
||||
extra_features: "tracing,io-uring,taskdump"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -863,7 +866,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -879,7 +882,7 @@ jobs:
|
||||
name: Check README
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Verify that both READMEs are identical
|
||||
run: diff README.md tokio/README.md
|
||||
|
||||
@@ -898,7 +901,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -950,7 +953,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -994,7 +997,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1011,7 +1014,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -1035,7 +1038,7 @@ jobs:
|
||||
- name: macros sync time rt
|
||||
features: "macros sync time rt"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1058,7 +1061,7 @@ jobs:
|
||||
- wasm32-wasip1
|
||||
- wasm32-wasip1-threads
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1114,6 +1117,32 @@ jobs:
|
||||
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -W shared-memory=y -S threads=y --"
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864
|
||||
|
||||
wasm32-wasip2:
|
||||
name: test tokio for wasm32-wasip2
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
targets: wasm32-wasip2
|
||||
|
||||
- name: Install cargo-nextest, wasmtime
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest,wasmtime-cli
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: test tokio --target wasm32-wasip2
|
||||
run: cargo nextest run --target wasm32-wasip2 --features net,macros,rt,io-util
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable
|
||||
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -Sinherit-network
|
||||
|
||||
check-external-types:
|
||||
name: check-external-types (${{ matrix.os }})
|
||||
needs: basics
|
||||
@@ -1128,7 +1157,7 @@ jobs:
|
||||
# includes all unstable features.
|
||||
extra_features: "tracing,io-uring,taskdump"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1137,7 +1166,7 @@ jobs:
|
||||
toolchain: nightly-2025-08-06
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v1
|
||||
uses: taiki-e/cache-cargo-install-action@v3
|
||||
with:
|
||||
tool: [email protected]
|
||||
- name: check-external-types
|
||||
@@ -1152,7 +1181,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1172,7 +1201,7 @@ jobs:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -1181,7 +1210,7 @@ jobs:
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-spellcheck
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Make sure dictionary words are sorted and unique
|
||||
run: |
|
||||
FILE="spellcheck.dic"
|
||||
@@ -1263,3 +1292,74 @@ jobs:
|
||||
uses: ./.github/workflows/uring-kernel-version-test.yml
|
||||
with:
|
||||
kernel_version: ${{ matrix.kernel_version }}
|
||||
|
||||
freebsd-x86_64:
|
||||
name: FreeBSD x86_64
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Test in FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: '14.4'
|
||||
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
|
||||
prepare: |
|
||||
pkg install -y curl
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
sh rustup.sh -y --profile minimal --default-toolchain ${{ env.rust_stable }}
|
||||
run: |
|
||||
. $HOME/.cargo/env
|
||||
cargo test --workspace --features $TOKIO_STABLE_FEATURES
|
||||
# Enable all unstable features except `io_uring` and `taskdump`,
|
||||
# which are Linux-only features.
|
||||
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable" \
|
||||
cargo test \
|
||||
--features $TOKIO_STABLE_FEATURES,tracing
|
||||
|
||||
freebsd-docs:
|
||||
name: FreeBSD docs
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Test in FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
with:
|
||||
release: '14.4'
|
||||
envs: "TOKIO_STABLE_FEATURES RUSTDOCFLAGS RUSTFLAGS"
|
||||
prepare: |
|
||||
pkg install -y curl
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
sh rustup.sh -y --profile minimal --default-toolchain ${{ env.rust_nightly }}
|
||||
run: |
|
||||
. $HOME/.cargo/env
|
||||
# We use `--features $TOKIO_STABLE_FEATURES,io-uring,tracing` instead of
|
||||
# `--all-features` to exclude `taskdump` and `io_uring`, which are Linux-only
|
||||
# features.
|
||||
cargo doc --lib --no-deps --features $TOKIO_STABLE_FEATURES,tracing \
|
||||
--document-private-items
|
||||
|
||||
freebsd-i686:
|
||||
name: FreeBSD i686
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Test in FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: '14.4'
|
||||
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
|
||||
prepare: |
|
||||
pkg install -y curl
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
sh rustup.sh -y --profile minimal --default-toolchain ${{ env.rust_stable }}
|
||||
run: |
|
||||
. $HOME/.cargo/env
|
||||
rustup target add i686-unknown-freebsd
|
||||
cargo test --workspace --features $TOKIO_STABLE_FEATURES \
|
||||
--target i686-unknown-freebsd
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'tokio-rs'
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-blocking') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- 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-sync') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- 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-time-driver') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
- scope: loom_multi_thread::group_c
|
||||
- scope: loom_multi_thread::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-util') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
@@ -19,5 +19,5 @@ jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
stress-test:
|
||||
- simple_echo_tcp
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
env:
|
||||
KERNEL_VERSION: ${{ inputs.kernel_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
- name: Cache Linux source
|
||||
id: cache-kernel
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: linux-${{ env.KERNEL_VERSION }}
|
||||
key: kernel-${{ env.KERNEL_VERSION }}
|
||||
|
||||
@@ -17,6 +17,10 @@ members = [
|
||||
|
||||
[patch.crates-io]
|
||||
tokio = { path = "tokio" }
|
||||
tokio-macros = { path = "tokio-macros" }
|
||||
tokio-stream = { path = "tokio-stream" }
|
||||
tokio-test = { path = "tokio-test" }
|
||||
tokio-util = { path = "tokio-util" }
|
||||
|
||||
[workspace.metadata.spellcheck]
|
||||
config = "spellcheck.toml"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
*[TokioConf 2026 program and tickets are now available!](https://tokioconf.com)*
|
||||
|
||||
---
|
||||
|
||||
# Tokio
|
||||
|
||||
A runtime for writing reliable, asynchronous, and slim applications with
|
||||
@@ -56,7 +60,7 @@ Make sure you enable the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
tokio = { version = "1.52.4", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -217,18 +221,18 @@ warrants a patch release with a fix for the bug, it will be backported and
|
||||
released as a new patch release for each LTS minor version. Our current LTS
|
||||
releases are:
|
||||
|
||||
* `1.43.x` - LTS release until March 2026. (MSRV 1.70)
|
||||
* `1.47.x` - LTS release until September 2026. (MSRV 1.70)
|
||||
* `1.51.x` - LTS release until March 2027. (MSRV 1.71)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
use an LTS release.
|
||||
|
||||
To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.43.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.47.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.43", features = [...] }
|
||||
tokio = { version = "~1.47", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -241,6 +245,7 @@ tokio = { version = "~1.43", features = [...] }
|
||||
* `1.32.x` - LTS release until September 2024.
|
||||
* `1.36.x` - LTS release until March 2025.
|
||||
* `1.38.x` - LTS release until July 2025.
|
||||
* `1.43.x` - LTS release until March 2026.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -101,5 +101,10 @@ name = "spawn_blocking"
|
||||
path = "spawn_blocking.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "remote_spawn"
|
||||
path = "remote_spawn.rs"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
//! Benchmark remote task spawning (push_remote_task) at different concurrency
|
||||
//! levels on the multi-threaded scheduler.
|
||||
//!
|
||||
//! This measures contention on the scheduler's inject queue mutex when multiple
|
||||
//! external (non-worker) threads spawn tasks into the tokio runtime simultaneously.
|
||||
//! Every rt.spawn() from an external thread unconditionally goes through
|
||||
//! push_remote_task, making this a direct measurement of inject queue contention.
|
||||
//!
|
||||
//! For each parallelism level N (1, 2, 4, 8, 16, 32, 64, capped at available parallelism):
|
||||
//! - Spawns N std::threads (external to the runtime)
|
||||
//! - Each thread spawns TOTAL_TASKS / N tasks into the runtime via rt.spawn()
|
||||
//! - All threads are synchronized with a barrier to maximize contention
|
||||
//! - Tasks are trivial no-ops to isolate the push overhead
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use std::sync::Barrier;
|
||||
use tokio::runtime::{self, Runtime};
|
||||
|
||||
/// Total number of tasks spawned across all threads per iteration.
|
||||
/// Must be divisible by the largest parallelism level (64).
|
||||
const TOTAL_TASKS: usize = 12_800;
|
||||
const _: () = assert!(
|
||||
TOTAL_TASKS.is_multiple_of(64),
|
||||
"TOTAL_TASKS must be divisible by 64"
|
||||
);
|
||||
|
||||
fn remote_spawn_contention(c: &mut Criterion) {
|
||||
let parallelism_levels = parallelism_levels();
|
||||
let mut group = c.benchmark_group("remote_spawn");
|
||||
|
||||
for num_threads in ¶llelism_levels {
|
||||
let num_threads = *num_threads;
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("threads", num_threads),
|
||||
&num_threads,
|
||||
|b, &num_threads| {
|
||||
let rt = rt();
|
||||
let tasks_per_thread = TOTAL_TASKS / num_threads;
|
||||
let barrier = Barrier::new(num_threads);
|
||||
|
||||
b.iter_custom(|iters| {
|
||||
let mut total_duration = std::time::Duration::ZERO;
|
||||
for _ in 0..iters {
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let all_handles = std::thread::scope(|s| {
|
||||
let handles: Vec<_> = (0..num_threads)
|
||||
.map(|_| {
|
||||
let barrier = &barrier;
|
||||
let rt = &rt;
|
||||
s.spawn(move || {
|
||||
let mut join_handles = Vec::with_capacity(tasks_per_thread);
|
||||
barrier.wait();
|
||||
|
||||
for _ in 0..tasks_per_thread {
|
||||
join_handles.push(rt.spawn(async {}));
|
||||
}
|
||||
join_handles
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
handles
|
||||
.into_iter()
|
||||
.flat_map(|h| h.join().unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
total_duration += start.elapsed();
|
||||
|
||||
rt.block_on(async {
|
||||
for h in all_handles {
|
||||
h.await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
total_duration
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn parallelism_levels() -> Vec<usize> {
|
||||
let max_parallelism = std::thread::available_parallelism()
|
||||
.map(|p| p.get())
|
||||
.unwrap_or(1);
|
||||
|
||||
[1, 2, 4, 8, 16, 32, 64]
|
||||
.into_iter()
|
||||
.filter(|&n| n <= max_parallelism)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread().build().unwrap()
|
||||
}
|
||||
|
||||
criterion_group!(remote_spawn_benches, remote_spawn_contention);
|
||||
|
||||
criterion_main!(remote_spawn_benches);
|
||||
@@ -1,7 +1,7 @@
|
||||
# How to specify crates dependencies versions
|
||||
|
||||
Each crate (e.g., `tokio-util`, `tokio-stream`, etc.) should specify dependencies
|
||||
according to these rules:
|
||||
according to the following rules:
|
||||
|
||||
1. The listed version should be the oldest version that the crate works with
|
||||
(e.g., if `tokio-util` works with `tokio` version `1.44` but not `1.43`, then
|
||||
@@ -10,7 +10,7 @@ We don't require users to use the latest version unnecessarily.
|
||||
2. When a crate starts using a newer feature in a dependency, the version
|
||||
should be bumped to the version that introduced it.
|
||||
3. If a crate depends on an unreleased feature in a dependency, it may use
|
||||
`path = ` dependency to specify this. Since path dependencies must be removed
|
||||
`path =` dependency to specify this. Since path dependencies must be removed
|
||||
during the release of the crate, this ensures that it can't be released until
|
||||
the dependency has a new version.
|
||||
|
||||
@@ -20,12 +20,13 @@ Consider the following example from `tokio-stream`:
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.11"
|
||||
tokio = { path = "../tokio", features = ["sync"] }
|
||||
tokio = { version = "1.38.0", path = "../tokio", features = ["sync"] }
|
||||
```
|
||||
|
||||
In this case, local development of `tokio-stream` uses the local version
|
||||
of `tokio` via the `path` dependency. This means that it's currently not
|
||||
possible to release `tokio-stream`. Once a new version of `tokio` is
|
||||
released, the path dependency will be removed from `tokio-stream`.
|
||||
possible to release `tokio-stream`, and `tokio` should be released first.
|
||||
Once a new version of `tokio` is released (in this example the `1.38.0`),
|
||||
the path dependency should be removed.
|
||||
As mentioned before, this version should only be bumped when adding a new
|
||||
feature in the crate that relies on a newer version.
|
||||
|
||||
@@ -24,6 +24,9 @@ httparse = "1.0"
|
||||
httpdate = "1.0"
|
||||
once_cell = "1.5.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing", "taskdump"] }
|
||||
|
||||
@@ -46,6 +49,10 @@ path = "connect-udp.rs"
|
||||
name = "echo-tcp"
|
||||
path = "echo-tcp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "graceful-shutdown"
|
||||
path = "graceful-shutdown.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-udp"
|
||||
path = "echo-udp.rs"
|
||||
@@ -102,5 +109,9 @@ path = "named-pipe-multi-client.rs"
|
||||
name = "dump"
|
||||
path = "dump.rs"
|
||||
|
||||
[[example]]
|
||||
name = "prewarm-fd-table"
|
||||
path = "prewarm-fd-table.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
//! Graceful shutdown example.
|
||||
//!
|
||||
//! This example follows the same approach described in the
|
||||
//! [Graceful Shutdown tutorial](https://tokio.rs/tokio/topics/shutdown):
|
||||
//!
|
||||
//! - A [`CancellationToken`] tells tasks to stop accepting new work.
|
||||
//! - A [`TaskTracker`] waits for in-flight work to complete.
|
||||
//!
|
||||
//! It runs a TCP echo server on `127.0.0.1:6142`. When Ctrl+C is
|
||||
//! pressed, the server stops accepting connections and waits for all
|
||||
//! active connections to finish before exiting.
|
||||
//!
|
||||
//! Start the server:
|
||||
//!
|
||||
//! cargo run --example graceful-shutdown
|
||||
//!
|
||||
//! Then connect with:
|
||||
//!
|
||||
//! nc 127.0.0.1 6142
|
||||
//!
|
||||
//! Press Ctrl+C on the server to trigger a graceful shutdown.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::time::{self, Duration};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:6142").await?;
|
||||
println!("listening on 127.0.0.1:6142");
|
||||
|
||||
let token = CancellationToken::new();
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = listener.accept() => {
|
||||
let (socket, addr) = match result {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
// Transient errors (e.g. fd exhaustion) are recoverable,
|
||||
// so we log and continue. A production server might add a
|
||||
// backoff or break on fatal errors to avoid a busy loop.
|
||||
eprintln!("failed to accept: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
println!("accepted connection from {addr}");
|
||||
|
||||
let token = token.clone();
|
||||
tracker.spawn(handle_connection(socket, addr, token));
|
||||
}
|
||||
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
println!("\nshutdown signal received, waiting for connections to finish");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Signal all tasks to stop and wait for them to complete.
|
||||
token.cancel();
|
||||
tracker.close();
|
||||
tracker.wait().await;
|
||||
|
||||
println!("shutdown complete");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_connection(mut socket: TcpStream, addr: SocketAddr, token: CancellationToken) {
|
||||
tokio::select! {
|
||||
_ = echo(&mut socket) => {}
|
||||
_ = token.cancelled() => {
|
||||
notify_shutdown(&mut socket).await;
|
||||
}
|
||||
}
|
||||
|
||||
println!("connection from {addr} closed");
|
||||
}
|
||||
|
||||
/// Reads lines from the client and writes them back.
|
||||
///
|
||||
/// Called for every accepted connection. Runs until the client disconnects
|
||||
/// or a read/write error occurs.
|
||||
async fn echo(socket: &mut TcpStream) {
|
||||
let (reader, mut writer) = socket.split();
|
||||
let mut reader = BufReader::new(reader);
|
||||
let mut line = String::new();
|
||||
|
||||
loop {
|
||||
match reader.read_line(&mut line).await {
|
||||
Ok(0) | Err(_) => return,
|
||||
Ok(_) => {
|
||||
if writer.write_all(line.as_bytes()).await.is_err() {
|
||||
return;
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a shutdown notice to the client before closing the connection.
|
||||
///
|
||||
/// Called when the cancellation token fires. Uses a timeout so that a
|
||||
/// slow or unresponsive client cannot hold up the server shutdown.
|
||||
async fn notify_shutdown(socket: &mut TcpStream) {
|
||||
let _ = time::timeout(
|
||||
Duration::from_secs(1),
|
||||
socket.write_all(b"server shutting down\n"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//! Demonstrates pre-warming the Linux file descriptor table to avoid latency
|
||||
//! spikes caused by file descriptor table growth in multi-threaded processes.
|
||||
//!
|
||||
//! On Linux, the kernel's FD table is grown lazily and protected by RCU
|
||||
//! synchronization. In multi-threaded processes, when a syscall like `socket()`
|
||||
//! triggers a table resize, the calling thread blocks until all RCU readers
|
||||
//! quiesce. This can cause stalls of tens of milliseconds on tokio worker threads,
|
||||
//! blocking the entire event loop (not just one task).
|
||||
//!
|
||||
//! The workaround is to force the kernel to expand the FD table once per process
|
||||
//! (before any runtime starts), by duplicating an FD to a high slot and then
|
||||
//! closing it. The kernel never shrinks the FD table during a process's lifetime,
|
||||
//! so the capacity persists.
|
||||
//!
|
||||
//! This is most relevant for services that open many connections concurrently
|
||||
//! (e.g. HTTP servers, connection pools). The pre-warm target should be at least
|
||||
//! your expected peak FD count, and must not exceed `RLIMIT_NOFILE`.
|
||||
//!
|
||||
//! See: <https://github.com/tokio-rs/tokio/issues/7970>
|
||||
//!
|
||||
//! Usage:
|
||||
//!
|
||||
//! cargo run --example prewarm-fd-table
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
/// Pre-warms the FD table using `fcntl(F_DUPFD_CLOEXEC)` to duplicate an FD
|
||||
/// into a high slot, expanding the table in a single syscall. `F_DUPFD_CLOEXEC`
|
||||
/// allocates the lowest available FD >= `target`, so it never clobbers an
|
||||
/// existing FD.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn prewarm_fd_table(target: i32) -> std::io::Result<()> {
|
||||
use std::os::unix::io::{FromRawFd, OwnedFd};
|
||||
|
||||
let dev_null = std::fs::File::open("/dev/null")?;
|
||||
let raw = unsafe {
|
||||
libc::fcntl(
|
||||
std::os::unix::io::AsRawFd::as_raw_fd(&dev_null),
|
||||
libc::F_DUPFD_CLOEXEC,
|
||||
target,
|
||||
)
|
||||
};
|
||||
if raw < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
// Close both FDs. The table capacity persists.
|
||||
let _owned = unsafe { OwnedFd::from_raw_fd(raw) };
|
||||
drop(dev_null);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fully safe alternative using only stdlib. Requires O(n) syscalls instead of
|
||||
/// one, but avoids `unsafe` entirely.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[allow(dead_code)]
|
||||
fn prewarm_fd_table_safe(target: i32) -> std::io::Result<()> {
|
||||
let f = std::fs::File::open("/dev/null")?;
|
||||
let _fds: Vec<_> = (0..target)
|
||||
.map(|_| f.try_clone())
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
const FD_TARGET: i32 = 10_000;
|
||||
|
||||
println!("Pre-warming FD table to {FD_TARGET} entries...");
|
||||
if let Err(e) = prewarm_fd_table(FD_TARGET) {
|
||||
eprintln!("Warning: failed to pre-warm FD table: {e}");
|
||||
} else {
|
||||
println!("FD table pre-warmed successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
// Build the runtime *after* pre-warming.
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {});
|
||||
}
|
||||
+5
-1
@@ -1,4 +1,4 @@
|
||||
312
|
||||
316
|
||||
&
|
||||
+
|
||||
<
|
||||
@@ -138,6 +138,7 @@ hashmaps
|
||||
HashMaps
|
||||
hashsets
|
||||
HdrHistogram
|
||||
ICMP
|
||||
ie
|
||||
Illumos
|
||||
impl
|
||||
@@ -209,6 +210,7 @@ POSIX
|
||||
proxied
|
||||
qos
|
||||
RAII
|
||||
RCU
|
||||
reallocations
|
||||
recv's
|
||||
refactors
|
||||
@@ -240,6 +242,7 @@ spawner
|
||||
Splitter
|
||||
spmc
|
||||
spsc
|
||||
SQE
|
||||
src
|
||||
stabilised
|
||||
startup
|
||||
@@ -309,5 +312,6 @@ wakers
|
||||
Wakers
|
||||
wakeup
|
||||
wakeups
|
||||
WASI
|
||||
workstealing
|
||||
ZST
|
||||
|
||||
@@ -68,4 +68,7 @@ async fn test_has_second_test_attr_rust_2021() {}
|
||||
#[tokio::test]
|
||||
async fn test_has_generated_second_test_attr() {}
|
||||
|
||||
#[tokio::test(name = 123)]
|
||||
async fn test_name_not_string() {}
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -4,7 +4,7 @@ error: the `async` keyword is missing from the function declaration
|
||||
6 | fn main_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
|
||||
--> tests/fail/macros_invalid_input.rs:8:15
|
||||
|
|
||||
8 | #[tokio::main(foo)]
|
||||
@@ -22,13 +22,13 @@ error: the `async` keyword is missing from the function declaration
|
||||
15 | fn test_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
|
||||
--> tests/fail/macros_invalid_input.rs:17:15
|
||||
|
|
||||
17 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
|
||||
--> tests/fail/macros_invalid_input.rs:20:15
|
||||
|
|
||||
20 | #[tokio::test(foo = 123)]
|
||||
@@ -119,3 +119,9 @@ error: second test attribute is supplied, consider removing or changing the orde
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `tokio::test` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: Failed to parse value of `name` as string.
|
||||
--> tests/fail/macros_invalid_input.rs:71:22
|
||||
|
|
||||
71 | #[tokio::test(name = 123)]
|
||||
| ^^^
|
||||
|
||||
@@ -37,7 +37,7 @@ help: consider importing this trait
|
||||
1 + use std::future::Future;
|
||||
|
|
||||
|
||||
error[E0433]: failed to resolve: use of undeclared type `Pin`
|
||||
error[E0433]: cannot find type `Pin` in this scope
|
||||
--> tests/fail/macros_join.rs:35:17
|
||||
|
|
||||
35 | let _ = Pin::new(&mut x);
|
||||
|
||||
@@ -37,7 +37,7 @@ help: consider importing this trait
|
||||
1 + use std::future::Future;
|
||||
|
|
||||
|
||||
error[E0433]: failed to resolve: use of undeclared type `Pin`
|
||||
error[E0433]: cannot find type `Pin` in this scope
|
||||
--> tests/fail/macros_try_join.rs:35:17
|
||||
|
|
||||
35 | let _ = Pin::new(&mut x);
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# 2.7.0 (April 3rd, 2026)
|
||||
|
||||
- macros: stabilize `LocalRuntime` ([#7557])
|
||||
- macros: add runtime name ([#7924])
|
||||
|
||||
[#7557]: https://github.com/tokio-rs/tokio/pull/7557
|
||||
[#7924]: https://github.com/tokio-rs/tokio/pull/7924
|
||||
|
||||
# 2.6.1 (Mar 2nd, 2026)
|
||||
|
||||
- macros: improve error message for return type mismatch in #[tokio::main] ([#7856])
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-macros"
|
||||
# - Remove path dependencies (if any)
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-x.y.z" git tag.
|
||||
version = "2.6.1"
|
||||
version = "2.7.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.71"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
|
||||
+25
-25
@@ -53,6 +53,7 @@ impl UnhandledPanic {
|
||||
}
|
||||
|
||||
struct FinalConfig {
|
||||
name: Option<String>,
|
||||
flavor: RuntimeFlavor,
|
||||
worker_threads: Option<usize>,
|
||||
start_paused: Option<bool>,
|
||||
@@ -62,6 +63,7 @@ struct FinalConfig {
|
||||
|
||||
/// Config used in case of the attribute not being able to build a valid config
|
||||
const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
|
||||
name: None,
|
||||
flavor: RuntimeFlavor::CurrentThread,
|
||||
worker_threads: None,
|
||||
start_paused: None,
|
||||
@@ -70,6 +72,7 @@ const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
name: Option<String>,
|
||||
rt_multi_thread_available: bool,
|
||||
default_flavor: RuntimeFlavor,
|
||||
flavor: Option<RuntimeFlavor>,
|
||||
@@ -83,6 +86,7 @@ struct Configuration {
|
||||
impl Configuration {
|
||||
fn new(is_test: bool, rt_multi_thread: bool) -> Self {
|
||||
Configuration {
|
||||
name: None,
|
||||
rt_multi_thread_available: rt_multi_thread,
|
||||
default_flavor: match is_test {
|
||||
true => RuntimeFlavor::CurrentThread,
|
||||
@@ -97,6 +101,16 @@ impl Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> {
|
||||
if self.name.is_some() {
|
||||
return Err(syn::Error::new(span, "`name` set multiple times."));
|
||||
}
|
||||
|
||||
let runtime_name = parse_string(name, span, "name")?;
|
||||
self.name = Some(runtime_name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_flavor(&mut self, runtime: syn::Lit, span: Span) -> Result<(), syn::Error> {
|
||||
if self.flavor.is_some() {
|
||||
return Err(syn::Error::new(span, "`flavor` set multiple times."));
|
||||
@@ -227,6 +241,7 @@ impl Configuration {
|
||||
};
|
||||
|
||||
Ok(FinalConfig {
|
||||
name: self.name.clone(),
|
||||
crate_name: self.crate_name.clone(),
|
||||
flavor,
|
||||
worker_threads,
|
||||
@@ -372,9 +387,12 @@ fn build_config(
|
||||
config
|
||||
.set_unhandled_panic(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"name" => {
|
||||
config.set_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
name => {
|
||||
let msg = format!(
|
||||
"Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
|
||||
"Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.",
|
||||
);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
@@ -397,11 +415,12 @@ fn build_config(
|
||||
"Set the runtime flavor with #[{macro_name}(flavor = \"current_thread\")]."
|
||||
)
|
||||
}
|
||||
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
|
||||
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic"
|
||||
| "name" => {
|
||||
format!("The `{name}` attribute requires an argument.")
|
||||
}
|
||||
name => {
|
||||
format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.")
|
||||
format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.")
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
@@ -457,12 +476,7 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
},
|
||||
};
|
||||
|
||||
let mut checks = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
let build = if let RuntimeFlavor::Local = config.flavor {
|
||||
checks.push(quote! { tokio_unstable });
|
||||
errors.push("The local runtime flavor is only available when `tokio_unstable` is set.");
|
||||
quote_spanned! {last_stmt_start_span=> build_local(Default::default())}
|
||||
} else {
|
||||
quote_spanned! {last_stmt_start_span=> build()}
|
||||
@@ -478,6 +492,9 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
let unhandled_panic = v.into_tokens(&crate_path);
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) };
|
||||
}
|
||||
if let Some(v) = config.name {
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.name(#v) };
|
||||
}
|
||||
|
||||
let generated_attrs = if is_test {
|
||||
quote! {
|
||||
@@ -487,23 +504,10 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let do_checks: TokenStream = checks
|
||||
.iter()
|
||||
.zip(&errors)
|
||||
.map(|(check, error)| {
|
||||
quote! {
|
||||
#[cfg(not(#check))]
|
||||
compile_error!(#error);
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let body_ident = quote! { body };
|
||||
// This explicit `return` is intentional. See tokio-rs/tokio#4636
|
||||
let last_block = quote_spanned! {last_stmt_end_span=>
|
||||
#do_checks
|
||||
|
||||
#[cfg(all(#(#checks),*))]
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return, clippy::unwrap_in_result)]
|
||||
{
|
||||
#use_builder
|
||||
@@ -515,10 +519,6 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
.block_on(#body_ident);
|
||||
}
|
||||
|
||||
#[cfg(not(all(#(#checks),*)))]
|
||||
{
|
||||
panic!("fell through checks")
|
||||
}
|
||||
};
|
||||
|
||||
let body = input.body();
|
||||
|
||||
+49
-13
@@ -73,16 +73,11 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ## Local
|
||||
///
|
||||
/// [Unstable API][unstable] only.
|
||||
///
|
||||
/// To use the [local runtime], the macro can be configured using
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(tokio_unstable)]
|
||||
/// #[tokio::main(flavor = "local")]
|
||||
/// # async fn main() {}
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// # Function arguments
|
||||
@@ -91,6 +86,30 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ## Set the name of the runtime
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(name = "my-runtime")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .name("my-runtime")
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Using the multi-threaded runtime
|
||||
///
|
||||
/// ```rust
|
||||
@@ -141,25 +160,19 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ## Using the local runtime
|
||||
///
|
||||
/// Available in the [unstable API][unstable] only.
|
||||
///
|
||||
/// The [local runtime] is similar to the current-thread runtime but
|
||||
/// supports [`task::spawn_local`](../tokio/task/fn.spawn_local.html).
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(tokio_unstable)]
|
||||
/// #[tokio::main(flavor = "local")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(tokio_unstable)]
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
@@ -169,8 +182,6 @@ use proc_macro::TokenStream;
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
@@ -408,6 +419,31 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Set the name of the runtime
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(name = "my-test-runtime")]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::test]`
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[test]
|
||||
/// fn my_test() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .name("my-test-runtime")
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// assert!(true);
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using the multi-thread runtime
|
||||
///
|
||||
/// ```no_run
|
||||
|
||||
@@ -6,6 +6,8 @@ use core::mem;
|
||||
use core::pin::Pin;
|
||||
use core::task::{ready, Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
|
||||
use std::hash::Hash;
|
||||
|
||||
// Do not export this struct until `FromStream` can be unsealed.
|
||||
pin_project! {
|
||||
@@ -135,6 +137,139 @@ impl<T> sealed::FromStreamPriv<T> for Vec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromStream<T> for VecDeque<T> {}
|
||||
|
||||
impl<T> sealed::FromStreamPriv<T> for VecDeque<T> {
|
||||
type InternalCollection = VecDeque<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> VecDeque<T> {
|
||||
VecDeque::with_capacity(lower)
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut VecDeque<T>, item: T) -> bool {
|
||||
collection.push_back(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut VecDeque<T>) -> VecDeque<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromStream<T> for LinkedList<T> {}
|
||||
|
||||
impl<T> sealed::FromStreamPriv<T> for LinkedList<T> {
|
||||
type InternalCollection = LinkedList<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> LinkedList<T> {
|
||||
LinkedList::new()
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut LinkedList<T>, item: T) -> bool {
|
||||
collection.push_back(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut LinkedList<T>) -> LinkedList<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ord> FromStream<T> for BTreeSet<T> {}
|
||||
|
||||
impl<T: Ord> sealed::FromStreamPriv<T> for BTreeSet<T> {
|
||||
type InternalCollection = BTreeSet<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> BTreeSet<T> {
|
||||
BTreeSet::new()
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut BTreeSet<T>, item: T) -> bool {
|
||||
collection.insert(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut BTreeSet<T>) -> BTreeSet<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Ord, V> FromStream<(K, V)> for BTreeMap<K, V> {}
|
||||
|
||||
impl<K: Ord, V> sealed::FromStreamPriv<(K, V)> for BTreeMap<K, V> {
|
||||
type InternalCollection = BTreeMap<K, V>;
|
||||
|
||||
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> BTreeMap<K, V> {
|
||||
BTreeMap::new()
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut BTreeMap<K, V>, (key, value): (K, V)) -> bool {
|
||||
collection.insert(key, value);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut BTreeMap<K, V>) -> BTreeMap<K, V> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash> FromStream<T> for HashSet<T> {}
|
||||
|
||||
impl<T: Eq + Hash> sealed::FromStreamPriv<T> for HashSet<T> {
|
||||
type InternalCollection = HashSet<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> HashSet<T> {
|
||||
HashSet::with_capacity(lower)
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut HashSet<T>, item: T) -> bool {
|
||||
collection.insert(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut HashSet<T>) -> HashSet<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq + Hash, V> FromStream<(K, V)> for HashMap<K, V> {}
|
||||
|
||||
impl<K: Eq + Hash, V> sealed::FromStreamPriv<(K, V)> for HashMap<K, V> {
|
||||
type InternalCollection = HashMap<K, V>;
|
||||
|
||||
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> HashMap<K, V> {
|
||||
HashMap::with_capacity(lower)
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut HashMap<K, V>, (key, value): (K, V)) -> bool {
|
||||
collection.insert(key, value);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut HashMap<K, V>) -> HashMap<K, V> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ord> FromStream<T> for BinaryHeap<T> {}
|
||||
|
||||
impl<T: Ord> sealed::FromStreamPriv<T> for BinaryHeap<T> {
|
||||
type InternalCollection = BinaryHeap<T>;
|
||||
|
||||
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> BinaryHeap<T> {
|
||||
BinaryHeap::with_capacity(lower)
|
||||
}
|
||||
|
||||
fn extend(_: sealed::Internal, collection: &mut BinaryHeap<T>, item: T) -> bool {
|
||||
collection.push(item);
|
||||
true
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut BinaryHeap<T>) -> BinaryHeap<T> {
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromStream<T> for Box<[T]> {}
|
||||
|
||||
impl<T> sealed::FromStreamPriv<T> for Box<[T]> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::Stream;
|
||||
|
||||
use futures_core::FusedStream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
use std::task::{ready, Context, Poll};
|
||||
@@ -51,3 +52,12 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FusedStream for Fuse<T>
|
||||
where
|
||||
T: Stream,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.stream.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
|
||||
|
||||
use tokio_stream::{self as stream, StreamExt};
|
||||
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok, task};
|
||||
|
||||
@@ -61,6 +63,155 @@ async fn collect_vec_items() {
|
||||
assert_eq!(vec![1, 2], coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_vecdeque_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<VecDeque<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
let xs = [1, 2, 42, 3];
|
||||
for x in xs {
|
||||
tx.send(x).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
|
||||
assert_eq!(coll, VecDeque::from(xs));
|
||||
assert_eq!(coll.into_iter().collect::<Vec<_>>(), xs);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_linkedlist_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<LinkedList<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
let xs = [1, 2, 42, 3];
|
||||
for x in xs {
|
||||
tx.send(x).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
|
||||
assert_eq!(coll, LinkedList::from(xs));
|
||||
assert_eq!(coll.into_iter().collect::<Vec<_>>(), xs);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_btreeset_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<BTreeSet<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(2).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(1).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(BTreeSet::from([1, 2]), coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_btreemap_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<BTreeMap<i32, i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send((3, 4)).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send((1, 2)).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(BTreeMap::from([(1, 2), (3, 4)]), coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_hashset_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<HashSet<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(1).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(2).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(HashSet::from([1, 2]), coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_hashmap_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<HashMap<i32, i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send((1, 2)).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send((3, 4)).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(HashMap::from([(1, 2), (3, 4)]), coll);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_binaryheap_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<BinaryHeap<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(2).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
tx.send(1).unwrap();
|
||||
assert!(fut.is_woken());
|
||||
assert_pending!(fut.poll());
|
||||
|
||||
drop(tx);
|
||||
assert!(fut.is_woken());
|
||||
let coll = assert_ready!(fut.poll());
|
||||
assert_eq!(vec![1, 2], coll.into_sorted_vec());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_string_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use futures_core::FusedStream;
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
|
||||
use std::pin::Pin;
|
||||
@@ -37,16 +38,22 @@ async fn basic_usage() {
|
||||
// however, once it is fused
|
||||
let mut stream = stream.fuse();
|
||||
|
||||
assert!(!stream.is_terminated());
|
||||
assert_eq!(stream.size_hint(), (0, None));
|
||||
assert_eq!(stream.next().await, Some(4));
|
||||
|
||||
assert!(!stream.is_terminated());
|
||||
assert_eq!(stream.size_hint(), (0, None));
|
||||
assert_eq!(stream.next().await, None);
|
||||
|
||||
assert!(stream.is_terminated());
|
||||
|
||||
// it will always return `None` after the first time.
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
assert_eq!(stream.next().await, None);
|
||||
assert_eq!(stream.size_hint(), (0, Some(0)));
|
||||
|
||||
assert!(stream.is_terminated());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
+175
-1
@@ -1,3 +1,155 @@
|
||||
# 1.52.4 (July 16th, 2026)
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: don't skip the driver when `before_park` schedules work ([#8222])
|
||||
|
||||
### Fixed (unstable)
|
||||
|
||||
- taskdump: remove crate disambiguators from output ([#8264])
|
||||
|
||||
[#8264]: https://github.com/tokio-rs/tokio/pull/8264
|
||||
|
||||
# 1.52.3 (May 8th, 2026)
|
||||
|
||||
### Fixed
|
||||
|
||||
* sync: fix underflow in mpsc channel `len()` ([#8062])
|
||||
* sync: notify receivers in mpsc `OwnedPermit::release()` method ([#8075])
|
||||
* sync: require that an `RwLock` has `max_readers != 0` ([#8076])
|
||||
* sync: return `Empty` from `try_recv()` when mpsc is closed with outstanding permits ([#8074])
|
||||
|
||||
# 1.52.2 (May 4th, 2026)
|
||||
|
||||
This release reverts the LIFO slot stealing change introduced in 1.51.0
|
||||
([#7431]), due to [its performance impact][#8065]. ([#8100])
|
||||
|
||||
# 1.52.1 (April 16th, 2026)
|
||||
|
||||
## Fixed
|
||||
|
||||
- runtime: revert [#7757] to fix [a regression][#8056] that causes `spawn_blocking` to hang ([#8057])
|
||||
|
||||
[#7757]: https://github.com/tokio-rs/tokio/pull/7757
|
||||
[#8056]: https://github.com/tokio-rs/tokio/pull/8056
|
||||
[#8057]: https://github.com/tokio-rs/tokio/pull/8057
|
||||
|
||||
# 1.52.0 (April 14th, 2026)
|
||||
|
||||
## Added
|
||||
|
||||
- io: `AioSource::register_borrowed` for I/O safety support ([#7992])
|
||||
- net: add `try_io` function to `unix::pipe` sender and receiver types ([#8030])
|
||||
|
||||
## Added (unstable)
|
||||
|
||||
- runtime: `Builder::enable_eager_driver_handoff` setting enable eager hand off of the I/O and time drivers before polling tasks ([#8010])
|
||||
- taskdump: add `trace_with()` for customized task dumps ([#8025])
|
||||
- taskdump: allow `impl FnMut()` in `trace_with` instead of just `fn()` ([#8040])
|
||||
- fs: support `io_uring` in `AsyncRead` for `File` ([#7907])
|
||||
|
||||
## Changed
|
||||
|
||||
- runtime: improve `spawn_blocking` scalability with sharded queue ([#7757])
|
||||
- runtime: use `compare_exchange_weak()` in worker queue ([#8028])
|
||||
|
||||
## Fixed
|
||||
|
||||
- runtime: overflow second half of tasks when local queue is filled instead of first half ([#8029])
|
||||
|
||||
## Documented
|
||||
|
||||
- docs: fix typo in `oneshot::Sender::send` docs ([#8026])
|
||||
- docs: hide #[tokio::main] attribute in the docs of `sync::watch` ([#8035])
|
||||
- net: add docs on `ConnectionRefused` errors with UDP sockets ([#7870])
|
||||
|
||||
[#7757]: https://github.com/tokio-rs/tokio/pull/7757
|
||||
[#7870]: https://github.com/tokio-rs/tokio/pull/7870
|
||||
[#7907]: https://github.com/tokio-rs/tokio/pull/7907
|
||||
[#7992]: https://github.com/tokio-rs/tokio/pull/7992
|
||||
[#8010]: https://github.com/tokio-rs/tokio/pull/8010
|
||||
[#8025]: https://github.com/tokio-rs/tokio/pull/8025
|
||||
[#8026]: https://github.com/tokio-rs/tokio/pull/8026
|
||||
[#8028]: https://github.com/tokio-rs/tokio/pull/8028
|
||||
[#8029]: https://github.com/tokio-rs/tokio/pull/8029
|
||||
[#8030]: https://github.com/tokio-rs/tokio/pull/8030
|
||||
[#8035]: https://github.com/tokio-rs/tokio/pull/8035
|
||||
[#8040]: https://github.com/tokio-rs/tokio/pull/8040
|
||||
|
||||
# 1.51.4 (July 16th, 2026)
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: don't skip the driver when `before_park` schedules work ([#8222])
|
||||
|
||||
[#8222]: https://github.com/tokio-rs/tokio/pull/8222
|
||||
|
||||
# 1.51.3 (May 8th, 2026)
|
||||
|
||||
### Fixed
|
||||
|
||||
* sync: fix underflow in mpsc channel `len()` ([#8062])
|
||||
* sync: notify receivers in mpsc `OwnedPermit::release()` method ([#8075])
|
||||
* sync: require that an `RwLock` has `max_readers != 0` ([#8076])
|
||||
* sync: return `Empty` from `try_recv()` when mpsc is closed with outstanding permits ([#8074])
|
||||
|
||||
# 1.51.2 (May 4th, 2026)
|
||||
|
||||
This release reverts the LIFO slot stealing change introduced in 1.51.0
|
||||
([#7431]), due to [its performance impact][#8065]. ([#8100])
|
||||
|
||||
[#8065]: https://github.com/tokio-rs/tokio/pull/8065
|
||||
[#8100]: https://github.com/tokio-rs/tokio/pull/8100
|
||||
|
||||
# 1.51.1 (April 8th, 2026)
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: fix semaphore reopens after forget ([#8021])
|
||||
- net: surface errors from `SO_ERROR` on `recv` for UDP sockets on Linux ([#8001])
|
||||
|
||||
### Fixed (unstable)
|
||||
|
||||
- metrics: fix `worker_local_schedule_count` test ([#8008])
|
||||
- rt: do not leak fd when cancelling io\_uring open operation ([#7983])
|
||||
|
||||
[#7983]: https://github.com/tokio-rs/tokio/pull/7983
|
||||
[#8001]: https://github.com/tokio-rs/tokio/pull/8001
|
||||
[#8008]: https://github.com/tokio-rs/tokio/pull/8008
|
||||
[#8021]: https://github.com/tokio-rs/tokio/pull/8021
|
||||
|
||||
# 1.51.0 (April 3rd, 2026)
|
||||
|
||||
### Added
|
||||
|
||||
- net: implement `get_peer_cred` on Hurd ([#7989])
|
||||
- runtime: add `tokio::runtime::worker_index()` ([#7921])
|
||||
- runtime: add runtime name ([#7924])
|
||||
- runtime: stabilize `LocalRuntime` ([#7557])
|
||||
- wasm: add wasm32-wasip2 networking support ([#7933])
|
||||
|
||||
### Changed
|
||||
|
||||
- runtime: steal tasks from the LIFO slot ([#7431])
|
||||
|
||||
### Fixed
|
||||
|
||||
- docs: do not show "Available on non-loom only." doc label ([#7977])
|
||||
- macros: improve overall macro hygiene ([#7997])
|
||||
- sync: fix `notify_waiters` priority in `Notify` ([#7996])
|
||||
- sync: fix panic in `Chan::recv_many` when called with non-empty vector on closed channel ([#7991])
|
||||
|
||||
[#7431]: https://github.com/tokio-rs/tokio/pull/7431
|
||||
[#7557]: https://github.com/tokio-rs/tokio/pull/7557
|
||||
[#7921]: https://github.com/tokio-rs/tokio/pull/7921
|
||||
[#7924]: https://github.com/tokio-rs/tokio/pull/7924
|
||||
[#7933]: https://github.com/tokio-rs/tokio/pull/7933
|
||||
[#7977]: https://github.com/tokio-rs/tokio/pull/7977
|
||||
[#7989]: https://github.com/tokio-rs/tokio/pull/7989
|
||||
[#7991]: https://github.com/tokio-rs/tokio/pull/7991
|
||||
[#7996]: https://github.com/tokio-rs/tokio/pull/7996
|
||||
[#7997]: https://github.com/tokio-rs/tokio/pull/7997
|
||||
|
||||
# 1.50.0 (Mar 3rd, 2026)
|
||||
|
||||
### Added
|
||||
@@ -273,7 +425,29 @@ The MSRV is increased to 1.71.
|
||||
[#7672]: https://github.com/tokio-rs/tokio/pull/7672
|
||||
[#7675]: https://github.com/tokio-rs/tokio/pull/7675
|
||||
|
||||
# 1.47.3 (Januar 3rd, 2026)
|
||||
# 1.47.5 (May 7th, 2026)
|
||||
|
||||
### Fixed
|
||||
|
||||
* sync: fix underflow in mpsc channel `len()` ([#8062])
|
||||
* sync: notify receivers in mpsc `OwnedPermit::release()` method ([#8075])
|
||||
* sync: require that an `RwLock` has `max_readers != 0` ([#8076])
|
||||
* sync: return `Empty` from `try_recv()` when mpsc is closed with outstanding permits ([#8074])
|
||||
|
||||
[#8062]: https://github.com/tokio-rs/tokio/pull/8062
|
||||
[#8074]: https://github.com/tokio-rs/tokio/pull/8074
|
||||
[#8075]: https://github.com/tokio-rs/tokio/pull/8075
|
||||
[#8076]: https://github.com/tokio-rs/tokio/pull/8076
|
||||
|
||||
# 1.47.4 (April 2nd, 2026)
|
||||
|
||||
### Fixed
|
||||
|
||||
* sync: fix panic in `Chan::recv_many` when called with non-empty vector on closed channel ([#7991])
|
||||
|
||||
[#7991]: https://github.com/tokio-rs/tokio/pull/7991
|
||||
|
||||
# 1.47.3 (January 3rd, 2026)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
+14
-8
@@ -6,7 +6,7 @@ name = "tokio"
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.x.y" git tag.
|
||||
version = "1.50.0"
|
||||
version = "1.52.4"
|
||||
edition = "2021"
|
||||
rust-version = "1.71"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -90,17 +90,17 @@ io-uring = ["dep:io-uring", "libc", "mio/os-poll", "mio/os-ext", "dep:slab"]
|
||||
taskdump = ["dep:backtrace"]
|
||||
|
||||
[dependencies]
|
||||
tokio-macros = { version = "~2.6.0", optional = true }
|
||||
tokio-macros = { version = "~2.7.0", optional = true }
|
||||
|
||||
pin-project-lite = "0.2.11"
|
||||
|
||||
# Everything else is optional...
|
||||
bytes = { version = "1.2.1", optional = true }
|
||||
mio = { version = "1.0.1", optional = true, default-features = false }
|
||||
mio = { version = "1.2.0", optional = true, default-features = false }
|
||||
parking_lot = { version = "0.12.0", optional = true }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
socket2 = { version = "0.6.0", optional = true, features = ["all"] }
|
||||
[target.'cfg(any(not(target_family = "wasm"), all(target_os = "wasi", not(target_env = "p1"))))'.dependencies]
|
||||
socket2 = { version = "0.6.3", optional = true, features = ["all"] }
|
||||
|
||||
# Currently unstable. The API exposed by these features may be broken at any time.
|
||||
# Requires `--cfg tokio_unstable` to enable.
|
||||
@@ -112,17 +112,23 @@ tracing = { version = "0.1.29", default-features = false, features = ["std"], op
|
||||
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dependencies]
|
||||
io-uring = { version = "0.7.11", default-features = false, optional = true }
|
||||
libc = { version = "0.2.168", optional = true }
|
||||
mio = { version = "1.0.1", default-features = false, features = ["os-poll", "os-ext"], optional = true }
|
||||
mio = { version = "1.2.0", default-features = false, features = ["os-poll", "os-ext"], optional = true }
|
||||
slab = { version = "0.4.9", optional = true }
|
||||
backtrace = { version = "0.3.58", optional = true }
|
||||
|
||||
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
|
||||
backtrace = "0.3.58"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = { version = "0.2.168", optional = true }
|
||||
signal-hook-registry = { version = "1.1.1", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "wasi")'.dependencies]
|
||||
libc = { version = "0.2.168", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
libc = { version = "0.2.168" }
|
||||
nix = { version = "0.29.0", default-features = false, features = ["aio", "fs", "socket"] }
|
||||
nix = { version = "0.31.0", default-features = false, features = ["aio", "fs", "socket"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.61"
|
||||
@@ -157,7 +163,7 @@ rand = "0.9"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
|
||||
mio-aio = { version = "1", features = ["tokio"] }
|
||||
mio-aio = { version = "2", features = ["tokio"] }
|
||||
|
||||
[target.'cfg(loom)'.dev-dependencies]
|
||||
loom = { version = "0.7", features = ["futures", "checkpoint"] }
|
||||
|
||||
+9
-4
@@ -1,3 +1,7 @@
|
||||
*[TokioConf 2026 program and tickets are now available!](https://tokioconf.com)*
|
||||
|
||||
---
|
||||
|
||||
# Tokio
|
||||
|
||||
A runtime for writing reliable, asynchronous, and slim applications with
|
||||
@@ -56,7 +60,7 @@ Make sure you enable the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
tokio = { version = "1.52.4", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -217,18 +221,18 @@ warrants a patch release with a fix for the bug, it will be backported and
|
||||
released as a new patch release for each LTS minor version. Our current LTS
|
||||
releases are:
|
||||
|
||||
* `1.43.x` - LTS release until March 2026. (MSRV 1.70)
|
||||
* `1.47.x` - LTS release until September 2026. (MSRV 1.70)
|
||||
* `1.51.x` - LTS release until March 2027. (MSRV 1.71)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
use an LTS release.
|
||||
|
||||
To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.43.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.47.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.43", features = [...] }
|
||||
tokio = { version = "~1.47", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -241,6 +245,7 @@ tokio = { version = "~1.43", features = [...] }
|
||||
* `1.32.x` - LTS release until September 2024.
|
||||
* `1.36.x` - LTS release until March 2025.
|
||||
* `1.38.x` - LTS release until July 2025.
|
||||
* `1.43.x` - LTS release until March 2026.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+125
-7
@@ -30,6 +30,11 @@ use crate::blocking::{spawn_blocking, spawn_mandatory_blocking};
|
||||
#[cfg(not(test))]
|
||||
use std::fs::File as StdFile;
|
||||
|
||||
cfg_io_uring! {
|
||||
#[cfg(not(test))]
|
||||
use crate::spawn;
|
||||
}
|
||||
|
||||
/// A reference to an open file on the filesystem.
|
||||
///
|
||||
/// This is a specialized version of [`std::fs::File`] for usage from the
|
||||
@@ -613,13 +618,7 @@ impl AsyncRead for File {
|
||||
let std = me.std.clone();
|
||||
|
||||
let max_buf_size = cmp::min(dst.remaining(), me.max_buf_size);
|
||||
inner.state = State::Busy(spawn_blocking(move || {
|
||||
// SAFETY: the `Read` implementation of `std` does not
|
||||
// read from the buffer it is borrowing and correctly
|
||||
// reports the length of the data written into the buffer.
|
||||
let res = unsafe { buf.read_from(&mut &*std, max_buf_size) };
|
||||
(Operation::Read(res), buf)
|
||||
}));
|
||||
inner.state = State::Busy(Inner::poll_read_inner(std, buf, max_buf_size)?);
|
||||
}
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, mut buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
@@ -952,6 +951,125 @@ cfg_windows! {
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn poll_read_inner(
|
||||
std: Arc<StdFile>,
|
||||
buf: Buf,
|
||||
max_buf_size: usize,
|
||||
) -> io::Result<JoinHandle<(Operation, Buf)>> {
|
||||
// Unit tests use `MockFile` and the mock `spawn_blocking` infrastructure,
|
||||
// which can't drive real io_uring operations. The io_uring read path
|
||||
// is tested through integration tests in `tests/fs_uring_file_read.rs`.
|
||||
#[cfg(all(
|
||||
not(test),
|
||||
tokio_unstable,
|
||||
feature = "io-uring",
|
||||
feature = "rt",
|
||||
feature = "fs",
|
||||
target_os = "linux",
|
||||
))]
|
||||
{
|
||||
if let Ok(handle) = crate::runtime::Handle::try_current() {
|
||||
let driver_handle = handle.inner.driver().io();
|
||||
|
||||
if driver_handle.is_uring_ready(io_uring::opcode::Read::CODE) {
|
||||
// Fast path: uring already initialized and Read supported.
|
||||
let fd: crate::io::uring::utils::ArcFd = std;
|
||||
return Ok(spawn(Self::uring_read(fd, buf, max_buf_size)));
|
||||
}
|
||||
|
||||
if !driver_handle.is_uring_probed() {
|
||||
// Not yet probed: lazy init inside an async task so
|
||||
// `File::from_std()` can still benefit from io-uring.
|
||||
return Ok(spawn(Self::lazy_init_read(std, buf, max_buf_size)));
|
||||
}
|
||||
// Probed but unsupported: fall through to spawn_blocking.
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: spawn_blocking
|
||||
let join = Self::spawn_blocking_read(buf, std, max_buf_size);
|
||||
Ok(join)
|
||||
}
|
||||
|
||||
/// Perform an io-uring read with interrupt retry.
|
||||
#[cfg(all(
|
||||
not(test),
|
||||
tokio_unstable,
|
||||
feature = "io-uring",
|
||||
feature = "rt",
|
||||
feature = "fs",
|
||||
target_os = "linux",
|
||||
))]
|
||||
async fn uring_read(
|
||||
mut fd: crate::io::uring::utils::ArcFd,
|
||||
mut buf: Buf,
|
||||
max_buf_size: usize,
|
||||
) -> (Operation, Buf) {
|
||||
use crate::runtime::driver::op::Op;
|
||||
|
||||
loop {
|
||||
let (res, r_fd, r_buf) =
|
||||
// u64::MAX to use and advance the file position
|
||||
Op::read_at(fd, buf, max_buf_size, u64::MAX).await;
|
||||
match res {
|
||||
Err(e) if e.kind() == io::ErrorKind::Interrupted => {
|
||||
buf = r_buf;
|
||||
fd = r_fd;
|
||||
continue;
|
||||
}
|
||||
Err(e) => break (Operation::Read(Err(e)), r_buf),
|
||||
Ok(n) => break (Operation::Read(Ok(n as usize)), r_buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt lazy io-uring initialization, then read via uring or fall back
|
||||
/// to a blocking read. Covers the `File::from_std()` path where
|
||||
/// `check_and_init()` hasn't been called yet.
|
||||
#[cfg(all(
|
||||
not(test),
|
||||
tokio_unstable,
|
||||
feature = "io-uring",
|
||||
feature = "rt",
|
||||
feature = "fs",
|
||||
target_os = "linux",
|
||||
))]
|
||||
async fn lazy_init_read(std: Arc<StdFile>, buf: Buf, max_buf_size: usize) -> (Operation, Buf) {
|
||||
let handle = crate::runtime::Handle::current();
|
||||
let driver_handle = handle.inner.driver().io();
|
||||
if driver_handle
|
||||
.check_and_init(io_uring::opcode::Read::CODE)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let fd: crate::io::uring::utils::ArcFd = std;
|
||||
Self::uring_read(fd, buf, max_buf_size).await
|
||||
} else {
|
||||
match Self::spawn_blocking_read(buf, std, max_buf_size).await {
|
||||
Ok(result) => result,
|
||||
Err(e) => (
|
||||
Operation::Read(Err(io::Error::new(io::ErrorKind::Other, e))),
|
||||
Buf::with_capacity(0),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_blocking_read(
|
||||
buf: Buf,
|
||||
std: Arc<StdFile>,
|
||||
max_buf_size: usize,
|
||||
) -> JoinHandle<(Operation, Buf)> {
|
||||
spawn_blocking(move || {
|
||||
let mut buf = buf;
|
||||
// SAFETY: the `Read` implementation of `std` does not
|
||||
// read from the buffer it is borrowing and correctly
|
||||
// reports the length of the data written into the buffer.
|
||||
let res = unsafe { buf.read_from(&mut &*std, max_buf_size) };
|
||||
(Operation::Read(res), buf)
|
||||
})
|
||||
}
|
||||
|
||||
async fn complete_inflight(&mut self) {
|
||||
use std::future::poll_fn;
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ async fn op_read(
|
||||
read_len: u32,
|
||||
) -> io::Result<(OwnedFd, Vec<u8>, bool)> {
|
||||
loop {
|
||||
let (res, r_fd, r_buf) = Op::read(fd, buf, read_len, *offset).await;
|
||||
let (res, r_fd, r_buf) = Op::read_at(fd, buf, read_len as usize, *offset).await;
|
||||
|
||||
match res {
|
||||
Err(e) if e.kind() == ErrorKind::Interrupted => {
|
||||
|
||||
@@ -277,6 +277,39 @@ impl Buf {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_io_uring! {
|
||||
impl Buf {
|
||||
/// Prepare the internal buffer for an io-uring read operation.
|
||||
///
|
||||
/// Returns a pointer to the spare capacity and the length available
|
||||
/// for the kernel to write into.
|
||||
pub(crate) fn prepare_uring_read(&mut self, max_buf_size: usize) -> (*mut u8, u32) {
|
||||
assert!(self.is_empty());
|
||||
self.buf.reserve(max_buf_size);
|
||||
let spare = self.buf.spare_capacity_mut();
|
||||
let len = std::cmp::min(spare.len(), max_buf_size);
|
||||
let ptr = spare.as_mut_ptr().cast::<u8>();
|
||||
(ptr, len as u32)
|
||||
}
|
||||
|
||||
/// Complete an io-uring read operation.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that the kernel wrote exactly `n` bytes
|
||||
/// into the buffer that was returned by `prepare_uring_read`.
|
||||
pub(crate) unsafe fn complete_uring_read(&mut self, n: usize) {
|
||||
assert_eq!(self.pos, 0);
|
||||
// SAFETY: `prepare_uring_read` handed out a pointer to
|
||||
// `self.buf.spare_capacity_mut()` after asserting it's empty.
|
||||
// The caller guarantees the kernel initialised exactly `n` bytes
|
||||
// starting at that pointer, so bytes `0..n` are now initialised and
|
||||
// it is sound to set the Vec length to `n`.
|
||||
unsafe { self.buf.set_len(n) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_fs! {
|
||||
impl Buf {
|
||||
pub(crate) fn discard_read(&mut self) -> i64 {
|
||||
|
||||
@@ -9,17 +9,36 @@ use mio::Token;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::prelude::RawFd;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::task::{ready, Context, Poll};
|
||||
|
||||
/// Like [`mio::event::Source`], but for POSIX AIO only.
|
||||
///
|
||||
/// Tokio's consumer must pass an implementor of this trait to create a
|
||||
/// [`Aio`] object.
|
||||
/// [`Aio`] object. Implementors must implement at least one of [`AioSource::register`] and
|
||||
/// [`AioSource::register_borrowed`].
|
||||
pub trait AioSource {
|
||||
/// Registers this AIO event source with Tokio's reactor.
|
||||
fn register(&mut self, kq: RawFd, token: usize);
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// It's memory-safe, but not I/O safe. If the file referenced by `kq` gets dropped, then this
|
||||
/// source may end up notifying the wrong file.
|
||||
#[deprecated(since = "1.52.0", note = "use register_borrowed instead")]
|
||||
fn register(&mut self, _kq: RawFd, _token: usize) {
|
||||
// This default implementation exists so new AioSource implementors that implement the
|
||||
// register_borrowed method can compile without the need to implement register.
|
||||
unimplemented!("Use AioSource::register_borrowed instead")
|
||||
}
|
||||
|
||||
/// Registers this AIO event source with Tokio's reactor.
|
||||
fn register_borrowed(&mut self, kq: BorrowedFd<'_>, token: usize) {
|
||||
// This default implementation serves to provide backwards compatibility with AioSource
|
||||
// implementors written before 1.52.0 that only implemented the unsafe `register` method.
|
||||
#[allow(deprecated)]
|
||||
self.register(kq.as_raw_fd(), token)
|
||||
}
|
||||
|
||||
/// Deregisters this AIO event source with Tokio's reactor.
|
||||
fn deregister(&mut self);
|
||||
@@ -37,7 +56,8 @@ impl<T: AioSource> Source for MioSource<T> {
|
||||
interests: mio::Interest,
|
||||
) -> io::Result<()> {
|
||||
assert!(interests.is_aio() || interests.is_lio());
|
||||
self.0.register(registry.as_raw_fd(), usize::from(token));
|
||||
self.0
|
||||
.register_borrowed(registry.as_fd(), usize::from(token));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -53,7 +73,8 @@ impl<T: AioSource> Source for MioSource<T> {
|
||||
interests: mio::Interest,
|
||||
) -> io::Result<()> {
|
||||
assert!(interests.is_aio() || interests.is_lio());
|
||||
self.0.register(registry.as_raw_fd(), usize::from(token));
|
||||
self.0
|
||||
.register_borrowed(registry.as_fd(), usize::from(token));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+83
-30
@@ -1,27 +1,74 @@
|
||||
use crate::io::blocking::Buf;
|
||||
use crate::io::uring::utils::{ArcFd, UringFd};
|
||||
use crate::runtime::driver::op::{CancelData, Cancellable, Completable, CqeResult, Op};
|
||||
|
||||
use io_uring::{opcode, types};
|
||||
use std::fmt;
|
||||
use std::io::{self, Error};
|
||||
use std::os::fd::{AsRawFd, OwnedFd};
|
||||
use std::os::fd::OwnedFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Read {
|
||||
fd: OwnedFd,
|
||||
buf: Vec<u8>,
|
||||
/// Trait for buffers that can be used with io-uring read operations.
|
||||
pub(crate) trait ReadBuffer: Send + 'static {
|
||||
/// Prepare the buffer for a read operation.
|
||||
/// Returns a pointer and length for the io-uring SQE.
|
||||
fn uring_read_prepare(&mut self, max_len: usize) -> (*mut u8, u32);
|
||||
|
||||
/// Complete a read of `n` bytes.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the kernel wrote exactly `n` bytes
|
||||
/// into the buffer at the pointer returned by `uring_read_prepare`.
|
||||
unsafe fn uring_read_complete(&mut self, n: u32);
|
||||
}
|
||||
|
||||
impl Completable for Read {
|
||||
type Output = (io::Result<u32>, OwnedFd, Vec<u8>);
|
||||
impl ReadBuffer for Vec<u8> {
|
||||
fn uring_read_prepare(&mut self, max_len: usize) -> (*mut u8, u32) {
|
||||
assert!(self.spare_capacity_mut().len() >= max_len);
|
||||
let ptr = self.spare_capacity_mut().as_mut_ptr().cast();
|
||||
(ptr, max_len as u32)
|
||||
}
|
||||
|
||||
unsafe fn uring_read_complete(&mut self, n: u32) {
|
||||
// SAFETY: the kernel wrote `n` bytes into spare capacity starting
|
||||
// at the old self.len(), so self.len() + n bytes are now initialized.
|
||||
unsafe { self.set_len(self.len() + n as usize) };
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadBuffer for Buf {
|
||||
fn uring_read_prepare(&mut self, max_len: usize) -> (*mut u8, u32) {
|
||||
self.prepare_uring_read(max_len)
|
||||
}
|
||||
|
||||
unsafe fn uring_read_complete(&mut self, n: u32) {
|
||||
// SAFETY: caller guarantees kernel wrote exactly n bytes.
|
||||
unsafe { self.complete_uring_read(n as usize) };
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Read<B, F = ArcFd> {
|
||||
fd: F,
|
||||
buf: B,
|
||||
}
|
||||
|
||||
impl<B: fmt::Debug, F> fmt::Debug for Read<B, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Read")
|
||||
.field("buf", &self.buf)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: ReadBuffer, F: UringFd> Completable for Read<B, F> {
|
||||
type Output = (io::Result<u32>, F, B);
|
||||
|
||||
fn complete(self, cqe: CqeResult) -> Self::Output {
|
||||
let mut buf = self.buf;
|
||||
|
||||
if let Ok(len) = cqe.result {
|
||||
let new_len = buf.len() + len as usize;
|
||||
// SAFETY: Kernel read len bytes
|
||||
unsafe { buf.set_len(new_len) };
|
||||
// SAFETY: kernel wrote exactly `len` bytes into the prepared buffer.
|
||||
unsafe { buf.uring_read_complete(len) };
|
||||
}
|
||||
|
||||
(cqe.result, self.fd, buf)
|
||||
}
|
||||
|
||||
@@ -30,32 +77,38 @@ impl Completable for Read {
|
||||
}
|
||||
}
|
||||
|
||||
impl Cancellable for Read {
|
||||
impl Cancellable for Read<Vec<u8>, OwnedFd> {
|
||||
fn cancel(self) -> CancelData {
|
||||
CancelData::Read(self)
|
||||
CancelData::ReadVec(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Op<Read> {
|
||||
// Submit a request to read a FD at given length and offset into a
|
||||
// dynamic buffer with uninitialized memory. The read happens on uninitialized
|
||||
// buffer and no overwriting happens.
|
||||
impl Cancellable for Read<Buf, ArcFd> {
|
||||
fn cancel(self) -> CancelData {
|
||||
CancelData::ReadBuf(self)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: The `len` of the amount to be read and the buffer that is passed
|
||||
// should have capacity > len.
|
||||
//
|
||||
// If `len` read is higher than vector capacity then setting its length by
|
||||
// the caller in terms of size_read can be unsound.
|
||||
pub(crate) fn read(fd: OwnedFd, mut buf: Vec<u8>, len: u32, offset: u64) -> Self {
|
||||
// don't overwrite on already written part
|
||||
assert!(buf.spare_capacity_mut().len() >= len as usize);
|
||||
let buf_mut_ptr = buf.spare_capacity_mut().as_mut_ptr().cast();
|
||||
impl<B, F> Op<Read<B, F>>
|
||||
where
|
||||
B: ReadBuffer + fmt::Debug,
|
||||
F: UringFd,
|
||||
Read<B, F>: Cancellable,
|
||||
{
|
||||
/// Submit a read operation via io-uring.
|
||||
///
|
||||
/// `max_len` is the maximum number of bytes to read.
|
||||
/// `offset` is the file offset; use `u64::MAX` for the current cursor.
|
||||
pub(crate) fn read_at(fd: F, mut buf: B, max_len: usize, offset: u64) -> Self {
|
||||
let (ptr, len) = buf.uring_read_prepare(max_len);
|
||||
|
||||
let read_op = opcode::Read::new(types::Fd(fd.as_raw_fd()), buf_mut_ptr, len)
|
||||
let sqe = opcode::Read::new(types::Fd(UringFd::as_raw_fd(&fd)), ptr, len)
|
||||
.offset(offset)
|
||||
.build();
|
||||
|
||||
// SAFETY: Parameters are valid for the entire duration of the operation
|
||||
unsafe { Op::new(read_op, Read { fd, buf }) }
|
||||
// SAFETY: `fd` and `buf`, which owns the heap buffer, are moved into `Read`,
|
||||
// which is held by the `Op` for the entire duration of the io-uring operation.
|
||||
// The buffer pointer remains valid because Vec heap data doesn't move.
|
||||
unsafe { Op::new(sqe, Read { fd, buf }) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::sync::Arc;
|
||||
use std::{ffi::CString, io, path::Path};
|
||||
|
||||
pub(crate) type ArcFd = Arc<dyn AsRawFd + Send + Sync + 'static>;
|
||||
|
||||
/// Raw file descriptor trait for io-uring operations.
|
||||
///
|
||||
/// `Arc<dyn AsRawFd>` does not satisfy `AsRawFd` because the blanket impl
|
||||
/// for `Arc<T>` requires `T: Sized`. This trait bridges that gap so both
|
||||
/// `OwnedFd` and `ArcFd` can be used generically with `Op::read_at`.
|
||||
pub(crate) trait UringFd: Send + Sync + 'static {
|
||||
fn as_raw_fd(&self) -> RawFd;
|
||||
}
|
||||
|
||||
impl UringFd for OwnedFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
AsRawFd::as_raw_fd(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl UringFd for ArcFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
(**self).as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cstr(p: &Path) -> io::Result<CString> {
|
||||
Ok(CString::new(p.as_os_str().as_bytes())?)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
// loom is an internal implementation detail.
|
||||
// Do not show "Available on non-loom only" label
|
||||
#![cfg_attr(docsrs, doc(auto_cfg(hide(loom))))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, allow(unused_attributes))]
|
||||
#![cfg_attr(loom, allow(dead_code, unreachable_pub))]
|
||||
|
||||
@@ -58,13 +58,6 @@ pub(crate) mod sync {
|
||||
}
|
||||
|
||||
pub(crate) use loom::sync::*;
|
||||
|
||||
pub(crate) mod atomic {
|
||||
pub(crate) use loom::sync::atomic::*;
|
||||
|
||||
// TODO: implement a loom version
|
||||
pub(crate) type StaticAtomicU64 = std::sync::atomic::AtomicU64;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod rand {
|
||||
|
||||
@@ -16,4 +16,4 @@ cfg_not_has_atomic_u64! {
|
||||
mod imp;
|
||||
}
|
||||
|
||||
pub(crate) use imp::{AtomicU64, StaticAtomicU64};
|
||||
pub(crate) use imp::AtomicU64;
|
||||
|
||||
@@ -71,7 +71,7 @@ pub(crate) mod sync {
|
||||
pub(crate) mod atomic {
|
||||
pub(crate) use crate::loom::std::atomic_u16::AtomicU16;
|
||||
pub(crate) use crate::loom::std::atomic_u32::AtomicU32;
|
||||
pub(crate) use crate::loom::std::atomic_u64::{AtomicU64, StaticAtomicU64};
|
||||
pub(crate) use crate::loom::std::atomic_u64::AtomicU64;
|
||||
pub(crate) use crate::loom::std::atomic_usize::AtomicUsize;
|
||||
|
||||
pub(crate) use std::sync::atomic::{fence, AtomicBool, AtomicPtr, AtomicU8, Ordering};
|
||||
|
||||
@@ -58,6 +58,18 @@ macro_rules! cfg_unix {
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables Unix-specific code, including WASI.
|
||||
/// Use this macro instead of `cfg(any(unix, target_os = "wasi"))` to generate docs properly.
|
||||
macro_rules! cfg_unix_or_wasi {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(any(all(doc, docsrs), unix, target_os = "wasi"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(unix, target_os = "wasi"))))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables unstable Windows-specific code.
|
||||
/// Use this macro instead of `cfg(windows)` to generate docs properly.
|
||||
macro_rules! cfg_unstable_windows {
|
||||
@@ -508,6 +520,20 @@ macro_rules! cfg_taskdump {
|
||||
target_arch = "x86_64"
|
||||
)
|
||||
))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(all(
|
||||
tokio_unstable,
|
||||
feature = "taskdump",
|
||||
feature = "rt",
|
||||
target_os = "linux",
|
||||
any(
|
||||
target_arch = "aarch64",
|
||||
target_arch = "x86",
|
||||
target_arch = "x86_64"
|
||||
)
|
||||
)))
|
||||
)]
|
||||
$item
|
||||
)*
|
||||
};
|
||||
@@ -674,6 +700,15 @@ macro_rules! cfg_not_wasi {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_not_wasip1 {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(not(all(target_os = "wasi", target_env = "p1")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_is_wasm_not_wasi {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
|
||||
@@ -659,7 +659,7 @@ doc! {macro_rules! select {
|
||||
$crate::macros::support::poll_fn(|cx| {
|
||||
// Return `Pending` when the task budget is depleted since budget-aware futures
|
||||
// are going to yield anyway and other futures will not cooperate.
|
||||
::std::task::ready!($crate::macros::support::poll_budget_available(cx));
|
||||
$crate::macros::support::ready!($crate::macros::support::poll_budget_available(cx));
|
||||
|
||||
// Track if any branch returns pending. If no branch completes
|
||||
// **or** returns pending, this implies that all branches are
|
||||
@@ -699,7 +699,7 @@ doc! {macro_rules! select {
|
||||
|
||||
// Safety: future is stored on the stack above
|
||||
// and never moved.
|
||||
let mut fut = unsafe { Pin::new_unchecked(fut) };
|
||||
let mut fut = unsafe { $crate::macros::support::Pin::new_unchecked(fut) };
|
||||
|
||||
// Try polling it
|
||||
let out = match $crate::macros::support::Future::poll(fut, cx) {
|
||||
|
||||
@@ -29,4 +29,5 @@ cfg_macros! {
|
||||
|
||||
pub use std::future::{Future, IntoFuture};
|
||||
pub use std::pin::Pin;
|
||||
pub use std::task::{Context, Poll};
|
||||
pub use std::result::Result;
|
||||
pub use std::task::{ready, Context, Poll};
|
||||
|
||||
@@ -12,13 +12,13 @@ cfg_trace! {
|
||||
macro_rules! trace_poll_op {
|
||||
($name:expr, $poll:expr $(,)*) => {
|
||||
match $poll {
|
||||
std::task::Poll::Ready(t) => {
|
||||
$crate::macros::support::Poll::Ready(t) => {
|
||||
trace_op!($name, true);
|
||||
std::task::Poll::Ready(t)
|
||||
$crate::macros::support::Poll::Ready(t)
|
||||
}
|
||||
std::task::Poll::Pending => {
|
||||
$crate::macros::support::Poll::Pending => {
|
||||
trace_op!($name, false);
|
||||
return std::task::Poll::Pending;
|
||||
return $crate::macros::support::Poll::Pending;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -229,7 +229,7 @@ doc! {macro_rules! try_join {
|
||||
if $crate::macros::support::Future::poll(fut.as_mut(), cx).is_pending() {
|
||||
is_pending = true;
|
||||
} else if fut.as_mut().output_mut().expect("expected completed future").is_err() {
|
||||
return $crate::macros::support::Poll::Ready(Err(fut.take_output().expect("expected completed future").err().unwrap()))
|
||||
return $crate::macros::support::Poll::Ready($crate::macros::support::Result::Err(fut.take_output().expect("expected completed future").err().unwrap()))
|
||||
}
|
||||
} else {
|
||||
// Future skipped, one less future to skip in the next iteration
|
||||
@@ -241,7 +241,7 @@ doc! {macro_rules! try_join {
|
||||
if is_pending {
|
||||
$crate::macros::support::Poll::Pending
|
||||
} else {
|
||||
$crate::macros::support::Poll::Ready(Ok(($({
|
||||
$crate::macros::support::Poll::Ready($crate::macros::support::Result::Ok(($({
|
||||
// Extract the future for this branch from the tuple.
|
||||
let ( $($skip,)* fut, .. ) = &mut futures;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
//! [`AsyncFd`]: crate::io::unix::AsyncFd
|
||||
|
||||
mod addr;
|
||||
cfg_not_wasi! {
|
||||
cfg_not_wasip1! {
|
||||
#[cfg(feature = "net")]
|
||||
pub(crate) use addr::to_socket_addrs;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ cfg_net! {
|
||||
pub mod tcp;
|
||||
pub use tcp::listener::TcpListener;
|
||||
pub use tcp::stream::TcpStream;
|
||||
cfg_not_wasi! {
|
||||
cfg_not_wasip1! {
|
||||
pub use tcp::socket::TcpSocket;
|
||||
|
||||
mod udp;
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::io::{Interest, PollEvented};
|
||||
use crate::net::tcp::TcpStream;
|
||||
use crate::util::check_socket_for_blocking;
|
||||
|
||||
cfg_not_wasi! {
|
||||
cfg_not_wasip1! {
|
||||
use crate::net::{to_socket_addrs, ToSocketAddrs};
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ cfg_net! {
|
||||
}
|
||||
|
||||
impl TcpListener {
|
||||
cfg_not_wasi! {
|
||||
cfg_not_wasip1! {
|
||||
/// Creates a new `TcpListener`, which will be bound to the specified address.
|
||||
///
|
||||
/// The returned listener is ready for accepting connections.
|
||||
@@ -292,7 +292,7 @@ impl TcpListener {
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
{
|
||||
use std::os::wasi::io::{FromRawFd, IntoRawFd};
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
@@ -300,7 +300,7 @@ impl TcpListener {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_not_wasi! {
|
||||
cfg_not_wasip1! {
|
||||
pub(crate) fn new(listener: mio::net::TcpListener) -> io::Result<TcpListener> {
|
||||
let io = PollEvented::new(listener)?;
|
||||
Ok(TcpListener { io })
|
||||
@@ -427,7 +427,7 @@ cfg_unstable! {
|
||||
#[cfg(target_os = "wasi")]
|
||||
mod sys {
|
||||
use super::TcpListener;
|
||||
use std::os::wasi::prelude::*;
|
||||
use std::os::fd::{RawFd, BorrowedFd, AsRawFd, AsFd};
|
||||
|
||||
impl AsRawFd for TcpListener {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
pub(crate) mod listener;
|
||||
|
||||
cfg_not_wasi! {
|
||||
cfg_not_wasip1! {
|
||||
pub(crate) mod socket;
|
||||
}
|
||||
|
||||
|
||||
+31
-21
@@ -4,8 +4,8 @@ use std::fmt;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::time::Duration;
|
||||
|
||||
cfg_windows! {
|
||||
@@ -170,7 +170,8 @@ impl TcpSocket {
|
||||
target_os = "illumos",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
target_os = "openbsd",
|
||||
target_os = "wasi",
|
||||
))]
|
||||
let ty = ty.nonblocking();
|
||||
let inner = socket2::Socket::new(domain, ty, Some(socket2::Protocol::TCP))?;
|
||||
@@ -182,7 +183,8 @@ impl TcpSocket {
|
||||
target_os = "illumos",
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
target_os = "openbsd",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
inner.set_nonblocking(true)?;
|
||||
Ok(TcpSocket { inner })
|
||||
@@ -597,7 +599,8 @@ impl TcpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
@@ -606,7 +609,8 @@ impl TcpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
))))
|
||||
)]
|
||||
pub fn tos_v4(&self) -> io::Result<u32> {
|
||||
@@ -625,7 +629,8 @@ impl TcpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
@@ -634,7 +639,8 @@ impl TcpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
))))
|
||||
)]
|
||||
pub fn tos(&self) -> io::Result<u32> {
|
||||
@@ -657,7 +663,8 @@ impl TcpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
@@ -666,7 +673,8 @@ impl TcpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
))))
|
||||
)]
|
||||
pub fn set_tos_v4(&self, tos: u32) -> io::Result<()> {
|
||||
@@ -685,7 +693,8 @@ impl TcpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
@@ -694,7 +703,8 @@ impl TcpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
))))
|
||||
)]
|
||||
pub fn set_tos(&self, tos: u32) -> io::Result<()> {
|
||||
@@ -826,7 +836,7 @@ impl TcpSocket {
|
||||
/// ```
|
||||
pub async fn connect(self, addr: SocketAddr) -> io::Result<TcpStream> {
|
||||
if let Err(err) = self.inner.connect(&addr.into()) {
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(windows))]
|
||||
if err.raw_os_error() != Some(libc::EINPROGRESS) {
|
||||
return Err(err);
|
||||
}
|
||||
@@ -835,9 +845,9 @@ impl TcpSocket {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(windows))]
|
||||
let mio = {
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
|
||||
let raw_fd = self.inner.into_raw_fd();
|
||||
unsafe { mio::net::TcpStream::from_raw_fd(raw_fd) }
|
||||
@@ -891,9 +901,9 @@ impl TcpSocket {
|
||||
/// ```
|
||||
pub fn listen(self, backlog: u32) -> io::Result<TcpListener> {
|
||||
self.inner.listen(backlog as i32)?;
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(windows))]
|
||||
let mio = {
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
|
||||
let raw_fd = self.inner.into_raw_fd();
|
||||
unsafe { mio::net::TcpListener::from_raw_fd(raw_fd) }
|
||||
@@ -945,9 +955,9 @@ impl TcpSocket {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn from_std_stream(std_stream: std::net::TcpStream) -> TcpSocket {
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
|
||||
let raw_fd = std_stream.into_raw_fd();
|
||||
unsafe { TcpSocket::from_raw_fd(raw_fd) }
|
||||
@@ -981,8 +991,8 @@ impl fmt::Debug for TcpSocket {
|
||||
|
||||
// These trait implementations can't be build on Windows, so we completely
|
||||
// ignore them, even when building documentation.
|
||||
#[cfg(unix)]
|
||||
cfg_unix! {
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
cfg_unix_or_wasi! {
|
||||
impl AsRawFd for TcpSocket {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.inner.as_raw_fd()
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
cfg_not_wasi! {
|
||||
use std::time::Duration;
|
||||
}
|
||||
|
||||
cfg_not_wasip1! {
|
||||
use crate::net::{to_socket_addrs, ToSocketAddrs};
|
||||
use std::future::poll_fn;
|
||||
use std::time::Duration;
|
||||
}
|
||||
|
||||
use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf, Ready};
|
||||
@@ -73,7 +76,7 @@ cfg_net! {
|
||||
}
|
||||
|
||||
impl TcpStream {
|
||||
cfg_not_wasi! {
|
||||
cfg_not_wasip1! {
|
||||
/// Opens a TCP connection to a remote host.
|
||||
///
|
||||
/// `addr` is an address of the remote host. Anything which implements the
|
||||
@@ -272,7 +275,7 @@ impl TcpStream {
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
{
|
||||
use std::os::wasi::io::{FromRawFd, IntoRawFd};
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
@@ -1564,7 +1567,7 @@ cfg_windows! {
|
||||
#[cfg(all(tokio_unstable, target_os = "wasi"))]
|
||||
mod sys {
|
||||
use super::TcpStream;
|
||||
use std::os::wasi::prelude::*;
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
|
||||
impl AsRawFd for TcpStream {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
|
||||
+47
-22
@@ -254,9 +254,9 @@ impl UdpSocket {
|
||||
/// [`std::net::UdpSocket`]: std::net::UdpSocket
|
||||
/// [`set_nonblocking`]: fn@std::net::UdpSocket::set_nonblocking
|
||||
pub fn into_std(self) -> io::Result<std::net::UdpSocket> {
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(IntoRawFd::into_raw_fd)
|
||||
@@ -531,7 +531,12 @@ impl UdpSocket {
|
||||
/// The [`connect`] method will connect this socket to a remote address.
|
||||
/// This method will fail if the socket is not connected.
|
||||
///
|
||||
/// This method may fail with a [`ConnectionRefused`] error if the remote
|
||||
/// address has replied with ICMP Unreachable to a previously sent packet.
|
||||
/// However, this behavior depends on the OS.
|
||||
///
|
||||
/// [`connect`]: method@Self::connect
|
||||
/// [`ConnectionRefused`]: std::io::ErrorKind::ConnectionRefused
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
@@ -785,7 +790,7 @@ impl UdpSocket {
|
||||
pub async fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(Interest::READABLE, || self.io.recv(buf))
|
||||
.async_io(Interest::READABLE | Interest::ERROR, || self.io.recv(buf))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -986,7 +991,9 @@ impl UdpSocket {
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn recv_buf<B: BufMut>(&self, buf: &mut B) -> io::Result<usize> {
|
||||
self.io.registration().async_io(Interest::READABLE, || {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(Interest::READABLE | Interest::ERROR, || {
|
||||
let dst = buf.chunk_mut();
|
||||
let dst =
|
||||
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
|
||||
@@ -1000,7 +1007,8 @@ impl UdpSocket {
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
}).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Tries to receive a single datagram message on the socket. On success,
|
||||
@@ -1117,7 +1125,9 @@ impl UdpSocket {
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn recv_buf_from<B: BufMut>(&self, buf: &mut B) -> io::Result<(usize, SocketAddr)> {
|
||||
self.io.registration().async_io(Interest::READABLE, || {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(Interest::READABLE | Interest::ERROR, || {
|
||||
let dst = buf.chunk_mut();
|
||||
let dst =
|
||||
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
|
||||
@@ -1130,8 +1140,9 @@ impl UdpSocket {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
|
||||
Ok((n,addr))
|
||||
}).await
|
||||
Ok((n, addr))
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,7 +1326,9 @@ impl UdpSocket {
|
||||
pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(Interest::READABLE, || self.io.recv_from(buf))
|
||||
.async_io(Interest::READABLE | Interest::ERROR, || {
|
||||
self.io.recv_from(buf)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1556,7 +1569,7 @@ impl UdpSocket {
|
||||
pub async fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(Interest::READABLE, || self.io.peek(buf))
|
||||
.async_io(Interest::READABLE | Interest::ERROR, || self.io.peek(buf))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1698,7 +1711,9 @@ impl UdpSocket {
|
||||
pub async fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(Interest::READABLE, || self.io.peek_from(buf))
|
||||
.async_io(Interest::READABLE | Interest::ERROR, || {
|
||||
self.io.peek_from(buf)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1815,7 +1830,9 @@ impl UdpSocket {
|
||||
pub async fn peek_sender(&self) -> io::Result<SocketAddr> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(Interest::READABLE, || self.peek_sender_inner())
|
||||
.async_io(Interest::READABLE | Interest::ERROR, || {
|
||||
self.peek_sender_inner()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -2084,7 +2101,8 @@ impl UdpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
@@ -2093,7 +2111,8 @@ impl UdpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
))))
|
||||
)]
|
||||
pub fn tos_v4(&self) -> io::Result<u32> {
|
||||
@@ -2112,7 +2131,8 @@ impl UdpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
@@ -2121,7 +2141,8 @@ impl UdpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
))))
|
||||
)]
|
||||
pub fn tos(&self) -> io::Result<u32> {
|
||||
@@ -2144,7 +2165,8 @@ impl UdpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
@@ -2153,7 +2175,8 @@ impl UdpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
))))
|
||||
)]
|
||||
pub fn set_tos_v4(&self, tos: u32) -> io::Result<()> {
|
||||
@@ -2172,7 +2195,8 @@ impl UdpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
@@ -2181,7 +2205,8 @@ impl UdpSocket {
|
||||
target_os = "redox",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku"
|
||||
target_os = "haiku",
|
||||
target_os = "wasi",
|
||||
))))
|
||||
)]
|
||||
pub fn set_tos(&self, tos: u32) -> io::Result<()> {
|
||||
@@ -2297,10 +2322,10 @@ impl fmt::Debug for UdpSocket {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(windows))]
|
||||
mod sys {
|
||||
use super::UdpSocket;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
|
||||
impl AsRawFd for UdpSocket {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
|
||||
@@ -617,6 +617,14 @@ impl Sender {
|
||||
/// number of bytes written. If the pipe is not ready to write data,
|
||||
/// `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// To avoid unnecessary syscalls, this will only attempt the write
|
||||
/// operation if the OS has informed Tokio that this pipe has become
|
||||
/// writable. Because of this, `try_write()` may fail with a
|
||||
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
|
||||
/// this pipe has become writable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -650,6 +658,8 @@ impl Sender {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
|
||||
pub fn try_write(&self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.io
|
||||
.registration()
|
||||
@@ -681,6 +691,14 @@ impl Sender {
|
||||
/// number of bytes written. If the pipe is not ready to write data,
|
||||
/// `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// To avoid unnecessary syscalls, this will only attempt the write
|
||||
/// operation if the OS has informed Tokio that this pipe has become
|
||||
/// writable. Because of this, `try_write_vectored()` may fail with a
|
||||
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
|
||||
/// this pipe has become writable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -716,12 +734,46 @@ impl Sender {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
|
||||
pub fn try_write_vectored(&self, buf: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::WRITABLE, || (&*self.io).write_vectored(buf))
|
||||
}
|
||||
|
||||
/// Tries to write from the socket using a user-provided IO operation.
|
||||
///
|
||||
/// If the socket is ready, the provided closure is called. The closure
|
||||
/// should attempt to perform IO operation on the socket by manually
|
||||
/// calling the appropriate syscall. If the operation fails because the
|
||||
/// socket is not actually ready, then the closure should return a
|
||||
/// `WouldBlock` error and the readiness flag is cleared. The return value
|
||||
/// of the closure is then returned by `try_io`.
|
||||
///
|
||||
/// If the socket is not ready, then the closure is not called
|
||||
/// and a `WouldBlock` error is returned.
|
||||
///
|
||||
/// The closure should only return a `WouldBlock` error if it has performed
|
||||
/// an IO operation on the socket that failed due to the socket not being
|
||||
/// ready. Returning a `WouldBlock` error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the socket to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio `pipe::Sender` type, as this will mess with the
|
||||
/// readiness flag and can cause the socket to behave incorrectly.
|
||||
///
|
||||
/// Usually, [`writable()`] or [`ready()`] is used with this function.
|
||||
///
|
||||
/// [`writable()`]: Self::writable()
|
||||
/// [`ready()`]: Self::ready()
|
||||
pub fn try_io<R>(&self, f: impl FnOnce() -> io::Result<R>) -> io::Result<R> {
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::WRITABLE, || self.io.try_io(f))
|
||||
}
|
||||
|
||||
/// Converts the pipe into an [`OwnedFd`] in blocking mode.
|
||||
///
|
||||
/// This function will deregister this pipe end from the event loop, set
|
||||
@@ -1120,6 +1172,14 @@ impl Receiver {
|
||||
/// If the pipe is not ready to read data,
|
||||
/// `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// To avoid unnecessary syscalls, this will only attempt the read
|
||||
/// operation if the OS has informed Tokio that this pipe has become
|
||||
/// readable. Because of this, `try_read()` may fail with a
|
||||
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
|
||||
/// this pipe has become readable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -1157,6 +1217,8 @@ impl Receiver {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
|
||||
pub fn try_read(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.io
|
||||
.registration()
|
||||
@@ -1188,6 +1250,14 @@ impl Receiver {
|
||||
/// closed and will no longer write data. If the pipe is not ready to read
|
||||
/// data `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// To avoid unnecessary syscalls, this will only attempt the read
|
||||
/// operation if the OS has informed Tokio that this pipe has become
|
||||
/// readable. Because of this, `try_read_vectored()` may fail with a
|
||||
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
|
||||
/// this pipe has become readable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -1231,12 +1301,46 @@ impl Receiver {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
|
||||
pub fn try_read_vectored(&self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::READABLE, || (&*self.io).read_vectored(bufs))
|
||||
}
|
||||
|
||||
/// Tries to read to the socket using a user-provided IO operation.
|
||||
///
|
||||
/// If the socket is ready, the provided closure is called. The closure
|
||||
/// should attempt to perform IO operation on the socket by manually
|
||||
/// calling the appropriate syscall. If the operation fails because the
|
||||
/// socket is not actually ready, then the closure should return a
|
||||
/// `WouldBlock` error and the readiness flag is cleared. The return value
|
||||
/// of the closure is then returned by `try_io`.
|
||||
///
|
||||
/// If the socket is not ready, then the closure is not called
|
||||
/// and a `WouldBlock` error is returned.
|
||||
///
|
||||
/// The closure should only return a `WouldBlock` error if it has performed
|
||||
/// an IO operation on the socket that failed due to the socket not being
|
||||
/// ready. Returning a `WouldBlock` error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the socket to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio `pipe::Receiver` type, as this will mess with the
|
||||
/// readiness flag and can cause the socket to behave incorrectly.
|
||||
///
|
||||
/// Usually, [`readable()`] or [`ready()`] is used with this function.
|
||||
///
|
||||
/// [`readable()`]: Self::readable()
|
||||
/// [`ready()`]: Self::ready()
|
||||
pub fn try_io<R>(&self, f: impl FnOnce() -> io::Result<R>) -> io::Result<R> {
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::READABLE, || self.io.try_io(f))
|
||||
}
|
||||
|
||||
cfg_io_util! {
|
||||
/// Tries to read data from the pipe into the provided buffer, advancing the
|
||||
/// buffer's internal cursor, returning how many bytes were read.
|
||||
@@ -1258,6 +1362,14 @@ impl Receiver {
|
||||
/// closed and will no longer write data. If the pipe is not ready to read
|
||||
/// data `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// To avoid unnecessary syscalls, this will only attempt the read
|
||||
/// operation if the OS has informed Tokio that this pipe has become
|
||||
/// readable. Because of this, `try_read_buf()` may fail with a
|
||||
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
|
||||
/// this pipe has become readable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -1294,6 +1406,8 @@ impl Receiver {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
|
||||
pub fn try_read_buf<B: BufMut>(&self, buf: &mut B) -> io::Result<usize> {
|
||||
self.io.registration().try_io(Interest::READABLE, || {
|
||||
use std::io::Read;
|
||||
|
||||
@@ -62,7 +62,7 @@ pub(crate) use self::impl_solaris::get_peer_cred;
|
||||
#[cfg(target_os = "aix")]
|
||||
pub(crate) use self::impl_aix::get_peer_cred;
|
||||
|
||||
#[cfg(any(target_os = "espidf", target_os = "vita"))]
|
||||
#[cfg(any(target_os = "espidf", target_os = "vita", target_os = "hurd"))]
|
||||
pub(crate) use self::impl_noproc::get_peer_cred;
|
||||
|
||||
#[cfg(any(
|
||||
@@ -317,7 +317,7 @@ pub(crate) mod impl_aix {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "espidf", target_os = "vita"))]
|
||||
#[cfg(any(target_os = "espidf", target_os = "vita", target_os = "hurd"))]
|
||||
pub(crate) mod impl_noproc {
|
||||
use crate::net::unix::UnixStream;
|
||||
use std::io;
|
||||
|
||||
@@ -5,7 +5,9 @@ use crate::runtime::{
|
||||
blocking, driver, Callback, HistogramBuilder, Runtime, TaskCallback, TimerFlavor,
|
||||
};
|
||||
#[cfg(tokio_unstable)]
|
||||
use crate::runtime::{metrics::HistogramConfiguration, LocalOptions, LocalRuntime, TaskMeta};
|
||||
use crate::runtime::{metrics::HistogramConfiguration, TaskMeta};
|
||||
|
||||
use crate::runtime::{LocalOptions, LocalRuntime};
|
||||
use crate::util::rand::{RngSeed, RngSeedGenerator};
|
||||
|
||||
use crate::runtime::blocking::BlockingPool;
|
||||
@@ -54,6 +56,9 @@ pub struct Builder {
|
||||
/// Runtime type
|
||||
kind: Kind,
|
||||
|
||||
/// Name of the runtime.
|
||||
name: Option<String>,
|
||||
|
||||
/// Whether or not to enable the I/O driver
|
||||
enable_io: bool,
|
||||
nevents: usize,
|
||||
@@ -137,6 +142,10 @@ pub struct Builder {
|
||||
pub(super) unhandled_panic: UnhandledPanic,
|
||||
|
||||
timer_flavor: TimerFlavor,
|
||||
|
||||
/// Whether or not to enable eager hand-off for the I/O and time drivers (in
|
||||
/// `tokio_unstable`).
|
||||
enable_eager_driver_handoff: bool,
|
||||
}
|
||||
|
||||
cfg_unstable! {
|
||||
@@ -238,7 +247,7 @@ impl Builder {
|
||||
/// Configuration methods can be chained on the return value.
|
||||
///
|
||||
/// To spawn non-`Send` tasks on the resulting runtime, combine it with a
|
||||
/// [`LocalSet`], or call [`build_local`] to create a [`LocalRuntime`] (unstable).
|
||||
/// [`LocalSet`], or call [`build_local`] to create a [`LocalRuntime`].
|
||||
///
|
||||
/// [`LocalSet`]: crate::task::LocalSet
|
||||
/// [`LocalRuntime`]: crate::runtime::LocalRuntime
|
||||
@@ -271,6 +280,9 @@ impl Builder {
|
||||
Builder {
|
||||
kind,
|
||||
|
||||
// Default runtime name
|
||||
name: None,
|
||||
|
||||
// I/O defaults to "off"
|
||||
enable_io: false,
|
||||
nevents: 1024,
|
||||
@@ -326,6 +338,9 @@ impl Builder {
|
||||
disable_lifo_slot: false,
|
||||
|
||||
timer_flavor: TimerFlavor::Traditional,
|
||||
|
||||
// Eager driver handoff is disabled by default.
|
||||
enable_eager_driver_handoff: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +421,40 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable eager hand-off of the I/O and time drivers for multi-threaded
|
||||
/// runtimes, which is disabled by default.
|
||||
///
|
||||
/// When this option is enabled, a worker thread which has parked on the I/O
|
||||
/// or time driver will notify another worker thread once it is preparing to
|
||||
/// begin polling a task from the run queue, so that the notified worker can
|
||||
/// begin polling the I/O or time driver. This can reduce the latency with
|
||||
/// which I/O and timer notifications are processed, especially when some
|
||||
/// tasks have polls that take a long time to complete. In addition, it can
|
||||
/// reduce the risk of a deadlock which may occur when a task blocks the
|
||||
/// worker thread which is holding the I/O or time driver until some other
|
||||
/// task, which is waiting for a notification from *that* driver, unblocks
|
||||
/// it.
|
||||
///
|
||||
/// This option is disabled by default, as enabling it may potentially
|
||||
/// increase contention due to extra synchronization in cross-driver
|
||||
/// wakeups.
|
||||
///
|
||||
/// This option only applies to multi-threaded runtimes. Attempting to use
|
||||
/// this option with any other runtime type will have no effect.
|
||||
///
|
||||
/// **Note**: This is an [unstable API][unstable]. Eager driver hand-off is
|
||||
/// an experimental feature whose behavior may be removed or changed in 1.x
|
||||
/// releases. See [the documentation on unstable features][unstable] for
|
||||
/// details.
|
||||
///
|
||||
/// [unstable]: crate#unstable-features
|
||||
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt-multi-thread"))))]
|
||||
pub fn enable_eager_driver_handoff(&mut self) -> &mut Self {
|
||||
self.enable_eager_driver_handoff = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the number of worker threads the `Runtime` will use.
|
||||
///
|
||||
/// This can be any number above 0 though it is advised to keep this value
|
||||
@@ -538,6 +587,34 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the name of the runtime.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(not(target_family = "wasm"))]
|
||||
/// # {
|
||||
/// # use tokio::runtime;
|
||||
///
|
||||
/// # pub fn main() {
|
||||
/// let rt = runtime::Builder::new_multi_thread()
|
||||
/// .name("my-runtime")
|
||||
/// .build();
|
||||
/// # }
|
||||
/// # }
|
||||
/// ```
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if an empty value is passed as an argument.
|
||||
///
|
||||
#[track_caller]
|
||||
pub fn name(&mut self, val: impl Into<String>) -> &mut Self {
|
||||
let val = val.into();
|
||||
assert!(!val.trim().is_empty(), "runtime name shouldn't be empty");
|
||||
self.name = Some(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a function used to generate the name of threads spawned by the `Runtime`'s thread pool.
|
||||
///
|
||||
/// The default name fn is `|| "tokio-rt-worker".into()`.
|
||||
@@ -1014,8 +1091,6 @@ impl Builder {
|
||||
/// });
|
||||
/// ```
|
||||
#[allow(unused_variables, unreachable_patterns)]
|
||||
#[cfg(tokio_unstable)]
|
||||
#[cfg_attr(docsrs, doc(cfg(tokio_unstable)))]
|
||||
pub fn build_local(&mut self, options: LocalOptions) -> io::Result<LocalRuntime> {
|
||||
match &self.kind {
|
||||
Kind::CurrentThread => self.build_current_thread_local_runtime(),
|
||||
@@ -1571,7 +1646,6 @@ impl Builder {
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
fn build_current_thread_local_runtime(&mut self) -> io::Result<LocalRuntime> {
|
||||
use crate::runtime::local_runtime::LocalRuntimeScheduler;
|
||||
|
||||
@@ -1629,10 +1703,15 @@ impl Builder {
|
||||
#[cfg(tokio_unstable)]
|
||||
unhandled_panic: self.unhandled_panic.clone(),
|
||||
disable_lifo_slot: self.disable_lifo_slot,
|
||||
// This setting never makes sense for a current thread runtime,
|
||||
// as it only configures how the I/O driver is stolen across
|
||||
// workers.
|
||||
enable_eager_driver_handoff: false,
|
||||
seed_generator: seed_generator_1,
|
||||
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
|
||||
},
|
||||
local_tid,
|
||||
self.name.clone(),
|
||||
);
|
||||
|
||||
let handle = Handle {
|
||||
@@ -1810,10 +1889,12 @@ cfg_rt_multi_thread! {
|
||||
#[cfg(tokio_unstable)]
|
||||
unhandled_panic: self.unhandled_panic.clone(),
|
||||
disable_lifo_slot: self.disable_lifo_slot,
|
||||
enable_eager_driver_handoff: self.enable_eager_driver_handoff,
|
||||
seed_generator: seed_generator_1,
|
||||
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
|
||||
},
|
||||
self.timer_flavor,
|
||||
self.name.clone(),
|
||||
);
|
||||
|
||||
let handle = Handle { inner: scheduler::Handle::MultiThread(handle) };
|
||||
@@ -1829,7 +1910,13 @@ cfg_rt_multi_thread! {
|
||||
|
||||
impl fmt::Debug for Builder {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Builder")
|
||||
let mut debug = fmt.debug_struct("Builder");
|
||||
|
||||
if let Some(name) = &self.name {
|
||||
debug.field("name", name);
|
||||
}
|
||||
|
||||
debug
|
||||
.field("worker_threads", &self.worker_threads)
|
||||
.field("max_blocking_threads", &self.max_blocking_threads)
|
||||
.field(
|
||||
@@ -1841,6 +1928,15 @@ impl fmt::Debug for Builder {
|
||||
.field("before_stop", &self.before_stop.as_ref().map(|_| "..."))
|
||||
.field("before_park", &self.before_park.as_ref().map(|_| "..."))
|
||||
.field("after_unpark", &self.after_unpark.as_ref().map(|_| "..."))
|
||||
.finish()
|
||||
.field(
|
||||
"enable_eager_driver_handoff",
|
||||
&self.enable_eager_driver_handoff,
|
||||
);
|
||||
|
||||
if self.name.is_none() {
|
||||
debug.finish_non_exhaustive()
|
||||
} else {
|
||||
debug.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,9 @@ pub(crate) struct Config {
|
||||
#[cfg(tokio_unstable)]
|
||||
/// How to respond to unhandled task panics.
|
||||
pub(crate) unhandled_panic: crate::runtime::UnhandledPanic,
|
||||
|
||||
/// If `true`, an idle worker is woken whenever a worker thread transitions
|
||||
/// from polling the I/O driver to polling its own tasks (requires
|
||||
/// `tokio_unstable`).
|
||||
pub(crate) enable_eager_driver_handoff: bool,
|
||||
}
|
||||
|
||||
@@ -159,6 +159,11 @@ cfg_rt! {
|
||||
CONTEXT.try_with(|ctx| ctx.current_task_id.get()).unwrap_or(None)
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
pub(crate) fn worker_index() -> Option<usize> {
|
||||
with_scheduler(|ctx| ctx.and_then(|c| c.worker_index()))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn defer(waker: &Waker) {
|
||||
with_scheduler(|maybe_scheduler| {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::io::blocking::Buf;
|
||||
use crate::io::uring::open::Open;
|
||||
use crate::io::uring::read::Read;
|
||||
use crate::io::uring::utils::ArcFd;
|
||||
use crate::io::uring::write::Write;
|
||||
|
||||
use crate::runtime::Handle;
|
||||
|
||||
use io_uring::cqueue;
|
||||
@@ -8,6 +11,7 @@ use io_uring::squeue::Entry;
|
||||
use std::future::Future;
|
||||
use std::io::{self, Error};
|
||||
use std::mem;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
|
||||
@@ -18,7 +22,8 @@ use std::task::{Context, Poll, Waker};
|
||||
pub(crate) enum CancelData {
|
||||
Open(Open),
|
||||
Write(Write),
|
||||
Read(Read),
|
||||
ReadVec(Read<Vec<u8>, OwnedFd>),
|
||||
ReadBuf(Read<Buf, ArcFd>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use crate::task::Id;
|
||||
use std::{fmt, future::Future, path::Path};
|
||||
|
||||
pub use crate::runtime::task::trace::Root;
|
||||
pub use crate::runtime::task::trace::{trace_with, Root, TraceMeta};
|
||||
|
||||
/// A snapshot of a runtime's state.
|
||||
///
|
||||
|
||||
@@ -474,6 +474,27 @@ impl Handle {
|
||||
runtime::Id::new(owned_id)
|
||||
}
|
||||
|
||||
/// Returns the name of the current `Runtime`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::runtime::Handle;
|
||||
///
|
||||
/// #[tokio::main(flavor = "current_thread", name = "my-runtime")]
|
||||
/// async fn main() {
|
||||
/// println!("Current runtime name: {}", Handle::current().name().unwrap());
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
match &self.inner {
|
||||
scheduler::Handle::CurrentThread(handle) => handle.name(),
|
||||
#[cfg(feature = "rt-multi-thread")]
|
||||
scheduler::Handle::MultiThread(handle) => handle.name(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a view that lets you get information about how the runtime
|
||||
/// is performing.
|
||||
pub fn metrics(&self) -> RuntimeMetrics {
|
||||
|
||||
@@ -2,12 +2,14 @@ use io_uring::{squeue::Entry, IoUring, Probe};
|
||||
use mio::unix::SourceFd;
|
||||
use slab::Slab;
|
||||
|
||||
use crate::runtime::driver::op::CancelData;
|
||||
use crate::runtime::driver::op::CqeResult;
|
||||
use crate::runtime::driver::op::{Cancellable, Lifecycle};
|
||||
use crate::{io::Interest, loom::sync::Mutex};
|
||||
|
||||
use super::{Handle, TOKEN_WAKEUP};
|
||||
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::{io, mem, task::Waker};
|
||||
|
||||
const DEFAULT_RING_SIZE: u32 = 256;
|
||||
@@ -77,9 +79,16 @@ impl UringContext {
|
||||
waker.wake_by_ref();
|
||||
*ops.get_mut(idx).unwrap() = Lifecycle::Completed(cqe);
|
||||
}
|
||||
Some(Lifecycle::Cancelled(_)) => {
|
||||
Some(Lifecycle::Cancelled(cancel_data)) => {
|
||||
if let CancelData::Open(_) = cancel_data {
|
||||
if let Ok(fd) = CqeResult::from(cqe).result {
|
||||
// SAFETY: the successful CQE result provides
|
||||
// a non-negative integer, and the event is
|
||||
// related to an open operation.
|
||||
unsafe { OwnedFd::from_raw_fd(fd as i32) };
|
||||
}
|
||||
}
|
||||
// Op future was cancelled, so we discard the result.
|
||||
// We just remove the entry from the slab.
|
||||
ops.remove(idx);
|
||||
}
|
||||
Some(other) => {
|
||||
@@ -147,6 +156,16 @@ impl Drop for UringContext {
|
||||
|
||||
for cqe in self.ring_mut().completion() {
|
||||
let idx = cqe.user_data() as usize;
|
||||
|
||||
if let Some(Lifecycle::Cancelled(CancelData::Open(_))) = ops.get_mut(idx) {
|
||||
if let Ok(fd) = CqeResult::from(cqe).result {
|
||||
// SAFETY: the successful CQE result provides
|
||||
// a non-negative integer, and the event is
|
||||
// related to an open operation.
|
||||
unsafe { OwnedFd::from_raw_fd(fd as i32) };
|
||||
}
|
||||
};
|
||||
|
||||
ops.remove(idx);
|
||||
}
|
||||
}
|
||||
@@ -164,6 +183,26 @@ impl Handle {
|
||||
&self.uring_context
|
||||
}
|
||||
|
||||
/// Returns `true` if io_uring has already been initialized and the given
|
||||
/// opcode is supported. Returns `false` if io_uring hasn't been
|
||||
/// initialized yet or is unsupported. Unlike `check_and_init`, this
|
||||
/// doesn't attempt initialization.
|
||||
#[cfg_attr(test, allow(dead_code))]
|
||||
pub(crate) fn is_uring_ready(&self, opcode: u8) -> bool {
|
||||
self.uring_probe
|
||||
.get()
|
||||
.and_then(|opt| opt.as_ref())
|
||||
.is_some_and(|probe| probe.is_supported(opcode))
|
||||
}
|
||||
|
||||
/// Returns `true` if the io_uring probe has already been attempted
|
||||
/// (regardless of whether io_uring is supported). Returns `false` if
|
||||
/// no probe has been attempted yet.
|
||||
#[cfg_attr(test, allow(dead_code))]
|
||||
pub(crate) fn is_uring_probed(&self) -> bool {
|
||||
self.uring_probe.get().is_some()
|
||||
}
|
||||
|
||||
/// Check if the io_uring context is initialized. If not, it will try to initialize it.
|
||||
/// Then, check if the provided opcode is supported.
|
||||
///
|
||||
@@ -272,7 +311,15 @@ impl Handle {
|
||||
match mem::replace(lifecycle, Lifecycle::Cancelled(cancel_data)) {
|
||||
Lifecycle::Submitted | Lifecycle::Waiting(_) => (),
|
||||
// The driver saw the completion, but it was never polled.
|
||||
Lifecycle::Completed(_) => {
|
||||
Lifecycle::Completed(cqe) => {
|
||||
if let Lifecycle::Cancelled(CancelData::Open(_)) = lifecycle {
|
||||
if let Ok(fd) = CqeResult::from(cqe).result {
|
||||
// SAFETY: the successful CQE result provides
|
||||
// a non-negative integer, and the event is
|
||||
// related to an open operation.
|
||||
unsafe { OwnedFd::from_raw_fd(fd as i32) };
|
||||
}
|
||||
}
|
||||
// We can safely remove the entry from the slab, as it has already been completed.
|
||||
ops.remove(index);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Registration {
|
||||
|
||||
// Uses the poll path, requiring the caller to ensure mutual exclusion for
|
||||
// correctness. Only the last task to call this function is notified.
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
#[cfg(not(all(target_os = "wasi", target_env = "p1")))]
|
||||
pub(crate) fn poll_read_io<R>(
|
||||
&self,
|
||||
cx: &mut Context<'_>,
|
||||
|
||||
@@ -29,7 +29,6 @@ use std::time::Duration;
|
||||
/// [runtime]: crate::runtime::Runtime
|
||||
/// [module]: crate::runtime
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(tokio_unstable)))]
|
||||
pub struct LocalRuntime {
|
||||
/// Task scheduler
|
||||
scheduler: LocalRuntimeScheduler,
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
//! | Yes | No
|
||||
//! | |
|
||||
//! V |
|
||||
//! +--------------------------+ |
|
||||
//! | Local Runtime (unstable) | |
|
||||
//! +--------------------------+ |
|
||||
//! +---------------+ |
|
||||
//! | Local Runtime | |
|
||||
//! +---------------+ |
|
||||
//! |
|
||||
//! v
|
||||
//! +------------------------+
|
||||
@@ -373,6 +373,13 @@
|
||||
//! When a task is woken from a thread that is not a worker thread, then the
|
||||
//! task is placed in the global queue.
|
||||
//!
|
||||
//! # Performance tuning
|
||||
//!
|
||||
//! ## File descriptor table pre-warming
|
||||
//!
|
||||
//! On Linux, file descriptor table growth can stall worker threads. See the
|
||||
//! [`prewarm-fd-table`] example.
|
||||
//!
|
||||
//! [`poll`]: std::future::Future::poll
|
||||
//! [`wake`]: std::task::Waker::wake
|
||||
//! [`yield_now`]: crate::task::yield_now
|
||||
@@ -385,6 +392,7 @@
|
||||
//! [the lifo slot optimization]: crate::runtime::Builder::disable_lifo_slot
|
||||
//! [coop budget]: crate::task::coop#cooperative-scheduling
|
||||
//! [`worker_mean_poll_time`]: crate::runtime::RuntimeMetrics::worker_mean_poll_time
|
||||
//! [`prewarm-fd-table`]: https://github.com/tokio-rs/tokio/blob/master/examples/prewarm-fd-table.rs
|
||||
|
||||
// At the top due to macros
|
||||
#[cfg(test)]
|
||||
@@ -567,8 +575,42 @@ cfg_rt! {
|
||||
pub use self::builder::UnhandledPanic;
|
||||
pub use crate::util::rand::RngSeed;
|
||||
|
||||
mod local_runtime;
|
||||
pub use local_runtime::{LocalRuntime, LocalOptions};
|
||||
/// Returns the index of the current worker thread, if called from a
|
||||
/// runtime worker thread.
|
||||
///
|
||||
/// The returned value is a 0-based index matching the worker indices
|
||||
/// used by [`RuntimeMetrics`] methods such as
|
||||
/// [`worker_total_busy_duration`](RuntimeMetrics::worker_total_busy_duration).
|
||||
///
|
||||
/// Returns `None` when called from outside a runtime worker thread
|
||||
/// (for example, from a blocking thread or a non-Tokio thread). On the
|
||||
/// multi-thread runtime, the thread that calls [`Runtime::block_on`] is
|
||||
/// not a worker thread, so this also returns `None` there.
|
||||
///
|
||||
/// For the current-thread runtime and [`LocalRuntime`], this always
|
||||
/// returns `Some(0)` (including inside `block_on`, since the calling
|
||||
/// thread *is* the worker thread).
|
||||
///
|
||||
/// Note that the result may change across `.await` points, as the
|
||||
/// task may be moved to a different worker thread by the scheduler.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(not(target_family = "wasm"))]
|
||||
/// # {
|
||||
/// #[tokio::main(flavor = "multi_thread", worker_threads = 4)]
|
||||
/// async fn main() {
|
||||
/// let index = tokio::spawn(async {
|
||||
/// tokio::runtime::worker_index()
|
||||
/// }).await.unwrap();
|
||||
/// println!("Task ran on worker {:?}", index);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn worker_index() -> Option<usize> {
|
||||
context::worker_index()
|
||||
}
|
||||
}
|
||||
|
||||
cfg_taskdump! {
|
||||
@@ -590,6 +632,9 @@ cfg_rt! {
|
||||
mod runtime;
|
||||
pub use runtime::{Runtime, RuntimeFlavor, is_rt_shutdown_err};
|
||||
|
||||
mod local_runtime;
|
||||
pub use local_runtime::{LocalRuntime, LocalOptions};
|
||||
|
||||
mod id;
|
||||
pub use id::Id;
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ pub(crate) struct CurrentThread {
|
||||
|
||||
/// Handle to the current thread scheduler
|
||||
pub(crate) struct Handle {
|
||||
/// The name of the runtime
|
||||
name: Option<String>,
|
||||
|
||||
/// Scheduler state shared across threads
|
||||
shared: Shared,
|
||||
|
||||
@@ -132,6 +135,7 @@ impl CurrentThread {
|
||||
seed_generator: RngSeedGenerator,
|
||||
config: Config,
|
||||
local_tid: Option<ThreadId>,
|
||||
name: Option<String>,
|
||||
) -> (CurrentThread, Arc<Handle>) {
|
||||
let worker_metrics = WorkerMetrics::from_config(&config);
|
||||
worker_metrics.set_thread_id(thread::current().id());
|
||||
@@ -142,6 +146,7 @@ impl CurrentThread {
|
||||
.unwrap_or(DEFAULT_GLOBAL_QUEUE_INTERVAL);
|
||||
|
||||
let handle = Arc::new(Handle {
|
||||
name,
|
||||
task_hooks: TaskHooks {
|
||||
task_spawn_callback: config.before_spawn.clone(),
|
||||
task_terminate_callback: config.after_termination.clone(),
|
||||
@@ -380,8 +385,6 @@ impl Context {
|
||||
core = c;
|
||||
}
|
||||
|
||||
// If `before_park` spawns a task (or otherwise schedules work for us), then we should not
|
||||
// park the thread.
|
||||
if !self.has_pending_work(&core) {
|
||||
// Park until the thread is signaled
|
||||
core.metrics.about_to_park();
|
||||
@@ -391,6 +394,15 @@ impl Context {
|
||||
|
||||
core.metrics.unparked();
|
||||
core.submit_metrics(handle);
|
||||
} else {
|
||||
// `before_park` scheduled work (e.g. an `on_thread_park` hook that woke the
|
||||
// `block_on` future), so we don't block. We must still poll the driver once
|
||||
// without blocking, or timer and I/O events would stall under a runtime driven
|
||||
// by repeated short `block_on` calls. See
|
||||
// <https://github.com/tokio-rs/tokio/issues/8212>.
|
||||
core.submit_metrics(handle);
|
||||
|
||||
core = self.park_internal(core, handle, &mut driver, Some(Duration::from_millis(0)));
|
||||
}
|
||||
|
||||
if let Some(f) = &handle.shared.config.after_unpark {
|
||||
@@ -639,6 +651,10 @@ impl Handle {
|
||||
pub(crate) fn owned_id(&self) -> NonZeroU64 {
|
||||
self.shared.owned.id
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Handle {
|
||||
|
||||
@@ -293,6 +293,15 @@ cfg_rt! {
|
||||
match_flavor!(self, Context(context) => context.defer(waker));
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
pub(crate) fn worker_index(&self) -> Option<usize> {
|
||||
match self {
|
||||
Context::CurrentThread(_) => Some(0),
|
||||
#[cfg(feature = "rt-multi-thread")]
|
||||
Context::MultiThread(context) => Some(context.worker_index()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "time", feature = "rt-multi-thread"))]
|
||||
pub(crate) fn with_time_temp_local_context<F, R>(&self, f: F) -> R
|
||||
where
|
||||
|
||||
@@ -23,6 +23,9 @@ use crate::loom::sync::atomic::{AtomicBool, Ordering::SeqCst};
|
||||
|
||||
/// Handle to the multi thread scheduler
|
||||
pub(crate) struct Handle {
|
||||
/// The name of the runtime
|
||||
pub(super) name: Option<String>,
|
||||
|
||||
/// Task spawner
|
||||
pub(super) shared: worker::Shared,
|
||||
|
||||
@@ -123,6 +126,10 @@ impl Handle {
|
||||
pub(crate) fn owned_id(&self) -> NonZeroU64 {
|
||||
self.shared.owned.id
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Handle {
|
||||
|
||||
@@ -54,6 +54,7 @@ pub(crate) struct MultiThread;
|
||||
// ===== impl MultiThread =====
|
||||
|
||||
impl MultiThread {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
size: usize,
|
||||
driver: Driver,
|
||||
@@ -62,6 +63,7 @@ impl MultiThread {
|
||||
seed_generator: RngSeedGenerator,
|
||||
config: Config,
|
||||
timer_flavor: TimerFlavor,
|
||||
name: Option<String>,
|
||||
) -> (MultiThread, Arc<Handle>, Launch) {
|
||||
let parker = Parker::new(driver);
|
||||
let (handle, launch) = worker::create(
|
||||
@@ -72,6 +74,7 @@ impl MultiThread {
|
||||
seed_generator,
|
||||
config,
|
||||
timer_flavor,
|
||||
name,
|
||||
);
|
||||
|
||||
(MultiThread, handle, launch)
|
||||
|
||||
@@ -21,6 +21,13 @@ pub(crate) struct Unparker {
|
||||
inner: Arc<Inner>,
|
||||
}
|
||||
|
||||
/// Represents how a worker thread was parked
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub(crate) enum HadDriver {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
/// Avoids entering the park if possible
|
||||
state: AtomicUsize,
|
||||
@@ -66,8 +73,8 @@ impl Parker {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn park(&mut self, handle: &driver::Handle) {
|
||||
self.inner.park(handle);
|
||||
pub(crate) fn park(&mut self, handle: &driver::Handle) -> HadDriver {
|
||||
self.inner.park(handle)
|
||||
}
|
||||
|
||||
/// Parks the current thread for up to `duration`.
|
||||
@@ -75,11 +82,16 @@ impl Parker {
|
||||
/// This function tries to acquire the driver lock. If it succeeds, it
|
||||
/// parks using the driver. Otherwise, it fails back to using a condvar,
|
||||
/// unless the duration is zero, in which case it returns immediately.
|
||||
pub(crate) fn park_timeout(&mut self, handle: &driver::Handle, duration: Duration) {
|
||||
pub(crate) fn park_timeout(
|
||||
&mut self,
|
||||
handle: &driver::Handle,
|
||||
duration: Duration,
|
||||
) -> HadDriver {
|
||||
if let Some(mut driver) = self.inner.shared.driver.try_lock() {
|
||||
self.inner.park_driver(&mut driver, handle, Some(duration));
|
||||
self.inner.park_driver(&mut driver, handle, Some(duration))
|
||||
} else if !duration.is_zero() {
|
||||
self.inner.park_condvar(Some(duration));
|
||||
HadDriver::No
|
||||
} else {
|
||||
// https://github.com/tokio-rs/tokio/issues/6536
|
||||
// Hacky, but it's just for loom tests. The counter gets incremented during
|
||||
@@ -87,6 +99,7 @@ impl Parker {
|
||||
// lock.
|
||||
#[cfg(loom)]
|
||||
CURRENT_THREAD_PARK_COUNT.with(|count| count.fetch_add(1, SeqCst));
|
||||
HadDriver::No
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +129,7 @@ impl Unparker {
|
||||
|
||||
impl Inner {
|
||||
/// Parks the current thread for at most `dur`.
|
||||
fn park(&self, handle: &driver::Handle) {
|
||||
fn park(&self, handle: &driver::Handle) -> HadDriver {
|
||||
// If we were previously notified then we consume this notification and
|
||||
// return quickly.
|
||||
if self
|
||||
@@ -124,13 +137,14 @@ impl Inner {
|
||||
.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
|
||||
.is_ok()
|
||||
{
|
||||
return;
|
||||
return HadDriver::No;
|
||||
}
|
||||
|
||||
if let Some(mut driver) = self.shared.driver.try_lock() {
|
||||
self.park_driver(&mut driver, handle, None);
|
||||
self.park_driver(&mut driver, handle, None)
|
||||
} else {
|
||||
self.park_condvar(None);
|
||||
HadDriver::No
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,12 +230,12 @@ impl Inner {
|
||||
driver: &mut Driver,
|
||||
handle: &driver::Handle,
|
||||
duration: Option<Duration>,
|
||||
) {
|
||||
) -> HadDriver {
|
||||
if duration.as_ref().is_some_and(Duration::is_zero) {
|
||||
// zero duration doesn't actually park the thread, it just
|
||||
// polls the I/O events, timers, etc.
|
||||
driver.park_timeout(handle, Duration::ZERO);
|
||||
return;
|
||||
return HadDriver::Yes;
|
||||
}
|
||||
|
||||
match self
|
||||
@@ -239,7 +253,7 @@ impl Inner {
|
||||
let old = self.state.swap(EMPTY, SeqCst);
|
||||
debug_assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
|
||||
|
||||
return;
|
||||
return HadDriver::No;
|
||||
}
|
||||
Err(actual) => panic!("inconsistent park state; actual = {actual}"),
|
||||
}
|
||||
@@ -256,6 +270,8 @@ impl Inner {
|
||||
PARKED_DRIVER => {} // no notification, alas
|
||||
n => panic!("inconsistent park_timeout state: {n}"),
|
||||
}
|
||||
|
||||
HadDriver::Yes
|
||||
}
|
||||
|
||||
fn unpark(&self, driver: &driver::Handle) {
|
||||
|
||||
@@ -274,9 +274,7 @@ impl<T> Local<T> {
|
||||
"queue is not full; tail = {tail}; head = {head}"
|
||||
);
|
||||
|
||||
let prev = pack(head, head);
|
||||
|
||||
// Claim a bunch of tasks
|
||||
// Claim all tasks.
|
||||
//
|
||||
// We are claiming the tasks **before** reading them out of the buffer.
|
||||
// This is safe because only the **current** thread is able to push new
|
||||
@@ -289,15 +287,7 @@ impl<T> Local<T> {
|
||||
if self
|
||||
.inner
|
||||
.head
|
||||
.compare_exchange(
|
||||
prev,
|
||||
pack(
|
||||
head.wrapping_add(NUM_TASKS_TAKEN),
|
||||
head.wrapping_add(NUM_TASKS_TAKEN),
|
||||
),
|
||||
Release,
|
||||
Relaxed,
|
||||
)
|
||||
.compare_exchange_weak(pack(head, head), pack(tail, tail), Release, Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
// We failed to claim the tasks, losing the race. Return out of
|
||||
@@ -306,6 +296,29 @@ impl<T> Local<T> {
|
||||
return Err(task);
|
||||
}
|
||||
|
||||
// Add back the first half of tasks.
|
||||
//
|
||||
// We are doing it this way instead of just taking half of the tasks because we want the
|
||||
// *second* half of the tasks, and if you just incremented `head` by `NUM_TASKS_TAKEN`,
|
||||
// then you would be taking the first half instead of the second half.
|
||||
//
|
||||
// Pushing the second half of the local queue to the injection queue is better because when
|
||||
// we take tasks *out* of the injection queue, we always place them in the first half. This
|
||||
// means that if a task is in the second half, then we know for sure that this task is not
|
||||
// a task we just got from the injection queue. This ensures that when we take a task out
|
||||
// of the injection queue, then it will not be moved back into the injection queue (at
|
||||
// least not until after we have polled it at least once).
|
||||
//
|
||||
// Note that if a concurrent worker tries to steal from us between these two operations and
|
||||
// sees that the worker queue is empty, then that worker may go to sleep, and we do not
|
||||
// notify it about these tasks becoming available for stealing again. Ordinarily this would
|
||||
// be a problem, but it isn't in this case because the worker will be notified about the
|
||||
// tasks we are adding to the injection queue instead, which ensures that the stealer wakes
|
||||
// up again to take the tasks from the injection queue.
|
||||
self.inner
|
||||
.tail
|
||||
.store(tail.wrapping_add(NUM_TASKS_TAKEN), Release);
|
||||
|
||||
/// An iterator that takes elements out of the run queue.
|
||||
struct BatchTaskIter<'a, T: 'static> {
|
||||
buffer: &'a [UnsafeCell<MaybeUninit<task::Notified<T>>>; LOCAL_QUEUE_CAPACITY],
|
||||
@@ -337,7 +350,7 @@ impl<T> Local<T> {
|
||||
// values again, and we are the only producer.
|
||||
let batch_iter = BatchTaskIter {
|
||||
buffer: &self.inner.buffer,
|
||||
head: head as UnsignedLong,
|
||||
head: head.wrapping_add(NUM_TASKS_TAKEN) as UnsignedLong,
|
||||
i: 0,
|
||||
};
|
||||
overflow.push_batch(batch_iter.chain(std::iter::once(task)));
|
||||
@@ -378,7 +391,7 @@ impl<T> Local<T> {
|
||||
let res = self
|
||||
.inner
|
||||
.head
|
||||
.compare_exchange(head, next, AcqRel, Acquire);
|
||||
.compare_exchange_weak(head, next, AcqRel, Acquire);
|
||||
|
||||
match res {
|
||||
Ok(_) => break real as usize & MASK,
|
||||
@@ -494,7 +507,7 @@ impl<T> Steal<T> {
|
||||
let res = self
|
||||
.0
|
||||
.head
|
||||
.compare_exchange(prev_packed, next_packed, AcqRel, Acquire);
|
||||
.compare_exchange_weak(prev_packed, next_packed, AcqRel, Acquire);
|
||||
|
||||
match res {
|
||||
Ok(_) => break n,
|
||||
@@ -543,17 +556,11 @@ impl<T> Steal<T> {
|
||||
let res = self
|
||||
.0
|
||||
.head
|
||||
.compare_exchange(prev_packed, next_packed, AcqRel, Acquire);
|
||||
.compare_exchange_weak(prev_packed, next_packed, AcqRel, Acquire);
|
||||
|
||||
match res {
|
||||
Ok(_) => return n,
|
||||
Err(actual) => {
|
||||
let (actual_steal, actual_real) = unpack(actual);
|
||||
|
||||
assert_ne!(actual_steal, actual_real);
|
||||
|
||||
prev_packed = actual;
|
||||
}
|
||||
Err(actual) => prev_packed = actual,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
use crate::loom::sync::{Arc, Mutex};
|
||||
use crate::runtime;
|
||||
use crate::runtime::scheduler::multi_thread::{
|
||||
idle, queue, Counters, Handle, Idle, Overflow, Parker, Stats, TraceStatus, Unparker,
|
||||
idle, park, queue, Counters, Handle, Idle, Overflow, Parker, Stats, TraceStatus, Unparker,
|
||||
};
|
||||
use crate::runtime::scheduler::{inject, Defer, Lock};
|
||||
use crate::runtime::task::OwnedTasks;
|
||||
@@ -139,6 +139,15 @@ struct Core {
|
||||
/// True if the scheduler is being traced
|
||||
is_traced: bool,
|
||||
|
||||
/// Whether or not the worker has just returned from a park in which we
|
||||
/// parked on the I/O driver.
|
||||
had_driver: park::HadDriver,
|
||||
|
||||
/// If `true`, the worker should eagerly notify another worker when polling
|
||||
/// the first task after returning from a park in which it parked on the I/O
|
||||
/// or time driver.
|
||||
enable_eager_driver_handoff: bool,
|
||||
|
||||
/// Parker
|
||||
///
|
||||
/// Stored in an `Option` as the parker is added / removed to make the
|
||||
@@ -254,6 +263,7 @@ type Notified = task::Notified<Arc<Handle>>;
|
||||
/// improvements.
|
||||
const MAX_LIFO_POLLS_PER_TICK: usize = 3;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn create(
|
||||
size: usize,
|
||||
park: Parker,
|
||||
@@ -262,6 +272,7 @@ pub(super) fn create(
|
||||
seed_generator: RngSeedGenerator,
|
||||
config: Config,
|
||||
timer_flavor: TimerFlavor,
|
||||
name: Option<String>,
|
||||
) -> (Arc<Handle>, Launch) {
|
||||
let mut cores = Vec::with_capacity(size);
|
||||
let mut remotes = Vec::with_capacity(size);
|
||||
@@ -286,6 +297,8 @@ pub(super) fn create(
|
||||
is_searching: false,
|
||||
is_shutdown: false,
|
||||
is_traced: false,
|
||||
enable_eager_driver_handoff: config.enable_eager_driver_handoff,
|
||||
had_driver: park::HadDriver::No,
|
||||
park: Some(park),
|
||||
global_queue_interval: stats.tuned_global_queue_interval(&config),
|
||||
stats,
|
||||
@@ -301,6 +314,7 @@ pub(super) fn create(
|
||||
|
||||
let remotes_len = remotes.len();
|
||||
let handle = Arc::new(Handle {
|
||||
name,
|
||||
task_hooks: TaskHooks::from_config(&config),
|
||||
shared: Shared {
|
||||
remotes: remotes.into_boxed_slice(),
|
||||
@@ -621,7 +635,30 @@ impl Context {
|
||||
|
||||
// Make sure the worker is not in the **searching** state. This enables
|
||||
// another idle worker to try to steal work.
|
||||
core.transition_from_searching(&self.worker);
|
||||
let notified_parked_worker = core.transition_from_searching(&self.worker);
|
||||
|
||||
// If the setting to wake eagerly when releasing the I/O driver is
|
||||
// enabled, and this worker had the driver, wake a parked worker to come
|
||||
// grab it from us.
|
||||
//
|
||||
// Note that this is only done when we are *actually* about to poll a
|
||||
// task, rather than whenever the worker has unparked. When the worker
|
||||
// has been unparked, it may not actually have any tasks to poll, and if
|
||||
// it's still holding the I/O driver, it should just go back to polling
|
||||
// the driver again, rather than trying to wake someone else spuriously.
|
||||
//
|
||||
// Note that this explicitly checks `cfg!(tokio_unstable)` in addition,
|
||||
// as that should result in this whole expression being eliminated at
|
||||
// compile-time when unstable features are disabled.
|
||||
if cfg!(tokio_unstable)
|
||||
&& core.enable_eager_driver_handoff
|
||||
&& core.had_driver == park::HadDriver::Yes
|
||||
&& !notified_parked_worker
|
||||
// don't do it a second time
|
||||
{
|
||||
core.had_driver = park::HadDriver::No;
|
||||
self.worker.handle.notify_parked_local();
|
||||
}
|
||||
|
||||
self.assert_lifo_enabled_is_correct(&core);
|
||||
|
||||
@@ -830,11 +867,11 @@ impl Context {
|
||||
};
|
||||
|
||||
// Park thread
|
||||
if let Some(timeout) = duration {
|
||||
park.park_timeout(&self.worker.handle.driver, timeout);
|
||||
let had_driver = if let Some(timeout) = duration {
|
||||
park.park_timeout(&self.worker.handle.driver, timeout)
|
||||
} else {
|
||||
park.park(&self.worker.handle.driver);
|
||||
}
|
||||
park.park(&self.worker.handle.driver)
|
||||
};
|
||||
|
||||
self.defer.wake();
|
||||
|
||||
@@ -859,6 +896,8 @@ impl Context {
|
||||
|
||||
// Place `park` back in `core`
|
||||
core.park = Some(park);
|
||||
core.had_driver = had_driver;
|
||||
|
||||
if core.should_notify_others() {
|
||||
self.worker.handle.notify_parked_local();
|
||||
}
|
||||
@@ -1006,6 +1045,11 @@ impl Context {
|
||||
None => f(None),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
pub(crate) fn worker_index(&self) -> usize {
|
||||
self.worker.index
|
||||
}
|
||||
}
|
||||
|
||||
impl Core {
|
||||
@@ -1035,12 +1079,27 @@ impl Core {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Other threads can only **remove** tasks from the current worker's
|
||||
// `run_queue`. So, we can be confident that by the time we call
|
||||
// `run_queue.push_back` below, there will be *at least* `cap`
|
||||
// available slots in the queue.
|
||||
let cap = usize::min(
|
||||
// Other threads can only **remove** tasks from the current
|
||||
// worker's `run_queue`. So, we can be confident that by the
|
||||
// time we call `run_queue.push_back` below, there will be *at
|
||||
// least* `cap` available slots in the queue.
|
||||
//
|
||||
// Note that even though `next_local_task()` just returned
|
||||
// `None`, this may be different from `max_capacity()` if
|
||||
// another worker is currently stealing tasks from us.
|
||||
self.run_queue.remaining_slots(),
|
||||
// We want to make sure that all of the tasks we take end up in
|
||||
// the first half of the local queue. This ensures that the
|
||||
// tasks do not get pushed to the inject queue again if overflow
|
||||
// occurs, as overflow only affects tasks in the second half of
|
||||
// the local queue.
|
||||
//
|
||||
// Note that even if there are concurrent stealers, we do not
|
||||
// need to consider the value of `remaining_slots()` because a
|
||||
// future call to `push_overflow()` can only succeed once that
|
||||
// concurrent stealer has finished stealing, so at that point
|
||||
// the tasks we are adding now will be in the first half.
|
||||
self.run_queue.max_capacity() / 2,
|
||||
);
|
||||
|
||||
@@ -1117,13 +1176,13 @@ impl Core {
|
||||
self.is_searching
|
||||
}
|
||||
|
||||
fn transition_from_searching(&mut self, worker: &Worker) {
|
||||
fn transition_from_searching(&mut self, worker: &Worker) -> bool {
|
||||
if !self.is_searching {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
self.is_searching = false;
|
||||
worker.handle.transition_worker_from_searching();
|
||||
worker.handle.transition_worker_from_searching()
|
||||
}
|
||||
|
||||
fn has_tasks(&self) -> bool {
|
||||
@@ -1376,12 +1435,18 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_parked_local(&self) {
|
||||
/// Notify a parked worker.
|
||||
///
|
||||
/// Returns `true` if a worker was notified, `false` otherwise.
|
||||
fn notify_parked_local(&self) -> bool {
|
||||
super::counters::inc_num_inc_notify_local();
|
||||
|
||||
if let Some(index) = self.shared.idle.worker_to_notify(&self.shared) {
|
||||
super::counters::inc_num_unparks_local();
|
||||
self.shared.remotes[index].unpark.unpark(&self.driver);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1410,11 +1475,14 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
fn transition_worker_from_searching(&self) {
|
||||
/// Returns `true` if another parked worker was notified, `false` otherwise.
|
||||
fn transition_worker_from_searching(&self) -> bool {
|
||||
if self.shared.idle.transition_worker_from_searching() {
|
||||
// We are the final searching worker. Because work was found, we
|
||||
// need to notify another worker.
|
||||
self.notify_parked_local();
|
||||
self.notify_parked_local()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,16 +67,15 @@ impl fmt::Display for Id {
|
||||
|
||||
impl Id {
|
||||
pub(crate) fn next() -> Self {
|
||||
use crate::loom::sync::atomic::Ordering::Relaxed;
|
||||
use crate::loom::sync::atomic::StaticAtomicU64;
|
||||
use crate::loom::sync::atomic::{AtomicU64, Ordering::Relaxed};
|
||||
|
||||
#[cfg(all(test, loom))]
|
||||
crate::loom::lazy_static! {
|
||||
static ref NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(1);
|
||||
static ref NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
||||
}
|
||||
|
||||
#[cfg(not(all(test, loom)))]
|
||||
static NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(1);
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
loop {
|
||||
let id = NEXT_ID.fetch_add(1, Relaxed);
|
||||
|
||||
@@ -10,10 +10,11 @@ use std::ffi::c_void;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::ptr::NonNull;
|
||||
use std::task::{self, Poll};
|
||||
|
||||
mod symbol;
|
||||
mod trace_impl;
|
||||
mod tree;
|
||||
|
||||
use symbol::Symbol;
|
||||
@@ -29,8 +30,12 @@ pub(crate) struct Context {
|
||||
/// The address of [`Trace::root`] establishes an upper unwinding bound on
|
||||
/// the backtraces in `Trace`.
|
||||
active_frame: Cell<Option<NonNull<Frame>>>,
|
||||
/// The place to stash backtraces.
|
||||
collector: Cell<Option<Trace>>,
|
||||
|
||||
/// The function that is invoked at each leaf future inside of Tokio
|
||||
///
|
||||
/// For example, within tokio::time:sleep, sockets. etc.
|
||||
#[allow(clippy::type_complexity)]
|
||||
trace_leaf_fn: Cell<Option<NonNull<dyn FnMut(&TraceMeta)>>>,
|
||||
}
|
||||
|
||||
/// A [`Frame`] in an intrusive, doubly-linked tree of [`Frame`]s.
|
||||
@@ -39,6 +44,8 @@ struct Frame {
|
||||
inner_addr: *const c_void,
|
||||
|
||||
/// The parent frame, if any.
|
||||
///
|
||||
/// Tracking parent allows nested `Root` futures to correctly manage their boundaries
|
||||
parent: Option<NonNull<Frame>>,
|
||||
}
|
||||
|
||||
@@ -72,7 +79,7 @@ impl Context {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Context {
|
||||
active_frame: Cell::new(None),
|
||||
collector: Cell::new(None),
|
||||
trace_leaf_fn: Cell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,28 +103,152 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
fn with_current_collector<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&Cell<Option<Trace>>) -> R,
|
||||
{
|
||||
// SAFETY: This call can only access the collector field, so it cannot
|
||||
// break the trace frame linked list.
|
||||
fn current_frame_addr() -> Option<*const c_void> {
|
||||
// SAFETY: This call does not modify the linked list structure
|
||||
unsafe {
|
||||
Self::try_with_current(|context| f(&context.collector)).expect(FAIL_NO_THREAD_LOCAL)
|
||||
Context::try_with_current(|ctx| {
|
||||
ctx.active_frame
|
||||
.get()
|
||||
.map(|frame| frame.as_ref().inner_addr)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the provided closure if we are being traced.
|
||||
fn try_with_current_trace_leaf_fn<F, R>(f: F) -> Option<R>
|
||||
where
|
||||
F: for<'a> FnOnce(&'a mut dyn FnMut(&TraceMeta)) -> R,
|
||||
{
|
||||
let mut ret = None;
|
||||
|
||||
let inner = |context: &Context| {
|
||||
if let Some(mut trace_leaf_fn) = context.trace_leaf_fn.replace(None) {
|
||||
let _restore = defer(move || {
|
||||
context.trace_leaf_fn.set(Some(trace_leaf_fn));
|
||||
});
|
||||
|
||||
// SAFETY: The trace leaf fn is valid for the duration in which it's stored in the
|
||||
// context. Furthermore, re-entrant calls are not possible because we store `None` for
|
||||
// the duration in which we hold a mutable reference, so access is exclusive for that
|
||||
// duration.
|
||||
ret = Some(f(unsafe { trace_leaf_fn.as_mut() }));
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: This call can only access the trace_leaf_fn field, so it cannot break the trace
|
||||
// frame linked list.
|
||||
unsafe { Self::try_with_current(inner) };
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Produces `true` if the current task is being traced; otherwise false.
|
||||
pub(crate) fn is_tracing() -> bool {
|
||||
Self::with_current_collector(|maybe_collector| {
|
||||
let collector = maybe_collector.take();
|
||||
let result = collector.is_some();
|
||||
maybe_collector.set(collector);
|
||||
result
|
||||
})
|
||||
// SAFETY: This call can only access the trace_leaf_fn field, so it cannot break the trace
|
||||
// frame linked list.
|
||||
unsafe { Self::try_with_current(|ctx| ctx.trace_leaf_fn.get().is_some()).unwrap_or(false) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata passed into the `trace_leaf` callback for [`trace_with`]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub struct TraceMeta {
|
||||
/// The root boundary address set by [`Root::poll`] if any.
|
||||
///
|
||||
/// When using unwinding the stack, this is the address at which
|
||||
/// stack walking should stop. It corresponds to the `Root::poll` function pointer.
|
||||
pub root_addr: Option<*const c_void>,
|
||||
|
||||
/// The address of the internal `trace_leaf` function that triggered this callback.
|
||||
///
|
||||
/// When capturing a backtrace, use this as the lower bound — frames at or below
|
||||
/// this address are internal implementation details and should be excluded.
|
||||
pub trace_leaf_addr: *const c_void,
|
||||
}
|
||||
|
||||
/// Runs `f`. If `f` hits a Tokio yield point `trace_leaf` will be invoked.
|
||||
///
|
||||
/// This allows taking a task dump with caller-provided task dump machinery. If `f` is the poll
|
||||
/// function of a future and that future returns `Poll::Pending`, then `trace_leaf` will be
|
||||
/// invoked. `trace_leaf` can then take a backtrace to determine exactly where the yield occurred.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::future::Future;
|
||||
/// use std::task::Poll;
|
||||
/// use tokio::runtime::dump::{trace_with, Trace, TraceMeta};
|
||||
///
|
||||
/// fn my_trace_leaf(_meta: &TraceMeta, count: &mut u32) {
|
||||
/// *count += 1;
|
||||
/// }
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let mut fut = std::pin::pin!(async {
|
||||
/// tokio::task::yield_now().await;
|
||||
/// });
|
||||
///
|
||||
/// let mut leaf_count = 0;
|
||||
///
|
||||
/// Trace::root(std::future::poll_fn(|cx| {
|
||||
/// trace_with(
|
||||
/// || { let _ = fut.as_mut().poll(cx); },
|
||||
/// |meta| my_trace_leaf(meta, &mut leaf_count),
|
||||
/// );
|
||||
/// Poll::Ready(())
|
||||
/// })).await;
|
||||
///
|
||||
/// assert!(leaf_count > 0);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn trace_with<FN, FT, R>(f: FN, mut trace_leaf: FT) -> R
|
||||
where
|
||||
FN: FnOnce() -> R,
|
||||
FT: FnMut(&TraceMeta),
|
||||
{
|
||||
let trace_leaf_dyn = (&mut trace_leaf) as &mut (dyn FnMut(&TraceMeta) + '_);
|
||||
// SAFETY: The raw pointer is removed from the thread local before `trace_leaf` is dropped, so
|
||||
// this transmute cannot lead to the violation of any lifetime requirements.
|
||||
let trace_leaf_dyn = unsafe {
|
||||
std::mem::transmute::<
|
||||
*mut (dyn FnMut(&TraceMeta) + '_),
|
||||
*mut (dyn FnMut(&TraceMeta) + 'static),
|
||||
>(trace_leaf_dyn)
|
||||
};
|
||||
// SAFETY: Pointer comes from reference, so not null.
|
||||
let trace_leaf_dyn = unsafe { NonNull::new_unchecked(trace_leaf_dyn) };
|
||||
|
||||
let mut old_trace_leaf_fn = None;
|
||||
|
||||
// Even if this access fails, that's okay. In that case, we still call the closure without
|
||||
// actually performing any tracing.
|
||||
//
|
||||
// SAFETY: This call can only access the trace_leaf_fn field, so it cannot break the trace
|
||||
// frame linked list.
|
||||
unsafe {
|
||||
Context::try_with_current(|ctx| {
|
||||
old_trace_leaf_fn = ctx.trace_leaf_fn.replace(Some(trace_leaf_dyn));
|
||||
})
|
||||
};
|
||||
|
||||
let _restore = defer(move || {
|
||||
// This ensures that `trace_leaf_fn` cannot be accessed after this call returns.
|
||||
//
|
||||
// SAFETY: This call can only access the trace_leaf_fn field, so it cannot
|
||||
// break the trace frame linked list.
|
||||
unsafe {
|
||||
Context::try_with_current(|ctx| {
|
||||
ctx.trace_leaf_fn.set(old_trace_leaf_fn);
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
f()
|
||||
}
|
||||
|
||||
impl Trace {
|
||||
/// Invokes `f`, returning both its result and the collection of backtraces
|
||||
/// captured at each sub-invocation of [`trace_leaf`].
|
||||
@@ -126,16 +257,15 @@ impl Trace {
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
let collector = Trace { backtraces: vec![] };
|
||||
trace_impl::capture(f)
|
||||
}
|
||||
|
||||
let previous = Context::with_current_collector(|current| current.replace(Some(collector)));
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self { backtraces: vec![] }
|
||||
}
|
||||
|
||||
let result = f();
|
||||
|
||||
let collector =
|
||||
Context::with_current_collector(|current| current.replace(previous)).unwrap();
|
||||
|
||||
(result, collector)
|
||||
fn push_backtrace(&mut self, bt: Vec<BacktraceFrame>) {
|
||||
self.backtraces.push(bt);
|
||||
}
|
||||
|
||||
/// The root of a trace.
|
||||
@@ -160,44 +290,15 @@ impl Trace {
|
||||
// internal implementation details of this crate).
|
||||
#[inline(never)]
|
||||
pub(crate) fn trace_leaf(cx: &mut task::Context<'_>) -> Poll<()> {
|
||||
// Safety: We don't manipulate the current context's active frame.
|
||||
let did_trace = unsafe {
|
||||
Context::try_with_current(|context_cell| {
|
||||
if let Some(mut collector) = context_cell.collector.take() {
|
||||
let mut frames = vec![];
|
||||
let mut above_leaf = false;
|
||||
let root_addr = Context::current_frame_addr();
|
||||
|
||||
if let Some(active_frame) = context_cell.active_frame.get() {
|
||||
let active_frame = active_frame.as_ref();
|
||||
let ret = Context::try_with_current_trace_leaf_fn(|leaf_fn| {
|
||||
let meta = TraceMeta {
|
||||
root_addr,
|
||||
trace_leaf_addr: trace_leaf as *const c_void,
|
||||
};
|
||||
leaf_fn(&meta);
|
||||
|
||||
backtrace::trace(|frame| {
|
||||
let below_root = !ptr::eq(frame.symbol_address(), active_frame.inner_addr);
|
||||
|
||||
// only capture frames above `Trace::leaf` and below
|
||||
// `Trace::root`.
|
||||
if above_leaf && below_root {
|
||||
frames.push(frame.to_owned().into());
|
||||
}
|
||||
|
||||
if ptr::eq(frame.symbol_address(), trace_leaf as *const _) {
|
||||
above_leaf = true;
|
||||
}
|
||||
|
||||
// only continue unwinding if we're below `Trace::root`
|
||||
below_root
|
||||
});
|
||||
}
|
||||
collector.backtraces.push(frames);
|
||||
context_cell.collector.set(Some(collector));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
if did_trace {
|
||||
// Use the same logic that `yield_now` uses to send out wakeups after
|
||||
// the task yields.
|
||||
context::with_scheduler(|scheduler| {
|
||||
@@ -209,10 +310,11 @@ pub(crate) fn trace_leaf(cx: &mut task::Context<'_>) -> Poll<()> {
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
match ret {
|
||||
Some(()) => Poll::Pending,
|
||||
None => Poll::Ready(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ impl Eq for Symbol {}
|
||||
impl fmt::Display for Symbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(name) = self.symbol.name() {
|
||||
let name = name.to_string();
|
||||
// Printing this using :# formatting omits crate disambiguators.
|
||||
let name = format!("{name:#}");
|
||||
let name = if let Some((name, _)) = name.rsplit_once("::") {
|
||||
name
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//! Current `backtrace::trace` + collector based backtrace implementation
|
||||
//!
|
||||
//! This implementation may eventually be extracted into a separate `tokio-taskdump` crate.
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use crate::runtime::task::trace::{trace_with, Trace, TraceMeta};
|
||||
|
||||
/// Capture using the default `backtrace::trace`-based implementation.
|
||||
#[inline(never)]
|
||||
pub(super) fn capture<F, R>(f: F) -> (R, Trace)
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
let mut trace = Trace::empty();
|
||||
|
||||
let result = trace_with(f, |meta| trace_leaf(meta, &mut trace));
|
||||
|
||||
(result, trace)
|
||||
}
|
||||
|
||||
/// Capture a backtrace via `backtrace::trace` and collect it into `trace`.
|
||||
pub(crate) fn trace_leaf(meta: &TraceMeta, trace: &mut Trace) {
|
||||
let mut frames: Vec<backtrace::BacktraceFrame> = vec![];
|
||||
let mut above_leaf = false;
|
||||
|
||||
if let Some(root_addr) = meta.root_addr {
|
||||
backtrace::trace(|frame| {
|
||||
let below_root = !ptr::eq(frame.symbol_address(), root_addr);
|
||||
|
||||
if above_leaf && below_root {
|
||||
frames.push(frame.to_owned().into());
|
||||
}
|
||||
|
||||
if ptr::eq(frame.symbol_address(), meta.trace_leaf_addr) {
|
||||
above_leaf = true;
|
||||
}
|
||||
|
||||
below_root
|
||||
});
|
||||
}
|
||||
trace.push_backtrace(frames);
|
||||
}
|
||||
@@ -5,9 +5,15 @@ pub(crate) struct ThreadId(NonZeroU64);
|
||||
|
||||
impl ThreadId {
|
||||
pub(crate) fn next() -> Self {
|
||||
use crate::loom::sync::atomic::{Ordering::Relaxed, StaticAtomicU64};
|
||||
use crate::loom::sync::atomic::{AtomicU64, Ordering::Relaxed};
|
||||
|
||||
static NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(0);
|
||||
#[cfg(all(test, loom))]
|
||||
crate::loom::lazy_static! {
|
||||
static ref NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
}
|
||||
|
||||
#[cfg(not(all(test, loom)))]
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
let mut last = NEXT_ID.load(Relaxed);
|
||||
loop {
|
||||
|
||||
@@ -5,7 +5,7 @@ mod level;
|
||||
pub(crate) use self::level::Expiration;
|
||||
use self::level::Level;
|
||||
|
||||
use std::{array, ptr::NonNull};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use super::entry::STATE_DEREGISTERED;
|
||||
use super::EntryList;
|
||||
@@ -50,9 +50,13 @@ pub(super) const MAX_DURATION: u64 = (1 << (6 * NUM_LEVELS)) - 1;
|
||||
impl Wheel {
|
||||
/// Creates a new timing wheel.
|
||||
pub(crate) fn new() -> Wheel {
|
||||
let mut levels = Vec::with_capacity(NUM_LEVELS);
|
||||
for i in 0..NUM_LEVELS {
|
||||
levels.push(Level::new(i));
|
||||
}
|
||||
Wheel {
|
||||
elapsed: 0,
|
||||
levels: Box::new(array::from_fn(Level::new)),
|
||||
levels: levels.into_boxed_slice().try_into().unwrap(),
|
||||
pending: EntryList::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ use self::level::Level;
|
||||
use super::cancellation_queue::Sender;
|
||||
use super::{EntryHandle, EntryList, WakeQueue};
|
||||
|
||||
use std::array;
|
||||
|
||||
/// Hashed timing wheel implementation.
|
||||
///
|
||||
/// See [`Driver`] documentation for some implementation notes.
|
||||
@@ -41,9 +39,13 @@ pub(super) const MAX_DURATION: u64 = (1 << (6 * NUM_LEVELS)) - 1;
|
||||
impl Wheel {
|
||||
/// Creates a new timing wheel.
|
||||
pub(crate) fn new() -> Wheel {
|
||||
let mut levels = Vec::with_capacity(NUM_LEVELS);
|
||||
for i in 0..NUM_LEVELS {
|
||||
levels.push(Level::new(i));
|
||||
}
|
||||
Wheel {
|
||||
elapsed: 0,
|
||||
levels: Box::new(array::from_fn(Level::new)),
|
||||
levels: levels.into_boxed_slice().try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -384,7 +384,7 @@ impl Semaphore {
|
||||
let new = curr.saturating_sub(n);
|
||||
match self.permits.compare_exchange_weak(
|
||||
curr_bits,
|
||||
new << Self::PERMIT_SHIFT,
|
||||
(new << Self::PERMIT_SHIFT) | (curr_bits & Self::CLOSED),
|
||||
AcqRel,
|
||||
Acquire,
|
||||
) {
|
||||
|
||||
@@ -220,11 +220,6 @@ impl<T> Block<T> {
|
||||
self.header.ready_slots.fetch_or(TX_CLOSED, Release);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn is_closed(&self) -> bool {
|
||||
let ready_bits = self.header.ready_slots.load(Acquire);
|
||||
is_tx_closed(ready_bits)
|
||||
}
|
||||
|
||||
/// Resets the block to a blank state. This enables reusing blocks in the
|
||||
/// channel.
|
||||
///
|
||||
|
||||
@@ -1853,14 +1853,12 @@ impl<T> OwnedPermit<T> {
|
||||
///
|
||||
/// [`Sender`]: Sender
|
||||
pub fn release(mut self) -> Sender<T> {
|
||||
use chan::Semaphore;
|
||||
|
||||
let chan = self.chan.take().unwrap_or_else(|| {
|
||||
unreachable!("OwnedPermit channel is only taken when the permit is moved")
|
||||
});
|
||||
|
||||
// Add the permit back to the semaphore
|
||||
chan.semaphore().add_permit();
|
||||
drop(Permit { chan: &chan });
|
||||
Sender { chan }
|
||||
}
|
||||
|
||||
@@ -1919,21 +1917,10 @@ impl<T> OwnedPermit<T> {
|
||||
|
||||
impl<T> Drop for OwnedPermit<T> {
|
||||
fn drop(&mut self) {
|
||||
use chan::Semaphore;
|
||||
|
||||
// Are we still holding onto the sender?
|
||||
if let Some(chan) = self.chan.take() {
|
||||
let semaphore = chan.semaphore();
|
||||
|
||||
// Add the permit back to the semaphore
|
||||
semaphore.add_permit();
|
||||
|
||||
// If this `OwnedPermit` is holding the last sender for this
|
||||
// channel, wake the receiver so that it can be notified that the
|
||||
// channel is closed.
|
||||
if semaphore.is_closed() && semaphore.is_idle() {
|
||||
chan.wake_rx();
|
||||
}
|
||||
// Reuse Drop impl of non-owned Permit.
|
||||
drop(Permit { chan: &chan });
|
||||
}
|
||||
|
||||
// Otherwise, do nothing.
|
||||
|
||||
@@ -306,13 +306,11 @@ impl<T, S: Semaphore> Rx<T, S> {
|
||||
return Ready(Some(value));
|
||||
}
|
||||
Some(Read::Closed) => {
|
||||
// TODO: This check may not be required as it most
|
||||
// likely can only return `true` at this point. A
|
||||
// channel is closed when all tx handles are
|
||||
// A channel is closed when all tx handles are
|
||||
// dropped. Dropping a tx handle releases memory,
|
||||
// which ensures that if dropping the tx handle is
|
||||
// visible, then all messages sent are also visible.
|
||||
assert!(self.inner.semaphore.is_idle());
|
||||
debug_assert!(self.inner.semaphore.is_idle());
|
||||
coop.made_progress();
|
||||
return Ready(None);
|
||||
}
|
||||
@@ -380,13 +378,11 @@ impl<T, S: Semaphore> Rx<T, S> {
|
||||
if number_added > 0 {
|
||||
self.inner.semaphore.add_permits(number_added);
|
||||
}
|
||||
// TODO: This check may not be required as it most
|
||||
// likely can only return `true` at this point. A
|
||||
// channel is closed when all tx handles are
|
||||
// A channel is closed when all tx handles are
|
||||
// dropped. Dropping a tx handle releases memory,
|
||||
// which ensures that if dropping the tx handle is
|
||||
// visible, then all messages sent are also visible.
|
||||
assert!(self.inner.semaphore.is_idle());
|
||||
debug_assert!(self.inner.semaphore.is_idle());
|
||||
coop.made_progress();
|
||||
return Ready(number_added);
|
||||
}
|
||||
@@ -415,7 +411,7 @@ impl<T, S: Semaphore> Rx<T, S> {
|
||||
try_recv!();
|
||||
|
||||
if rx_fields.rx_closed && self.inner.semaphore.is_idle() {
|
||||
assert!(buffer.is_empty());
|
||||
debug_assert_eq!(buffer.len(), initial_length);
|
||||
coop.made_progress();
|
||||
Ready(0usize)
|
||||
} else {
|
||||
@@ -440,7 +436,9 @@ impl<T, S: Semaphore> Rx<T, S> {
|
||||
}
|
||||
TryPopResult::Closed => return Err(TryRecvError::Disconnected),
|
||||
// If close() was called, an empty queue should report Disconnected.
|
||||
TryPopResult::Empty if rx_fields.rx_closed => {
|
||||
TryPopResult::Empty
|
||||
if rx_fields.rx_closed && self.inner.semaphore.is_idle() =>
|
||||
{
|
||||
return Err(TryRecvError::Disconnected)
|
||||
}
|
||||
TryPopResult::Empty => return Err(TryRecvError::Empty),
|
||||
|
||||
+50
-12
@@ -239,15 +239,6 @@ impl<T> Tx<T> {
|
||||
let _ = unsafe { Box::from_raw(block.as_ptr()) };
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_closed(&self) -> bool {
|
||||
let tail = self.block_tail.load(Acquire);
|
||||
|
||||
unsafe {
|
||||
let tail_block = &*tail;
|
||||
tail_block.is_closed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Tx<T> {
|
||||
@@ -271,11 +262,58 @@ impl<T> Rx<T> {
|
||||
self.len(tx) == 0
|
||||
}
|
||||
|
||||
// Guaranteed to return true if `slot_index` is the fake message sent on channel close.
|
||||
// Guaranteed to return false if `slot_index` is a fully sent message.
|
||||
//
|
||||
// For messages that are partially sent, may return either true or false.
|
||||
fn is_maybe_closed(&self, tx: &Tx<T>, slot_index: usize) -> bool {
|
||||
let start_index = block::start_index(slot_index);
|
||||
|
||||
let tail = tx.block_tail.load(Acquire);
|
||||
// SAFETY: Only the receiver frees blocks, so since we are the receiver, this will not be
|
||||
// freed right now.
|
||||
let tail_ref = unsafe { &*tail };
|
||||
if tail_ref.is_at_index(start_index) {
|
||||
return !tail_ref.has_value(slot_index);
|
||||
}
|
||||
|
||||
// This method is optimized for checking whether the last value is present, so most of the
|
||||
// time it is in `block_tail`. However, this isn't always the case since it's possible
|
||||
// that the list was grown with an empty block, in which case `block_tail` points one block
|
||||
// too far. To handle this case, we walk the list from the head.
|
||||
let mut block_ptr = Some(self.head);
|
||||
|
||||
while let Some(block) = block_ptr {
|
||||
// SAFETY: Only the receiver frees blocks, so since we are the receiver, this will not
|
||||
// be freed right now.
|
||||
let block_ref = unsafe { block.as_ref() };
|
||||
if block_ref.is_at_index(start_index) {
|
||||
return !block_ref.has_value(slot_index);
|
||||
}
|
||||
block_ptr = block_ref.load_next(Acquire);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self, tx: &Tx<T>) -> usize {
|
||||
// When all the senders are dropped, there will be a last block in the tail position,
|
||||
// but it will be closed
|
||||
let tail_position = tx.tail_position.load(Acquire);
|
||||
tail_position - self.index - (tx.is_closed() as usize)
|
||||
let mut len = tail_position.wrapping_sub(self.index);
|
||||
debug_assert!(0 <= len as isize);
|
||||
if len == 0 {
|
||||
return 0;
|
||||
}
|
||||
// There are messages present in the queue. However, it's possible that the last message is
|
||||
// a fake "closed" message that we do not wish to count. To avoid counting it, we do not
|
||||
// count the last message if the ready bit is unset.
|
||||
//
|
||||
// Note that it is also possible for the ready bit to be unset on a normal message, but
|
||||
// this happens only if that message is currently being sent *right now* in parallel on
|
||||
// another thread. That is okay because it is optional to count messages that are currently
|
||||
// being sent.
|
||||
if self.is_maybe_closed(tx, tail_position.wrapping_sub(1)) {
|
||||
len -= 1;
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
/// Pops the next value off the queue.
|
||||
|
||||
@@ -137,10 +137,10 @@ pub mod error;
|
||||
/// This value must be a power of 2. It also must be smaller than the number of
|
||||
/// bits in `usize`.
|
||||
#[cfg(all(target_pointer_width = "64", not(loom)))]
|
||||
const BLOCK_CAP: usize = 32;
|
||||
pub(crate) const BLOCK_CAP: usize = 32;
|
||||
|
||||
#[cfg(all(not(target_pointer_width = "64"), not(loom)))]
|
||||
const BLOCK_CAP: usize = 16;
|
||||
pub(crate) const BLOCK_CAP: usize = 16;
|
||||
|
||||
#[cfg(loom)]
|
||||
const BLOCK_CAP: usize = 2;
|
||||
pub(crate) const BLOCK_CAP: usize = 2;
|
||||
|
||||
@@ -1118,6 +1118,14 @@ impl NotifiedProject<'_> {
|
||||
State::Init => {
|
||||
let curr = notify.state.load(SeqCst);
|
||||
|
||||
// Check if `notify_waiters` was called before attempting to acquire
|
||||
// the `NOTIFIED` state. If a broadcast occurred, we will be woken by it,
|
||||
// leaving the `notify_one` permit for other waiters.
|
||||
if get_num_notify_waiters_calls(curr) != *notify_waiters_calls {
|
||||
*state = State::Done;
|
||||
continue 'outer_loop;
|
||||
}
|
||||
|
||||
// Optimistically try acquiring a pending notification
|
||||
let res = notify.state.compare_exchange(
|
||||
set_state(curr, NOTIFIED),
|
||||
|
||||
@@ -583,7 +583,7 @@ impl<T> Sender<T> {
|
||||
/// not be sent.
|
||||
///
|
||||
/// This method consumes `self` as only one value may ever be sent on a `oneshot`
|
||||
/// channel. It is not marked async because sending a message to an `oneshot`
|
||||
/// channel. It is not marked async because sending a message to a `oneshot`
|
||||
/// channel never requires any form of waiting. Because of this, the `send`
|
||||
/// method can be used in both synchronous and asynchronous code without
|
||||
/// problems.
|
||||
|
||||
@@ -265,12 +265,13 @@ impl<T: ?Sized> RwLock<T> {
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `max_reads` is more than `u32::MAX >> 3`.
|
||||
/// Panics if `max_reads` is `0` or is bigger than `u32::MAX >> 3`.
|
||||
#[track_caller]
|
||||
pub fn with_max_readers(value: T, max_reads: u32) -> RwLock<T>
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
assert_ne!(max_reads, 0, "a RwLock may not be created with 0 readers");
|
||||
assert!(
|
||||
max_reads <= MAX_READS,
|
||||
"a RwLock may not be created with more than {MAX_READS} readers"
|
||||
@@ -366,11 +367,16 @@ impl<T: ?Sized> RwLock<T> {
|
||||
///
|
||||
/// static LOCK: RwLock<i32> = RwLock::const_with_max_readers(5, 1024);
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `max_reads` is `0` or is bigger than `u32::MAX >> 3`.
|
||||
#[cfg(not(all(loom, test)))]
|
||||
pub const fn const_with_max_readers(value: T, max_reads: u32) -> RwLock<T>
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
assert!(max_reads != 0, "a RwLock may not be created with 0 readers");
|
||||
assert!(max_reads <= MAX_READS);
|
||||
|
||||
RwLock {
|
||||
@@ -773,6 +779,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
/// ```
|
||||
pub async fn write(&self) -> RwLockWriteGuard<'_, T> {
|
||||
let acquire_fut = async {
|
||||
debug_assert_ne!(self.mr, 0);
|
||||
self.s.acquire(self.mr as usize).await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
@@ -911,6 +918,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
let resource_span = self.resource_span.clone();
|
||||
|
||||
let acquire_fut = async {
|
||||
debug_assert_ne!(self.mr, 0);
|
||||
self.s.acquire(self.mr as usize).await.unwrap_or_else(|_| {
|
||||
// The semaphore was closed. but, we never explicitly close it, and we have a
|
||||
// handle to it through the Arc, which means that this can never happen.
|
||||
@@ -975,6 +983,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn try_write(&self) -> Result<RwLockWriteGuard<'_, T>, TryLockError> {
|
||||
debug_assert_ne!(self.mr, 0);
|
||||
match self.s.try_acquire(self.mr as usize) {
|
||||
Ok(permit) => permit,
|
||||
Err(TryAcquireError::NoPermits) => return Err(TryLockError(())),
|
||||
@@ -1033,6 +1042,7 @@ impl<T: ?Sized> RwLock<T> {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn try_write_owned(self: Arc<Self>) -> Result<OwnedRwLockWriteGuard<T>, TryLockError> {
|
||||
debug_assert_ne!(self.mr, 0);
|
||||
match self.s.try_acquire(self.mr as usize) {
|
||||
Ok(permit) => permit,
|
||||
Err(TryAcquireError::NoPermits) => return Err(TryLockError(())),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::sync::mpsc;
|
||||
use crate::sync::mpsc::{self, BLOCK_CAP};
|
||||
|
||||
use loom::future::block_on;
|
||||
use loom::sync::Arc;
|
||||
@@ -222,3 +222,56 @@ fn nonempty_after_send() {
|
||||
join.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_empty_during_close() {
|
||||
loom::model(|| {
|
||||
let (tx, rx) = mpsc::channel::<()>(1);
|
||||
|
||||
let th1 = thread::spawn(move || {
|
||||
assert!(rx.is_empty());
|
||||
});
|
||||
|
||||
drop(tx);
|
||||
|
||||
th1.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn len_during_close_helper(n: usize) {
|
||||
loom::model(move || {
|
||||
let (tx, rx) = mpsc::channel::<()>(n + 1);
|
||||
|
||||
for _ in 0..n {
|
||||
tx.try_send(()).unwrap();
|
||||
}
|
||||
|
||||
let th1 = thread::spawn(move || {
|
||||
assert_eq!(rx.len(), n);
|
||||
});
|
||||
|
||||
drop(tx);
|
||||
|
||||
th1.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_during_close_0() {
|
||||
len_during_close_helper(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_during_close_1() {
|
||||
len_during_close_helper(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_during_close_block_cap() {
|
||||
len_during_close_helper(BLOCK_CAP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_during_close_block_cap_plus_1() {
|
||||
len_during_close_helper(BLOCK_CAP + 1);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
//! ```
|
||||
//! use tokio::sync::watch;
|
||||
//!
|
||||
//! #[tokio::main(flavor = "current_thread")]
|
||||
//! # #[tokio::main(flavor = "current_thread")]
|
||||
//! # async fn main() {
|
||||
//! let (tx, mut rx) = watch::channel("hello");
|
||||
//! tx.send("goodbye").unwrap();
|
||||
|
||||
@@ -328,9 +328,8 @@ impl<'a> Drop for LocalDataEnterGuard<'a> {
|
||||
cfg_rt! {
|
||||
/// Spawns a `!Send` future on the current [`LocalSet`] or [`LocalRuntime`].
|
||||
///
|
||||
/// This is possible when either using one of these types
|
||||
/// explicitly, or (with `tokio_unstable`) by opting to use the
|
||||
/// `"local"` runtime flavor in `tokio::main`:
|
||||
/// This is possible when either using one of these types explicitly, or by
|
||||
/// opting to use the `"local"` runtime flavor in `tokio::main`:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[tokio::main(flavor = "local")]
|
||||
@@ -374,10 +373,9 @@ cfg_rt! {
|
||||
/// }).await;
|
||||
/// # }
|
||||
/// ```
|
||||
/// With local runtime flavor ([Unstable API][unstable] only).
|
||||
/// With local runtime flavor.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(tokio_unstable)]
|
||||
/// #[tokio::main(flavor = "local")]
|
||||
/// async fn main() {
|
||||
/// let join = tokio::task::spawn_local(async {
|
||||
@@ -386,8 +384,6 @@ cfg_rt! {
|
||||
///
|
||||
/// join.await.unwrap()
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() {}
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -142,16 +142,31 @@ where
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if there is no current timer set.
|
||||
///
|
||||
/// It can be triggered when [`Builder::enable_time`] or
|
||||
/// [`Builder::enable_all`] are not included in the builder.
|
||||
///
|
||||
/// It can also panic whenever a timer is created outside of a
|
||||
/// Tokio runtime. That is why `rt.block_on(sleep(...))` will panic,
|
||||
/// since the function is executed outside of the runtime.
|
||||
/// Whereas `rt.block_on(async {sleep(...).await})` doesn't panic.
|
||||
/// And this is because wrapping the function on an async makes it lazy,
|
||||
/// and so gets executed inside the runtime successfully without
|
||||
/// panicking.
|
||||
///
|
||||
/// [`Builder::enable_time`]: crate::runtime::Builder::enable_time
|
||||
/// [`Builder::enable_all`]: crate::runtime::Builder::enable_all
|
||||
#[track_caller]
|
||||
pub fn timeout_at<F>(deadline: Instant, future: F) -> Timeout<F::IntoFuture>
|
||||
where
|
||||
F: IntoFuture,
|
||||
{
|
||||
let delay = sleep_until(deadline);
|
||||
|
||||
Timeout {
|
||||
value: future.into_future(),
|
||||
delay,
|
||||
}
|
||||
Timeout::new_with_delay(future.into_future(), delay)
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
|
||||
@@ -6,7 +6,7 @@ use tempfile::tempdir;
|
||||
use tokio::fs;
|
||||
|
||||
#[tokio::test]
|
||||
async fn symlink_file_windows() {
|
||||
async fn symlink_dir_windows() {
|
||||
const FILE_NAME: &str = "abc.txt";
|
||||
|
||||
let temp_dir = tempdir().unwrap();
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
//! Uring file operations tests.
|
||||
|
||||
#![cfg(all(
|
||||
tokio_unstable,
|
||||
feature = "io-uring",
|
||||
feature = "rt",
|
||||
feature = "fs",
|
||||
target_os = "linux"
|
||||
))]
|
||||
|
||||
use futures::future::FutureExt;
|
||||
use std::fs;
|
||||
use std::future::poll_fn;
|
||||
use std::task::Poll;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
// see: https://github.com/tokio-rs/tokio/issues/7979
|
||||
#[tokio::test]
|
||||
async fn file_descriptors_are_closed_when_cancelling_open_op() {
|
||||
let tmp = NamedTempFile::new().unwrap();
|
||||
let path = tmp.path().to_path_buf();
|
||||
|
||||
let fd_count_before_opens = fs::read_dir("/proc/self/fd").unwrap().count();
|
||||
|
||||
for _ in 0..128 {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let path = path.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
poll_fn(|cx| {
|
||||
let opt = {
|
||||
let mut opt = tokio::fs::OpenOptions::new();
|
||||
opt.read(true);
|
||||
opt
|
||||
};
|
||||
|
||||
let fut = opt.open(&path);
|
||||
|
||||
// If io_uring is enabled (and not falling back to the thread pool),
|
||||
// the first poll should return Pending. We don't check if the result
|
||||
// is actually pending because we run some checks on old kernel that
|
||||
// do not support uring.
|
||||
let _pending = Box::pin(fut).poll_unpin(cx);
|
||||
|
||||
tx.send(()).unwrap();
|
||||
|
||||
Poll::<()>::Pending
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
// Wait for the first poll
|
||||
rx.recv().await.unwrap();
|
||||
|
||||
handle.abort();
|
||||
|
||||
let res = handle.await.unwrap_err();
|
||||
assert!(res.is_cancelled());
|
||||
}
|
||||
|
||||
let fd_count_after_cancel = fs::read_dir("/proc/self/fd").unwrap().count();
|
||||
let leaked = fd_count_after_cancel.saturating_sub(fd_count_before_opens);
|
||||
|
||||
// Since we are opening 128 files, we expect that the related fds
|
||||
// related to this operation will be closed. Since some other fds
|
||||
// can be opened in the meantime, we expect this number to be higher
|
||||
// than the counter before opening the files. This number could be
|
||||
// lower, but to avoid test flakiness we check that this is at most
|
||||
// half the number of the file we opened to check if there's a leak.
|
||||
assert!(leaked <= 64);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
#![cfg(all(
|
||||
tokio_unstable,
|
||||
feature = "io-uring",
|
||||
feature = "rt",
|
||||
feature = "fs",
|
||||
target_os = "linux"
|
||||
))]
|
||||
|
||||
use std::fs;
|
||||
use std::future::Future;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||
use tokio::time::timeout;
|
||||
|
||||
/// Count currently-open fds in this process.
|
||||
fn fd_count() -> usize {
|
||||
fs::read_dir("/proc/self/fd").unwrap().count()
|
||||
}
|
||||
|
||||
/// First poll:
|
||||
/// - polls the inner `tokio::fs::OpenOptions::open()` future once,
|
||||
/// - expects `Pending` so we know we took the io_uring path,
|
||||
/// - registers the task waker with Tokio's uring machinery.
|
||||
///
|
||||
/// Second poll:
|
||||
/// - happens after the kernel completes the open and Tokio stores the CQE as
|
||||
/// `Lifecycle::Completed(cqe)` and wakes the task,
|
||||
/// - **intentionally does not poll the inner open future again**,
|
||||
/// - stays pending forever so the task can be aborted.
|
||||
///
|
||||
/// Aborting the task here drops the inner `open()` future while Tokio still has
|
||||
/// a completed CQE sitting in the slab.
|
||||
struct PollOpenOnceThenNeverRepoll<F> {
|
||||
inner: Pin<Box<F>>,
|
||||
first_poll_tx: Option<UnboundedSender<()>>,
|
||||
second_poll_tx: Option<UnboundedSender<()>>,
|
||||
polled_once: bool,
|
||||
}
|
||||
|
||||
impl<F: Future> Future for PollOpenOnceThenNeverRepoll<F> {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if !self.polled_once {
|
||||
// We don't check if the result is actually pending because
|
||||
// we run some checks on old kernel that do not support uring.
|
||||
let _pending = self.inner.as_mut().poll(cx);
|
||||
|
||||
self.polled_once = true;
|
||||
self.first_poll_tx.take().unwrap().send(()).unwrap();
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// We were polled again after the inner open completed and woke the task.
|
||||
// Crucially, we do *not* re-poll the inner future here.
|
||||
if let Some(tx) = self.second_poll_tx.take() {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
async fn completed_then_dropped_before_repoll(path: PathBuf) {
|
||||
let (first_tx, mut first_rx) = unbounded_channel();
|
||||
let (second_tx, mut second_rx) = unbounded_channel();
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let mut opt = tokio::fs::OpenOptions::new();
|
||||
opt.read(true);
|
||||
|
||||
PollOpenOnceThenNeverRepoll {
|
||||
inner: Box::pin(opt.open(&path)),
|
||||
first_poll_tx: Some(first_tx),
|
||||
second_poll_tx: Some(second_tx),
|
||||
polled_once: false,
|
||||
}
|
||||
.await;
|
||||
});
|
||||
|
||||
// Wait until the inner open has been polled once and registered with io_uring.
|
||||
first_rx.recv().await.unwrap();
|
||||
|
||||
// Wait until Tokio wakes the task because the open completed. At this point
|
||||
// the CQE should already be stored as `Lifecycle::Completed(cqe)`.
|
||||
let _ = timeout(Duration::from_secs(2), second_rx.recv()).await;
|
||||
|
||||
// Abort now, before the inner open future gets re-polled and consumes the CQE.
|
||||
handle.abort();
|
||||
let err = handle.await.unwrap_err();
|
||||
assert!(err.is_cancelled(), "task was not cancelled as expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uring_completed_then_dropped() {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let before = fd_count();
|
||||
let tmp = NamedTempFile::new().unwrap();
|
||||
let path = tmp.path().to_path_buf();
|
||||
|
||||
for _ in 0..128 {
|
||||
completed_then_dropped_before_repoll(path.clone()).await;
|
||||
}
|
||||
|
||||
// Give completions a moment to settle before counting fds.
|
||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
||||
|
||||
let after = fd_count();
|
||||
let leaked = after.saturating_sub(before);
|
||||
|
||||
// Since we are opening 128 files, we expect that the related fds
|
||||
// related to this operation will be closed. Since some other fds
|
||||
// can be opened in the meantime, we expect this number to be higher
|
||||
// than the counter before opening the files. This number could be
|
||||
// lower, but to avoid test flakiness we check that this is at most
|
||||
// half the number of the file we opened to check if there's a leak.
|
||||
assert!(leaked <= 64);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
//! Tests for AsyncRead on tokio::fs::File via io-uring.
|
||||
|
||||
#![cfg(all(
|
||||
tokio_unstable,
|
||||
feature = "io-uring",
|
||||
feature = "rt",
|
||||
feature = "fs",
|
||||
target_os = "linux"
|
||||
))]
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
use tokio_util::task::TaskTracker;
|
||||
|
||||
fn multi_rt(n: usize) -> Box<dyn Fn() -> Runtime> {
|
||||
Box::new(move || {
|
||||
Builder::new_multi_thread()
|
||||
.worker_threads(n)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn current_rt() -> Box<dyn Fn() -> Runtime> {
|
||||
Box::new(|| Builder::new_current_thread().enable_all().build().unwrap())
|
||||
}
|
||||
|
||||
fn rt_combinations() -> Vec<Box<dyn Fn() -> Runtime>> {
|
||||
vec![current_rt(), multi_rt(1), multi_rt(4), multi_rt(64)]
|
||||
}
|
||||
|
||||
fn create_temp_file(data: &[u8]) -> (NamedTempFile, PathBuf) {
|
||||
let mut tmp = NamedTempFile::new().unwrap();
|
||||
tmp.write_all(data).unwrap();
|
||||
tmp.flush().unwrap();
|
||||
let path = tmp.path().to_path_buf();
|
||||
(tmp, path)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read() {
|
||||
let data = b"hello io-uring";
|
||||
let (_tmp, path) = create_temp_file(data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = vec![0u8; data.len()];
|
||||
let n = file.read(&mut buf).await.unwrap();
|
||||
assert_eq!(n, data.len());
|
||||
assert_eq!(&buf[..n], data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_exact() {
|
||||
let data = b"exact read test data";
|
||||
let (_tmp, path) = create_temp_file(data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = vec![0u8; data.len()];
|
||||
file.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf, data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_to_end() {
|
||||
// Empty file
|
||||
{
|
||||
let (_tmp, path) = create_temp_file(b"");
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
let n = file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(n, 0);
|
||||
assert!(buf.is_empty());
|
||||
}
|
||||
|
||||
// Small file
|
||||
{
|
||||
let data: Vec<u8> = (0..100u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, data);
|
||||
}
|
||||
|
||||
// File larger than typical buffer
|
||||
{
|
||||
let data: Vec<u8> = (0..100_000u32).map(|i| (i % 256) as u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, data);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_to_string() {
|
||||
let text = "hello, io-uring world! 🦀";
|
||||
let (_tmp, path) = create_temp_file(text.as_bytes());
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut s = String::new();
|
||||
file.read_to_string(&mut s).await.unwrap();
|
||||
assert_eq!(s, text);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_empty() {
|
||||
let (_tmp, path) = create_temp_file(b"");
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = vec![0u8; 100];
|
||||
let n = file.read(&mut buf).await.unwrap();
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_large() {
|
||||
let data: Vec<u8> = (0..3_000_000u32).map(|i| (i % 256) as u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf.len(), data.len());
|
||||
assert_eq!(buf, data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_custom_buf_size() {
|
||||
let data: Vec<u8> = (0..1000u16).map(|i| (i % 256) as u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
file.set_max_buf_size(64);
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_partial_buf() {
|
||||
let data: Vec<u8> = (0..100u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
|
||||
// Read only 5 bytes
|
||||
let mut buf = vec![0u8; 5];
|
||||
file.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf, &data[..5]);
|
||||
|
||||
// Read the rest
|
||||
let mut rest = Vec::new();
|
||||
file.read_to_end(&mut rest).await.unwrap();
|
||||
assert_eq!(rest, data[5..]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_seek() {
|
||||
let data = b"hello world";
|
||||
let (_tmp, path) = create_temp_file(data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
file.seek(std::io::SeekFrom::Start(6)).await.unwrap();
|
||||
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, b"world");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_seek_back() {
|
||||
let data = b"hello world";
|
||||
let (_tmp, path) = create_temp_file(data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
|
||||
// Read first 5 bytes
|
||||
let mut buf = vec![0u8; 5];
|
||||
file.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf, b"hello");
|
||||
|
||||
// Seek back to start
|
||||
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
|
||||
|
||||
// Read full content
|
||||
let mut full = Vec::new();
|
||||
file.read_to_end(&mut full).await.unwrap();
|
||||
assert_eq!(full, data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_after_write() {
|
||||
let tmp = NamedTempFile::new().unwrap();
|
||||
let path = tmp.path().to_path_buf();
|
||||
|
||||
let mut file = tokio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
file.write_all(b"hello uring").await.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
|
||||
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, b"hello uring");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_cancel() {
|
||||
let data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
|
||||
let path2 = path.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
let mut file = File::open(&path2).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
buf
|
||||
});
|
||||
|
||||
handle.abort();
|
||||
let res = handle.await;
|
||||
assert!(res.unwrap_err().is_cancelled());
|
||||
|
||||
// Verify runtime still works
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, data);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_concurrent() {
|
||||
const NUM_FILES: usize = 100;
|
||||
|
||||
let files: Vec<_> = (0..NUM_FILES)
|
||||
.map(|i| {
|
||||
let data: Vec<u8> = (0..1024).map(|j| ((i as u16 + j) % 256) as u8).collect();
|
||||
create_temp_file(&data)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
for (i, (_tmp, path)) in files.iter().enumerate() {
|
||||
let path = path.clone();
|
||||
let expected: Vec<u8> = (0..1024).map(|j| ((i as u16 + j) % 256) as u8).collect();
|
||||
tracker.spawn(async move {
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, expected);
|
||||
});
|
||||
}
|
||||
|
||||
tracker.close();
|
||||
tracker.wait().await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_read_multi_runtime() {
|
||||
for rt_factory in rt_combinations() {
|
||||
let rt = rt_factory();
|
||||
let data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
|
||||
let result = rt.block_on(async {
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
buf
|
||||
});
|
||||
|
||||
assert_eq!(result, data);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shutdown_runtime_with_pending_reads() {
|
||||
for rt_factory in rt_combinations() {
|
||||
let rt = rt_factory();
|
||||
let (done_tx, done_rx) = mpsc::channel();
|
||||
|
||||
let data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
|
||||
for _ in 0..50 {
|
||||
let path = path.clone();
|
||||
rt.spawn(async move {
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
let _ = file.read_to_end(&mut buf).await;
|
||||
});
|
||||
}
|
||||
|
||||
std::thread::spawn(move || {
|
||||
rt.shutdown_timeout(Duration::from_millis(500));
|
||||
done_tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_read_from_std() {
|
||||
let mut tmp = NamedTempFile::new().unwrap();
|
||||
let data = b"hello from_std io-uring";
|
||||
tmp.write_all(data).unwrap();
|
||||
tmp.flush().unwrap();
|
||||
let path = tmp.path().to_owned();
|
||||
|
||||
let rt = Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let std_file = std::fs::File::open(&path).unwrap();
|
||||
let mut file = File::from_std(std_file);
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, data);
|
||||
});
|
||||
}
|
||||
|
||||
/// Read with a buffer smaller than the file content. Verifies internal
|
||||
/// buffering serves subsequent small reads without issuing new underlying read
|
||||
/// operations.
|
||||
#[tokio::test]
|
||||
async fn test_file_read_with_smaller_buf() {
|
||||
let data: Vec<u8> = (0..1024u16).map(|i| (i % 256) as u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
|
||||
// triggers an underlying read that fills the internal buffer with more data
|
||||
// than we consume here
|
||||
let mut buf = vec![0u8; 4];
|
||||
let n = file.read(&mut buf).await.unwrap();
|
||||
assert_eq!(n, 4);
|
||||
assert_eq!(&buf, &data[..4]);
|
||||
|
||||
// Second read: still smaller than what's buffered internally
|
||||
let mut buf = vec![0u8; 32];
|
||||
let n = file.read(&mut buf).await.unwrap();
|
||||
assert!(n > 0);
|
||||
assert_eq!(&buf[..n], &data[4..4 + n]);
|
||||
|
||||
// Read the rest
|
||||
let mut rest = Vec::new();
|
||||
file.read_to_end(&mut rest).await.unwrap();
|
||||
let total = 4 + n + rest.len();
|
||||
assert_eq!(total, data.len());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_read_with_bigger_buf() {
|
||||
let data = b"hello io-uring";
|
||||
let (_tmp, path) = create_temp_file(data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
|
||||
// Read with buffer larger than file's contents
|
||||
let mut buf = vec![0u8; 1024];
|
||||
let n = file.read(&mut buf).await.unwrap();
|
||||
assert!(n > 0 && n <= data.len());
|
||||
assert_eq!(&buf[..n], &data[..n]);
|
||||
|
||||
if n < data.len() {
|
||||
let mut rest = vec![0u8; 1024];
|
||||
let n2 = file.read(&mut rest).await.unwrap();
|
||||
assert_eq!(&rest[..n2], &data[n..n + n2]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a file larger than DEFAULT_MAX_BUF_SIZE (2 MiB). Verifies that
|
||||
/// chunked reads across multiple underlying operations produce correct data.
|
||||
#[tokio::test]
|
||||
async fn test_file_read_buffer_larger_than_max() {
|
||||
// 4 MiB + 1000 bytes to cross multiple chunk boundaries.
|
||||
let size = (4 << 20) + 1000;
|
||||
let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
|
||||
let (_tmp, path) = create_temp_file(&data);
|
||||
|
||||
let mut file = File::open(&path).await.unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).await.unwrap();
|
||||
assert_eq!(buf.len(), data.len());
|
||||
assert_eq!(buf, data);
|
||||
}
|
||||
|
||||
/// Read some bytes from a file, then write, verifying the implicit seek-back
|
||||
/// works correctly.
|
||||
#[tokio::test]
|
||||
async fn test_file_read_then_write() {
|
||||
let original = b"hello world, io-uring!";
|
||||
let (_tmp, path) = create_temp_file(original);
|
||||
|
||||
let mut file = tokio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut buf = vec![0u8; 5];
|
||||
file.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf, b"hello");
|
||||
|
||||
// Write at the current position
|
||||
file.write_all(b" REPLACED").await.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
|
||||
// Re-read the full file and verify the write landed correctly
|
||||
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
|
||||
let mut result = Vec::new();
|
||||
file.read_to_end(&mut result).await.unwrap();
|
||||
assert_eq!(&result[..5], b"hello");
|
||||
assert_eq!(&result[5..14], b" REPLACED");
|
||||
}
|
||||
|
||||
/// Partial read followed by write at a different position. Verifies the
|
||||
/// seek-back accounts for partially consumed internal buffer.
|
||||
#[tokio::test]
|
||||
async fn test_file_partial_read_then_write() {
|
||||
let data = b"abcdefghijklmnopqrstuvwxyz";
|
||||
let (_tmp, path) = create_temp_file(data);
|
||||
|
||||
let mut file = tokio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// First read fills internal buffer.
|
||||
let mut buf = vec![0u8; 10];
|
||||
file.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf, b"abcdefghij");
|
||||
|
||||
// Second read served from internal buffer
|
||||
let mut buf2 = vec![0u8; 3];
|
||||
file.read_exact(&mut buf2).await.unwrap();
|
||||
assert_eq!(&buf2, b"klm");
|
||||
|
||||
// Write should seek back past unconsumed buffered bytes, then write
|
||||
file.write_all(b"NOP").await.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
|
||||
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
|
||||
let mut result = Vec::new();
|
||||
file.read_to_end(&mut result).await.unwrap();
|
||||
assert_eq!(&result[..13], b"abcdefghijklm");
|
||||
assert_eq!(&result[13..16], b"NOP");
|
||||
assert_eq!(&result[16..], &data[16..]);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Uring file operations tests.
|
||||
|
||||
#![cfg(all(
|
||||
tokio_unstable,
|
||||
feature = "io-uring",
|
||||
feature = "rt",
|
||||
feature = "fs",
|
||||
target_os = "linux"
|
||||
))]
|
||||
|
||||
use futures::FutureExt;
|
||||
use std::fs;
|
||||
use std::future::poll_fn;
|
||||
use std::task::Poll;
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
// see: https://github.com/tokio-rs/tokio/issues/7979
|
||||
#[test]
|
||||
fn shutdown_runtime_while_performing_io_uring_ops() {
|
||||
let tmp = NamedTempFile::new().unwrap();
|
||||
let path = tmp.path().to_path_buf();
|
||||
|
||||
let fd_count_before_opens = fs::read_dir("/proc/self/fd").unwrap().count();
|
||||
|
||||
let rt = Builder::new_multi_thread().enable_all().build().unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
for _ in 0..128 {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let path = path.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
poll_fn(|cx| {
|
||||
let opt = {
|
||||
let mut opt = tokio::fs::OpenOptions::new();
|
||||
opt.read(true);
|
||||
opt
|
||||
};
|
||||
|
||||
let fut = opt.open(&path);
|
||||
|
||||
// If io_uring is enabled (and not falling back to the thread pool),
|
||||
// the first poll should return Pending. We don't check if the result
|
||||
// is actually a pending because we run some CI checks based on old
|
||||
// kernels that don't support uring.
|
||||
let _pending = Box::pin(fut).poll_unpin(cx);
|
||||
|
||||
tx.send(()).unwrap();
|
||||
|
||||
Poll::<()>::Pending
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
// Wait for the first poll
|
||||
rx.recv().await.unwrap();
|
||||
|
||||
handle.abort();
|
||||
|
||||
let res = handle.await.unwrap_err();
|
||||
assert!(res.is_cancelled());
|
||||
}
|
||||
});
|
||||
|
||||
rt.shutdown_background();
|
||||
|
||||
let fd_count_after_cancel = fs::read_dir("/proc/self/fd").unwrap().count();
|
||||
let leaked = fd_count_after_cancel.saturating_sub(fd_count_before_opens);
|
||||
|
||||
// Since we are opening 128 files, we expect that the related fds
|
||||
// related to this operation will be closed. Since some other fds
|
||||
// can be opened in the meantime, we expect this number to be higher
|
||||
// than the counter before opening the files. This number could be
|
||||
// lower, but to avoid test flakiness we check that this is at most
|
||||
// half the number of the file we opened to check if there's a leak.
|
||||
assert!(leaked <= 64);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use std::time::Duration;
|
||||
use std::{
|
||||
future::Future,
|
||||
io::{self, ErrorKind, Read, Write},
|
||||
os::fd::OwnedFd,
|
||||
task::{Context, Waker},
|
||||
};
|
||||
|
||||
@@ -69,7 +70,7 @@ impl AsRawFd for FileDescriptor {
|
||||
|
||||
impl Read for &FileDescriptor {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
read(self.fd.as_raw_fd(), buf).map_err(io::Error::from)
|
||||
read(&self.fd, buf).map_err(io::Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +100,10 @@ impl Write for FileDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_nonblocking(fd: RawFd) {
|
||||
fn set_nonblocking(fd: &OwnedFd) {
|
||||
use nix::fcntl::{OFlag, F_GETFL, F_SETFL};
|
||||
|
||||
let flags = nix::fcntl::fcntl(fd, F_GETFL).expect("fcntl(F_GETFD)");
|
||||
let flags = nix::fcntl::fcntl(fd, F_GETFL).expect("fcntl(F_GETFL)");
|
||||
|
||||
if flags < 0 {
|
||||
panic!(
|
||||
@@ -114,7 +115,7 @@ fn set_nonblocking(fd: RawFd) {
|
||||
|
||||
let flags = OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK;
|
||||
|
||||
nix::fcntl::fcntl(fd, F_SETFL(flags)).expect("fcntl(F_SETFD)");
|
||||
nix::fcntl::fcntl(fd, F_SETFL(flags)).expect("fcntl(F_SETFL)");
|
||||
}
|
||||
|
||||
fn socketpair() -> (FileDescriptor, FileDescriptor) {
|
||||
@@ -129,8 +130,8 @@ fn socketpair() -> (FileDescriptor, FileDescriptor) {
|
||||
.expect("socketpair");
|
||||
let fds = (FileDescriptor { fd: fd_a }, FileDescriptor { fd: fd_b });
|
||||
|
||||
set_nonblocking(fds.0.fd.as_raw_fd());
|
||||
set_nonblocking(fds.1.fd.as_raw_fd());
|
||||
set_nonblocking(&fds.0.fd);
|
||||
set_nonblocking(&fds.1.fd);
|
||||
|
||||
fds
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user