Compare commits

..
Author SHA1 Message Date
Eliza Weisman 04353af3ea clear the bit 2026-04-18 14:15:36 -07:00
Eliza Weisman dc759b9dd6 defer lifo unpark w timer 2026-04-18 14:11:21 -07:00
Eliza Weisman 9dc092aeaf or how about this...? 2026-04-18 11:37:41 -07:00
Eliza Weisman 7c221a4f43 test: remove churn() task from lifo_stealable
Now, we don't rely on the presence of another task to prevent workers
from parking in the test. This way, it validates that we are correctly
unparking other workers. See
https://github.com/tokio-rs/tokio/pull/8069#issuecomment-4274244723
2026-04-18 11:01:28 -07:00
Eliza Weisman bc62e2d94c rt: don't notify when pushing task to empty LIFO slot
See #8065
2026-04-18 10:11:14 -07:00
391 changed files with 3165 additions and 11998 deletions
-2
View File
@@ -15,8 +15,6 @@ R-loom-time-driver:
- any-glob-to-any-file:
- tokio/src/runtime/time/*
- tokio/src/runtime/time/**/*
- tokio/src/runtime/time_alt/*
- tokio/src/runtime/time_alt/**/*
R-loom-current-thread:
- changed-files:
+1 -1
View File
@@ -20,5 +20,5 @@ jobs:
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: EmbarkStudios/cargo-deny-action@v2
+61 -170
View File
@@ -18,9 +18,7 @@ env:
rust_stable: stable
rust_nightly: nightly-2025-10-12
# Pin a specific miri version
rust_miri_nightly: nightly-2026-06-29
rust_emscripten_nightly: nightly-2026-08-17
emsdk_version: '6.0.8'
rust_miri_nightly: nightly-2025-11-13
rust_clippy: '1.88'
# When updating this, also update:
# - README.md
@@ -66,7 +64,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -77,9 +75,6 @@ jobs:
tool: cargo-nextest
- uses: Swatinem/rust-cache@v2
with:
# FIXME: temporary workaround, see: https://github.com/Swatinem/rust-cache/issues/341
cache-bin: ${{ matrix.os != 'macos-latest' }}
# Run `tokio` with stable features. This excludes testing utilities which
# can alter the runtime behavior of Tokio.
@@ -101,7 +96,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -112,9 +107,6 @@ jobs:
tool: cargo-nextest
- uses: Swatinem/rust-cache@v2
with:
# FIXME: temporary workaround, see: https://github.com/Swatinem/rust-cache/issues/341
cache-bin: ${{ matrix.os != 'macos-latest' }}
- name: test --features ${{ env.TOKIO_STABLE_FEATURES }}
run: |
@@ -143,7 +135,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -154,9 +146,6 @@ jobs:
tool: cargo-nextest
- uses: Swatinem/rust-cache@v2
with:
# FIXME: temporary workaround, see: https://github.com/Swatinem/rust-cache/issues/341
cache-bin: ${{ matrix.os != 'macos-latest' }}
- name: test --features ${{ env.TOKIO_STABLE_FEATURES }} panic=abort
run: |
@@ -179,7 +168,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -190,9 +179,6 @@ jobs:
tool: cargo-hack
- uses: Swatinem/rust-cache@v2
with:
# FIXME: temporary workaround, see: https://github.com/Swatinem/rust-cache/issues/341
cache-bin: ${{ matrix.os != 'macos-latest' }}
# Run integration tests for each feature
- name: test tests-integration --each-feature
@@ -222,7 +208,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -248,7 +234,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -289,7 +275,7 @@ jobs:
- { os: ubuntu-latest, extra_features: io-uring }
- { os: macos-latest, extra_features: "" }
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -301,9 +287,6 @@ jobs:
tool: cargo-nextest
- uses: Swatinem/rust-cache@v2
with:
# FIXME: temporary workaround, see: https://github.com/Swatinem/rust-cache/issues/341
cache-bin: ${{ matrix.os != 'macos-latest' }}
# Run `tokio` with "unstable" cfg flag.
- name: test tokio full --cfg unstable
run: |
@@ -317,30 +300,6 @@ jobs:
# the unstable cfg to RustDoc
RUSTDOCFLAGS: --cfg tokio_unstable
# Run the test suite with the sharded `spawn_blocking` queue.
test-sharded-blocking-queue:
name: test tokio full with sharded blocking queue
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_stable }}
- name: Install cargo-nextest
uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- uses: Swatinem/rust-cache@v2
- name: test tokio full with sharded blocking queue
run: cargo nextest run --features full
working-directory: tokio
env:
TOKIO_UNSTABLE_SHARDED_BLOCKING_QUEUE: "1"
test-unstable-taskdump:
name: test tokio full --unstable --taskdump
needs: basics
@@ -350,7 +309,7 @@ jobs:
include:
- os: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -384,7 +343,7 @@ jobs:
include:
- os: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -413,7 +372,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -436,7 +395,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -459,7 +418,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -478,7 +437,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install llvm
# Required to resolve symbols in sanitizer output
run: sudo apt-get install -y llvm
@@ -500,7 +459,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Check `tokio` semver
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
@@ -511,7 +470,7 @@ jobs:
# We don't care about the semver of unstable tokio features.
features: ${{ env.TOKIO_STABLE_FEATURES }}
- name: Check semver for rest of the workspace
if: ${{ !startsWith(github.event.pull_request.base.ref, 'tokio-1.') && !startsWith(github.ref_name, 'tokio-1.') }}
if: ${{ !startsWith(github.event.pull_request.base.ref, 'tokio-1.') }}
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
rust-toolchain: ${{ env.rust_stable }}
@@ -529,7 +488,7 @@ jobs:
- powerpc64-unknown-linux-gnu
- arm-linux-androideabi
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -554,7 +513,7 @@ jobs:
# - name: armv7-sony-vita-newlibeabihf
# exclude_features: "process,signal,rt-process-signal,full,taskdump"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@nightly
with:
@@ -588,7 +547,7 @@ jobs:
- target: aarch64-pc-windows-msvc
os: windows-11-arm
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
@@ -638,7 +597,7 @@ jobs:
- target: aarch64-pc-windows-msvc
os: windows-11-arm
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
@@ -679,7 +638,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -711,7 +670,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -727,9 +686,8 @@ jobs:
# https://github.com/tokio-rs/tokio/pull/5356
# https://github.com/tokio-rs/tokio/issues/5373
- name: Check
# We use `--skip io-uring,schedule-latency` since io-uring crate doesn't provide a binding for the i686 target
# and schedule latency tracking is only supported on 64-bit targets.
run: cargo hack check -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --feature-powerset --skip io-uring,schedule-latency --depth 2 --keep-going
# We use `--skip io-uring` since io-uring crate doesn't provide a binding for the i686 target.
run: cargo hack check -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --feature-powerset --skip io-uring --depth 2 --keep-going
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
@@ -742,15 +700,15 @@ jobs:
include:
- name: ""
rustflags: ""
exclude_features: "io-uring,taskdump,schedule-latency"
exclude_features: "io-uring,taskdump"
- name: "--unstable"
rustflags: "--cfg tokio_unstable -Dwarnings"
exclude_features: "io-uring,taskdump,schedule-latency"
- name: "--unstable io-uring,taskdump,schedule-latency"
exclude_features: "io-uring,taskdump"
- name: "--unstable io-uring,taskdump"
rustflags: "--cfg tokio_unstable -Dwarnings"
exclude_features: ""
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -769,7 +727,7 @@ jobs:
name: minrust
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_min }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -781,16 +739,15 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: "cargo check"
run: |
# `cargo check -p tokio --all-features` must pass without `tokio_unstable`;
# unstable Cargo features must be inert unless `tokio_unstable` is also enabled.
if [[ "${{ github.event.pull_request.base.ref }}" =~ ^tokio-1\..* ]]; then
# Only check `tokio` crate as the PR is backporting to an earlier tokio release.
cargo check -p tokio --all-features
cargo check -p tokio --features $TOKIO_STABLE_FEATURES
else
# Check all crates in the workspace
cargo check -p tokio --all-features
cargo check -p tokio --features $TOKIO_STABLE_FEATURES
# Other crates doesn't have unstable features, so we can use --all-features.
cargo hack check -p tokio-macros -p tokio-stream -p tokio-util -p tokio-test --all-features
fi
@@ -799,7 +756,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -832,7 +789,7 @@ jobs:
name: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -852,7 +809,7 @@ jobs:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_clippy }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -892,7 +849,7 @@ jobs:
extra_features: "tracing,io-uring,taskdump"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -909,7 +866,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -925,7 +882,7 @@ jobs:
name: Check README
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Verify that both READMEs are identical
run: diff README.md tokio/README.md
@@ -944,7 +901,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -980,8 +937,6 @@ jobs:
# relative to the `$workspace` and defaults to "target" if not explicitly given.
# default: ". -> target"
workspaces: "./hyper"
# FIXME: temporary workaround, see: https://github.com/Swatinem/rust-cache/issues/341
cache-bin: ${{ matrix.os != 'macos-latest' }}
- name: Test hyper
run: cargo test --features full
@@ -998,7 +953,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1030,8 +985,6 @@ jobs:
# relative to the `$workspace` and defaults to "target" if not explicitly given.
# default: ". -> target"
workspaces: "./quinn"
# FIXME: temporary workaround, see: https://github.com/Swatinem/rust-cache/issues/341
cache-bin: ${{ matrix.os != 'macos-latest' }}
- name: Test Quinn
working-directory: quinn
@@ -1044,7 +997,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1061,7 +1014,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
with:
@@ -1085,7 +1038,7 @@ jobs:
- name: macros sync time rt
features: "macros sync time rt"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1108,7 +1061,7 @@ jobs:
- wasm32-wasip1
- wasm32-wasip1-threads
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1119,10 +1072,7 @@ jobs:
- name: Install cargo-hack, wasmtime
uses: taiki-e/install-action@v2
with:
# Wasmtime v46.0.1 is the last version to support
# `wasm32-wasip1-threads` (which was an experiment that was never
# standardized):
tool: cargo-hack,[email protected]
tool: cargo-hack,wasmtime
- uses: Swatinem/rust-cache@v2
- name: WASI test tokio full
@@ -1172,7 +1122,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1182,9 +1132,7 @@ jobs:
- name: Install cargo-nextest, wasmtime
uses: taiki-e/install-action@v2
with:
# Wasmtime v47.0.0 or later should work, but we stick with a known,
# specific version to avoid surprises:
tool: cargo-nextest,[email protected]
tool: cargo-nextest,wasmtime-cli
- uses: Swatinem/rust-cache@v2
@@ -1195,59 +1143,6 @@ jobs:
RUSTFLAGS: --cfg tokio_unstable
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -Sinherit-network
wasm32-unknown-emscripten:
name: test tokio for wasm32-unknown-emscripten
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_stable }}
targets: wasm32-unknown-emscripten
- name: Install Emscripten
uses: mymindstorm/setup-emsdk@v14
with:
version: ${{ env.emsdk_version }}
- uses: actions/setup-node@v4
with:
node-version: 26
- uses: Swatinem/rust-cache@v2
- name: Install cargo-hack
uses: taiki-e/install-action@v2
with:
tool: cargo-hack
- name: Check tokio feature matrix for emscripten
run: cargo hack check -p tokio --each-feature --exclude-features full,net,process,signal,rt-multi-thread,io-uring,taskdump,schedule-latency --target wasm32-unknown-emscripten
working-directory: tokio
- name: Test tokio for emscripten
run: cargo test -p tokio --target wasm32-unknown-emscripten --features "rt,time,sync,macros,fs,io-util,io-std,test-util" --tests
working-directory: tokio
env:
CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node
RUSTFLAGS: "-Dwarnings -Clink-args=-sALLOW_MEMORY_GROWTH=1 -Clink-args=-sEXIT_RUNTIME=1 -Clink-args=-sSTACK_SIZE=1048576"
- name: Install Rust ${{ env.rust_emscripten_nightly }}
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ env.rust_emscripten_nightly }}
targets: wasm32-unknown-emscripten
components: rust-src
- name: Test tokio multi-thread runtime for emscripten (pthreads)
run: cargo +${{ env.rust_emscripten_nightly }} test -Zbuild-std=std,panic_abort -p tokio --target wasm32-unknown-emscripten --features "rt,rt-multi-thread,time,sync,macros" --test rt_multi_thread_emscripten
working-directory: tokio
env:
CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node
RUSTFLAGS: "-Dwarnings -Ctarget-feature=+atomics,+bulk-memory,+mutable-globals -Clink-args=-pthread -Clink-args=-sPTHREAD_POOL_SIZE=8 -Clink-args=-sINITIAL_MEMORY=134217728 -Clink-args=-sEXIT_RUNTIME=1 -Clink-args=-sSTACK_SIZE=1048576"
check-external-types:
name: check-external-types (${{ matrix.os }})
needs: basics
@@ -1262,7 +1157,7 @@ jobs:
# includes all unstable features.
extra_features: "tracing,io-uring,taskdump"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ matrix.rust }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1286,7 +1181,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1306,7 +1201,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1315,7 +1210,7 @@ jobs:
uses: taiki-e/install-action@v2
with:
tool: cargo-spellcheck
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Make sure dictionary words are sorted and unique
run: |
FILE="spellcheck.dic"
@@ -1403,14 +1298,12 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Test in FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: '14.4'
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
sync: rsync
copyback: false
prepare: |
pkg install -y curl
curl https://sh.rustup.rs -sSf --output rustup.sh
@@ -1421,7 +1314,8 @@ jobs:
# Enable all unstable features except `io_uring` and `taskdump`,
# which are Linux-only features.
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable" \
cargo test \
RUSTDOCFLAGS="--cfg tokio_unstable" \
cargo test \
--features $TOKIO_STABLE_FEATURES,tracing
freebsd-docs:
@@ -1429,24 +1323,23 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Build docs in FreeBSD
- 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
RUST_NIGHTLY: nightly-2025-10-12
with:
release: '14.4'
envs: "TOKIO_STABLE_FEATURES RUSTDOCFLAGS RUSTFLAGS"
sync: rsync
copyback: false
envs: "TOKIO_STABLE_FEATURES RUST_NIGHTLY 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 }}
sh rustup.sh -y --profile minimal --default-toolchain $RUST_NIGHTLY
run: |
. $HOME/.cargo/env
# We use `--features $TOKIO_STABLE_FEATURES,tracing` instead of
# We use `--features $TOKIO_STABLE_FEATURES,io-uring,tracing` instead of
# `--all-features` to exclude `taskdump` and `io_uring`, which are Linux-only
# features.
cargo doc --lib --no-deps --features $TOKIO_STABLE_FEATURES,tracing \
@@ -1457,14 +1350,12 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Test in FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: '14.4'
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
sync: rsync
copyback: false
prepare: |
pkg install -y curl
curl https://sh.rustup.rs -sSf --output rustup.sh
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'tokio-rs'
steps:
- uses: actions/labeler@v7
- uses: actions/labeler@v6
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
+6 -15
View File
@@ -28,15 +28,8 @@ jobs:
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-blocking') || (github.base_ref == null))
runs-on: ubuntu-latest
strategy:
matrix:
# Run the blocking pool loom tests against both `spawn_blocking`
# queue implementations.
include:
- sharded_blocking_queue: "0"
- sharded_blocking_queue: "1"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -45,8 +38,6 @@ jobs:
- name: run tests
run: cargo test --lib --release --features full -- --nocapture loom_blocking
working-directory: tokio
env:
TOKIO_UNSTABLE_SHARDED_BLOCKING_QUEUE: ${{ matrix.sharded_blocking_queue }}
loom-sync:
name: loom tokio::sync
@@ -54,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@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -70,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@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -86,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@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -109,7 +100,7 @@ jobs:
- scope: loom_multi_thread::group_c
- scope: loom_multi_thread::group_d
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -127,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@v7
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
+1 -1
View File
@@ -19,5 +19,5 @@ jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: EmbarkStudios/cargo-deny-action@v2
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
stress-test:
- simple_echo_tcp
steps:
- uses: actions/checkout@v7
- 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@v7
- uses: actions/checkout@v6
- name: Install system dependencies
run: |
@@ -25,7 +25,7 @@ jobs:
- name: Cache Linux source
id: cache-kernel
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: linux-${{ env.KERNEL_VERSION }}
key: kernel-${{ env.KERNEL_VERSION }}
@@ -40,8 +40,6 @@ jobs:
make defconfig
make -j$(nproc)
# We are running tests also on a Kernel version that does not support io_uring
# to check if the fallback mechanism works
- name: Generate test binaries with io_uring enabled
run: |
# Build both integration (tokio/tests/) and unit (e.g., tokio/src/fs/file/tests.rs) tests with io_uring enabled
@@ -89,31 +87,16 @@ jobs:
| cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz)
- name: Run tests in QEMU
shell: bash
run: |
set -euo pipefail
set +e
qemu-system-x86_64 \
-kernel linux-${{ env.KERNEL_VERSION }}/arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 rootfstype=ramfs panic=1" \
-nographic -no-reboot -m 1024 -action panic=exit-failure 2>&1 | tee qemu-output.log
qemu_status=${PIPESTATUS[0]} tee_status=${PIPESTATUS[1]}
set -e
if [ "$qemu_status" -ne 0 ]; then
echo "QEMU exited with status $qemu_status"
exit "$qemu_status"
fi
if [ "$tee_status" -ne 0 ]; then
echo "tee exited with status $tee_status"
exit "$tee_status"
fi
# qemu always exits with 0, so we check if the tests passed by using grep.
if grep -q "test result: FAILED" qemu-output.log; then
echo "tests reported failures"
echo "tests failed (QEMU exited abnormally)"
exit 1
else
echo "all tests passed"
+5 -1
View File
@@ -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.53.1", features = ["full"] }
tokio = { version = "1.52.1", features = ["full"] }
```
Then, on your main.rs:
+4 -4
View File
@@ -73,7 +73,7 @@ impl SlowHddWriter {
fn write_bytes(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
writable: usize,
writeable: usize,
) -> std::task::Poll<Result<usize, std::io::Error>> {
let service_res = self.as_mut().service_write(cx);
@@ -86,7 +86,7 @@ impl SlowHddWriter {
assert!(service_res.is_pending());
Poll::Pending
} else {
let written = available.min(writable);
let written = available.min(writeable);
self.buffer_used += written;
Poll::Ready(Ok(written))
}
@@ -123,8 +123,8 @@ impl AsyncWrite for SlowHddWriter {
cx: &mut std::task::Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> std::task::Poll<Result<usize, std::io::Error>> {
let writable = bufs.iter().fold(0, |acc, buf| acc + buf.len());
self.write_bytes(cx, writable)
let writeable = bufs.iter().fold(0, |acc, buf| acc + buf.len());
self.write_bytes(cx, writeable)
}
fn is_write_vectored(&self) -> bool {
+3 -1
View File
@@ -84,7 +84,9 @@ fn remote_spawn_contention(c: &mut Criterion) {
}
fn parallelism_levels() -> Vec<usize> {
let max_parallelism = std::thread::available_parallelism().map_or(1, |p| p.get());
let max_parallelism = std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(1);
[1, 2, 4, 8, 16, 32, 64]
.into_iter()
+3 -1
View File
@@ -15,7 +15,9 @@ const NUM_BATCHES: usize = 100;
const BATCH_SIZE: usize = 16;
fn spawn_blocking_concurrency(c: &mut Criterion) {
let max_parallelism = std::thread::available_parallelism().map_or(1, |p| p.get());
let max_parallelism = std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(1);
let parallelism_levels: Vec<usize> = [1, 2, 4, 8, 16, 32, 64]
.into_iter()
+2 -36
View File
@@ -4,9 +4,7 @@ use std::sync::Arc;
use tokio::sync::{broadcast, Notify};
use criterion::measurement::WallTime;
use criterion::{
black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion, Throughput,
};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
@@ -79,38 +77,6 @@ fn bench_contention(c: &mut Criterion) {
group.finish();
}
fn bench_try_recv(c: &mut Criterion) {
let mut group = c.benchmark_group("try_recv");
const MESSAGES: usize = 256;
for receiver_count in [1usize, 4, 16] {
group.throughput(Throughput::Elements((MESSAGES * receiver_count) as u64));
group.bench_function(receiver_count.to_string(), |b| {
let (tx, first_rx) = broadcast::channel::<usize>(MESSAGES);
let mut receivers = Vec::with_capacity(receiver_count);
receivers.push(first_rx);
for _ in 1..receiver_count {
receivers.push(tx.subscribe());
}
b.iter(|| {
for message in 0..MESSAGES {
tx.send(black_box(message)).unwrap();
}
for rx in &mut receivers {
for _ in 0..MESSAGES {
black_box(rx.try_recv().unwrap());
}
}
});
});
}
group.finish();
}
criterion_group!(contention, bench_contention, bench_try_recv);
criterion_group!(contention, bench_contention);
criterion_main!(contention);
+2 -4
View File
@@ -8,8 +8,7 @@ It should be considered a map to help you navigate the process.
If you are unsure where to begin, use the following guides:
- Want to report or triage a bug? Start with [Contributing in Issues](contributing-in-issues.md).
- Looking for something to work on? Check the open [`issues`](https://github.com/tokio-rs/tokio/issues)
(some might be in progress or still under discussion, so leave a comment before starting the work).
- Looking for something to work on? Filter issues by [`E-help-wanted`](https://github.com/tokio-rs/tokio/labels/E-help-wanted).
- Planning to submit a PR? Read [Pull Requests](pull-requests.md) for the full workflow and required checks.
- Want to understand what the labels on issues mean? See [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md).
- Interested in code review? See [Reviewing Pull Requests](reviewing-pull-requests.md).
@@ -22,9 +21,8 @@ If you are unsure where to begin, use the following guides:
- [Triaging a Bug Report](contributing-in-issues.md#triaging-a-bug-report)
- [Resolving a Bug Report](contributing-in-issues.md#resolving-a-bug-report)
- [Pull Requests](pull-requests.md)
- [Step-by-Step Contribution Workflow](pull-requests.md#step-by-step-contribution-workflow)
- [Cargo Commands](pull-requests.md#cargo-commands)
- [Performing spellcheck on Tokio codebase](pull-requests.md#performing-spellcheck-on-tokio-codebase)
- [Performing spellcheck on tokio codebase](pull-requests.md#performing-spellcheck-on-tokio-codebase)
- [Tests](pull-requests.md#tests)
- [Integration tests](pull-requests.md#integration-tests)
- [Fuzz tests](pull-requests.md#fuzz-tests)
@@ -81,10 +81,8 @@ The module label provides a more fine grained categorization than **Area**.
Some extra information.
- **T-docs** This is about documentation.
- **T-io-uring** This is about io-uring (Linux).
- **T-performance** This is about performance.
- **T-v0.1.x** This is about old Tokio.
- **T-wasm** This is about Web Assembly.
Any label not listed here is not in active use.
+8 -66
View File
@@ -4,71 +4,10 @@ Pull Requests are the way concrete changes are made to the code, documentation,
and dependencies in the Tokio repository.
Even tiny pull requests (e.g., one-character pull request fixing a typo in API
documentation) are greatly appreciated.
> [!NOTE]
> Before making a large change, it is usually a good idea to first open an
> issue describing the change to solicit feedback and guidance.
> This will increase the likelihood of the PR getting merged.
### Step-by-Step Contribution Workflow
Once you've found an issue you'd like to work on, follow these steps before opening a Pull Request. This workflow provides a chronological overview of the contribution process and points to the relevant documentation where additional detail is available.
#### 1. Fork and Clone the Repository
Fork the repository to your GitHub account and clone your fork locally.
If you've previously cloned the repository, ensure your local copy is up to date before creating a new branch.
```bash
git checkout master
git pull upstream master
```
---
#### 2. Create a Feature Branch
Never commit directly to your local `master` branch. Instead, create a descriptive feature branch for each change you work on.
```bash
git checkout -b my-feature
```
Using a dedicated branch keeps your default branch clean and makes it easier to update your Pull Request during code review.
---
#### 3. Implement Your Changes
Make the code or documentation changes needed. Check your work with `git diff` to confirm the changes look correct before moving on.
If your changes introduce new functionality or modify existing behavior, consider whether additional tests or documentation should also be added.
---
#### 4. Verify Your Changes
Before opening a Pull Request, run the project's verification steps. Refer to the sections below for details on when each command should be used.
---
#### 5. Commit and Push Your Changes
Commit your changes following the [project's commit message guidelines](#commit-message-guidelines).
```bash
git add .
git commit -m "module: describe your change"
git push origin my-feature
```
---
#### 6. Open a Pull Request
Open a Pull Request from your feature branch to the main repository. See [Opening the Pull Request](#opening-the-pull-request) for what to include and how the review process works.
documentation) are greatly appreciated. Before making a large change, it is
usually a good idea to first open an issue describing the change to solicit
feedback and guidance. This will increase the likelihood of the PR getting
merged.
### Cargo Commands
@@ -84,6 +23,9 @@ cargo check --all-features
cargo test --all-features
```
**NOTE**: there are some features that are not supported in every system, so you might
need to specify which features you want to pass to cargo (e.g., `cargo check --features=full,io-uring`)
Ideally, you should use the same version of clippy as the one used in CI
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
might have new lints:
@@ -157,7 +99,7 @@ MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance" \
cargo +nightly miri test --features full --lib --tests
```
### Performing spellcheck on Tokio codebase
### Performing spellcheck on tokio codebase
You can perform a spell-check on the Tokio codebase. For details of how to use the spellcheck tool, feel free to visit
https://github.com/drahnr/cargo-spellcheck
+2 -12
View File
@@ -5,12 +5,7 @@
#[cfg(all(
tokio_unstable,
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "s390x"
)
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
))]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -87,12 +82,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(not(all(
tokio_unstable,
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "s390x"
)
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
)))]
fn main() {
println!("task dumps are not available")
-22
View File
@@ -9,28 +9,6 @@
//! cargo run --example connect-udp 127.0.0.1:8080
//!
//! Each line you type in to the `connect-udp` terminal should be echo'd back to you!
//!
//! # Binding to all interfaces
//!
//! By default this example binds to `127.0.0.1` so it is only reachable
//! from the local machine.
//!
//! To listen on all interfaces instead:
//!
//! ```sh
//! cargo run --example echo-udp -- 0.0.0.0:8080
//! ```
//!
//! Binding to `0.0.0.0` exposes the server on all network interfaces.
//! Only do this in trusted network environments.
//!
//! On multi-homed systems, a UDP socket bound to a wildcard address
//! (`0.0.0.0` or `::`) cannot always send replies from the same local IP
//! that received the packet. Replies may therefore originate from a
//! different address than the client targeted. See [this Cloudflare blog
//! post][udp-blog] for more details.
//!
//! [udp-blog]: https://blog.cloudflare.com/everything-you-ever-wanted-to-know-about-udp-sockets-but-were-afraid-to-ask-part-1
#![warn(rust_2018_idioms)]
+9 -12
View File
@@ -25,6 +25,7 @@
use tokio::io::copy_bidirectional;
use tokio::net::{TcpListener, TcpStream};
use futures::FutureExt;
use std::env;
use std::error::Error;
@@ -43,20 +44,16 @@ async fn main() -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind(listen_addr).await?;
while let Ok((mut inbound, _)) = listener.accept().await {
let server_addr = server_addr.clone();
let mut outbound = TcpStream::connect(server_addr.clone()).await?;
tokio::spawn(async move {
let mut outbound = match TcpStream::connect(server_addr).await {
Ok(outbound) => outbound,
Err(e) => {
println!("Failed to connect; error={e}");
return;
}
};
if let Err(e) = copy_bidirectional(&mut inbound, &mut outbound).await {
println!("Failed to transfer; error={e}");
}
copy_bidirectional(&mut inbound, &mut outbound)
.map(|r| {
if let Err(e) = r {
println!("Failed to transfer; error={e}");
}
})
.await
});
}
+2 -1
View File
@@ -264,7 +264,8 @@ mod date {
let now = SystemTime::now();
let now_unix = now
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |since_epoch| since_epoch.as_secs());
.map(|since_epoch| since_epoch.as_secs())
.unwrap_or(0);
if cache.unix_date != now_unix {
cache.update(now, now_unix);
}
+1 -16
View File
@@ -1,4 +1,4 @@
331
316
&
+
<
@@ -89,7 +89,6 @@ decrementing
demangled
dequeued
dereferenced
derefs
deregister
deregistered
deregistering
@@ -106,8 +105,6 @@ dns
DNS
DoS
dwOpenMode
Emscripten
Emscripten's
endian
enqueue
enqueued
@@ -143,7 +140,6 @@ hashsets
HdrHistogram
ICMP
ie
iff
Illumos
impl
implementers
@@ -193,17 +189,14 @@ mutex
Mutex
Nagle
namespace
NetBSD
nonblocking
nondecreasing
noop
ntasks
NTO
NUMA
ok
oneshot
opcode
OpenBSD
ORed
os
parker
@@ -215,13 +208,11 @@ plaintext
poller
POSIX
proxied
pthreads
qos
RAII
RCU
reallocations
recv's
Redox
refactors
refcount
refcounting
@@ -233,7 +224,6 @@ reregistering
resize
resized
RMW
RNG
runtime
runtime's
runtimes
@@ -249,7 +239,6 @@ signalling
SmallCrush
Solaris
spawner
spawners
Splitter
spmc
spsc
@@ -290,7 +279,6 @@ tokio's
Tokio's
tuple
Tuple
tvOS
tx
udp
UDP
@@ -318,7 +306,6 @@ vec
versa
versioned
versioning
visionOS
vtable
waker
wakers
@@ -326,7 +313,5 @@ Wakers
wakeup
wakeups
WASI
Wasm
watchOS
workstealing
ZST
+2
View File
@@ -16,6 +16,7 @@ async fn spawning() -> usize {
join.await.unwrap()
}
#[cfg(tokio_unstable)]
#[tokio::main(flavor = "local")]
async fn local_main() -> usize {
let join = tokio::task::spawn_local(async { 1 });
@@ -32,5 +33,6 @@ fn shell() {
assert_eq!(1, basic_main());
assert_eq!(bool::default(), generic_fun::<bool>());
#[cfg(tokio_unstable)]
assert_eq!(1, local_main());
}
-12
View File
@@ -1,15 +1,3 @@
# 2.7.2 (July 29th, 2026)
- macros: upgrade syn to v3 ([#8304])
[#8304]: https://github.com/tokio-rs/tokio/pull/8304
# 2.7.1 (July 17th, 2026)
- macros: clarify `tokio::main` expansion ([#8193])
[#8193]: https://github.com/tokio-rs/tokio/pull/8193
# 2.7.0 (April 3rd, 2026)
- macros: stabilize `LocalRuntime` ([#7557])
+2 -2
View File
@@ -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.7.2"
version = "2.7.0"
edition = "2021"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
@@ -24,7 +24,7 @@ proc-macro = true
[dependencies]
proc-macro2 = "1.0.60"
quote = "1"
syn = { version = "3.0", features = ["full"] }
syn = { version = "2.0", features = ["full"] }
[dev-dependencies]
tokio = { version = "1.0.0", features = ["full", "test-util"] }
+6 -6
View File
@@ -326,7 +326,7 @@ fn contains_impl_trait(ty: &syn::Type) -> bool {
_ => false,
}),
syn::PathArguments::Parenthesized(args) => {
args.inputs.iter().any(|arg| contains_impl_trait(&arg.ty))
args.inputs.iter().any(contains_impl_trait)
|| matches!(&args.output, syn::ReturnType::Type(_, t) if contains_impl_trait(t))
}
syn::PathArguments::None => false,
@@ -453,13 +453,13 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
(start, end)
};
let crate_path = config.crate_name.map_or_else(
|| {
let crate_path = config
.crate_name
.map(ToTokens::into_token_stream)
.unwrap_or_else(|| {
Ident::new("tokio", Span::call_site().located_at(last_stmt_start_span))
.into_token_stream()
},
ToTokens::into_token_stream,
);
});
let use_builder = quote_spanned! {Span::call_site().located_at(last_stmt_start_span)=>
use #crate_path::runtime::Builder;
+4 -6
View File
@@ -29,11 +29,10 @@ use proc_macro::TokenStream;
/// powerful interface.
///
/// Note: This macro can be used on any function and not just the `main`
/// function. Although the function is written with `async fn`, this macro
/// expands it to a synchronous function that starts a runtime each time it is
/// called. If the function is called often, it is preferable to create the
/// runtime using the runtime builder so the runtime can be reused across calls.
/// For details on the expansion, see [Bridging with sync code][bridging].
/// function. Using it on a non-main function makes the function behave as if it
/// was synchronous by starting a new runtime each time it is called. If the
/// function is called often, it is preferable to create the runtime using the
/// runtime builder so the runtime can be reused across calls.
///
/// # Non-worker async function
///
@@ -309,7 +308,6 @@ use proc_macro::TokenStream;
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
/// [unstable]: ../tokio/index.html#unstable-features
/// [local runtime]: ../tokio/runtime/struct.LocalRuntime.html
/// [bridging]: https://tokio.rs/tokio/topics/bridging#what-tokiomain-expands-to
#[proc_macro_attribute]
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
entry::main(args.into(), item.into(), true).into()
+1 -36
View File
@@ -1,38 +1,3 @@
# 0.1.19 (July 22nd, 2026)
### Added
- stream: implement `FromStream` for standard collections ([#7954], [#7966])
- stream: implement `FusedStream` for various stream adaptors ([#7854], [#8090], [#8096])
- stream: implement `Peekable::size_hint` ([#8109])
- task: add `JoinSetStream` wrapper for `JoinSet` ([#8189])
### Changed
- stream: bump minimum required `tokio` version to `1.38.0` ([#7887])
- stream: use cooperative budgeting in `tokio_stream::iter` ([#8218])
- stream: update coop handling for `empty` and `once` ([#8227])
### Fixed
- stream: fix overflow in `StreamMap::size_hint` ([#8216])
- stream: honor `StreamMap::next_many` limit ([#8215])
- stream: stop polling underlying stream once `map_while` yields `None` ([#8233])
[#7854]: https://github.com/tokio-rs/tokio/pull/7854
[#7887]: https://github.com/tokio-rs/tokio/pull/7887
[#7954]: https://github.com/tokio-rs/tokio/pull/7954
[#7966]: https://github.com/tokio-rs/tokio/pull/7966
[#8090]: https://github.com/tokio-rs/tokio/pull/8090
[#8096]: https://github.com/tokio-rs/tokio/pull/8096
[#8109]: https://github.com/tokio-rs/tokio/pull/8109
[#8189]: https://github.com/tokio-rs/tokio/pull/8189
[#8215]: https://github.com/tokio-rs/tokio/pull/8215
[#8216]: https://github.com/tokio-rs/tokio/pull/8216
[#8218]: https://github.com/tokio-rs/tokio/pull/8218
[#8227]: https://github.com/tokio-rs/tokio/pull/8227
[#8233]: https://github.com/tokio-rs/tokio/pull/8233
# 0.1.18 (January 4th, 2026)
### Added
@@ -47,7 +12,7 @@
### Documented
- stream: improve the docs of `TcpListenerStream` ([#7578])
- stream: improve the the docs of `TcpListenerStream` ([#7578])
[#7024]: https://github.com/tokio-rs/tokio/pull/7024
[#7492]: https://github.com/tokio-rs/tokio/pull/7492
+1 -3
View File
@@ -4,7 +4,7 @@ name = "tokio-stream"
# - Remove path dependencies (if any)
# - Update CHANGELOG.md.
# - Create "tokio-stream-0.1.x" git tag.
version = "0.1.19"
version = "0.1.18"
edition = "2021"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
@@ -24,7 +24,6 @@ full = [
"net",
"io-util",
"fs",
"rt",
"sync",
"signal"
]
@@ -33,7 +32,6 @@ time = ["tokio/time"]
net = ["tokio/net"]
io-util = ["tokio/io-util"]
fs = ["tokio/fs"]
rt = ["tokio/rt"]
sync = ["tokio/sync", "tokio-util"]
signal = ["tokio/signal"]
+1 -9
View File
@@ -40,15 +40,7 @@ pub const fn empty<T>() -> Empty<T> {
impl<T> Stream for Empty<T> {
type Item = T;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<T>> {
#[cfg(feature = "rt")]
{
use tokio::task::coop;
let coop = std::task::ready!(coop::poll_proceed(_cx));
coop.made_progress();
}
fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<T>> {
Poll::Ready(None)
}
+8 -26
View File
@@ -8,7 +8,6 @@ use core::task::{Context, Poll};
#[must_use = "streams do nothing unless polled"]
pub struct Iter<I> {
iter: I,
#[cfg(not(feature = "rt"))]
yield_amt: usize,
}
@@ -37,7 +36,6 @@ where
{
Iter {
iter: i.into_iter(),
#[cfg(not(feature = "rt"))]
yield_amt: 0,
}
}
@@ -49,33 +47,17 @@ where
type Item = I::Item;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<I::Item>> {
#[cfg(feature = "rt")]
{
use tokio::task::coop;
// TODO: add coop back
if self.yield_amt >= 32 {
self.yield_amt = 0;
let coop = std::task::ready!(coop::poll_proceed(cx));
let item = self.iter.next();
cx.waker().wake_by_ref();
coop.made_progress();
Poll::Pending
} else {
self.yield_amt += 1;
Poll::Ready(item)
}
#[cfg(not(feature = "rt"))]
{
if self.yield_amt >= 32 {
self.yield_amt = 0;
cx.waker().wake_by_ref();
Poll::Pending
} else {
let item = self.iter.next();
self.yield_amt += 1;
Poll::Ready(item)
}
Poll::Ready(self.iter.next())
}
}
-10
View File
@@ -57,13 +57,3 @@ macro_rules! cfg_signal {
)*
}
}
macro_rules! cfg_rt {
($($item:item)*) => {
$(
#[cfg(feature = "rt")]
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
$item
)*
}
}
+9 -19
View File
@@ -1,5 +1,6 @@
use crate::Stream;
use crate::{Iter, Stream};
use core::option;
use core::pin::Pin;
use core::task::{Context, Poll};
@@ -7,7 +8,7 @@ use core::task::{Context, Poll};
#[derive(Debug)]
#[must_use = "streams do nothing unless polled"]
pub struct Once<T> {
value: Option<T>,
iter: Iter<option::IntoIter<T>>,
}
impl<I> Unpin for Once<I> {}
@@ -33,30 +34,19 @@ impl<I> Unpin for Once<I> {}
/// # }
/// ```
pub fn once<T>(value: T) -> Once<T> {
Once { value: Some(value) }
Once {
iter: crate::iter(Some(value)),
}
}
impl<T> Stream for Once<T> {
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<T>> {
#[cfg(feature = "rt")]
{
use tokio::task::coop;
let coop = std::task::ready!(coop::poll_proceed(_cx));
coop.made_progress();
}
Poll::Ready(self.value.take())
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
Pin::new(&mut self.iter).poll_next(cx)
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.value.is_some() {
(1, Some(1))
} else {
(0, Some(0))
}
self.iter.size_hint()
}
}
-11
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::pin::Pin;
use core::task::{ready, Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -49,13 +48,3 @@ where
super::merge_size_hints(self.a.size_hint(), self.b.size_hint())
}
}
impl<T, U> FusedStream for Chain<T, U>
where
T: Stream,
U: FusedStream<Item = T::Item>,
{
fn is_terminated(&self) -> bool {
self.a.is_terminated() && self.b.is_terminated()
}
}
-37
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{ready, Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -31,32 +30,6 @@ impl<St, F> Filter<St, F> {
pub(super) fn new(stream: St, f: F) -> Self {
Self { stream, f }
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
}
}
impl<St, F> Stream for Filter<St, F>
@@ -83,13 +56,3 @@ where
(0, self.stream.size_hint().1) // can't know a lower bound, due to the predicate
}
}
impl<St, F> FusedStream for Filter<St, F>
where
St: FusedStream,
F: FnMut(&St::Item) -> bool,
{
fn is_terminated(&self) -> bool {
self.stream.is_terminated()
}
}
-37
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{ready, Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -31,32 +30,6 @@ impl<St, F> FilterMap<St, F> {
pub(super) fn new(stream: St, f: F) -> Self {
Self { stream, f }
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
}
}
impl<St, F, T> Stream for FilterMap<St, F>
@@ -83,13 +56,3 @@ where
(0, self.stream.size_hint().1) // can't know a lower bound, due to the predicate
}
}
impl<St, F, T> FusedStream for FilterMap<St, F>
where
St: FusedStream,
F: FnMut(St::Item) -> Option<T>,
{
fn is_terminated(&self) -> bool {
self.stream.is_terminated()
}
}
-10
View File
@@ -1,6 +1,5 @@
use crate::Stream;
use futures_core::FusedStream;
use pin_project_lite::pin_project;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
@@ -52,12 +51,3 @@ where
}
}
}
impl<T> FusedStream for Fuse<T>
where
T: Stream,
{
fn is_terminated(&self) -> bool {
self.stream.is_none()
}
}
-37
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -29,32 +28,6 @@ impl<St, F> Map<St, F> {
pub(super) fn new(stream: St, f: F) -> Self {
Map { stream, f }
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
}
}
impl<St, F, T> Stream for Map<St, F>
@@ -76,13 +49,3 @@ where
self.stream.size_hint()
}
}
impl<St, F, T> FusedStream for Map<St, F>
where
St: FusedStream,
F: FnMut(St::Item) -> T,
{
fn is_terminated(&self) -> bool {
self.stream.is_terminated()
}
}
+2 -59
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -13,7 +12,6 @@ pin_project! {
#[pin]
stream: St,
f: F,
done: bool,
}
}
@@ -24,44 +22,13 @@ where
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapWhile")
.field("stream", &self.stream)
.field("done", &self.done)
.finish()
}
}
impl<St, F> MapWhile<St, F> {
pub(super) fn new(stream: St, f: F) -> Self {
MapWhile {
stream,
f,
done: false,
}
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
MapWhile { stream, f }
}
}
@@ -74,36 +41,12 @@ where
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
let me = self.project();
if *me.done {
return Poll::Ready(None);
}
let f = me.f;
let done = me.done;
me.stream.poll_next(cx).map(|opt| {
let mapped = opt.and_then(f);
if mapped.is_none() {
*done = true;
}
mapped
})
me.stream.poll_next(cx).map(|opt| opt.and_then(f))
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.done {
return (0, Some(0));
}
let (_, upper) = self.stream.size_hint();
(0, upper)
}
}
impl<St, F> FusedStream for MapWhile<St, F>
where
Self: Stream,
{
fn is_terminated(&self) -> bool {
self.done
}
}
-11
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::pin::Pin;
use core::task::{Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -58,16 +57,6 @@ where
}
}
impl<T, U> FusedStream for Merge<T, U>
where
T: Stream,
U: Stream<Item = T::Item>,
{
fn is_terminated(&self) -> bool {
self.a.is_terminated() && self.b.is_terminated()
}
}
fn poll_next<T, U>(
first: Pin<&mut T>,
second: Pin<&mut U>,
-35
View File
@@ -34,33 +34,6 @@ impl<T: Stream> Peekable<T> {
self.peek.as_ref()
}
}
/// Peek at the next item in the stream as a mutable reference.
pub async fn peek_mut(&mut self) -> Option<&mut T::Item>
where
T: Unpin,
{
if let Some(ref mut it) = self.peek {
Some(it)
} else {
self.peek = self.next().await;
self.peek.as_mut()
}
}
/// Poll to peek at the next item in the stream as a mutable reference.
pub fn poll_peek(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<&mut T::Item>> {
let mut this = self.project();
if this.peek.is_none() {
match this.stream.as_mut().poll_next(cx) {
Poll::Ready(item) => *this.peek = item,
Poll::Pending => return Poll::Pending,
}
}
Poll::Ready(this.peek.as_mut())
}
}
impl<T: Stream> Stream for Peekable<T> {
@@ -74,12 +47,4 @@ impl<T: Stream> Stream for Peekable<T> {
this.stream.poll_next(cx)
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let peek_len = if self.peek.is_some() { 1 } else { 0 };
let (lo, hi) = self.stream.size_hint();
let lo = lo.saturating_add(peek_len);
let hi = hi.and_then(|x| x.checked_add(peek_len));
(lo, hi)
}
}
-36
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{ready, Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -31,32 +30,6 @@ impl<St> Skip<St> {
pub(super) fn new(stream: St, remaining: usize) -> Self {
Self { stream, remaining }
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
}
}
impl<St> Stream for Skip<St>
@@ -88,12 +61,3 @@ where
(lower, upper)
}
}
impl<St> FusedStream for Skip<St>
where
St: FusedStream,
{
fn is_terminated(&self) -> bool {
self.stream.is_terminated()
}
}
-37
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{ready, Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -34,32 +33,6 @@ impl<St, F> SkipWhile<St, F> {
predicate: Some(predicate),
}
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
}
}
impl<St, F> Stream for SkipWhile<St, F>
@@ -98,13 +71,3 @@ where
(lower, upper)
}
}
impl<St, F> FusedStream for SkipWhile<St, F>
where
St: FusedStream,
F: FnMut(&St::Item) -> bool,
{
fn is_terminated(&self) -> bool {
self.stream.is_terminated()
}
}
-36
View File
@@ -4,7 +4,6 @@ use core::cmp;
use core::fmt;
use core::pin::Pin;
use core::task::{Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -32,32 +31,6 @@ impl<St> Take<St> {
pub(super) fn new(stream: St, remaining: usize) -> Self {
Self { stream, remaining }
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
}
}
impl<St> Stream for Take<St>
@@ -101,12 +74,3 @@ where
(lower, upper)
}
}
impl<St> FusedStream for Take<St>
where
St: Stream,
{
fn is_terminated(&self) -> bool {
self.remaining == 0
}
}
+7 -38
View File
@@ -3,7 +3,6 @@ use crate::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -37,32 +36,6 @@ impl<St, F> TakeWhile<St, F> {
done: false,
}
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
}
}
impl<St, F> Stream for TakeWhile<St, F>
@@ -75,7 +48,13 @@ where
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if !*self.as_mut().project().done {
self.as_mut().project().stream.poll_next(cx).map(|ready| {
let ready = ready.filter(self.as_mut().project().predicate);
let ready = ready.and_then(|item| {
if !(self.as_mut().project().predicate)(&item) {
None
} else {
Some(item)
}
});
if ready.is_none() {
*self.as_mut().project().done = true;
@@ -98,13 +77,3 @@ where
(0, upper)
}
}
impl<St, F> FusedStream for TakeWhile<St, F>
where
St: Stream,
F: FnMut(&St::Item) -> bool,
{
fn is_terminated(&self) -> bool {
self.done
}
}
-38
View File
@@ -4,7 +4,6 @@ use core::fmt;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use futures_core::FusedStream;
use pin_project_lite::pin_project;
pin_project! {
@@ -38,32 +37,6 @@ impl<St, Fut, F> Then<St, Fut, F> {
f,
}
}
/// Returns a reference to the inner stream.
pub fn get_ref(&self) -> &St {
&self.stream
}
/// Returns a mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_mut(&mut self) -> &mut St {
&mut self.stream
}
/// Returns a pinned mutable reference to the inner stream.
///
/// Mutating the inner stream may confuse this combinator.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut St> {
self.project().stream
}
/// Consumes this combinator and returns the inner stream.
///
/// This may discard intermediate combinator state.
pub fn into_inner(self) -> St {
self.stream
}
}
impl<St, F, Fut> Stream for Then<St, Fut, F>
@@ -108,14 +81,3 @@ where
(lower, upper)
}
}
impl<St, F, Fut> FusedStream for Then<St, Fut, F>
where
St: FusedStream,
Fut: Future,
F: FnMut(St::Item) -> Fut,
{
fn is_terminated(&self) -> bool {
self.future.is_none() && self.stream.is_terminated()
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
//! Slow down a stream by enforcing a delay between items.
use crate::Stream;
use tokio::time::{sleep, Duration, Sleep};
use tokio::time::{Duration, Instant, Sleep};
use std::future::Future;
use std::pin::Pin;
@@ -14,7 +14,7 @@ where
T: Stream,
{
Throttle {
delay: sleep(duration),
delay: tokio::time::sleep_until(Instant::now() + duration),
duration,
has_delayed: true,
stream,
@@ -81,7 +81,7 @@ impl<T: Stream> Stream for Throttle<T> {
if value.is_some() {
if !is_zero(dur) {
me.delay.set(sleep(dur));
me.delay.reset(Instant::now() + dur);
}
*me.has_delayed = false;
+6 -4
View File
@@ -1,6 +1,6 @@
use crate::stream_ext::Fuse;
use crate::Stream;
use tokio::time::{sleep, Sleep};
use tokio::time::{Instant, Sleep};
use core::future::Future;
use core::pin::Pin;
@@ -29,7 +29,8 @@ pub struct Elapsed(());
impl<S: Stream> Timeout<S> {
pub(super) fn new(stream: S, duration: Duration) -> Self {
let deadline = sleep(duration);
let next = Instant::now() + duration;
let deadline = tokio::time::sleep_until(next);
Timeout {
stream: Fuse::new(stream),
@@ -44,12 +45,13 @@ impl<S: Stream> Stream for Timeout<S> {
type Item = Result<S::Item, Elapsed>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut me = self.project();
let me = self.project();
match me.stream.poll_next(cx) {
Poll::Ready(v) => {
if v.is_some() {
me.deadline.set(sleep(*me.duration));
let next = Instant::now() + *me.duration;
me.deadline.reset(next);
*me.poll_deadline = true;
}
return Poll::Ready(v.map(Ok));
+3 -7
View File
@@ -632,10 +632,6 @@ where
should_loop = true;
idx = idx.wrapping_add(1) % self.entries.len();
if added == limit {
break;
}
}
Poll::Ready(None) => {
// Remove the entry
@@ -689,15 +685,15 @@ where
}
fn size_hint(&self) -> (usize, Option<usize>) {
let mut ret: (usize, Option<usize>) = (0, Some(0));
let mut ret = (0, Some(0));
for (_, stream) in &self.entries {
let hint = stream.size_hint();
ret.0 = ret.0.saturating_add(hint.0);
ret.0 += hint.0;
match (ret.1, hint.1) {
(Some(a), Some(b)) => ret.1 = a.checked_add(b),
(Some(a), Some(b)) => ret.1 = Some(a + b),
(Some(_), None) => ret.1 = None,
_ => {}
}
-5
View File
@@ -13,11 +13,6 @@ pub use mpsc_bounded::ReceiverStream;
mod mpsc_unbounded;
pub use mpsc_unbounded::UnboundedReceiverStream;
cfg_rt! {
mod task;
pub use task::JoinSetStream;
}
cfg_sync! {
mod broadcast;
pub use broadcast::BroadcastStream;
-78
View File
@@ -1,78 +0,0 @@
use crate::Stream;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::task::{JoinError, JoinSet};
/// A wrapper around [`tokio::task::JoinSet`] that implements [`Stream`].
///
/// # Example
///
/// ```
/// use tokio::task::JoinSet;
/// use tokio_stream::wrappers::JoinSetStream;
/// use tokio_stream::StreamExt;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<(), tokio::task::JoinError> {
/// let set: JoinSet<_> = (0..2).map(|i| async move { i }).collect();
///
/// let mut stream = JoinSetStream::new(set);
/// assert_eq!(stream.next().await.transpose()?, Some(0));
/// assert_eq!(stream.next().await.transpose()?, Some(1));
/// assert_eq!(stream.next().await.transpose()?, None);
/// # Ok(())
/// # }
/// ```
///
/// [`tokio::task::JoinSet`]: struct@tokio::task::JoinSet
/// [`Stream`]: trait@crate::Stream
#[derive(Debug)]
pub struct JoinSetStream<T> {
inner: JoinSet<T>,
}
impl<T> JoinSetStream<T> {
/// Create a new `JoinSetStream`.
pub fn new(join_set: JoinSet<T>) -> Self {
Self { inner: join_set }
}
/// Get back the inner `JoinSet`.
pub fn into_inner(self) -> JoinSet<T> {
self.inner
}
}
impl<T: 'static> Stream for JoinSetStream<T> {
type Item = Result<T, JoinError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.inner.poll_join_next(cx)
}
/// Returns the bounds of the stream based on the underlying `JoinSet`.
///
/// It returns `(set.len(), Some(set.len()))`.
fn size_hint(&self) -> (usize, Option<usize>) {
let size = self.inner.len();
(size, Some(size))
}
}
impl<T> AsRef<JoinSet<T>> for JoinSetStream<T> {
fn as_ref(&self) -> &JoinSet<T> {
&self.inner
}
}
impl<T> AsMut<JoinSet<T>> for JoinSetStream<T> {
fn as_mut(&mut self) -> &mut JoinSet<T> {
&mut self.inner
}
}
impl<T> From<JoinSet<T>> for JoinSetStream<T> {
fn from(join_set: JoinSet<T>) -> Self {
Self::new(join_set)
}
}
-39
View File
@@ -1,39 +0,0 @@
#![cfg(feature = "rt")]
use futures::{Stream, StreamExt};
use std::collections::HashSet;
use tokio::task::JoinSet;
use tokio_stream::wrappers::JoinSetStream;
#[tokio::test]
async fn size_hint_stream() {
let set: JoinSet<_> = (0..2).map(|i| async move { i }).collect();
let mut stream = JoinSetStream::new(set);
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn join_set_as_stream() {
let set: JoinSet<_> = (0..2).map(|i| async move { i }).collect();
let stream = JoinSetStream::new(set);
let values: HashSet<_> = stream.map(|result| result.unwrap()).collect().await;
assert_eq!(values, HashSet::from([0, 1]));
}
// Cannot run this test when “unwind” is disabled
// since `JoinSet` use it to catch futures that panics.
#[cfg(panic = "unwind")]
#[tokio::test]
async fn join_set_as_stream_panics_with_error() {
let set: JoinSet<_> = std::iter::once(async move { panic!("boom!") }).collect();
let mut stream = JoinSetStream::new(set);
let result = stream.next().await.transpose();
assert!(matches!(result, Err(e) if e.is_panic()));
}
-152
View File
@@ -1,152 +0,0 @@
use std::pin::Pin;
use tokio_stream::{self as stream, Iter, StreamExt};
fn base() -> Iter<std::vec::IntoIter<i32>> {
stream::iter(vec![1, 2, 3])
}
#[tokio::test]
async fn map_accessors() {
let mut map = base().map(|x| x * 2);
let _: &Iter<_> = map.get_ref();
let _: &mut Iter<_> = map.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut map).get_pin_mut();
assert_eq!(map.next().await, Some(2));
// The recovered inner stream continues from where the combinator left it.
let mut inner = map.into_inner();
assert_eq!(inner.next().await, Some(2));
assert_eq!(inner.next().await, Some(3));
assert_eq!(inner.next().await, None);
}
#[tokio::test]
async fn take_accessors() {
let mut take = base().take(2);
let _: &Iter<_> = take.get_ref();
let _: &mut Iter<_> = take.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut take).get_pin_mut();
assert_eq!(take.next().await, Some(1));
// `take(2)` would stop after item 2, but the inner stream still has
// everything that was not yet pulled.
let mut inner = take.into_inner();
assert_eq!(inner.next().await, Some(2));
assert_eq!(inner.next().await, Some(3));
}
#[tokio::test]
async fn skip_accessors() {
let mut skip = base().skip(1);
let _: &Iter<_> = skip.get_ref();
let _: &mut Iter<_> = skip.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut skip).get_pin_mut();
assert_eq!(skip.next().await, Some(2));
let mut inner = skip.into_inner();
assert_eq!(inner.next().await, Some(3));
}
#[tokio::test]
async fn filter_accessors() {
let mut filter = base().filter(|&x| x % 2 == 1);
let _: &Iter<_> = filter.get_ref();
let _: &mut Iter<_> = filter.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut filter).get_pin_mut();
assert_eq!(filter.next().await, Some(1));
let mut inner = filter.into_inner();
assert_eq!(inner.next().await, Some(2));
}
#[tokio::test]
async fn filter_map_accessors() {
let mut filter_map = base().filter_map(|x| (x % 2 == 1).then_some(x * 10));
let _: &Iter<_> = filter_map.get_ref();
let _: &mut Iter<_> = filter_map.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut filter_map).get_pin_mut();
assert_eq!(filter_map.next().await, Some(10));
let mut inner = filter_map.into_inner();
assert_eq!(inner.next().await, Some(2));
}
#[tokio::test]
async fn map_while_accessors() {
let mut map_while = base().map_while(|x| (x < 3).then_some(x + 100));
let _: &Iter<_> = map_while.get_ref();
let _: &mut Iter<_> = map_while.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut map_while).get_pin_mut();
assert_eq!(map_while.next().await, Some(101));
let mut inner = map_while.into_inner();
assert_eq!(inner.next().await, Some(2));
}
#[tokio::test]
async fn take_while_accessors() {
let mut take_while = base().take_while(|&x| x < 3);
let _: &Iter<_> = take_while.get_ref();
let _: &mut Iter<_> = take_while.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut take_while).get_pin_mut();
assert_eq!(take_while.next().await, Some(1));
let mut inner = take_while.into_inner();
assert_eq!(inner.next().await, Some(2));
}
#[tokio::test]
async fn skip_while_accessors() {
let mut skip_while = base().skip_while(|&x| x < 2);
let _: &Iter<_> = skip_while.get_ref();
let _: &mut Iter<_> = skip_while.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut skip_while).get_pin_mut();
assert_eq!(skip_while.next().await, Some(2));
let mut inner = skip_while.into_inner();
assert_eq!(inner.next().await, Some(3));
}
#[tokio::test]
async fn then_accessors() {
let mut then = base().then(|x| std::future::ready(x + 1));
let _: &Iter<_> = then.get_ref();
let _: &mut Iter<_> = then.get_mut();
let _: Pin<&mut Iter<_>> = Pin::new(&mut then).get_pin_mut();
assert_eq!(then.next().await, Some(2));
let mut inner = then.into_inner();
assert_eq!(inner.next().await, Some(2));
}
#[tokio::test]
async fn get_mut_can_mutate_inner() {
// Mutating through `get_mut` is observed by the combinator.
let mut map = base().map(|x| x * 2);
// Skip one item directly on the inner stream.
let inner = map.get_mut();
assert_eq!(inner.next().await, Some(1));
// The combinator now sees the stream from item 2 onwards.
assert_eq!(map.next().await, Some(4));
}
-7
View File
@@ -1,4 +1,3 @@
use futures_core::FusedStream;
use tokio_stream::{Stream, StreamExt};
use std::pin::Pin;
@@ -38,22 +37,16 @@ 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]
-208
View File
@@ -1,208 +0,0 @@
use futures_core::FusedStream;
use tokio_stream::StreamExt;
// Helper: a fused base stream built from a vec
fn fused_iter<T>(items: Vec<T>) -> impl FusedStream<Item = T> {
tokio_stream::iter(items).fuse()
}
// ── map ──────────────────────────────────────────────────────────────────────
#[tokio::test]
async fn map_not_terminated_before_done() {
let stream = fused_iter(vec![1, 2]).map(|x| x * 2);
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn map_terminated_after_inner_done() {
let mut stream = fused_iter(vec![1]).map(|x| x * 2);
assert_eq!(stream.next().await, Some(2));
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
}
// ── filter ───────────────────────────────────────────────────────────────────
#[tokio::test]
async fn filter_not_terminated_before_done() {
let stream = fused_iter(vec![1, 2]).filter(|x| *x > 0);
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn filter_terminated_after_inner_done() {
let mut stream = fused_iter(vec![1]).filter(|x| *x > 0);
assert_eq!(stream.next().await, Some(1));
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
}
// ── filter_map ───────────────────────────────────────────────────────────────
#[tokio::test]
async fn filter_map_not_terminated_before_done() {
let stream = fused_iter(vec![1, 2]).filter_map(Some);
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn filter_map_terminated_after_inner_done() {
let mut stream = fused_iter(vec![1]).filter_map(|x| Some(x * 10));
assert_eq!(stream.next().await, Some(10));
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
}
// ── skip ─────────────────────────────────────────────────────────────────────
#[tokio::test]
async fn skip_not_terminated_before_done() {
let stream = fused_iter(vec![1, 2, 3]).skip(1);
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn skip_terminated_after_inner_done() {
let mut stream = fused_iter(vec![1, 2]).skip(1);
assert_eq!(stream.next().await, Some(2));
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
}
// ── skip_while ───────────────────────────────────────────────────────────────
#[tokio::test]
async fn skip_while_not_terminated_before_done() {
let stream = fused_iter(vec![1, 2, 3]).skip_while(|x| *x < 2);
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn skip_while_terminated_after_inner_done() {
let mut stream = fused_iter(vec![1, 2]).skip_while(|x| *x < 2);
assert_eq!(stream.next().await, Some(2));
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
}
// ── take ─────────────────────────────────────────────────────────────────────
#[tokio::test]
async fn take_not_terminated_before_limit() {
let stream = tokio_stream::iter(vec![1, 2, 3]).take(2);
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn take_terminated_when_remaining_zero() {
let mut stream = tokio_stream::iter(vec![1, 2]).take(2);
assert_eq!(stream.next().await, Some(1));
assert!(!stream.is_terminated());
assert_eq!(stream.next().await, Some(2));
// remaining hits 0 after getting the second item
assert!(stream.is_terminated());
assert_eq!(stream.next().await, None);
}
#[tokio::test]
async fn take_zero_is_immediately_terminated() {
let stream = tokio_stream::iter(vec![1, 2]).take(0);
assert!(stream.is_terminated());
}
// ── take_while ───────────────────────────────────────────────────────────────
#[tokio::test]
async fn take_while_not_terminated_before_predicate_fails() {
let stream = tokio_stream::iter(vec![1, 2, 3]).take_while(|x| *x < 10);
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn take_while_terminated_after_predicate_fails() {
let mut stream = tokio_stream::iter(vec![1, 5, 2]).take_while(|x| *x < 3);
assert_eq!(stream.next().await, Some(1));
assert!(!stream.is_terminated());
// predicate fails on 5 → done flag set
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
}
// ── map_while ─────────────────────────────────────────────────────────────────
#[tokio::test]
async fn map_while_not_terminated_before_closure_returns_none() {
let stream =
tokio_stream::iter(vec![1, 2, 3]).map_while(|x| if x < 10 { Some(x) } else { None });
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn map_while_terminated_after_closure_returns_none() {
let mut stream =
tokio_stream::iter(vec![1, 5, 2]).map_while(|x| if x < 3 { Some(x) } else { None });
assert_eq!(stream.next().await, Some(1));
assert!(!stream.is_terminated());
// closure returns `None` on 5 → done flag set
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
}
// ── then ─────────────────────────────────────────────────────────────────────
#[tokio::test]
async fn then_not_terminated_before_done() {
let stream = fused_iter(vec![1, 2]).then(|x| async move { x * 2 });
tokio::pin!(stream);
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn then_terminated_after_inner_done_and_no_pending_future() {
let stream = fused_iter(vec![1]).then(|x| async move { x * 2 });
tokio::pin!(stream);
assert_eq!(stream.next().await, Some(2));
assert_eq!(stream.next().await, None);
// inner stream done AND no in-flight future
assert!(stream.is_terminated());
}
// ── chain ────────────────────────────────────────────────────────────────────
#[tokio::test]
async fn chain_not_terminated_while_either_has_items() {
let stream = fused_iter(vec![1]).chain(fused_iter(vec![2]));
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn chain_terminated_only_after_both_done() {
let mut stream = fused_iter(vec![1]).chain(fused_iter(vec![2]));
assert_eq!(stream.next().await, Some(1));
assert!(!stream.is_terminated()); // b still has items
assert_eq!(stream.next().await, Some(2));
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated()); // both done now
}
// ── merge ────────────────────────────────────────────────────────────────────
#[tokio::test]
async fn merge_not_terminated_while_either_has_items() {
let stream = fused_iter(vec![1]).merge(fused_iter(vec![2]));
assert!(!stream.is_terminated());
}
#[tokio::test]
async fn merge_terminated_only_after_both_done() {
let mut stream = fused_iter(vec![1]).merge(fused_iter(vec![2]));
// drain both
let mut collected = vec![];
while let Some(x) = stream.next().await {
collected.push(x);
}
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
assert_eq!(collected.len(), 2);
}
+3 -37
View File
@@ -1,6 +1,7 @@
use tokio_stream as stream;
use tokio_test::task;
use std::iter;
use tokio_stream::{self as stream, Stream};
use tokio_test::{assert_pending, assert_ready, task};
#[tokio::test]
async fn coop() {
@@ -8,7 +9,6 @@ async fn coop() {
for _ in 0..10_000 {
if stream.poll_next().is_pending() {
tokio::task::yield_now().await;
assert!(stream.is_woken());
return;
}
@@ -16,37 +16,3 @@ async fn coop() {
panic!("did not yield");
}
#[tokio::test]
async fn test_iter_coop_budget() {
let mut stream = task::spawn(stream::iter(iter::repeat(1)));
// Tokio's default budget is 128.
// Fallback yield_amt is 32.
let limit = if cfg!(feature = "rt") { 128 } else { 32 };
for i in 0..limit {
let res = stream.poll_next();
assert!(res.is_ready(), "Should be ready at index {i}");
}
// Next poll should be pending
assert_pending!(stream.poll_next());
tokio::task::yield_now().await;
assert!(stream.is_woken());
}
#[tokio::test]
async fn test_iter_size_hint() {
let stream = stream::iter(vec![1, 2, 3]);
assert_eq!(stream.size_hint(), (3, Some(3)));
}
#[tokio::test]
async fn test_iter_eof_behavior() {
let mut stream = task::spawn(stream::iter(vec![1]));
assert_ready!(stream.poll_next());
assert_ready!(stream.poll_next()); // EOF should be ready None
}
-22
View File
@@ -1,22 +0,0 @@
use tokio_stream::StreamExt;
#[tokio::test]
async fn map_while_yields_until_closure_returns_none() {
let mut stream =
tokio_stream::iter(1..=10).map_while(|x| if x < 4 { Some(x + 3) } else { None });
assert_eq!(stream.next().await, Some(4));
assert_eq!(stream.next().await, Some(5));
assert_eq!(stream.next().await, Some(6));
assert_eq!(stream.next().await, None);
}
#[tokio::test]
async fn map_while_does_not_poll_after_closure_returns_none() {
// Once the closure returns `None`, the underlying stream must not be polled
// again, so the trailing `2` is never yielded.
let mut stream =
tokio_stream::iter(vec![1, 5, 2]).map_while(|x| if x < 3 { Some(x) } else { None });
assert_eq!(stream.next().await, Some(1));
assert_eq!(stream.next().await, None);
assert_eq!(stream.next().await, None);
}
-217
View File
@@ -1,217 +0,0 @@
use tokio_stream::{self as stream, Stream, StreamExt};
use tokio_test::{assert_pending, assert_ready, task};
use std::pin::Pin;
use std::task::{Context, Poll};
#[tokio::test]
async fn size_hint_without_peek() {
let mut s = stream::iter(vec![1, 2, 3]).peekable();
assert_eq!(s.size_hint(), (3, Some(3)));
s.next().await;
assert_eq!(s.size_hint(), (2, Some(2)));
s.next().await;
assert_eq!(s.size_hint(), (1, Some(1)));
s.next().await;
assert_eq!(s.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn size_hint_with_peek() {
let mut s = stream::iter(vec![1, 2, 3]).peekable();
// before peek: all items are in inner stream
assert_eq!(s.size_hint(), (3, Some(3)));
// after peek: one item moves into self.peek buffer — total must still be 3
let _ = s.peek().await;
assert_eq!(s.size_hint(), (3, Some(3)));
// consume the peeked item via next()
assert_eq!(s.next().await, Some(1));
assert_eq!(s.size_hint(), (2, Some(2)));
// peek again
let _ = s.peek().await;
assert_eq!(s.size_hint(), (2, Some(2)));
s.next().await;
assert_eq!(s.size_hint(), (1, Some(1)));
// peek the last item
let _ = s.peek().await;
assert_eq!(s.size_hint(), (1, Some(1)));
s.next().await;
assert_eq!(s.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn size_hint_empty_stream() {
let mut s = stream::iter(Vec::<i32>::new()).peekable();
assert_eq!(s.size_hint(), (0, Some(0)));
assert_eq!(s.peek().await, None);
assert_eq!(s.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn peek_returns_correct_item() {
let mut s = stream::iter(vec![10, 20, 30]).peekable();
assert_eq!(s.peek().await, Some(&10));
assert_eq!(s.peek().await, Some(&10)); // second peek returns same item
assert_eq!(s.next().await, Some(10)); // next() gives same item
assert_eq!(s.next().await, Some(20));
assert_eq!(s.next().await, Some(30));
assert_eq!(s.next().await, None);
}
#[tokio::test]
async fn size_hint_overflow() {
// When inner stream reports usize::MAX upper bound and an item is peeked,
// checked_add must return None rather than wrapping.
struct MaxHint(bool); // bool = whether to return one item
impl Stream for MaxHint {
type Item = ();
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<()>> {
if self.0 {
self.0 = false;
std::task::Poll::Ready(Some(()))
} else {
std::task::Poll::Ready(None)
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, Some(usize::MAX))
}
}
let mut s = MaxHint(true).peekable();
// before peek: delegates directly to inner
assert_eq!(s.size_hint(), (usize::MAX, Some(usize::MAX)));
// after peek: peek_len=1, checked_add(1) on usize::MAX must give None not panic
let _ = s.peek().await;
assert_eq!(s.size_hint(), (usize::MAX, None));
}
#[tokio::test]
async fn size_hint_unbounded_upper() {
// A stream that reports unknown upper bound
struct Unbounded;
impl Stream for Unbounded {
type Item = u32;
fn poll_next(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<u32>> {
std::task::Poll::Ready(Some(42))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(1, None)
}
}
let mut s = Unbounded.peekable();
assert_eq!(s.size_hint(), (1, None));
let _ = s.peek().await;
assert_eq!(s.size_hint(), (2, None)); // still unbounded after peek
}
#[tokio::test]
async fn peek_does_not_consume() {
let mut stream = stream::iter(vec![1, 2, 3]).peekable();
assert_eq!(stream.peek().await, Some(&1));
assert_eq!(stream.peek().await, Some(&1));
assert_eq!(stream.next().await, Some(1));
assert_eq!(stream.next().await, Some(2));
}
#[tokio::test]
async fn peek_mut_mutates_yielded_item() {
let mut stream = stream::iter(vec![1, 2, 3]).peekable();
if let Some(item) = stream.peek_mut().await {
*item += 10;
}
assert_eq!(stream.next().await, Some(11));
assert_eq!(stream.next().await, Some(2));
}
#[tokio::test]
async fn peek_on_empty_stream() {
let mut stream = stream::iter(Vec::<i32>::new()).peekable();
assert_eq!(stream.peek().await, None);
assert_eq!(stream.peek_mut().await, None);
assert_eq!(stream.next().await, None);
}
#[test]
fn poll_peek_does_not_advance_stream() {
let mut stream = task::spawn(stream::iter(vec![1, 2, 3]).peekable());
let first = stream.enter(|cx, s| s.poll_peek(cx).map(|opt| opt.copied()));
assert_eq!(assert_ready!(first), Some(1));
let second = stream.enter(|cx, s| s.poll_peek(cx).map(|opt| opt.copied()));
assert_eq!(assert_ready!(second), Some(1));
let next = stream.enter(|cx, s| s.poll_next(cx));
assert_eq!(assert_ready!(next), Some(1));
let next = stream.enter(|cx, s| s.poll_next(cx));
assert_eq!(assert_ready!(next), Some(2));
}
#[test]
fn poll_peek_mutates_buffered_item() {
let mut stream = task::spawn(stream::iter(vec![1, 2, 3]).peekable());
stream.enter(|cx, s| {
if let Poll::Ready(Some(item)) = s.poll_peek(cx) {
*item += 100;
}
});
let next = stream.enter(|cx, s| s.poll_next(cx));
assert_eq!(assert_ready!(next), Some(101));
}
struct PendingOnce {
polled: bool,
}
impl Stream for PendingOnce {
type Item = i32;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<i32>> {
if self.polled {
Poll::Ready(Some(7))
} else {
self.polled = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
#[test]
fn poll_peek_propagates_pending() {
let mut stream = task::spawn(PendingOnce { polled: false }.peekable());
assert_pending!(stream.enter(|cx, s| s.poll_peek(cx).map(|opt| opt.copied())));
let ready = stream.enter(|cx, s| s.poll_peek(cx).map(|opt| opt.copied()));
assert_eq!(assert_ready!(ready), Some(7));
}
-66
View File
@@ -225,30 +225,6 @@ fn size_hint_without_upper() {
assert_eq!(size_hint, (3, None));
}
#[test]
fn size_hint_overflow() {
struct Monster;
impl Stream for Monster {
type Item = ();
fn poll_next(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<Option<()>> {
panic!()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, Some(usize::MAX))
}
}
let mut map = StreamMap::new();
map.insert("a", Monster);
map.insert("b", Monster);
assert_eq!(map.size_hint(), (usize::MAX, None));
}
#[test]
fn new_capacity_zero() {
let map = StreamMap::<&str, stream::Pending<()>>::new();
@@ -429,27 +405,6 @@ async fn poll_next_many_enough() {
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn poll_next_many_does_not_exceed_limit() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 1)).await;
assert_eq!(n, 1);
assert_eq!(buffer.len(), 1);
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 1)).await;
assert_eq!(n, 1);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn poll_next_many_correctly_loops_around() {
for _ in 0..10 {
@@ -564,27 +519,6 @@ async fn next_many_enough() {
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn next_many_does_not_exceed_limit() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 1)).poll(cx)).await;
assert_eq!(n, 1);
assert_eq!(buffer.len(), 1);
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 1)).poll(cx)).await;
assert_eq!(n, 1);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn next_many_correctly_loops_around() {
for _ in 0..10 {
-8
View File
@@ -107,11 +107,3 @@ async fn no_timeouts() {
assert_ready_eq!(stream.poll_next(), Some(Ok(5)));
assert_ready_eq!(stream.poll_next(), None);
}
#[tokio::test]
async fn duration_max_does_not_overflow() {
let stream = stream::iter([1]).timeout(Duration::MAX);
let mut stream = task::spawn(stream);
assert_ready_eq!(stream.poll_next(), Some(Ok(1)));
}
-7
View File
@@ -26,10 +26,3 @@ async fn usage() {
assert_ready!(stream.poll_next());
}
#[tokio::test]
async fn duration_max_does_not_overflow() {
let mut stream = task::spawn(futures::stream::iter([1]).throttle(Duration::MAX));
assert_ready_eq!(stream.poll_next(), Some(1));
}
+8 -2
View File
@@ -198,7 +198,10 @@ macro_rules! assert_ready_eq {
/// ```
#[macro_export]
macro_rules! assert_ok {
($e:expr $(,)?) => {{
($e:expr) => {
assert_ok!($e,)
};
($e:expr,) => {{
use std::result::Result::*;
match $e {
Ok(v) => v,
@@ -238,7 +241,10 @@ macro_rules! assert_ok {
/// ```
#[macro_export]
macro_rules! assert_err {
($e:expr $(,)?) => {{
($e:expr) => {
assert_err!($e,);
};
($e:expr,) => {{
use std::result::Result::*;
match $e {
Ok(v) => panic!("assertion failed: Ok({:?})", v),
-40
View File
@@ -70,9 +70,6 @@ const IDLE: usize = 0;
const WAKE: usize = 1;
const SLEEP: usize = 2;
/// Default maximum number of poll iterations in [`Spawn::poll_until_idle`].
const POLL_UNTIL_IDLE_MAX_ITERATIONS: usize = 150;
impl<T> Spawn<T> {
/// Consumes `self` returning the inner value
pub fn into_inner(self) -> T
@@ -126,43 +123,6 @@ impl<T: Future> Spawn<T> {
let fut = self.future.as_mut();
self.task.enter(|cx| fut.poll(cx))
}
/// Polls the future until it is idle.
///
/// A future is considered idle when it either completes, or returns
/// [`Poll::Pending`] without a pending wake notification.
///
/// Unlike [`poll`](Self::poll), this method keeps polling while the future
/// returns [`Poll::Pending`] but has received a wake notification, advancing
/// the future as far as possible without waiting for external events.
///
/// Polling is bounded to avoid infinite loops when a future wakes without
/// making progress.
///
/// # Panics
///
/// Panics if the iteration limit is exceeded.
///
/// # Example
///
/// ```
/// use tokio_test::task;
///
/// let mut task = task::spawn(async { 42 });
///
/// assert!(task.poll_until_idle().is_ready());
/// ```
pub fn poll_until_idle(&mut self) -> Poll<T::Output> {
for _ in 0..POLL_UNTIL_IDLE_MAX_ITERATIONS {
let result = self.poll();
if result.is_ready() || !self.is_woken() {
return result;
}
}
panic!(
"poll_until_idle exceeded {POLL_UNTIL_IDLE_MAX_ITERATIONS} iterations; future may be waking without making progress"
);
}
}
impl<T: Stream> Spawn<T> {
-79
View File
@@ -1,4 +1,3 @@
use std::future::{pending, Future};
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio_stream::Stream;
@@ -24,81 +23,3 @@ fn test_spawn_stream_size_hint() {
let spawn = task::spawn(SizedStream);
assert_eq!(spawn.size_hint(), (100, Some(200)));
}
#[test]
fn poll_until_idle_ready() {
let mut task = task::spawn(async { 42 });
assert_eq!(task.poll_until_idle(), Poll::Ready(42));
}
#[test]
fn poll_until_idle_pending_not_woken() {
let mut task = task::spawn(pending::<()>());
assert!(task.poll_until_idle().is_pending());
}
struct WakeThenReady {
step: u8,
}
impl Future for WakeThenReady {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
match self.step {
0 => {
self.step = 1;
cx.waker().wake_by_ref();
Poll::Pending
}
_ => Poll::Ready(()),
}
}
}
#[test]
fn poll_until_idle_advances_on_wake() {
let mut task = task::spawn(WakeThenReady { step: 0 });
assert!(task.poll_until_idle().is_ready());
}
struct WakeNTimes {
remaining: u8,
}
impl Future for WakeNTimes {
type Output = u8;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u8> {
if self.remaining == 0 {
return Poll::Ready(0);
}
self.remaining -= 1;
cx.waker().wake_by_ref();
Poll::Pending
}
}
#[test]
fn poll_until_idle_multiple_wakes() {
let mut task = task::spawn(WakeNTimes { remaining: 3 });
assert_eq!(task.poll_until_idle(), Poll::Ready(0));
}
struct WakeForever;
impl Future for WakeForever {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
cx.waker().wake_by_ref();
Poll::Pending
}
}
#[test]
#[should_panic(expected = "poll_until_idle exceeded 150 iterations")]
fn poll_until_idle_panics_on_infinite_wake() {
let mut task = task::spawn(WakeForever);
let _ = task.poll_until_idle();
}
-36
View File
@@ -1,39 +1,3 @@
# 0.7.19 (July 21st, 2026)
### Added
- io: add `write_all_vectored` ([#7768], [#8159])
- sync: add `DropGuard::token` for cancellation tokens ([#8226])
- sync: implement `PartialEq` and `Eq` for `CancellationToken` ([#8110])
- task: add `AbortOnDrop` ([#7855])
- task: add `JoinMap::try_join_next` ([#8099])
### Changed
- codec: use `libc::memchr` for `LinesCodec` delimiter scan ([#8141])
### Fixed
- codec: fix `is_readable` when buffer is not empty ([#7912])
- task: avoid replacing the `JoinQueue` waker in `try_join_next` ([#8279])
- time: wake `DelayQueue` after resetting to expired ([#8274])
### Documented
- codec: document `UdpFramed` decoder errors ([#8248])
[#7768]: https://github.com/tokio-rs/tokio/pull/7768
[#7855]: https://github.com/tokio-rs/tokio/pull/7855
[#7912]: https://github.com/tokio-rs/tokio/pull/7912
[#8099]: https://github.com/tokio-rs/tokio/pull/8099
[#8110]: https://github.com/tokio-rs/tokio/pull/8110
[#8141]: https://github.com/tokio-rs/tokio/pull/8141
[#8159]: https://github.com/tokio-rs/tokio/pull/8159
[#8226]: https://github.com/tokio-rs/tokio/pull/8226
[#8248]: https://github.com/tokio-rs/tokio/pull/8248
[#8274]: https://github.com/tokio-rs/tokio/pull/8274
[#8279]: https://github.com/tokio-rs/tokio/pull/8279
# 0.7.18 (January 4th, 2026)
### Added
+2 -5
View File
@@ -4,7 +4,7 @@ name = "tokio-util"
# - Remove path dependencies (if any)
# - Update CHANGELOG.md.
# - Create "tokio-util-0.7.x" git tag.
version = "0.7.19"
version = "0.7.18"
edition = "2021"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
@@ -25,7 +25,7 @@ full = ["codec", "compat", "io-util", "time", "net", "rt", "join-map"]
net = ["tokio/net"]
compat = ["futures-io"]
codec = ["libc"]
codec = []
time = ["tokio/time", "slab"]
io = []
io-util = ["io", "tokio/rt", "tokio/io-util"]
@@ -46,9 +46,6 @@ slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
tracing = { version = "0.1.29", default-features = false, features = ["std"], optional = true }
hashbrown = { version = "0.15.0", default-features = false, optional = true }
[target.'cfg(unix)'.dependencies]
libc = { version = "0.2.168", optional = true } # Backs the LinesCodec delimiter scan via libc::memchr
[dev-dependencies]
tokio = { version = "1.0.0", features = ["full"] }
tokio-test = "0.4.0"
+1 -1
View File
@@ -47,7 +47,7 @@ pub trait Decoder {
/// implementation of `decode_eof` to yield an `io::Error` when the decoder
/// fails to consume all available data.
///
/// Note that implementers of this trait can simply indicate `type Error =
/// Note that implementors of this trait can simply indicate `type Error =
/// io::Error` to use I/O errors as this type.
///
/// [`FramedRead`]: crate::codec::FramedRead
+27 -60
View File
@@ -493,12 +493,8 @@ impl LengthDelimitedCodec {
/// words, if a frame is currently in process of being decoded with a frame
/// size greater than `val` but less than the max frame length in effect
/// before calling this function, then the frame will be allowed.
///
/// If `val` is larger than what the length field can represent, it is
/// clipped to the maximum representable value.
pub fn set_max_frame_length(&mut self, val: usize) {
self.builder.max_frame_length(val);
self.builder.adjust_max_frame_len();
}
fn decode_head(&mut self, src: &mut BytesMut) -> io::Result<Option<usize>> {
@@ -603,10 +599,10 @@ impl Decoder for LengthDelimitedCodec {
}
}
impl Encoder<&[u8]> for LengthDelimitedCodec {
impl Encoder<Bytes> for LengthDelimitedCodec {
type Error = io::Error;
fn encode(&mut self, data: &[u8], dst: &mut BytesMut) -> Result<(), io::Error> {
fn encode(&mut self, data: Bytes, dst: &mut BytesMut) -> Result<(), io::Error> {
let n = data.len();
if n > self.builder.max_frame_len {
@@ -631,8 +627,8 @@ impl Encoder<&[u8]> for LengthDelimitedCodec {
})?;
// Reserve capacity in the destination buffer to fit the frame and
// length field.
dst.reserve(self.builder.length_field_len + data.len());
// length field (plus adjustment).
dst.reserve(self.builder.length_field_len + n);
if self.builder.length_field_is_big_endian {
dst.put_uint(n as u64, self.builder.length_field_len);
@@ -641,20 +637,12 @@ impl Encoder<&[u8]> for LengthDelimitedCodec {
}
// Write the frame to the buffer
dst.extend_from_slice(data);
dst.extend_from_slice(&data[..]);
Ok(())
}
}
impl Encoder<Bytes> for LengthDelimitedCodec {
type Error = io::Error;
fn encode(&mut self, data: Bytes, dst: &mut BytesMut) -> Result<(), io::Error> {
Encoder::<&[u8]>::encode(self, data.as_ref(), dst)
}
}
impl Default for LengthDelimitedCodec {
fn default() -> Self {
Self::new()
@@ -687,21 +675,18 @@ impl Builder {
/// # Examples
///
/// ```
/// # use tokio::io::AsyncRead;
/// use tokio_util::codec::LengthDelimitedCodec;
/// use tokio_stream::StreamExt;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let io: &[u8] = b"\x00\x0bhello world";
/// let mut reader = LengthDelimitedCodec::builder()
/// # fn bind_read<T: AsyncRead>(io: T) {
/// LengthDelimitedCodec::builder()
/// .length_field_offset(0)
/// .length_field_type::<u16>()
/// .length_adjustment(0)
/// .num_skip(0)
/// .new_read(io);
///
/// let frame = reader.next().await.unwrap().unwrap();
/// assert_eq!(&frame[..], b"hello world");
/// # }
/// # pub fn main() {}
/// ```
pub fn new() -> Builder {
Builder {
@@ -964,20 +949,14 @@ impl Builder {
/// # Examples
///
/// ```
/// use bytes::{Bytes, BytesMut};
/// use tokio_util::codec::{Decoder, Encoder, LengthDelimitedCodec};
///
/// use tokio_util::codec::LengthDelimitedCodec;
/// # pub fn main() {
/// let mut codec = LengthDelimitedCodec::builder()
/// LengthDelimitedCodec::builder()
/// .length_field_offset(0)
/// .length_field_type::<u16>()
/// .length_adjustment(0)
/// .num_skip(0)
/// .new_codec();
///
/// let mut buf = BytesMut::new();
/// codec.encode(Bytes::from_static(b"hello world"), &mut buf).unwrap();
/// let frame = codec.decode(&mut buf).unwrap().unwrap();
/// assert_eq!(&frame[..], b"hello world");
/// # }
/// ```
pub fn new_codec(&self) -> LengthDelimitedCodec {
@@ -996,21 +975,18 @@ impl Builder {
/// # Examples
///
/// ```
/// # use tokio::io::AsyncRead;
/// use tokio_util::codec::LengthDelimitedCodec;
/// use tokio_stream::StreamExt;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let io: &[u8] = b"\x00\x0bhello world";
/// let mut reader = LengthDelimitedCodec::builder()
/// # fn bind_read<T: AsyncRead>(io: T) {
/// LengthDelimitedCodec::builder()
/// .length_field_offset(0)
/// .length_field_type::<u16>()
/// .length_adjustment(0)
/// .num_skip(0)
/// .new_read(io);
///
/// let frame = reader.next().await.unwrap().unwrap();
/// assert_eq!(&frame[..], b"hello world");
/// # }
/// # pub fn main() {}
/// ```
pub fn new_read<T>(&self, upstream: T) -> FramedRead<T, LengthDelimitedCodec>
where
@@ -1064,7 +1040,7 @@ impl Builder {
fn num_head_bytes(&self) -> usize {
let num = self.length_field_offset + self.length_field_len;
cmp::max(num, self.num_skip.unwrap_or_default())
cmp::max(num, self.num_skip.unwrap_or(0))
}
fn get_num_skip(&self) -> usize {
@@ -1073,25 +1049,16 @@ impl Builder {
}
fn adjust_max_frame_len(&mut self) {
let max_allowed_len = self.max_allowed_frame_len();
if self.max_frame_len > max_allowed_len {
self.max_frame_len = max_allowed_len;
}
}
fn max_allowed_frame_len(&self) -> usize {
let max_allowed_len = self
.max_length_field_value()
.saturating_add_signed(self.length_adjustment as i64);
usize::try_from(max_allowed_len).unwrap_or(usize::MAX)
}
fn max_length_field_value(&self) -> u64 {
match 1u64.checked_shl((8 * self.length_field_len) as u32) {
// Calculate the maximum number that can be represented using `length_field_len` bytes.
let max_number = match 1u64.checked_shl((8 * self.length_field_len) as u32) {
Some(shl) => shl - 1,
None => u64::MAX,
};
let max_allowed_len = max_number.saturating_add_signed(self.length_adjustment as i64);
if self.max_frame_len as u64 > max_allowed_len {
self.max_frame_len = usize::try_from(max_allowed_len).unwrap_or(usize::MAX);
}
}
}
+3 -1
View File
@@ -115,7 +115,9 @@ impl Decoder for LinesCodec {
// there's no max_length set, we'll read to the end of the buffer.
let read_to = cmp::min(self.max_length.saturating_add(1), buf.len());
let newline_offset = crate::util::memchr::memchr(b'\n', &buf[self.next_index..read_to]);
let newline_offset = buf[self.next_index..read_to]
.iter()
.position(|b| *b == b'\n');
match (self.is_discarding, newline_offset) {
(true, Some(offset)) => {
+2 -2
View File
@@ -12,7 +12,7 @@
//! The following example demonstrates how to use a codec such as [`LinesCodec`] to
//! write framed data. [`FramedWrite`] can be used to achieve this. Data sent to
//! [`FramedWrite`] are first framed according to a specific codec, and then sent to
//! an implementer of [`AsyncWrite`].
//! an implementor of [`AsyncWrite`].
//!
//! ```
//! use futures::sink::SinkExt;
@@ -43,7 +43,7 @@
//! # Example decoding using `LinesCodec`
//! The following example demonstrates how to use a codec such as [`LinesCodec`] to
//! read a stream of framed data. [`FramedRead`] can be used to achieve this. [`FramedRead`]
//! will keep reading from an [`AsyncRead`] implementer until a whole frame, according to a codec,
//! will keep reading from an [`AsyncRead`] implementor until a whole frame, according to a codec,
//! can be parsed.
//!
//!```
-4
View File
@@ -291,10 +291,6 @@ impl AsyncWrite for Sender {
return Poll::Ready(Err(IoError::new(IoErrorKind::BrokenPipe, CLOSED_ERROR_MSG)));
}
if bufs.iter().all(|buf| buf.is_empty()) {
return Poll::Ready(Ok(0));
}
let free = inner
.backpressure_boundary
.checked_sub(inner.buf.len())
+1 -10
View File
@@ -158,7 +158,6 @@ pub struct StreamReader<S, B> {
inner: S,
// This field is not pinned.
chunk: Option<B>,
eof: bool,
}
impl<S, B, E> StreamReader<S, B>
@@ -180,7 +179,6 @@ where
Self {
inner: stream,
chunk: None,
eof: false,
}
}
@@ -279,8 +277,6 @@ where
// This unwrap is very sad, but it can't be avoided.
let buf = self.project().chunk.as_ref().unwrap().chunk();
return Poll::Ready(Ok(buf));
} else if *self.as_mut().project().eof {
return Poll::Ready(Ok(&[]));
} else {
match self.as_mut().project().inner.poll_next(cx) {
Poll::Ready(Some(Ok(chunk))) => {
@@ -288,10 +284,7 @@ where
*self.as_mut().project().chunk = Some(chunk);
}
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
Poll::Ready(None) => {
*self.as_mut().project().eof = true;
return Poll::Ready(Ok(&[]));
}
Poll::Ready(None) => return Poll::Ready(Ok(&[])),
Poll::Pending => return Poll::Pending,
}
}
@@ -318,7 +311,6 @@ impl<S: Unpin, B> Unpin for StreamReader<S, B> {}
struct StreamReaderProject<'a, S, B> {
inner: Pin<&'a mut S>,
chunk: &'a mut Option<B>,
eof: &'a mut bool,
}
impl<S, B> StreamReader<S, B> {
@@ -330,7 +322,6 @@ impl<S, B> StreamReader<S, B> {
StreamReaderProject {
inner: unsafe { Pin::new_unchecked(&mut me.inner) },
chunk: &mut me.chunk,
eof: &mut me.eof,
}
}
}
+2 -2
View File
@@ -147,8 +147,8 @@ fn advance_slices<'a>(bufs: &mut &mut [IoSlice<'a>], n: usize) {
*bufs = &mut std::mem::take(bufs)[remove..];
if let Some(first) = bufs.first_mut() {
let buf = &first[left..];
// Necessary due to a limitation in the borrow checker,
let buf = &first[..left];
// necessary due to limitating in the borrow checker,
// when tokio MSRV reaches 1.81.0 this entire function
// can be replaced with `IoSlice::advance_slices`
//
-20
View File
@@ -122,26 +122,6 @@ impl Clone for CancellationToken {
}
}
impl PartialEq for CancellationToken {
/// Checks if two tokens are equal in terms of their cancellation operation.
///
/// Two tokens are considered equal if cancelling one will always also cancel the other and vice
/// versa. This is only true for cloned tokens and not for tokens in a parent-child
/// relationship.
fn eq(&self, other: &CancellationToken) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for CancellationToken {}
impl core::hash::Hash for CancellationToken {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.inner).hash(state);
}
}
impl Drop for CancellationToken {
fn drop(&mut self) {
tree_node::decrease_handle_refcount(&self.inner);
@@ -10,13 +10,6 @@ pub struct DropGuard {
}
impl DropGuard {
/// Returns a reference to the cancellation token wrapped by this guard.
pub fn token(&self) -> &CancellationToken {
self.inner
.as_ref()
.expect("`inner` can only be None in a destructor")
}
/// Returns stored cancellation token and removes this drop guard instance
/// (i.e. it will no longer cancel token). Other guards for this token
/// are not affected.
@@ -13,13 +13,6 @@ pub struct DropGuardRef<'a> {
}
impl<'a> DropGuardRef<'a> {
/// Returns a reference to the cancellation token wrapped by this guard.
pub fn token(&self) -> &CancellationToken {
self.inner
.as_ref()
.expect("`inner` can only be None in a destructor")
}
/// Returns stored cancellation token and removes this drop guard instance
/// (i.e. it will no longer cancel token). Other guards for this token
/// are not affected.
+1 -55
View File
@@ -446,7 +446,7 @@ where
/// * `Some((key, Ok(value)))` if one of the tasks in this `JoinMap` has
/// completed. The `value` is the return value of that ask, and `key` is
/// the key associated with the task.
/// * `Some((key, Err(err)))` if one of the tasks in this `JoinMap` has
/// * `Some((key, Err(err))` if one of the tasks in this `JoinMap` has
/// panicked or been aborted. `key` is the key associated with the task
/// that panicked or was aborted.
/// * `None` if the `JoinMap` is empty.
@@ -468,60 +468,6 @@ where
}
}
/// Tries to join one of the tasks in the map that has completed and
/// returns its output, along with the key corresponding to that task.
///
/// Returns `None` if there are no completed tasks, or if the map is empty.
///
/// # Returns
///
/// This function returns:
///
/// * `Some((key, Ok(value)))` if one of the tasks in this `JoinMap` has
/// completed. The `value` is the return value of that task, and `key`
/// is the key associated with the task.
/// * `Some((key, Err(err)))` if one of the tasks in this `JoinMap` has
/// panicked or been aborted. `key` is the key associated with the task
/// that panicked or was aborted.
/// * `None` if there are no completed tasks ready to be joined, or the
/// `JoinMap` is empty.
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let mut map = JoinMap::new();
/// map.spawn("answer", async { 42 });
///
/// let (key, res) = loop {
/// if let Some(joined) = map.try_join_next() {
/// break joined;
/// }
/// tokio::task::yield_now().await;
/// };
///
/// assert_eq!(key, "answer");
/// assert_eq!(res.unwrap(), 42);
/// # }
/// ```
pub fn try_join_next(&mut self) -> Option<(K, Result<V, JoinError>)> {
loop {
let (res, id) = match self.tasks.try_join_next_with_id()? {
Ok((id, output)) => (Ok(output), id),
Err(e) => {
let id = e.id();
(Err(e), id)
}
};
if let Some(key) = self.remove_by_id(id) {
break Some((key, res));
}
}
}
/// Aborts all tasks and waits for them to finish shutting down.
///
/// Calling this method is equivalent to calling [`abort_all`] and then calling [`join_next`] in
-4
View File
@@ -188,10 +188,6 @@ impl<T> JoinQueue<T> {
/// Note that on success the handle will panic on subsequent polls
/// since it becomes consumed.
fn try_poll_handle(jh: &mut AbortOnDropHandle<T>) -> Option<Result<T, JoinError>> {
if !jh.is_finished() {
return None;
}
let waker = futures_util::task::noop_waker();
let mut cx = Context::from_waker(&waker);
+32 -38
View File
@@ -4,18 +4,19 @@ use std::fmt::{Debug, Formatter};
use std::future::Future;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::runtime::Builder;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot;
use tokio::task::{spawn_local, JoinHandle};
use tokio::task::{spawn_local, JoinHandle, LocalSet};
/// A cloneable handle to a local pool, used for spawning `!Send` tasks.
///
/// Internally the local pool uses a [`tokio::runtime::LocalRuntime`] for each worker thread
/// Internally the local pool uses a [`tokio::task::LocalSet`] for each worker thread
/// in the pool. Consequently you can also use [`tokio::task::spawn_local`] (which will
/// execute on the same thread) inside the Future you supply to the various spawn methods
/// of `LocalPoolHandle`.
///
/// [`tokio::runtime::LocalRuntime`]: tokio::runtime::LocalRuntime
/// [`tokio::task::LocalSet`]: tokio::task::LocalSet
/// [`tokio::task::spawn_local`]: tokio::task::spawn_local
///
/// # Examples
@@ -237,10 +238,10 @@ impl LocalPool {
let _abort_guard = AbortGuard(abort_handle);
// Inside the future we can't run spawn_local yet because we're not
// in the context of a LocalRuntime. We need to send create_task to the
// LocalRuntime task for spawning.
// in the context of a LocalSet. We need to send create_task to the
// LocalSet task for spawning.
let spawn_task = Box::new(move || {
// Once we're in the LocalRuntime context we can call spawn_local
// Once we're in the LocalSet context we can call spawn_local
let join_handle =
spawn_local(
async move { Abortable::new(create_task(), abort_registration).await },
@@ -254,7 +255,7 @@ impl LocalPool {
}
});
// Send the callback to the LocalRuntime task
// Send the callback to the LocalSet task
if let Err(e) = worker_spawner.send(spawn_task) {
// Propagate the error as a panic in the join handle.
panic!("Failed to send job to worker: {e}");
@@ -378,17 +379,15 @@ impl LocalWorkerHandle {
/// Create a new worker for executing pinned tasks
fn new_worker() -> LocalWorkerHandle {
let (sender, receiver) = unbounded_channel();
let (handle_sender, handle_receiver) = std::sync::mpsc::channel();
let runtime = Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to start a pinned worker thread runtime");
let runtime_handle = runtime.handle().clone();
let task_count = Arc::new(AtomicUsize::new(0));
let task_count_clone = Arc::clone(&task_count);
std::thread::spawn(|| Self::run(handle_sender, receiver, task_count_clone));
let runtime_handle = handle_receiver
.recv()
.expect("Failed to recv local runtime init result")
.expect("Failed to start local runtime");
std::thread::spawn(|| Self::run(runtime, receiver, task_count_clone));
LocalWorkerHandle {
runtime_handle,
@@ -398,38 +397,28 @@ impl LocalWorkerHandle {
}
fn run(
handle_sender: std::sync::mpsc::Sender<std::io::Result<tokio::runtime::Handle>>,
runtime: tokio::runtime::Runtime,
mut task_receiver: UnboundedReceiver<PinnedFutureSpawner>,
task_count: Arc<AtomicUsize>,
) {
let runtime = match tokio::runtime::LocalRuntime::new() {
Ok(runtime) => runtime,
Err(err) => {
let _ = handle_sender.send(Err(err));
return;
}
};
let runtime_handle = runtime.handle().clone();
handle_sender
.send(Ok(runtime_handle))
.expect("Failed to send local runtime handle");
drop(handle_sender);
runtime.block_on(async {
let local_set = LocalSet::new();
local_set.block_on(&runtime, async {
while let Some(spawn_task) = task_receiver.recv().await {
// Calls spawn_local(future)
(spawn_task)();
}
});
// If there are any tasks on the runtime that has already completed,
// but whose output has not yet been reported, let that task complete.
// If there are any tasks on the runtime associated with a LocalSet task
// that has already completed, but whose output has not yet been
// reported, let that task complete.
//
// Since the task_count is decremented when the runtime task exits,
// reading that counter lets us know if any such tasks completed during
// the call to `block_on`.
//
// Tasks on the LocalSet can't complete during this loop since they're
// stored on the LocalSet and we aren't accessing it.
let mut previous_task_count = task_count.load(Ordering::SeqCst);
loop {
// This call will also run tasks spawned on the runtime.
@@ -442,10 +431,15 @@ impl LocalWorkerHandle {
}
}
// It's now no longer possible for a task on the local runtime
// associated with task that has completed. Drop both
// local runtime to let tasks on the runtime be cancelled if and
// only if they are still on the runtime.
// It's now no longer possible for a task on the runtime to be
// associated with a LocalSet task that has completed. Drop both the
// LocalSet and runtime to let tasks on the runtime be cancelled if and
// only if they are still on the LocalSet.
//
// Drop the LocalSet task first so that anyone awaiting the runtime
// JoinHandle will see the cancelled error after the LocalSet task
// destructor has completed.
drop(local_set);
drop(runtime);
}
}
+10 -42
View File
@@ -580,7 +580,12 @@ impl<T> DelayQueue<T> {
/// current task for wakeup if the value is not yet available, and returning
/// `None` if the queue is exhausted.
pub fn poll_expired(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Expired<T>>> {
if !self.waker.as_ref().is_some_and(|w| w.will_wake(cx.waker())) {
if !self
.waker
.as_ref()
.map(|w| w.will_wake(cx.waker()))
.unwrap_or(false)
{
self.waker = Some(cx.waker().clone());
}
@@ -859,19 +864,11 @@ impl<T> DelayQueue<T> {
self.slab[*key].expired = false;
self.insert_idx(when, *key);
let inserted_expired = self.slab[*key].expired;
let next_deadline = self.next_deadline();
match (next_deadline, &mut self.delay) {
(None, _) => self.delay = None,
(Some(deadline), Some(delay)) => delay.as_mut().reset(deadline),
(Some(deadline), None) => self.delay = Some(Box::pin(sleep_until(deadline))),
}
if inserted_expired {
if let Some(waker) = self.waker.take() {
waker.wake();
}
if let (Some(ref mut delay), Some(deadline)) = (&mut self.delay, next_deadline) {
// This should awaken us if necessary (ie, if already expired)
delay.as_mut().reset(deadline);
}
}
@@ -951,7 +948,7 @@ impl<T> DelayQueue<T> {
pub fn peek(&self) -> Option<Key> {
use self::wheel::Stack;
self.expired.peek().or_else(|| self.wheel.peek(&self.slab))
self.expired.peek().or_else(|| self.wheel.peek())
}
/// Returns the next time to poll as determined by the wheel.
@@ -1031,18 +1028,10 @@ impl<T> DelayQueue<T> {
/// # }
/// ```
pub fn clear(&mut self) {
let had_entries = !self.slab.is_empty();
self.slab.clear();
self.expired = Stack::default();
self.wheel = Wheel::new();
self.delay = None;
if had_entries {
if let Some(waker) = self.waker.take() {
waker.wake();
}
}
}
/// Returns the number of elements the queue can hold without reallocating.
@@ -1260,27 +1249,6 @@ impl<T> wheel::Stack for Stack<T> {
self.head
}
fn peek_earliest(&self, store: &Self::Store) -> Option<Self::Owned> {
let head = self.head?;
let mut earliest = (head, store[head].when);
let mut curr = store[head].next;
while let Some(key) = curr {
let data = &store[key];
// The comparison is strict so that the first entry seen wins a tie,
// which agrees with `pop` when every entry in the slot shares a
// deadline.
if data.when < earliest.1 {
earliest = (key, data.when);
}
curr = data.next;
}
Some(earliest.0)
}
#[track_caller]
fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store) {
let key = *item;
+2 -2
View File
@@ -148,8 +148,8 @@ impl<T: Stack> Level<T> {
ret
}
pub(crate) fn peek_entry_slot(&self, slot: usize, store: &T::Store) -> Option<T::Owned> {
self.slot[slot].peek_earliest(store)
pub(crate) fn peek_entry_slot(&self, slot: usize) -> Option<T::Owned> {
self.slot[slot].peek()
}
}
+6 -5
View File
@@ -111,7 +111,8 @@ where
debug_assert!({
self.levels[level]
.next_expiration(self.elapsed)
.map_or(true, |e| e.deadline >= self.elapsed)
.map(|e| e.deadline >= self.elapsed)
.unwrap_or(true)
});
Ok(())
@@ -140,9 +141,9 @@ where
}
/// Next key that will expire
pub(crate) fn peek(&self, store: &T::Store) -> Option<T::Owned> {
pub(crate) fn peek(&self) -> Option<T::Owned> {
self.next_expiration()
.and_then(|expiration| self.peek_entry(&expiration, store))
.and_then(|expiration| self.peek_entry(&expiration))
}
/// Advances the timer up to the instant represented by `now`.
@@ -250,8 +251,8 @@ where
self.levels[expiration.level].pop_entry_slot(expiration.slot, store)
}
fn peek_entry(&self, expiration: &Expiration, store: &T::Store) -> Option<T::Owned> {
self.levels[expiration.level].peek_entry_slot(expiration.slot, store)
fn peek_entry(&self, expiration: &Expiration) -> Option<T::Owned> {
self.levels[expiration.level].peek_entry_slot(expiration.slot)
}
fn level_for(&self, when: u64) -> usize {
-7
View File
@@ -25,13 +25,6 @@ pub(crate) trait Stack: Default {
/// Peek into the stack.
fn peek(&self) -> Option<Self::Owned>;
/// Peek at the item in the stack with the earliest deadline.
///
/// Unlike `peek`, this does not have to agree with `pop`: a slot in a level
/// above zero spans a range of deadlines, so its entries are only ordered
/// once they cascade down.
fn peek_earliest(&self, store: &Self::Store) -> Option<Self::Owned>;
fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store);
fn when(item: &Self::Borrowed, store: &Self::Store) -> u64;
-6
View File
@@ -22,12 +22,6 @@ use std::{
/// handle encoding and decoding of messages frames. Note that the incoming and
/// outgoing frame types may be distinct.
///
/// A single datagram may decode into multiple frames. `UdpFramed` will keep
/// calling [`Decoder::decode_eof`] with the current datagram until it returns
/// `Ok(None)`. If a decoder wants to discard a malformed datagram and continue
/// receiving later datagrams, it should consume or clear the remaining bytes
/// from the buffer before returning `Err`.
///
/// This function returns a *single* object that is both [`Stream`] and [`Sink`];
/// grouping this into a single object is often useful for layering things which
/// require both read and write access to the underlying object.
-102
View File
@@ -1,102 +0,0 @@
//! Search for a byte in a byte array using libc.
//!
//! When nothing pulls in libc, then just use a trivial implementation. Note
//! that we only depend on libc on unix.
#[cfg(not(all(unix, feature = "libc")))]
fn memchr_inner(needle: u8, haystack: &[u8]) -> Option<usize> {
haystack.iter().position(|val| needle == *val)
}
#[cfg(all(unix, feature = "libc"))]
fn memchr_inner(needle: u8, haystack: &[u8]) -> Option<usize> {
let start = haystack.as_ptr();
// SAFETY: `start` is valid for `haystack.len()` bytes.
let ptr = (unsafe { libc::memchr(start.cast(), needle as _, haystack.len()) })
.cast::<u8>()
.cast_const();
if ptr.is_null() {
None
} else {
// SAFETY: `ptr` will always be in bounds, since libc guarantees that the ptr will either
// be to an element inside the array or the ptr will be null
// since the ptr is in bounds the offset must also always be non null
// and there can't be more than isize::MAX elements inside an array
// as rust guarantees that the maximum number of bytes a allocation
// may occupy is isize::MAX
unsafe {
// TODO(MSRV 1.87): When bumping MSRV, switch to `ptr.byte_offset_from_unsigned(start)`.
Some(usize::try_from(ptr.offset_from(start)).unwrap_unchecked())
}
}
}
pub(crate) fn memchr(needle: u8, haystack: &[u8]) -> Option<usize> {
let index = memchr_inner(needle, haystack)?;
// SAFETY: `memchr_inner` returns Some(index) and in that case index must point to an element in haystack
// or `memchr_inner` None which is guarded by the `?` operator above
// therefore the index must **always** point to an element in the array
// and so this indexing operation is safe
// TODO(MSRV 1.81): When bumping MSRV, switch to `std::hint::assert_unchecked(haystack.get(..=index).is_some());`
unsafe {
if haystack.get(..=index).is_none() {
std::hint::unreachable_unchecked()
}
}
Some(index)
}
#[cfg(test)]
mod tests {
use super::memchr;
#[test]
fn memchr_test() {
let haystack = b"123abc456\0\xffabc\n";
assert_eq!(memchr(b'1', haystack), Some(0));
assert_eq!(memchr(b'2', haystack), Some(1));
assert_eq!(memchr(b'3', haystack), Some(2));
assert_eq!(memchr(b'4', haystack), Some(6));
assert_eq!(memchr(b'5', haystack), Some(7));
assert_eq!(memchr(b'6', haystack), Some(8));
assert_eq!(memchr(b'7', haystack), None);
assert_eq!(memchr(b'a', haystack), Some(3));
assert_eq!(memchr(b'b', haystack), Some(4));
assert_eq!(memchr(b'c', haystack), Some(5));
assert_eq!(memchr(b'd', haystack), None);
assert_eq!(memchr(b'A', haystack), None);
assert_eq!(memchr(0, haystack), Some(9));
assert_eq!(memchr(0xff, haystack), Some(10));
assert_eq!(memchr(0xfe, haystack), None);
assert_eq!(memchr(1, haystack), None);
assert_eq!(memchr(b'\n', haystack), Some(14));
assert_eq!(memchr(b'\r', haystack), None);
}
#[test]
fn memchr_all() {
let mut arr = Vec::new();
for b in 0..=255 {
arr.push(b);
}
for b in 0..=255 {
assert_eq!(memchr(b, &arr), Some(b as usize));
}
arr.reverse();
for b in 0..=255 {
assert_eq!(memchr(b, &arr), Some(255 - b as usize));
}
}
#[test]
fn memchr_empty() {
for b in 0..=255 {
assert_eq!(memchr(b, b""), None);
}
}
}
-2
View File
@@ -1,6 +1,4 @@
mod maybe_dangling;
#[cfg(feature = "codec")]
pub(crate) mod memchr;
#[cfg(any(feature = "io", feature = "codec"))]
mod poll_buf;
-13
View File
@@ -354,16 +354,3 @@ async fn poll_write_vectored_3() {
let n = assert_ready!(tx.poll_write_vectored(&mut noop_context(), io_slices)).unwrap();
assert_eq!(n, 0);
}
/// The `Sender::poll_write_vectored` should return `Poll::Ready(Ok(0))`
/// if all the input buffers have zero length, even when the channel is full.
#[tokio::test]
async fn poll_write_vectored_4() {
let (mut tx, _rx) = simplex::new(1);
tx.write_all(&[1]).await.unwrap();
let io_slices = &[IoSlice::new(&[]), IoSlice::new(&[])];
tokio::pin!(tx);
let n = assert_ready!(tx.poll_write_vectored(&mut noop_context(), io_slices)).unwrap();
assert_eq!(n, 0);
}
-19
View File
@@ -33,22 +33,3 @@ async fn test_stream_reader() -> std::io::Result<()> {
Ok(())
}
#[tokio::test]
async fn test_stream_reader_does_not_poll_after_eof() -> std::io::Result<()> {
// the first poll of this stream will return `Poll::Ready(None)`,
// and the second poll will panic
let stream = futures::stream::unfold((), |_| async { None::<(std::io::Result<Bytes>, ())> });
let read = StreamReader::new(stream);
tokio::pin!(read);
let mut buf = [0; 1];
// the first poll hits the inner stream,
// and the inner stream returns `Poll::Ready(None)`.
assert_eq!(read.read(&mut buf).await?, 0);
// the second poll doesn't hit the inner stream,
// so this `.read()` doesn't panic.
assert_eq!(read.read(&mut buf).await?, 0);
Ok(())
}
-75
View File
@@ -10,55 +10,6 @@ use std::io::IoSlice;
use std::pin::Pin;
use std::task::{Context, Poll};
struct PartialVectoredWriter {
buf: BytesMut,
max_write: usize,
}
impl AsyncWrite for PartialVectoredWriter {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
_buf: &[u8],
) -> Poll<io::Result<usize>> {
panic!("shouldn't be called")
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Ok(()).into()
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Ok(()).into()
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<Result<usize, io::Error>> {
let mut remaining = self.max_write;
let mut written = 0;
for buf in bufs {
if remaining == 0 {
break;
}
let n = remaining.min(buf.len());
self.buf.extend_from_slice(&buf[..n]);
remaining -= n;
written += n;
}
Ok(written).into()
}
fn is_write_vectored(&self) -> bool {
true
}
}
#[tokio::test]
async fn test_write_all_vectored() {
struct Wr {
@@ -189,29 +140,3 @@ async fn write_all_vectored_with_empty_slice() {
write_all_vectored(&mut wr, buf).await.unwrap();
assert_eq!(&wr.buf[..], b"hello");
}
#[tokio::test]
async fn write_all_vectored_should_continue_with_unwritten_suffix_if_write_stops_inside_buffer() {
let mut wr = PartialVectoredWriter {
buf: BytesMut::with_capacity(64),
max_write: 3,
};
let buf = &mut [IoSlice::new(b"hello"), IoSlice::new(b"world")];
write_all_vectored(&mut wr, buf).await.unwrap();
assert_eq!(&wr.buf[..], b"helloworld");
}
#[tokio::test]
async fn write_all_vectored_should_continue_with_next_buffer_if_write_ends_on_boundary() {
let mut wr = PartialVectoredWriter {
buf: BytesMut::with_capacity(64),
max_write: 2,
};
let buf = &mut [IoSlice::new(b"ab"), IoSlice::new(b"cd")];
write_all_vectored(&mut wr, buf).await.unwrap();
assert_eq!(&wr.buf[..], b"abcd");
}
+31 -100
View File
@@ -70,26 +70,6 @@ macro_rules! assert_done {
}};
}
fn poll_ready_bytes<T>(
sink: Pin<&mut T>,
cx: &mut Context<'_>,
) -> Poll<Result<(), <T as Sink<Bytes>>::Error>>
where
T: Sink<Bytes>,
{
Sink::<Bytes>::poll_ready(sink, cx)
}
fn poll_flush_bytes<T>(
sink: Pin<&mut T>,
cx: &mut Context<'_>,
) -> Poll<Result<(), <T as Sink<Bytes>>::Error>>
where
T: Sink<Bytes>,
{
Sink::<Bytes>::poll_flush(sink, cx)
}
#[test]
fn read_empty_io_yields_nothing() {
let io = Box::pin(FramedRead::new(mock!(), LengthDelimitedCodec::new()));
@@ -443,9 +423,9 @@ fn write_single_frame_length_adjusted() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert!(io.get_ref().calls.is_empty());
});
}
@@ -456,7 +436,7 @@ fn write_nothing_yields_nothing() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_flush_bytes(io, cx));
assert_ready_ok!(io.poll_flush(cx));
});
}
@@ -473,31 +453,9 @@ fn write_single_frame_one_packet() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert!(io.get_ref().calls.is_empty());
});
}
#[test]
fn write_single_frame_from_slice() {
let io = FramedWrite::new(
mock! {
data(b"\x00\x00\x00\x09"),
data(b"abcdefghi"),
flush(),
},
LengthDelimitedCodec::new(),
);
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
let data: &[u8] = b"abcdefghi";
assert_ready_ok!(Sink::<&[u8]>::poll_ready(io.as_mut(), cx));
assert_ok!(io.as_mut().start_send(data));
assert_ready_ok!(Sink::<&[u8]>::poll_flush(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert!(io.get_ref().calls.is_empty());
});
}
@@ -519,16 +477,16 @@ fn write_single_multi_frame_one_packet() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("123")));
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("hello world")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert!(io.get_ref().calls.is_empty());
});
}
@@ -552,20 +510,20 @@ fn write_single_multi_frame_multi_packet() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("123")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("hello world")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert!(io.get_ref().calls.is_empty());
});
}
@@ -586,12 +544,12 @@ fn write_single_frame_would_block() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
assert_pending!(poll_flush_bytes(io.as_mut(), cx));
assert_pending!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_pending!(io.as_mut().poll_flush(cx));
assert_pending!(io.as_mut().poll_flush(cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert!(io.get_ref().calls.is_empty());
});
@@ -609,10 +567,10 @@ fn write_single_frame_little_endian() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert!(io.get_ref().calls.is_empty());
});
}
@@ -629,10 +587,10 @@ fn write_single_frame_with_short_length_field() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdefghi")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert!(io.get_ref().calls.is_empty());
});
@@ -646,7 +604,7 @@ fn write_max_frame_len() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_err!(io.as_mut().start_send(Bytes::from("abcdef")));
assert!(io.get_ref().calls.is_empty());
@@ -663,10 +621,10 @@ fn write_update_max_frame_len_at_rest() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdef")));
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
io.encoder_mut().set_max_frame_length(5);
@@ -688,14 +646,14 @@ fn write_update_max_frame_len_in_flight() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdef")));
assert_pending!(poll_flush_bytes(io.as_mut(), cx));
assert_pending!(io.as_mut().poll_flush(cx));
io.encoder_mut().set_max_frame_length(5);
assert_ready_ok!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_flush(cx));
assert_err!(io.as_mut().start_send(Bytes::from("abcdef")));
assert!(io.get_ref().calls.is_empty());
@@ -708,10 +666,10 @@ fn write_zero() {
pin_mut!(io);
task::spawn(()).enter(|cx, _| {
assert_ready_ok!(poll_ready_bytes(io.as_mut(), cx));
assert_ready_ok!(io.as_mut().poll_ready(cx));
assert_ok!(io.as_mut().start_send(Bytes::from("abcdef")));
assert_ready_err!(poll_flush_bytes(io.as_mut(), cx));
assert_ready_err!(io.as_mut().poll_flush(cx));
assert!(io.get_ref().calls.is_empty());
});
@@ -741,33 +699,6 @@ fn frame_does_not_fit() {
assert_eq!(codec.max_frame_length(), 255);
}
#[test]
fn runtime_max_frame_len_respects_length_field() {
for (adjustment, max_frame_len) in [(-1, 254), (0, 255), (1, 256)] {
let mut codec = LengthDelimitedCodec::builder()
.length_field_length(1)
.length_adjustment(adjustment)
.new_codec();
codec.set_max_frame_length(1_000);
assert_eq!(codec.max_frame_length(), max_frame_len);
let mut dst = BytesMut::new();
codec
.encode(Bytes::from(vec![0; max_frame_len]), &mut dst)
.unwrap();
assert_eq!(dst[0], u8::MAX);
let mut dst = BytesMut::from(&b"prefix"[..]);
let original = dst.clone();
let result = codec.encode(Bytes::from(vec![0; max_frame_len + 1]), &mut dst);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
assert_eq!(dst, original);
}
}
#[test]
fn neg_adjusted_frame_does_not_fit() {
let codec = LengthDelimitedCodec::builder()
@@ -5,10 +5,8 @@ use tokio::sync::oneshot;
use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
use core::future::Future;
use core::hash::Hash;
use core::task::{Context, Poll};
use futures_test::task::new_count_waker;
use std::hash::{DefaultHasher, Hasher};
#[test]
fn cancel_token() {
@@ -565,51 +563,3 @@ fn run_until_cancelled_owned_test() {
);
}
}
#[test]
fn cloned_cancellation_tokens_are_considered_equal() {
let token = CancellationToken::new();
let token_clone = token.clone();
assert_eq!(token, token_clone);
}
#[test]
fn child_cancellation_tokens_are_not_considered_equal() {
let token = CancellationToken::new();
let token_clone = token.child_token();
assert_ne!(token, token_clone);
}
#[test]
fn independent_cancellation_tokens_are_not_considered_equal() {
let token1 = CancellationToken::new();
let token2 = CancellationToken::new();
assert_ne!(token1, token2);
}
#[test]
fn cloned_cancellation_tokens_have_same_hash() {
let token1 = CancellationToken::new();
let token2 = token1.clone();
let mut state1 = DefaultHasher::default();
token1.hash(&mut state1);
let mut state2 = DefaultHasher::default();
token2.hash(&mut state2);
assert_eq!(state1.finish(), state2.finish());
}
#[test]
fn different_cancellation_tokens_have_different_hash() {
let token1 = CancellationToken::new();
let token2 = CancellationToken::new();
let mut state1 = DefaultHasher::default();
token1.hash(&mut state1);
let mut state2 = DefaultHasher::default();
token2.hash(&mut state2);
assert_ne!(state1.finish(), state2.finish());
}
+2 -125
View File
@@ -358,131 +358,6 @@ async fn abort_all() {
}
}
#[tokio::test]
async fn try_join_next_empty() {
let mut map: JoinMap<usize, ()> = JoinMap::new();
assert!(map.try_join_next().is_none());
}
#[tokio::test]
async fn try_join_next_no_ready_task() {
let mut map = JoinMap::new();
let (_tx, rx) = oneshot::channel::<()>();
map.spawn("pending", async move {
let _ = rx.await;
});
// Task is not yet ready.
assert!(map.try_join_next().is_none());
assert_eq!(map.len(), 1);
}
#[tokio::test]
async fn try_join_next_completed_task() {
let mut map = JoinMap::new();
map.spawn("hello", async { 42 });
let mut got = None;
while got.is_none() {
got = map.try_join_next();
if got.is_none() {
tokio::task::yield_now().await;
}
}
let (key, res) = got.unwrap();
assert_eq!(key, "hello");
assert_eq!(res.unwrap(), 42);
assert!(map.is_empty());
}
#[tokio::test]
async fn try_join_next_aborted_task() {
let mut map = JoinMap::new();
map.spawn("forever", async {
futures::future::pending::<()>().await;
});
assert!(map.abort("forever"));
let mut got = None;
while got.is_none() {
got = map.try_join_next();
if got.is_none() {
tokio::task::yield_now().await;
}
}
let (key, res) = got.unwrap();
assert_eq!(key, "forever");
assert!(res.unwrap_err().is_cancelled());
assert!(map.is_empty());
}
#[tokio::test(flavor = "current_thread")]
async fn try_join_next_advances_through_multiple() {
const N: u32 = 8;
static SEM: tokio::sync::Semaphore = tokio::sync::Semaphore::const_new(0);
let mut map = JoinMap::new();
for i in 0..N {
map.spawn(i, async move {
SEM.add_permits(1);
i
});
}
// Wait until all tasks have signalled completion. On the current_thread
// runtime this means they have actually finished.
let _ = SEM.acquire_many(N).await.unwrap();
let mut seen = vec![false; N as usize];
let mut count = 0;
loop {
match map.try_join_next() {
Some((key, res)) => {
let v = res.expect("task should have completed successfully");
assert_eq!(key, v);
seen[v as usize] = true;
count += 1;
}
None if map.is_empty() => break,
None => tokio::task::yield_now().await,
}
}
assert_eq!(count, N);
assert!(seen.into_iter().all(|b| b));
assert!(map.try_join_next().is_none());
}
#[tokio::test]
async fn try_join_next_skips_replaced_task() {
let mut map = JoinMap::new();
let (tx1, rx1) = oneshot::channel::<()>();
map.spawn(1, async {
let _ = rx1.await;
11
});
tx1.send(()).unwrap();
tokio::task::yield_now().await;
let (tx2, rx2) = oneshot::channel::<()>();
map.spawn(1, async {
let _ = rx2.await;
22
});
tx2.send(()).unwrap();
tokio::task::yield_now().await;
let (key, res) = map.try_join_next().unwrap();
assert_eq!(key, 1);
assert_eq!(res.unwrap(), 22);
assert!(map.try_join_next().is_none());
assert!(map.is_empty());
}
#[tokio::test]
async fn duplicate_keys() {
let mut map = JoinMap::new();
@@ -581,6 +456,7 @@ mod spawn_local {
map.spawn_local((), async {});
}
#[cfg(tokio_unstable)]
mod local_runtime {
use super::*;
@@ -692,6 +568,7 @@ mod spawn_local {
mod spawn_local_on {
use super::*;
#[cfg(tokio_unstable)]
mod local_runtime {
use super::*;
-57
View File
@@ -1,8 +1,5 @@
#![warn(rust_2018_idioms)]
use std::task::Context;
use futures_test::task::new_count_waker;
use tokio::sync::oneshot;
use tokio::task::yield_now;
use tokio::time::Duration;
@@ -279,60 +276,6 @@ async fn test_join_queue_try_join_next() {
check_try_join_next_is_noop(&mut queue);
}
#[tokio::test]
async fn test_join_queue_try_join_next_does_not_replace_waker() {
let (send, recv) = oneshot::channel();
let mut queue = JoinQueue::new();
queue.spawn(async move {
recv.await.unwrap();
42
});
let (waker, wake_count) = new_count_waker();
let mut cx = Context::from_waker(&waker);
assert_pending!(queue.poll_join_next(&mut cx));
assert_eq!(wake_count, 0);
assert!(queue.try_join_next().is_none());
send.send(()).unwrap();
yield_now().await;
assert_eq!(wake_count, 1);
assert_eq!(
assert_ready!(queue.poll_join_next(&mut cx))
.unwrap()
.unwrap(),
42
);
}
#[tokio::test]
async fn test_join_queue_try_join_next_with_id_does_not_replace_waker() {
let (send, recv) = oneshot::channel();
let mut queue = JoinQueue::new();
queue.spawn(async move {
recv.await.unwrap();
42
});
let (waker, wake_count) = new_count_waker();
let mut cx = Context::from_waker(&waker);
assert_pending!(queue.poll_join_next_with_id(&mut cx));
assert_eq!(wake_count, 0);
assert!(queue.try_join_next_with_id().is_none());
send.send(()).unwrap();
yield_now().await;
assert_eq!(wake_count, 1);
let (_, output) = assert_ready!(queue.poll_join_next_with_id(&mut cx))
.unwrap()
.unwrap();
assert_eq!(output, 42);
}
#[tokio::test]
async fn test_join_queue_try_join_next_disabled_coop() {
// This number is large enough to trigger coop. Without using `tokio::task::coop::unconstrained`
+4
View File
@@ -1,6 +1,7 @@
#![warn(rust_2018_idioms)]
use futures::future::pending;
#[cfg(tokio_unstable)]
use std::rc::Rc;
use tokio::sync::mpsc;
use tokio::task::LocalSet;
@@ -181,6 +182,7 @@ fn notify_many() {
}
}
#[cfg(tokio_unstable)]
mod spawn {
use super::*;
@@ -207,6 +209,7 @@ mod spawn {
}
}
#[cfg(tokio_unstable)]
mod spawn_local {
use super::*;
@@ -275,6 +278,7 @@ mod spawn_local {
mod spawn_local_on {
use super::*;
#[cfg(tokio_unstable)]
mod local_runtime {
use super::*;
-70
View File
@@ -202,27 +202,6 @@ async fn reset_entry() {
assert!(entry.is_none())
}
#[tokio::test]
async fn reset_to_past_wakes_pending_queue() {
time::pause();
let mut queue = task::spawn(DelayQueue::new());
let key = queue.insert("foo", ms(10_000));
assert_pending!(poll!(queue));
assert!(!queue.is_woken());
queue.reset_at(&key, Instant::now() - ms(100));
assert!(queue.is_woken());
let entry = assert_ready_some!(poll!(queue));
assert_eq!(*entry.get_ref(), "foo");
let entry = assert_ready!(poll!(queue));
assert!(entry.is_none());
}
// Reproduces tokio-rs/tokio#849.
#[tokio::test]
async fn reset_much_later() {
@@ -902,41 +881,6 @@ async fn peek() {
assert!(queue.peek().is_none());
}
#[tokio::test(start_paused = true)]
async fn peek_entries_sharing_a_wheel_slot() {
// Only the lowest level of the timer wheel has one deadline per slot. A
// level-one slot spans 64ms, so entries with different deadlines share it
// and are only ordered once the slot cascades down.
let mut queue = task::spawn(DelayQueue::new());
let now = Instant::now();
// 64ms..=127ms all fall in the same level-one slot. Entries are pushed onto
// the front of a slot, so inserting the later deadline second puts it at the
// head.
let early = queue.insert_at("early", now + ms(100));
let late = queue.insert_at("late", now + ms(120));
assert_eq!(queue.peek(), Some(early));
sleep(ms(105)).await;
assert_eq!(queue.peek(), Some(early));
let entry = assert_ready_some!(poll!(queue));
assert_eq!(entry.key(), early);
assert_eq!(entry.get_ref(), &"early");
assert_eq!(queue.peek(), Some(late));
sleep(ms(20)).await;
let entry = assert_ready_some!(poll!(queue));
assert_eq!(entry.key(), late);
assert!(queue.peek().is_none());
}
#[tokio::test(start_paused = true)]
async fn wake_after_remove_last() {
let mut queue = task::spawn(DelayQueue::new());
@@ -950,20 +894,6 @@ async fn wake_after_remove_last() {
assert!(assert_ready!(poll!(queue)).is_none());
}
#[tokio::test(start_paused = true)]
async fn wake_after_clear() {
let mut queue = task::spawn(DelayQueue::new());
queue.insert("foo", ms(1000));
assert_pending!(poll!(queue));
assert!(!queue.is_woken());
queue.clear();
assert!(queue.is_woken());
assert!(assert_ready!(poll!(queue)).is_none());
}
fn ms(n: u64) -> Duration {
Duration::from_millis(n)
}
+1 -1
View File
@@ -1,6 +1,6 @@
#![warn(rust_2018_idioms)]
#![cfg(not(target_os = "wasi"))] // Wasi doesn't support UDP
#![cfg(not(miri))] // No UDP sockets in Miri.
#![cfg(not(miri))] // No `socket` in Miri.
#![cfg(not(loom))] // No udp / UdpFramed in loom
use tokio::net::UdpSocket;
-169
View File
@@ -1,133 +1,3 @@
# 1.53.1 (July 20th, 2026)
### Fixed
- signal: restore MSRV by removing `OnceLock::wait` from the Windows handler ([#8300])
### Fixed (unstable)
- time: fix alt timer cancellation and insertion race ([#8252])
### Documented
- runtime: remove dead link definition in Runtime::block_on ([#8301])
[#8252]: https://github.com/tokio-rs/tokio/pull/8252
[#8300]: https://github.com/tokio-rs/tokio/pull/8300
[#8301]: https://github.com/tokio-rs/tokio/pull/8301
# 1.53.0 (July 17th, 2026)
### Added
- fs: implement `From<OwnedFd>` and `From<OwnedHandle>` for `File` ([#8266])
- metrics: add task schedule latency metric ([#7986])
- net: add `SocketAddr` methods to Unix sockets ([#8144])
### Changed
- io: add `#[inline]` to IO trait impls for in-memory types ([#8242])
- net: implement UCred::pid on FreeBSD ([#8086])
- net: support Nuttx target os ([#8259])
- signal: refactor global variables on Windows ([#8231])
- sync: `mpsc::{Receiver,UnboundedReceiver}` now drops waker on drop, even if there are still senders ([#8095])
- taskdump: support taskdumps on s390x ([#8192])
- time: add `#[track_caller]` to `timeout_at()` ([#8077])
- time: consolidate mutex locks on spurious poll ([#8124])
- time: defer waker clone on spurious poll ([#8107])
- time: move lazy-registration state into `Sleep` ([#8132])
- tracing: remove unnecessary span clone ([#8126])
### Fixed
- io: do not treat zero-length reads as EOF in `Chain` ([#8251])
- net: use getpeereid for QNX peer credentials ([#8270])
- runtime: avoid illegal state in `FastRand` ([#8078])
- sync: wake mpsc receiver when a queued `reserve[_many]` returns permits ([#8260])
- taskdump: skip double wake on `Trace::capture`/`Trace::trace_with` ([#8043])
- time: avoid stack overflow in runtime constructor ([#8093])
- time (alt timer): ensure timers stay in the same runtime after `.reset()` ([#8169])
### IO uring (unstable)
- fs: use io-uring for `fs::try_exists` ([#8080])
- fs: use io-uring for renaming files ([#7800])
- rt: flush io-uring CQE in case of CQE overflow ([#8277])
### Documented
- docs: clarify cancel safety wording ([#8181])
- fs: clarify `create_dir_all` succeeds if path exists ([#8149])
- io: add warning about stdout reordering with multiple handles ([#8276])
- net: document pipe `try_read*`/`try_write*` readiness behavior ([#8032])
- runtime: document interaction with fork() ([#8202])
- sync: clarify broadcast lagging semantics ([#8239])
- sync: document memory ordering guarantees for Semaphore ([#8119])
- task: explain why `yield_now` defers its waker ([#8254])
- time: add panic docs to `timeout_at()` ([#8077])
- time: fix reversed poll order in timeout doc ([#8214])
[#7800]: https://github.com/tokio-rs/tokio/pull/7800
[#7986]: https://github.com/tokio-rs/tokio/pull/7986
[#8032]: https://github.com/tokio-rs/tokio/pull/8032
[#8043]: https://github.com/tokio-rs/tokio/pull/8043
[#8077]: https://github.com/tokio-rs/tokio/pull/8077
[#8078]: https://github.com/tokio-rs/tokio/pull/8078
[#8080]: https://github.com/tokio-rs/tokio/pull/8080
[#8086]: https://github.com/tokio-rs/tokio/pull/8086
[#8093]: https://github.com/tokio-rs/tokio/pull/8093
[#8095]: https://github.com/tokio-rs/tokio/pull/8095
[#8107]: https://github.com/tokio-rs/tokio/pull/8107
[#8119]: https://github.com/tokio-rs/tokio/pull/8119
[#8124]: https://github.com/tokio-rs/tokio/pull/8124
[#8126]: https://github.com/tokio-rs/tokio/pull/8126
[#8132]: https://github.com/tokio-rs/tokio/pull/8132
[#8144]: https://github.com/tokio-rs/tokio/pull/8144
[#8149]: https://github.com/tokio-rs/tokio/pull/8149
[#8169]: https://github.com/tokio-rs/tokio/pull/8169
[#8181]: https://github.com/tokio-rs/tokio/pull/8181
[#8192]: https://github.com/tokio-rs/tokio/pull/8192
[#8193]: https://github.com/tokio-rs/tokio/pull/8193
[#8202]: https://github.com/tokio-rs/tokio/pull/8202
[#8214]: https://github.com/tokio-rs/tokio/pull/8214
[#8231]: https://github.com/tokio-rs/tokio/pull/8231
[#8239]: https://github.com/tokio-rs/tokio/pull/8239
[#8242]: https://github.com/tokio-rs/tokio/pull/8242
[#8251]: https://github.com/tokio-rs/tokio/pull/8251
[#8254]: https://github.com/tokio-rs/tokio/pull/8254
[#8259]: https://github.com/tokio-rs/tokio/pull/8259
[#8260]: https://github.com/tokio-rs/tokio/pull/8260
[#8266]: https://github.com/tokio-rs/tokio/pull/8266
[#8270]: https://github.com/tokio-rs/tokio/pull/8270
[#8276]: https://github.com/tokio-rs/tokio/pull/8276
[#8277]: https://github.com/tokio-rs/tokio/pull/8277
# 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
@@ -180,31 +50,6 @@ This release reverts the LIFO slot stealing change introduced in 1.51.0
[#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
@@ -529,20 +374,6 @@ 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.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
+5 -13
View File
@@ -6,7 +6,7 @@ name = "tokio"
# - README.md
# - Update CHANGELOG.md.
# - Create "v1.x.y" git tag.
version = "1.53.1"
version = "1.52.1"
edition = "2021"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
@@ -88,8 +88,6 @@ time = []
io-uring = ["dep:io-uring", "libc", "mio/os-poll", "mio/os-ext", "dep:slab"]
# Unstable feature. Requires `--cfg tokio_unstable` to enable.
taskdump = ["dep:backtrace"]
# Unstable feature. Requires `--cfg tokio_unstable` to enable.
schedule-latency = []
[dependencies]
tokio-macros = { version = "~2.7.0", optional = true }
@@ -101,7 +99,7 @@ bytes = { version = "1.2.1", optional = true }
mio = { version = "1.2.0", optional = true, default-features = false }
parking_lot = { version = "0.12.0", optional = true }
[target.'cfg(any(not(target_family = "wasm"), target_os = "emscripten", all(target_os = "wasi", not(target_env = "p1"))))'.dependencies]
[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.
@@ -130,8 +128,6 @@ libc = { version = "0.2.168", optional = true }
[target.'cfg(unix)'.dev-dependencies]
libc = { version = "0.2.168" }
[target.'cfg(all(unix, not(target_os = "emscripten")))'.dev-dependencies]
nix = { version = "0.31.0", default-features = false, features = ["aio", "fs", "socket"] }
[target.'cfg(windows)'.dependencies.windows-sys]
@@ -148,26 +144,22 @@ features = [
[dev-dependencies]
tokio-test = "0.4.0"
tokio-stream = "0.1"
tokio-util = { version = "0.7", features = ["rt"] }
futures = { version = "0.3.0", features = ["async-await"] }
futures-test = "0.3.31"
mockall = "0.13.0"
async-stream = "0.3"
futures-concurrency = "7.6.3"
[target.'cfg(not(target_os = "emscripten"))'.dev-dependencies]
tokio-util = { version = "0.7", features = ["rt"] }
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
socket2 = "0.6.0"
proptest = "1"
[target.'cfg(any(not(target_family = "wasm"), target_os = "emscripten"))'.dev-dependencies]
tempfile = "3.1.0"
proptest = "1"
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dev-dependencies]
rand = "0.9"
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dev-dependencies]
[target.'cfg(all(target_family = "wasm", not(target_os = "wasi")))'.dev-dependencies]
wasm-bindgen-test = "0.3.0"
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
+5 -1
View File
@@ -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.53.1", features = ["full"] }
tokio = { version = "1.52.1", features = ["full"] }
```
Then, on your main.rs:
-1
View File
@@ -52,7 +52,6 @@ impl Registration {
/// Clears resource level readiness represented by the specified `ReadyEvent`
async fn clear_readiness(&self, ready_event: ReadyEvent);
}
```
A new registration is created for a `T: mio::Evented` and a `interest`. This
-30
View File
@@ -1,42 +1,12 @@
cfg_rt! {
#[cfg(any(not(target_os = "emscripten"), target_feature = "atomics"))]
pub(crate) use crate::runtime::spawn_blocking;
cfg_fs! {
#[cfg(any(not(target_os = "emscripten"), target_feature = "atomics"))]
#[allow(unused_imports)]
pub(crate) use crate::runtime::spawn_mandatory_blocking;
}
#[cfg(any(not(target_os = "emscripten"), target_feature = "atomics"))]
pub(crate) use crate::task::JoinHandle;
// Non-pthread emscripten has no blocking pool, and the `std` calls behind
// `fs` and `io-std` complete synchronously there, so this internal shim
// runs the closure inline and hands back an already-completed future. The
// public `task::spawn_blocking` is not routed through here and keeps its
// native semantics. Pthread builds (`+atomics`) use the native pool.
#[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
pub(crate) type JoinHandle<T> = std::future::Ready<Result<T, crate::task::JoinError>>;
#[cfg(all(target_os = "emscripten", not(target_feature = "atomics")))]
pub(crate) fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
std::future::ready(Ok(f()))
}
#[cfg(all(target_os = "emscripten", not(target_feature = "atomics"), feature = "fs"))]
#[allow(dead_code)] // unit tests replace this with the `fs::mocks` version
pub(crate) fn spawn_mandatory_blocking<F, R>(f: F) -> Option<JoinHandle<R>>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
Some(spawn_blocking(f))
}
}
cfg_not_rt! {

Some files were not shown because too many files have changed in this diff Show More