Compare commits

..
Author SHA1 Message Date
noah d5ad1eba7a runtime: add on_task_poll_start hook
This hook will allow users to have a callback run before tasks are polled. Eventually this should be accompanied on_task_poll_stop, and maybe a few other callbacks.

This was implemented in order to allow tokio-uring to run a periodic maintenance loop.
2022-04-24 20:06:57 -05:00
397 changed files with 8261 additions and 21824 deletions
-2
View File
@@ -1,2 +0,0 @@
# [build]
# rustflags = ["--cfg", "tokio_unstable"]
+3 -3
View File
@@ -5,8 +5,8 @@ jobs:
image: ubuntu-2004:202101-01
resource_class: arm.medium
environment:
# Change to pin rust version
RUST_STABLE: 1.67.1
# Change to pin rust versino
RUST_STABLE: stable
steps:
- checkout
- run:
@@ -22,4 +22,4 @@ jobs:
workflows:
ci:
jobs:
- test-arm
- test-arm
+6 -8
View File
@@ -1,10 +1,8 @@
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
freebsd_instance:
image_family: freebsd-13-1
image: freebsd-12-3-release-amd64
env:
RUST_STABLE: 1.67.1
RUST_NIGHTLY: nightly-2022-10-25
RUST_STABLE: stable
RUST_NIGHTLY: nightly-2022-03-21
RUSTFLAGS: -D warnings
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
@@ -14,7 +12,7 @@ env:
task:
name: FreeBSD 64-bit
setup_script:
- pkg install -y bash
- pkg install -y bash curl
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
- . $HOME/.cargo/env
@@ -31,7 +29,7 @@ task:
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
setup_script:
- pkg install -y bash
- pkg install -y bash curl
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_NIGHTLY
- . $HOME/.cargo/env
@@ -45,7 +43,7 @@ task:
task:
name: FreeBSD 32-bit
setup_script:
- pkg install -y bash
- pkg install -y bash curl
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
- . $HOME/.cargo/env
-4
View File
@@ -1,4 +0,0 @@
contact_links:
- name: Question
url: https://github.com/tokio-rs/tokio/discussions
about: Questions about Tokio should be posted as a GitHub discussion.
+16
View File
@@ -0,0 +1,16 @@
---
name: Question
about: Please use the discussions tab for questions
title: ''
labels: ''
assignees: ''
---
Please post your question as a discussion here:
https://github.com/tokio-rs/tokio/discussions
You may also be able to find help here:
https://discord.gg/tokio
https://users.rust-lang.org/
+2 -10
View File
@@ -9,22 +9,14 @@ on:
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
permissions:
contents: read
jobs:
security-audit:
permissions:
checks: write # for rustsec/audit-check to create check
contents: read # for actions/checkout to fetch code
issues: write # for rustsec/audit-check to create issues
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Audit Check
# https://github.com/rustsec/audit-check/issues/2
uses: rustsec/audit-check@master
uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
+103 -264
View File
@@ -10,26 +10,15 @@ env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
# Change to specific Rust release to pin
rust_stable: 1.67.1
rust_nightly: nightly-2022-11-03
rust_clippy: 1.65.0
# When updating this, also update:
# - README.md
# - tokio/README.md
# - CONTRIBUTING.md
# - tokio/Cargo.toml
# - tokio-util/Cargo.toml
# - tokio-test/Cargo.toml
# - tokio-stream/Cargo.toml
rust_stable: stable
rust_nightly: nightly-2022-03-21
rust_clippy: 1.52.0
rust_min: 1.49.0
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
# Depends on all action sthat are required for a "successful" CI run.
tests-pass:
@@ -37,27 +26,20 @@ jobs:
runs-on: ubuntu-latest
needs:
- test
- test-parking_lot
- valgrind
- test-unstable
- test-parking_lot
- miri
- asan
- cross-check
- cross-test
- no-atomic-u64
- cross
- features
# - minrust
- minimal-versions
- minrust
- fmt
- clippy
- docs
- valgrind
- loom-compile
- check-readme
- test-hyper
- x86_64-fortanix-unknown-sgx
- wasm32-unknown-unknown
- wasm32-wasi
- check-external-types
steps:
- run: exit 0
@@ -71,16 +53,17 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
run: cargo install cargo-hack
# Run `tokio` with `full` features. This excludes testing utilities which
# can alter the runtime behavior of Tokio.
@@ -120,12 +103,13 @@ jobs:
name: compile tests with parking lot send guards
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: Enable parking_lot send_guard feature
# Inserts the line "plsend = ["parking_lot/send_guard"]" right after [features]
run: sed -i '/\[features\]/a plsend = ["parking_lot/send_guard"]' tokio/Cargo.toml
@@ -136,15 +120,18 @@ jobs:
name: valgrind
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: Install Valgrind
uses: taiki-e/install-action@valgrind
run: |
sudo apt-get update -y
sudo apt-get install -y valgrind
# Compile tests
- name: cargo build test-mem
@@ -174,12 +161,13 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
# Run `tokio` with "unstable" cfg flag.
- name: test tokio full --cfg unstable
run: cargo test --all-features
@@ -194,183 +182,116 @@ jobs:
name: miri
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_nightly }}
components: miri
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: miri
# Many of tests in tokio/tests and doctests use #[tokio::test] or
# #[tokio::main] that calls epoll_create1 that Miri does not support.
run: cargo miri test --features full --lib --no-fail-fast
working-directory: tokio
env:
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-tag-raw-pointers
PROPTEST_CASES: 10
asan:
name: asan
san:
name: san
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install llvm
# Required to resolve symbols in sanitizer output
run: sudo apt-get install -y llvm
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_nightly }}
override: true
# - uses: Swatinem/rust-cache@v1 -> CI failure observed due to insufficient storage space
- uses: Swatinem/rust-cache@v1
- name: asan
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1
run: cargo test --all-features --target x86_64-unknown-linux-gnu --lib -- --test-threads 1
working-directory: tokio
env:
RUSTFLAGS: -Z sanitizer=address
# Ignore `trybuild` errors as they are irrelevant and flaky on nightly
TRYBUILD: overwrite
ASAN_OPTIONS: detect_leaks=0
cross-check:
name: cross-check
cross:
name: cross
runs-on: ubuntu-latest
strategy:
matrix:
target:
- i686-unknown-linux-gnu
- powerpc-unknown-linux-gnu
- powerpc64-unknown-linux-gnu
- mips-unknown-linux-gnu
- arm-linux-androideabi
- mipsel-unknown-linux-musl
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
target: ${{ matrix.target }}
- name: Install cross
uses: taiki-e/install-action@cross
- run: cross check --workspace --all-features --target ${{ matrix.target }}
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
cross-test:
name: cross-test
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: i686-unknown-linux-gnu
- target: arm-unknown-linux-gnueabihf
- target: armv7-unknown-linux-gnueabihf
- target: aarch64-unknown-linux-gnu
# Run a platform without AtomicU64 and no const Mutex::new
- target: arm-unknown-linux-gnueabihf
rustflags: --cfg tokio_no_const_mutex_new
steps:
- uses: actions/checkout@v3
- name: Install Rust stable
uses: dtolnay/rust-toolchain@master
override: true
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/cargo@v1
with:
toolchain: ${{ env.rust_stable }}
target: ${{ matrix.target }}
- name: Install cross
uses: taiki-e/install-action@cross
# First run with all features (including parking_lot)
- run: cross test -p tokio --all-features --target ${{ matrix.target }} --tests
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 ${{ matrix.rustflags }}
# Now run without parking_lot
- name: Remove `parking_lot` from `full` feature
run: sed -i '0,/parking_lot/{/parking_lot/d;}' tokio/Cargo.toml
# The `tokio_no_parking_lot` cfg is here to ensure the `sed` above does not silently break.
- run: cross test -p tokio --features full,test-util --target ${{ matrix.target }} --tests
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 --cfg tokio_no_parking_lot ${{ matrix.rustflags }}
# See https://github.com/tokio-rs/tokio/issues/5187
no-atomic-u64:
name: Test i686-unknown-linux-gnu without AtomicU64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_nightly }}
components: rust-src
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
# Install linker and libraries for i686-unknown-linux-gnu
- uses: taiki-e/setup-cross-toolchain-action@v1
with:
target: i686-unknown-linux-gnu
- run: cargo test -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64
# https://github.com/tokio-rs/tokio/pull/5356
# https://github.com/tokio-rs/tokio/issues/5373
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64 --cfg tokio_no_const_mutex_new
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64
use-cross: true
command: check
args: --workspace --target ${{ matrix.target }}
features:
name: features
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_nightly }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: Install cargo-hack
uses: taiki-e/install-action@v2
with:
tool: cargo-hack
run: cargo install cargo-hack
- name: check --each-feature
run: cargo hack check --all --each-feature -Z avoid-dev-deps
# Try with unstable feature flags
- name: check --feature-powerset --unstable
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
- name: check --each-feature --unstable
run: cargo hack check --all --each-feature -Z avoid-dev-deps
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
# minrust:
# name: minrust
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - name: Install Rust ${{ env.rust_min }}
# uses: dtolnay/rust-toolchain@master
# with:
# toolchain: ${{ env.rust_min }}
# - uses: Swatinem/rust-cache@v2
# - name: "check --workspace --all-features"
# run: cargo check --workspace --all-features
# env:
# RUSTFLAGS: "" # remove -Dwarnings
minrust:
name: minrust
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_min }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_min }}
override: true
- uses: Swatinem/rust-cache@v1
- name: "test --workspace --all-features"
run: cargo check --workspace --all-features
minimal-versions:
name: minimal-versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_nightly }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: Install cargo-hack
uses: taiki-e/install-action@v2
with:
tool: cargo-hack
run: cargo install cargo-hack
- name: "check --all-features -Z minimal-versions"
run: |
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
@@ -394,13 +315,14 @@ jobs:
name: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
components: rustfmt
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1
# Check fmt
- name: "rustfmt --check"
# Workaround for rust-lang/cargo#7732
@@ -414,13 +336,14 @@ jobs:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_clippy }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_clippy }}
override: true
components: clippy
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1
# Run clippy
- name: "clippy --all"
run: cargo clippy --all --tests --all-features
@@ -429,12 +352,13 @@ jobs:
name: docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_nightly }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: "doc --lib --all-features"
run: cargo doc --lib --no-deps --all-features --document-private-items
env:
@@ -445,12 +369,13 @@ jobs:
name: build loom tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: build --cfg loom
run: cargo test --no-run --lib --features full
working-directory: tokio
@@ -461,7 +386,7 @@ jobs:
name: Check README
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Verify that both READMEs are identical
run: diff README.md tokio/README.md
@@ -479,12 +404,13 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust 1.65.0
uses: dtolnay/rust-toolchain@master
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: 1.65.0
- uses: Swatinem/rust-cache@v2
toolchain: ${{ env.rust_stable }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Test hyper
run: |
set -x
@@ -502,106 +428,19 @@ jobs:
git diff
cargo test --features full
x86_64-fortanix-unknown-sgx:
name: build tokio for x86_64-fortanix-unknown-sgx
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_nightly }}
target: x86_64-fortanix-unknown-sgx
- uses: Swatinem/rust-cache@v2
# NOTE: Currently the only test we can run is to build tokio with rt and sync features.
- name: build tokio
run: cargo build --target x86_64-fortanix-unknown-sgx --features rt,sync
working-directory: tokio
wasm32-unknown-unknown:
name: test tokio for wasm32-unknown-unknown
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: test tokio
run: wasm-pack test --node -- --features "macros sync"
working-directory: tokio
wasm32-wasi:
name: wasm32-wasi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
# Install dependencies
- name: Install cargo-hack
uses: taiki-e/install-action@v2
with:
tool: cargo-hack
- name: Install wasm32-wasi target
run: rustup target add wasm32-wasi
- name: Install wasmtime
uses: taiki-e/install-action@wasmtime
- name: Install cargo-wasi
run: cargo install cargo-wasi
- name: WASI test tokio full
run: cargo test -p tokio --target wasm32-wasi --features full
env:
CARGO_TARGET_WASM32_WASI_RUNNER: "wasmtime run --"
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
- name: WASI test tokio-util full
run: cargo test -p tokio-util --target wasm32-wasi --features full
env:
CARGO_TARGET_WASM32_WASI_RUNNER: "wasmtime run --"
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
- name: WASI test tokio-stream
run: cargo test -p tokio-stream --target wasm32-wasi --features time,net,io-util,sync
env:
CARGO_TARGET_WASM32_WASI_RUNNER: "wasmtime run --"
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
- name: test tests-integration --features wasi-rt
# TODO: this should become: `cargo hack wasi test --each-feature`
run: cargo wasi test --test rt_yield --features wasi-rt
working-directory: tests-integration
check-external-types:
name: check-external-types
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- windows-latest
- ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust nightly-2022-11-16
uses: dtolnay/rust-toolchain@master
with:
# `check-external-types` requires a specific Rust nightly version. See
# the README for details: https://github.com/awslabs/cargo-check-external-types
toolchain: nightly-2022-11-16
- uses: Swatinem/rust-cache@v2
- name: check-external-types
run: |
set -x
cargo install cargo-check-external-types --locked --version 0.1.6
cargo check-external-types --all-features --config external-types.toml
working-directory: tokio
-7
View File
@@ -4,16 +4,9 @@ on:
# See .github/labeler.yml file
permissions:
contents: read
jobs:
triage:
permissions:
contents: read # for actions/labeler to determine modified files
pull-requests: write # for actions/labeler to add labels to PRs
runs-on: ubuntu-latest
if: github.repository_owner == 'tokio-rs'
steps:
- uses: actions/labeler@v3
with:
+5 -8
View File
@@ -13,14 +13,11 @@ env:
# Change to specific Rust release to pin
rust_stable: stable
permissions:
contents: read
jobs:
loom:
name: loom
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom') || (github.base_ref == null))
if: contains(github.event.pull_request.labels.*.name, 'R-loom') || (github.base_ref == null)
runs-on: ubuntu-latest
strategy:
matrix:
@@ -32,17 +29,17 @@ jobs:
- loom_pool::group_d
- time::driver
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: loom ${{ matrix.scope }}
run: cargo test --lib --release --features full -- --nocapture $SCOPE
working-directory: tokio
env:
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
LOOM_MAX_PREEMPTIONS: 2
LOOM_MAX_BRANCHES: 10000
SCOPE: ${{ matrix.scope }}
+11 -7
View File
@@ -8,21 +8,25 @@ on:
paths:
- '**/Cargo.toml'
permissions:
contents: read
jobs:
security-audit:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install cargo-audit
run: cargo install cargo-audit
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-audit
- name: Generate lockfile
run: cargo generate-lockfile
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Audit dependencies
run: cargo audit
uses: actions-rs/cargo@v1
with:
command: audit
+9 -9
View File
@@ -11,11 +11,8 @@ env:
# Change to specific Rust release to pin
rust_stable: stable
permissions:
contents: read
jobs:
stress-test:
stess-test:
name: Stress Test
runs-on: ubuntu-latest
strategy:
@@ -23,14 +20,17 @@ jobs:
stress-test:
- simple_echo_tcp
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
override: true
- uses: Swatinem/rust-cache@v1
- name: Install Valgrind
uses: taiki-e/install-action@valgrind
run: |
sudo apt-get update -y
sudo apt-get install -y valgrind
# Compiles each of the stress test examples.
- name: Compile stress test examples
@@ -38,4 +38,4 @@ jobs:
# Runs each of the examples using Valgrind. Detects leaks and displays them.
- name: Run valgrind
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/release/examples/${{ matrix.stress-test }}
run: valgrind --leak-check=full --show-leak-kinds=all ./target/release/examples/${{ matrix.stress-test }}
+9 -21
View File
@@ -131,30 +131,12 @@ cargo check --all-features
cargo test --all-features
```
Clippy must be run using the MSRV, so Tokio can avoid having to `#[allow]` new
lints whose fixes would be incompatible with the current MSRV:
<!--
When updating this, also update:
- .github/workflows/ci.yml
- README.md
- tokio/README.md
- tokio/Cargo.toml
- tokio-util/Cargo.toml
- tokio-test/Cargo.toml
- tokio-stream/Cargo.toml
-->
```
cargo +1.49.0 clippy --all --tests --all-features
```
When building documentation normally, the markers that list the features
required for various parts of Tokio are missing. To build the documentation
correctly, use this command:
```
RUSTDOCFLAGS="--cfg docsrs" RUSTFLAGS="--cfg docsrs" cargo +nightly doc --all-features
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
```
To build documentation including Tokio's unstable features, it is necessary to
@@ -162,9 +144,15 @@ pass `--cfg tokio_unstable` to both RustDoc *and* rustc. To build the
documentation for unstable features, use this command:
```
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg tokio_unstable" cargo +nightly doc --all-features
```
There is currently a [bug in cargo] that means documentation cannot be built
from the root of the workspace. If you `cd` into the `tokio` subdirectory the
command shown above will work.
[bug in cargo]: https://github.com/rust-lang/cargo/issues/9274
The `cargo fmt` command does not work on the Tokio codebase. You can use the
command below instead:
@@ -556,7 +544,7 @@ Tokio ≥1.0.0 comes with LTS guarantees:
The goal of these guarantees is to provide stability to the ecosystem.
## Minimum Supported Rust Version (MSRV)
## Mininum Supported Rust Version (MSRV)
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
compiler release.
Generated
-1621
View File
File diff suppressed because it is too large Load Diff
-5
View File
@@ -1,5 +0,0 @@
[build.env]
passthrough = [
"RUSTFLAGS",
"RUST_BACKTRACE",
]
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2023 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+8 -30
View File
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.25.3", features = ["full"] }
tokio = { version = "1.17.0", features = ["full"] }
```
Then, on your main.rs:
@@ -161,30 +161,8 @@ several other libraries, including:
[`mio`]: https://github.com/tokio-rs/mio
[`bytes`]: https://github.com/tokio-rs/bytes
## Changelog
The Tokio repository contains multiple crates. Each crate has its own changelog.
* `tokio` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio/CHANGELOG.md)
* `tokio-util` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-util/CHANGELOG.md)
* `tokio-stream` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-stream/CHANGELOG.md)
* `tokio-macros` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-macros/CHANGELOG.md)
* `tokio-test` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-test/CHANGELOG.md)
## Supported Rust Versions
<!--
When updating this, also update:
- .github/workflows/ci.yml
- CONTRIBUTING.md
- README.md
- tokio/README.md
- tokio/Cargo.toml
- tokio-util/Cargo.toml
- tokio-test/Cargo.toml
- tokio-stream/Cargo.toml
-->
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
least** 6 months. When increasing the MSRV, the new Rust version must have been
released at least six months ago. The current MSRV is 1.49.0.
@@ -202,18 +180,18 @@ warrants a patch release with a fix for the bug, it will be backported and
released as a new patch release for each LTS minor version. Our current LTS
releases are:
* `1.18.x` - LTS release until June 2023
* `1.20.x` - LTS release until September 2023.
* `1.8.x` - LTS release until February 2022.
* `1.14.x` - LTS release until June 2022.
Each LTS release will continue to receive backported fixes for at least a year.
If you wish to use a fixed minor release in your project, we recommend that you
use an LTS release.
Each LTS release will continue to receive backported fixes for at least half a
year. If you wish to use a fixed minor release in your project, we recommend
that you use an LTS release.
To use a fixed minor version, you can specify the version with a tilde. For
example, to specify that you wish to use the newest `1.18.x` patch release, you
example, to specify that you wish to use the newest `1.8.x` patch release, you
can use the following dependency specification:
```text
tokio = { version = "~1.18", features = [...] }
tokio = { version = "~1.8", features = [...] }
```
## License
+4 -4
View File
@@ -1,13 +1,13 @@
## Report a security issue
The Tokio project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [[email protected]](mailto:[email protected]). Security issues should not be reported via the public GitHub Issue tracker.
The Tokio project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [[email protected]](mailto:[email protected]). Security issues should not be reported via the public Github Issue tracker.
## Vulnerability coordination
Remediation of security vulnerabilities is prioritized by the project team. The project team coordinates remediation with third-party project stakeholders via [GitHub Security Advisories](https://help.github.com/en/github/managing-security-vulnerabilities/about-github-security-advisories). Third-party stakeholders may include the reporter of the issue, affected direct or indirect users of Tokio, and maintainers of upstream dependencies if applicable.
Remediation of security vulnerabilities is prioritized by the project team. The project team coordinates remediation with third-party project stakeholders via [Github Security Advisories](https://help.github.com/en/github/managing-security-vulnerabilities/about-github-security-advisories). Third-party stakeholders may include the reporter of the issue, affected direct or indirect users of Tokio, and maintainers of upstream dependencies if applicable.
Downstream project maintainers and Tokio users can request participation in coordination of applicable security issues by sending your contact email address, GitHub username(s) and any other salient information to [[email protected]](mailto:[email protected]). Participation in security issue coordination processes is at the discretion of the Tokio team.
Downstream project maintainers and Tokio users can request participation in coordination of applicable security issues by sending your contact email address, Github username(s) and any other salient information to [[email protected]](mailto:[email protected]). Participation in security issue coordination processes is at the discretion of the Tokio team.
## Security advisories
The project team is committed to transparency in the security issue disclosure process. The Tokio team announces security issues via [project GitHub Release notes](https://github.com/tokio-rs/tokio/releases) and the [RustSec advisory database](https://github.com/RustSec/advisory-db) (i.e. `cargo-audit`).
The project team is committed to transparency in the security issue disclosure process. The Tokio team announces security issues via [project Github Release notes](https://github.com/tokio-rs/tokio/releases) and the [RustSec advisory database](https://github.com/RustSec/advisory-db) (i.e. `cargo-audit`).
-7
View File
@@ -7,8 +7,6 @@ edition = "2018"
[dependencies]
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
bencher = "0.1.5"
rand = "0.8"
rand_chacha = "0.3"
[dev-dependencies]
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
@@ -52,8 +50,3 @@ harness = false
name = "fs"
path = "fs.rs"
harness = false
[[bench]]
name = "copy"
path = "copy.rs"
harness = false
-238
View File
@@ -1,238 +0,0 @@
use bencher::{benchmark_group, benchmark_main, Bencher};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use tokio::io::{copy, repeat, AsyncRead, AsyncReadExt, AsyncWrite};
use tokio::time::{interval, Interval, MissedTickBehavior};
use std::task::Poll;
use std::time::Duration;
const KILO: usize = 1024;
// Tunable parameters if you want to change this benchmark. If reader and writer
// are matched in kilobytes per second, then this only exposes buffering to the
// benchmark.
const RNG_SEED: u64 = 0;
// How much data to copy in a single benchmark run
const SOURCE_SIZE: u64 = 256 * KILO as u64;
// Read side provides CHUNK_SIZE every READ_SERVICE_PERIOD. If it's not called
// frequently, it'll burst to catch up (representing OS buffers draining)
const CHUNK_SIZE: usize = 2 * KILO;
const READ_SERVICE_PERIOD: Duration = Duration::from_millis(1);
// Write side buffers up to WRITE_BUFFER, and flushes to disk every
// WRITE_SERVICE_PERIOD.
const WRITE_BUFFER: usize = 40 * KILO;
const WRITE_SERVICE_PERIOD: Duration = Duration::from_millis(20);
// How likely you are to have to wait for previously written data to be flushed
// because another writer claimed the buffer space
const PROBABILITY_FLUSH_WAIT: f64 = 0.1;
/// A slow writer that aims to simulate HDD behaviour under heavy load.
///
/// There is a limited buffer, which is fully drained on the next write after
/// a time limit is reached. Flush waits for the time limit to be reached
/// and then drains the buffer.
///
/// At random, the HDD will stall writers while it flushes out all buffers. If
/// this happens to you, you will be unable to write until the next time the
/// buffer is drained.
struct SlowHddWriter {
service_intervals: Interval,
blocking_rng: ChaCha20Rng,
buffer_size: usize,
buffer_used: usize,
}
impl SlowHddWriter {
fn new(service_interval: Duration, buffer_size: usize) -> Self {
let blocking_rng = ChaCha20Rng::seed_from_u64(RNG_SEED);
let mut service_intervals = interval(service_interval);
service_intervals.set_missed_tick_behavior(MissedTickBehavior::Delay);
Self {
service_intervals,
blocking_rng,
buffer_size,
buffer_used: 0,
}
}
fn service_write(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
// If we hit a service interval, the buffer can be cleared
let res = self.service_intervals.poll_tick(cx).map(|_| Ok(()));
if let Poll::Ready(_) = res {
self.buffer_used = 0;
}
res
}
fn write_bytes(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
writeable: usize,
) -> std::task::Poll<Result<usize, std::io::Error>> {
let service_res = self.as_mut().service_write(cx);
if service_res.is_pending() && self.blocking_rng.gen_bool(PROBABILITY_FLUSH_WAIT) {
return Poll::Pending;
}
let available = self.buffer_size - self.buffer_used;
if available == 0 {
assert!(service_res.is_pending());
Poll::Pending
} else {
let written = available.min(writeable);
self.buffer_used += written;
Poll::Ready(Ok(written))
}
}
}
impl Unpin for SlowHddWriter {}
impl AsyncWrite for SlowHddWriter {
fn poll_write(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
self.write_bytes(cx, buf.len())
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
self.service_write(cx)
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
self.service_write(cx)
}
fn poll_write_vectored(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> std::task::Poll<Result<usize, std::io::Error>> {
let writeable = bufs.into_iter().fold(0, |acc, buf| acc + buf.len());
self.write_bytes(cx, writeable)
}
fn is_write_vectored(&self) -> bool {
true
}
}
/// A reader that limits the maximum chunk it'll give you back
///
/// Simulates something reading from a slow link - you get one chunk per call,
/// and you are offered chunks on a schedule
struct ChunkReader {
data: Vec<u8>,
service_intervals: Interval,
}
impl ChunkReader {
fn new(chunk_size: usize, service_interval: Duration) -> Self {
let mut service_intervals = interval(service_interval);
service_intervals.set_missed_tick_behavior(MissedTickBehavior::Burst);
let data: Vec<u8> = std::iter::repeat(0).take(chunk_size).collect();
Self {
data,
service_intervals,
}
}
}
impl AsyncRead for ChunkReader {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
if self.service_intervals.poll_tick(cx).is_pending() {
return Poll::Pending;
}
buf.put_slice(&self.data[..buf.remaining().min(self.data.len())]);
Poll::Ready(Ok(()))
}
}
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap()
}
fn copy_mem_to_mem(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut source = repeat(0).take(SOURCE_SIZE);
let mut dest = Vec::new();
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
}
fn copy_mem_to_slow_hdd(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut source = repeat(0).take(SOURCE_SIZE);
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
}
fn copy_chunk_to_mem(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
let mut dest = Vec::new();
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
}
fn copy_chunk_to_slow_hdd(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
}
benchmark_group!(
copy_bench,
copy_mem_to_mem,
copy_mem_to_slow_hdd,
copy_chunk_to_mem,
copy_chunk_to_slow_hdd,
);
benchmark_main!(copy_bench);
+5 -5
View File
@@ -14,7 +14,7 @@ fn read_uncontended(b: &mut Bencher) {
rt.block_on(async move {
for _ in 0..6 {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
})
});
@@ -28,7 +28,7 @@ fn read_concurrent_uncontended_multi(b: &mut Bencher) {
async fn task(lock: Arc<RwLock<()>>) {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
let lock = Arc::new(RwLock::new(()));
@@ -55,7 +55,7 @@ fn read_concurrent_uncontended(b: &mut Bencher) {
async fn task(lock: Arc<RwLock<()>>) {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
let lock = Arc::new(RwLock::new(()));
@@ -82,7 +82,7 @@ fn read_concurrent_contended_multi(b: &mut Bencher) {
async fn task(lock: Arc<RwLock<()>>) {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
let lock = Arc::new(RwLock::new(()));
@@ -110,7 +110,7 @@ fn read_concurrent_contended(b: &mut Bencher) {
async fn task(lock: Arc<RwLock<()>>) {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
let lock = Arc::new(RwLock::new(()));
+3 -2
View File
@@ -24,8 +24,8 @@ httpdate = "1.0"
once_cell = "1.5.2"
rand = "0.8.3"
[target.'cfg(windows)'.dev-dependencies.windows-sys]
version = "0.42.0"
[target.'cfg(windows)'.dev-dependencies.winapi]
version = "0.3.8"
[[example]]
name = "chat"
@@ -75,6 +75,7 @@ path = "tinyhttp.rs"
name = "custom-executor"
path = "custom-executor.rs"
[[example]]
name = "custom-executor-tokio-context"
path = "custom-executor-tokio-context.rs"
+4 -2
View File
@@ -1,7 +1,9 @@
//! Hello world server.
//!
//! A simple client that opens a TCP stream, writes "hello world\n", and closes
//! the connection.
//!
//! To start a server that this client can talk to on port 6142, you can use this command:
//! You can test this out by running:
//!
//! ncat -l 6142
//!
@@ -24,7 +26,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect("127.0.0.1:6142").await?;
println!("created stream");
let result = stream.write_all(b"hello world\n").await;
let result = stream.write(b"hello world\n").await;
println!("wrote to stream; success={:?}", result.is_ok());
Ok(())
+2 -2
View File
@@ -6,7 +6,7 @@ async fn windows_main() -> io::Result<()> {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
use tokio::time;
use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;
use winapi::shared::winerror;
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client";
const N: usize = 10;
@@ -59,7 +59,7 @@ async fn windows_main() -> io::Result<()> {
let mut client = loop {
match ClientOptions::new().open(PIPE_NAME) {
Ok(client) => break client,
Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (),
Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (),
Err(e) => return Err(e),
}
-16
View File
@@ -1,16 +0,0 @@
[build]
command = """
rustup install nightly --profile minimal && cargo doc --no-deps --all-features
"""
publish = "target/doc"
[build.environment]
RUSTDOCFLAGS="""
--cfg docsrs \
--cfg tokio_unstable \
"""
RUSTFLAGS="--cfg tokio_unstable --cfg docsrs"
[[redirects]]
from = "/"
to = "/tokio"
-40
View File
@@ -1,40 +0,0 @@
{
"arch": "x86",
"cpu": "pentium4",
"crt-static-respected": true,
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128",
"dynamic-linking": true,
"env": "gnu",
"has-rpath": true,
"has-thread-local": true,
"llvm-target": "i686-unknown-linux-gnu",
"max-atomic-width": 32,
"os": "linux",
"position-independent-executables": true,
"pre-link-args": {
"gcc": [
"-m32"
]
},
"relro-level": "full",
"stack-probes": {
"kind": "inline-or-call",
"min-llvm-version-for-inline": [
16,
0,
0
]
},
"supported-sanitizers": [
"address"
],
"supported-split-debuginfo": [
"packed",
"unpacked",
"off"
],
"target-family": [
"unix"
],
"target-pointer-width": "32"
}
-8
View File
@@ -1,10 +1,2 @@
Tests the various combination of feature flags. This is broken out to a separate
crate to work around limitations with cargo features.
To run all of the tests in this directory, run the following commands:
```
cargo test --features full
cargo test --features rt
```
If one of the tests fail, you can pass `TRYBUILD=overwrite` to the `cargo test`
command that failed to have it regenerate the test output.
@@ -1,4 +1,4 @@
error: function `f` is never used
error: function is never used: `f`
--> $DIR/macros_dead_code.rs:6:10
|
6 | async fn f() {}
@@ -1,5 +1,3 @@
#![deny(duplicate_macro_attributes)]
use tests_build::tokio;
#[tokio::main]
@@ -1,101 +1,97 @@
error: the `async` keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:6:1
--> $DIR/macros_invalid_input.rs:4:1
|
6 | fn main_is_not_async() {}
4 | fn main_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:8:15
--> $DIR/macros_invalid_input.rs:6:15
|
8 | #[tokio::main(foo)]
6 | #[tokio::main(foo)]
| ^^^
error: Must have specified ident
--> $DIR/macros_invalid_input.rs:11:15
|
11 | #[tokio::main(threadpool::bar)]
| ^^^^^^^^^^^^^^^
--> $DIR/macros_invalid_input.rs:9:15
|
9 | #[tokio::main(threadpool::bar)]
| ^^^^^^^^^^^^^^^
error: the `async` keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:15:1
--> $DIR/macros_invalid_input.rs:13:1
|
15 | fn test_is_not_async() {}
13 | fn test_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:17:15
--> $DIR/macros_invalid_input.rs:15:15
|
17 | #[tokio::test(foo)]
15 | #[tokio::test(foo)]
| ^^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:20:15
--> $DIR/macros_invalid_input.rs:18:15
|
20 | #[tokio::test(foo = 123)]
18 | #[tokio::test(foo = 123)]
| ^^^^^^^^^
error: Failed to parse value of `flavor` as string.
--> $DIR/macros_invalid_input.rs:23:24
--> $DIR/macros_invalid_input.rs:21:24
|
23 | #[tokio::test(flavor = 123)]
21 | #[tokio::test(flavor = 123)]
| ^^^
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
--> $DIR/macros_invalid_input.rs:26:24
--> $DIR/macros_invalid_input.rs:24:24
|
26 | #[tokio::test(flavor = "foo")]
24 | #[tokio::test(flavor = "foo")]
| ^^^^^
error: The `start_paused` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
--> $DIR/macros_invalid_input.rs:29:55
--> $DIR/macros_invalid_input.rs:27:55
|
29 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
27 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
| ^^^^^
error: Failed to parse value of `worker_threads` as integer.
--> $DIR/macros_invalid_input.rs:32:57
--> $DIR/macros_invalid_input.rs:30:57
|
32 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
30 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
| ^^^^^
error: The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[tokio::test(flavor = "multi_thread")]`
--> $DIR/macros_invalid_input.rs:35:59
--> $DIR/macros_invalid_input.rs:33:59
|
35 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
33 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
| ^
error: Failed to parse value of `crate` as ident.
--> $DIR/macros_invalid_input.rs:38:23
--> $DIR/macros_invalid_input.rs:36:23
|
38 | #[tokio::test(crate = 456)]
36 | #[tokio::test(crate = 456)]
| ^^^
error: Failed to parse value of `crate` as ident: "456"
--> $DIR/macros_invalid_input.rs:41:23
--> $DIR/macros_invalid_input.rs:39:23
|
41 | #[tokio::test(crate = "456")]
39 | #[tokio::test(crate = "456")]
| ^^^^^
error: Failed to parse value of `crate` as ident: "abc::edf"
--> $DIR/macros_invalid_input.rs:44:23
--> $DIR/macros_invalid_input.rs:42:23
|
44 | #[tokio::test(crate = "abc::edf")]
42 | #[tokio::test(crate = "abc::edf")]
| ^^^^^^^^^^
error: second test attribute is supplied
--> $DIR/macros_invalid_input.rs:48:1
--> $DIR/macros_invalid_input.rs:46:1
|
48 | #[test]
46 | #[test]
| ^^^^^^^
error: duplicated attribute
--> $DIR/macros_invalid_input.rs:48:1
--> $DIR/macros_invalid_input.rs:46:1
|
48 | #[test]
46 | #[test]
| ^^^^^^^
|
note: the lint level is defined here
--> $DIR/macros_invalid_input.rs:1:9
|
1 | #![deny(duplicate_macro_attributes)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: `-D duplicate-macro-attributes` implied by `-D warnings`
@@ -23,13 +23,4 @@ async fn extra_semicolon() -> Result<(), ()> {
Ok(());
}
// https://github.com/tokio-rs/tokio/issues/4635
#[allow(redundant_semicolons)]
#[rustfmt::skip]
#[tokio::main]
async fn issue_4635() {
return 1;
;
}
fn main() {}
@@ -2,9 +2,11 @@ error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:5:5
|
4 | async fn missing_semicolon_or_return_type() {
| - help: a return type might be missing here: `-> _`
| - possibly return type missing here?
5 | Ok(())
| ^^^^^^ expected `()`, found enum `Result`
| ^^^^^^- help: consider using a semicolon here: `;`
| |
| expected `()`, found enum `Result`
|
= note: expected unit type `()`
found enum `Result<(), _>`
@@ -13,7 +15,7 @@ error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:10:5
|
9 | async fn missing_return_type() {
| - help: a return type might be missing here: `-> _`
| - possibly return type missing here?
10 | return Ok(());
| ^^^^^^^^^^^^^^ expected `()`, found enum `Result`
|
@@ -36,12 +38,3 @@ help: try adding an expression at the end of the block
23 ~ Ok(());;
24 + Ok(())
|
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:32:5
|
30 | async fn issue_4635() {
| - help: try adding a return type: `-> i32`
31 | return 1;
32 | ;
| ^ expected `()`, found integer
-19
View File
@@ -16,29 +16,11 @@ required-features = ["rt-net"]
name = "test-process-signal"
required-features = ["rt-process-signal"]
[[test]]
name = "macros_main"
[[test]]
name = "macros_pin"
[[test]]
name = "macros_select"
[[test]]
name = "rt_yield"
required-features = ["rt", "macros", "sync"]
[features]
# For mem check
rt-net = ["tokio/rt", "tokio/rt-multi-thread", "tokio/net"]
# For test-process-signal
rt-process-signal = ["rt-net", "tokio/process", "tokio/signal"]
# For testing wasi + rt/macros/sync features
#
# This is an explicit feature so we can use `cargo hack` testing single features
# instead of all possible permutations.
wasi-rt = ["rt", "macros", "sync"]
full = [
"macros",
@@ -58,4 +40,3 @@ tokio = { path = "../tokio" }
tokio-test = { path = "../tokio-test", optional = true }
doc-comment = "0.3.1"
futures = { version = "0.3.0", features = ["async-await"] }
bytes = "1.0.0"
+1 -5
View File
@@ -1,8 +1,4 @@
#![cfg(all(
feature = "macros",
feature = "rt-multi-thread",
not(target_os = "wasi")
))]
#![cfg(all(feature = "macros", feature = "rt"))]
#[tokio::main]
async fn basic_main() -> usize {
-1
View File
@@ -4,7 +4,6 @@ use futures::channel::oneshot;
use futures::executor::block_on;
use std::thread;
#[cfg_attr(target_os = "wasi", ignore = "WASI: std::thread::spawn not supported")]
#[test]
fn join_with_select() {
block_on(async {
+1 -52
View File
@@ -1,5 +1,5 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "full", not(target_os = "wasi")))]
#![cfg(feature = "full")]
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::join;
@@ -190,54 +190,3 @@ async fn pipe_from_one_command_to_another() {
assert!(second_status.expect("second status").success());
assert!(third_status.expect("third status").success());
}
#[tokio::test]
async fn vectored_writes() {
use bytes::{Buf, Bytes};
use std::{io::IoSlice, pin::Pin};
use tokio::io::AsyncWrite;
let mut cat = cat().spawn().unwrap();
let mut stdin = cat.stdin.take().unwrap();
let are_writes_vectored = stdin.is_write_vectored();
let mut stdout = cat.stdout.take().unwrap();
let write = async {
let mut input = Bytes::from_static(b"hello\n").chain(Bytes::from_static(b"world!\n"));
let mut writes_completed = 0;
futures::future::poll_fn(|cx| loop {
let mut slices = [IoSlice::new(&[]); 2];
let vectored = input.chunks_vectored(&mut slices);
if vectored == 0 {
return std::task::Poll::Ready(std::io::Result::Ok(()));
}
let n = futures::ready!(Pin::new(&mut stdin).poll_write_vectored(cx, &slices))?;
writes_completed += 1;
input.advance(n);
})
.await?;
drop(stdin);
std::io::Result::Ok(writes_completed)
};
let read = async {
let mut buffer = Vec::with_capacity(6 + 7);
stdout.read_to_end(&mut buffer).await?;
std::io::Result::Ok(buffer)
};
let (write, read, status) = future::join3(write, read, cat.wait()).await;
assert!(status.unwrap().success());
let writes_completed = write.unwrap();
// on unix our small payload should always fit in whatever default sized pipe with a single
// syscall. if multiple are used, then the forwarding does not work, or we are on a platform
// for which the `std` does not support vectored writes.
assert_eq!(writes_completed == 1, are_writes_vectored);
assert_eq!(&read.unwrap(), b"hello\nworld!\n");
}
-41
View File
@@ -1,41 +0,0 @@
use tokio::sync::oneshot;
use tokio::task;
async fn spawn_send() {
let (tx, rx) = oneshot::channel();
let task = tokio::spawn(async {
for _ in 0..10 {
task::yield_now().await;
}
tx.send("done").unwrap();
});
assert_eq!("done", rx.await.unwrap());
task.await.unwrap();
}
#[tokio::main(flavor = "current_thread")]
async fn entry_point() {
spawn_send().await;
}
#[tokio::test]
async fn test_macro() {
spawn_send().await;
}
#[test]
fn main_macro() {
entry_point();
}
#[test]
fn manual_rt() {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
rt.block_on(async { spawn_send().await });
}
+1 -27
View File
@@ -1,32 +1,6 @@
# 1.8.2 (November 30th, 2022)
- fix a regression introduced in 1.8.1 ([#5244])
[#5244]: https://github.com/tokio-rs/tokio/pull/5244
# 1.8.1 (November 29th, 2022)
(yanked)
- macros: Pin Futures in `#[tokio::test]` to stack ([#5205])
- macros: Reduce usage of last statement spans in proc-macros ([#5092])
- macros: Improve the documentation for `#[tokio::test]` ([#4761])
[#5205]: https://github.com/tokio-rs/tokio/pull/5205
[#5092]: https://github.com/tokio-rs/tokio/pull/5092
[#4761]: https://github.com/tokio-rs/tokio/pull/4761
# 1.8.0 (June 4th, 2022)
- macros: always emit return statement ([#4636])
- macros: support setting a custom crate name for `#[tokio::main]` and `#[tokio::test]` ([#4613])
[#4613]: https://github.com/tokio-rs/tokio/pull/4613
[#4636]: https://github.com/tokio-rs/tokio/pull/4636
# 1.7.0 (December 15th, 2021)
- macros: address remaining `clippy::semicolon_if_nothing_returned` warning ([#4252])
- macros: address remainging clippy::semicolon_if_nothing_returned warning ([#4252])
[#4252]: https://github.com/tokio-rs/tokio/pull/4252
+2 -2
View File
@@ -3,8 +3,8 @@ name = "tokio-macros"
# When releasing to crates.io:
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-macros-1.x.y" git tag.
version = "1.8.2"
# - Create "tokio-macros-1.0.x" git tag.
version = "1.7.0"
edition = "2018"
rust-version = "1.49"
authors = ["Tokio Contributors <[email protected]>"]
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2023 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+20 -41
View File
@@ -383,50 +383,29 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
let body = &input.block;
let brace_token = input.block.brace_token;
let body_ident = quote! { body };
let block_expr = quote_spanned! {last_stmt_end_span=>
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
let (tail_return, tail_semicolon) = match body.stmts.last() {
Some(syn::Stmt::Semi(syn::Expr::Return(_), _)) => (quote! { return }, quote! { ; }),
Some(syn::Stmt::Semi(..)) | Some(syn::Stmt::Local(..)) | None => {
match &input.sig.output {
syn::ReturnType::Type(_, ty) if matches!(&**ty, syn::Type::Tuple(ty) if ty.elems.is_empty()) =>
{
(quote! {}, quote! { ; }) // unit
}
syn::ReturnType::Default => (quote! {}, quote! { ; }), // unit
syn::ReturnType::Type(..) => (quote! {}, quote! {}), // ! or another
}
}
_ => (quote! {}, quote! {}),
};
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
{
return #rt
let body = async #body;
#[allow(clippy::expect_used)]
#tail_return #rt
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(#body_ident);
}
};
// For test functions pin the body to the stack and use `Pin<&mut dyn
// Future>` to reduce the amount of `Runtime::block_on` (and related
// functions) copies we generate during compilation due to the generic
// parameter `F` (the future to block on). This could have an impact on
// performance, but because it's only for testing it's unlikely to be very
// large.
//
// We don't do this for the main function as it should only be used once so
// there will be no benefit.
let body = if is_test {
let output_type = match &input.sig.output {
// For functions with no return value syn doesn't print anything,
// but that doesn't work as `Output` for our boxed `Future`, so
// default to `()` (the same type as the function output).
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
};
quote! {
let body = async #body;
#crate_ident::pin!(body);
let body: ::std::pin::Pin<&mut dyn ::std::future::Future<Output = #output_type>> = body;
}
} else {
quote! {
let body = async #body;
}
};
input.block = syn::parse2(quote! {
{
#body
#block_expr
.block_on(body)#tail_semicolon
}
})
.expect("Parsing failure");
@@ -480,7 +459,7 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
};
let config = if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) {
let msg = "second test attribute is supplied";
Err(syn::Error::new_spanned(attr, msg))
Err(syn::Error::new_spanned(&attr, msg))
} else {
AttributeArgs::parse_terminated
.parse(args)
+5 -113
View File
@@ -265,20 +265,11 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
entry::main(args, item, false)
}
/// Marks async function to be executed by runtime, suitable to test environment.
/// This macro helps set up a `Runtime` without requiring the user to use
/// [Runtime](../tokio/runtime/struct.Runtime.html) or
/// [Builder](../tokio/runtime/struct.Builder.html) directly.
/// Marks async function to be executed by runtime, suitable to test environment
///
/// Note: This macro is designed to be simplistic and targets applications that
/// do not require a complex setup. If the provided functionality is not
/// sufficient, you may be interested in using
/// [Builder](../tokio/runtime/struct.Builder.html), which provides a more
/// powerful interface.
/// ## Usage
///
/// # Multi-threaded runtime
///
/// To use the multi-threaded runtime, the macro can be configured using
/// ### Multi-thread runtime
///
/// ```no_run
/// #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
@@ -287,17 +278,9 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// }
/// ```
///
/// The `worker_threads` option configures the number of worker threads, and
/// defaults to the number of cpus on the system. This is the default
/// flavor.
/// ### Using default
///
/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
/// flag.
///
/// # Current thread runtime
///
/// The default test runtime is single-threaded. Each test gets a
/// separate current-thread runtime.
/// The default test runtime is single-threaded.
///
/// ```no_run
/// #[tokio::test]
@@ -306,81 +289,6 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// }
/// ```
///
/// ## Usage
///
/// ### Using the multi-thread runtime
///
/// ```no_run
/// #[tokio::test(flavor = "multi_thread")]
/// async fn my_test() {
/// assert!(true);
/// }
/// ```
///
/// Equivalent code not using `#[tokio::test]`
///
/// ```no_run
/// #[test]
/// fn my_test() {
/// tokio::runtime::Builder::new_multi_thread()
/// .enable_all()
/// .build()
/// .unwrap()
/// .block_on(async {
/// assert!(true);
/// })
/// }
/// ```
///
/// ### Using current thread runtime
///
/// ```no_run
/// #[tokio::test]
/// async fn my_test() {
/// assert!(true);
/// }
/// ```
///
/// Equivalent code not using `#[tokio::test]`
///
/// ```no_run
/// #[test]
/// fn my_test() {
/// tokio::runtime::Builder::new_current_thread()
/// .enable_all()
/// .build()
/// .unwrap()
/// .block_on(async {
/// assert!(true);
/// })
/// }
/// ```
///
/// ### Set number of worker threads
///
/// ```no_run
/// #[tokio::test(flavor ="multi_thread", worker_threads = 2)]
/// async fn my_test() {
/// assert!(true);
/// }
/// ```
///
/// Equivalent code not using `#[tokio::test]`
///
/// ```no_run
/// #[test]
/// fn my_test() {
/// tokio::runtime::Builder::new_multi_thread()
/// .worker_threads(2)
/// .enable_all()
/// .build()
/// .unwrap()
/// .block_on(async {
/// assert!(true);
/// })
/// }
/// ```
///
/// ### Configure the runtime to start with time paused
///
/// ```no_run
@@ -390,22 +298,6 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// }
/// ```
///
/// Equivalent code not using `#[tokio::test]`
///
/// ```no_run
/// #[test]
/// fn my_test() {
/// tokio::runtime::Builder::new_current_thread()
/// .enable_all()
/// .start_paused(true)
/// .build()
/// .unwrap()
/// .block_on(async {
/// assert!(true);
/// })
/// }
/// ```
///
/// Note that `start_paused` requires the `test-util` feature to be enabled.
///
/// ### Rename package
+2 -2
View File
@@ -100,10 +100,10 @@ fn clean_pattern(pat: &mut syn::Pat) {
}
syn::Pat::Reference(reference) => {
reference.mutability = None;
clean_pattern(&mut reference.pat);
clean_pattern(&mut *reference.pat);
}
syn::Pat::Type(type_pat) => {
clean_pattern(&mut type_pat.pat);
clean_pattern(&mut *type_pat.pat);
}
_ => {}
}
-34
View File
@@ -1,37 +1,3 @@
# 0.1.11 (October 11, 2022)
- time: allow `StreamExt::chunks_timeout` outside of a runtime ([#5036])
[#5036]: https://github.com/tokio-rs/tokio/pull/5036
# 0.1.10 (Sept 18, 2022)
- time: add `StreamExt::chunks_timeout` ([#4695])
- stream: add track_caller to public APIs ([#4786])
[#4695]: https://github.com/tokio-rs/tokio/pull/4695
[#4786]: https://github.com/tokio-rs/tokio/pull/4786
# 0.1.9 (June 4, 2022)
- deps: upgrade `tokio-util` dependency to `0.7.x` ([#3762])
- stream: add `StreamExt::map_while` ([#4351])
- stream: add `StreamExt::then` ([#4355])
- stream: add cancel-safety docs to `StreamExt::next` and `try_next` ([#4715])
- stream: expose `Elapsed` error ([#4502])
- stream: expose `Timeout` ([#4601])
- stream: implement `Extend` for `StreamMap` ([#4272])
- sync: add `Clone` to `RecvError` types ([#4560])
[#3762]: https://github.com/tokio-rs/tokio/pull/3762
[#4272]: https://github.com/tokio-rs/tokio/pull/4272
[#4351]: https://github.com/tokio-rs/tokio/pull/4351
[#4355]: https://github.com/tokio-rs/tokio/pull/4355
[#4502]: https://github.com/tokio-rs/tokio/pull/4502
[#4560]: https://github.com/tokio-rs/tokio/pull/4560
[#4601]: https://github.com/tokio-rs/tokio/pull/4601
[#4715]: https://github.com/tokio-rs/tokio/pull/4715
# 0.1.8 (October 29, 2021)
- stream: add `From<Receiver<T>>` impl for receiver streams ([#4080])
+1 -3
View File
@@ -4,7 +4,7 @@ name = "tokio-stream"
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-stream-0.1.x" git tag.
version = "0.1.11"
version = "0.1.8"
edition = "2018"
rust-version = "1.49"
authors = ["Tokio Contributors <[email protected]>"]
@@ -34,11 +34,9 @@ tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
[dev-dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
async-stream = "0.3"
parking_lot = "0.12.0"
tokio-test = { path = "../tokio-test" }
futures = { version = "0.3", default-features = false }
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
proptest = "1"
[package.metadata.docs.rs]
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2023 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+2 -75
View File
@@ -61,8 +61,6 @@ cfg_time! {
use tokio::time::Duration;
mod throttle;
use throttle::{throttle, Throttle};
mod chunks_timeout;
use chunks_timeout::ChunksTimeout;
}
/// An extension trait for the [`Stream`] trait that provides a variety of
@@ -115,12 +113,6 @@ pub trait StreamExt: Stream {
/// pinning it to the stack using the `pin_mut!` macro from the `pin_utils`
/// crate.
///
/// # Cancel safety
///
/// This method is cancel safe. The returned future only
/// holds onto a reference to the underlying stream,
/// so dropping it will never lose a value.
///
/// # Examples
///
/// ```
@@ -157,12 +149,6 @@ pub trait StreamExt: Stream {
/// an [`Option<Result<T, E>>`](Option), making for easy use
/// with the [`?`](std::ops::Try) operator.
///
/// # Cancel safety
///
/// This method is cancel safe. The returned future only
/// holds onto a reference to the underlying stream,
/// so dropping it will never lose a value.
///
/// # Examples
///
/// ```
@@ -982,8 +968,6 @@ pub trait StreamExt: Stream {
/// Slows down a stream by enforcing a delay between items.
///
/// The underlying timer behind this utility has a granularity of one millisecond.
///
/// # Example
///
/// Create a throttled stream.
@@ -1009,63 +993,6 @@ pub trait StreamExt: Stream {
{
throttle(duration, self)
}
/// Batches the items in the given stream using a maximum duration and size for each batch.
///
/// This stream returns the next batch of items in the following situations:
/// 1. The inner stream has returned at least `max_size` many items since the last batch.
/// 2. The time since the first item of a batch is greater than the given duration.
/// 3. The end of the stream is reached.
///
/// The length of the returned vector is never empty or greater than the maximum size. Empty batches
/// will not be emitted if no items are received upstream.
///
/// # Panics
///
/// This function panics if `max_size` is zero
///
/// # Example
///
/// ```rust
/// use std::time::Duration;
/// use tokio::time;
/// use tokio_stream::{self as stream, StreamExt};
/// use futures::FutureExt;
///
/// #[tokio::main]
/// # async fn _unused() {}
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
/// async fn main() {
/// let iter = vec![1, 2, 3, 4].into_iter();
/// let stream0 = stream::iter(iter);
///
/// let iter = vec![5].into_iter();
/// let stream1 = stream::iter(iter)
/// .then(move |n| time::sleep(Duration::from_secs(5)).map(move |_| n));
///
/// let chunk_stream = stream0
/// .chain(stream1)
/// .chunks_timeout(3, Duration::from_secs(2));
/// tokio::pin!(chunk_stream);
///
/// // a full batch was received
/// assert_eq!(chunk_stream.next().await, Some(vec![1,2,3]));
/// // deadline was reached before max_size was reached
/// assert_eq!(chunk_stream.next().await, Some(vec![4]));
/// // last element in the stream
/// assert_eq!(chunk_stream.next().await, Some(vec![5]));
/// }
/// ```
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
#[track_caller]
fn chunks_timeout(self, max_size: usize, duration: Duration) -> ChunksTimeout<Self>
where
Self: Sized,
{
assert!(max_size > 0, "`max_size` must be non-zero.");
ChunksTimeout::new(self, max_size, duration)
}
}
impl<St: ?Sized> StreamExt for St where St: Stream {}
@@ -1073,10 +1000,10 @@ impl<St: ?Sized> StreamExt for St where St: Stream {}
/// Merge the size hints from two streams.
fn merge_size_hints(
(left_low, left_high): (usize, Option<usize>),
(right_low, right_high): (usize, Option<usize>),
(right_low, right_hign): (usize, Option<usize>),
) -> (usize, Option<usize>) {
let low = left_low.saturating_add(right_low);
let high = match (left_high, right_high) {
let high = match (left_high, right_hign) {
(Some(h1), Some(h2)) => h1.checked_add(h2),
_ => None,
};
@@ -1,86 +0,0 @@
use crate::stream_ext::Fuse;
use crate::Stream;
use tokio::time::{sleep, Sleep};
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use pin_project_lite::pin_project;
use std::time::Duration;
pin_project! {
/// Stream returned by the [`chunks_timeout`](super::StreamExt::chunks_timeout) method.
#[must_use = "streams do nothing unless polled"]
#[derive(Debug)]
pub struct ChunksTimeout<S: Stream> {
#[pin]
stream: Fuse<S>,
#[pin]
deadline: Option<Sleep>,
duration: Duration,
items: Vec<S::Item>,
cap: usize, // https://github.com/rust-lang/futures-rs/issues/1475
}
}
impl<S: Stream> ChunksTimeout<S> {
pub(super) fn new(stream: S, max_size: usize, duration: Duration) -> Self {
ChunksTimeout {
stream: Fuse::new(stream),
deadline: None,
duration,
items: Vec::with_capacity(max_size),
cap: max_size,
}
}
}
impl<S: Stream> Stream for ChunksTimeout<S> {
type Item = Vec<S::Item>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut me = self.as_mut().project();
loop {
match me.stream.as_mut().poll_next(cx) {
Poll::Pending => break,
Poll::Ready(Some(item)) => {
if me.items.is_empty() {
me.deadline.set(Some(sleep(*me.duration)));
me.items.reserve_exact(*me.cap);
}
me.items.push(item);
if me.items.len() >= *me.cap {
return Poll::Ready(Some(std::mem::take(me.items)));
}
}
Poll::Ready(None) => {
// Returning Some here is only correct because we fuse the inner stream.
let last = if me.items.is_empty() {
None
} else {
Some(std::mem::take(me.items))
};
return Poll::Ready(last);
}
}
}
if !me.items.is_empty() {
if let Some(deadline) = me.deadline.as_pin_mut() {
ready!(deadline.poll(cx));
}
return Poll::Ready(Some(std::mem::take(me.items)));
}
Poll::Pending
}
fn size_hint(&self) -> (usize, Option<usize>) {
let chunk_len = if self.items.is_empty() { 0 } else { 1 };
let (lower, upper) = self.stream.size_hint();
let lower = (lower / self.cap).saturating_add(chunk_len);
let upper = upper.and_then(|x| x.checked_add(chunk_len));
(lower, upper)
}
}
+5 -1
View File
@@ -195,7 +195,11 @@ where
} else {
let res = mem::replace(collection, Ok(U::initialize(sealed::Internal, 0, Some(0))));
Err(res.map(drop).unwrap_err())
if let Err(err) = res {
Err(err)
} else {
unreachable!();
}
}
}
}
-7
View File
@@ -8,13 +8,6 @@ use pin_project_lite::pin_project;
pin_project! {
/// Future for the [`next`](super::StreamExt::next) method.
///
/// # Cancel safety
///
/// This method is cancel safe. It only
/// holds onto a reference to the underlying stream,
/// so dropping it will never lose a value.
///
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Next<'a, St: ?Sized> {
+1 -1
View File
@@ -72,7 +72,7 @@ where
}
fn size_hint(&self) -> (usize, Option<usize>) {
let future_len = usize::from(self.future.is_some());
let future_len = if self.future.is_some() { 1 } else { 0 };
let (lower, upper) = self.stream.size_hint();
let lower = lower.saturating_add(future_len);
+3 -1
View File
@@ -4,6 +4,7 @@ use crate::Stream;
use tokio::time::{Duration, Instant, Sleep};
use std::future::Future;
use std::marker::Unpin;
use std::pin::Pin;
use std::task::{self, Poll};
@@ -40,7 +41,8 @@ pin_project! {
}
}
impl<T> Throttle<T> {
// XXX: are these safe if `T: !Unpin`?
impl<T: Unpin> Throttle<T> {
/// Acquires a reference to the underlying stream that this combinator is
/// pulling from.
pub fn get_ref(&self) -> &T {
+1 -1
View File
@@ -24,7 +24,7 @@ pin_project! {
}
/// Error returned by `Timeout`.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq)]
pub struct Elapsed(());
impl<S: Stream> Timeout<S> {
-6
View File
@@ -9,12 +9,6 @@ use pin_project_lite::pin_project;
pin_project! {
/// Future for the [`try_next`](super::StreamExt::try_next) method.
///
/// # Cancel safety
///
/// This method is cancel safe. It only
/// holds onto a reference to the underlying stream,
/// so dropping it will never lose a value.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct TryNext<'a, St: ?Sized> {
+1 -1
View File
@@ -18,7 +18,7 @@ pub struct BroadcastStream<T> {
}
/// An error returned from the inner stream of a [`BroadcastStream`].
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Clone)]
pub enum BroadcastStreamRecvError {
/// The receiver lagged too far behind. Attempting to receive again will
/// return the oldest message still retained by the channel.
-84
View File
@@ -1,84 +0,0 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "time", feature = "sync", feature = "io-util"))]
use tokio::time;
use tokio_stream::{self as stream, StreamExt};
use tokio_test::assert_pending;
use tokio_test::task;
use futures::FutureExt;
use std::time::Duration;
#[tokio::test(start_paused = true)]
async fn usage() {
let iter = vec![1, 2, 3].into_iter();
let stream0 = stream::iter(iter);
let iter = vec![4].into_iter();
let stream1 =
stream::iter(iter).then(move |n| time::sleep(Duration::from_secs(3)).map(move |_| n));
let chunk_stream = stream0
.chain(stream1)
.chunks_timeout(4, Duration::from_secs(2));
let mut chunk_stream = task::spawn(chunk_stream);
assert_pending!(chunk_stream.poll_next());
time::advance(Duration::from_secs(2)).await;
assert_eq!(chunk_stream.next().await, Some(vec![1, 2, 3]));
assert_pending!(chunk_stream.poll_next());
time::advance(Duration::from_secs(2)).await;
assert_eq!(chunk_stream.next().await, Some(vec![4]));
}
#[tokio::test(start_paused = true)]
async fn full_chunk_with_timeout() {
let iter = vec![1, 2].into_iter();
let stream0 = stream::iter(iter);
let iter = vec![3].into_iter();
let stream1 =
stream::iter(iter).then(move |n| time::sleep(Duration::from_secs(1)).map(move |_| n));
let iter = vec![4].into_iter();
let stream2 =
stream::iter(iter).then(move |n| time::sleep(Duration::from_secs(3)).map(move |_| n));
let chunk_stream = stream0
.chain(stream1)
.chain(stream2)
.chunks_timeout(3, Duration::from_secs(2));
let mut chunk_stream = task::spawn(chunk_stream);
assert_pending!(chunk_stream.poll_next());
time::advance(Duration::from_secs(2)).await;
assert_eq!(chunk_stream.next().await, Some(vec![1, 2, 3]));
assert_pending!(chunk_stream.poll_next());
time::advance(Duration::from_secs(2)).await;
assert_eq!(chunk_stream.next().await, Some(vec![4]));
}
#[tokio::test]
#[ignore]
async fn real_time() {
let iter = vec![1, 2, 3, 4].into_iter();
let stream0 = stream::iter(iter);
let iter = vec![5].into_iter();
let stream1 =
stream::iter(iter).then(move |n| time::sleep(Duration::from_secs(5)).map(move |_| n));
let chunk_stream = stream0
.chain(stream1)
.chunks_timeout(3, Duration::from_secs(2));
let mut chunk_stream = task::spawn(chunk_stream);
assert_eq!(chunk_stream.next().await, Some(vec![1, 2, 3]));
assert_eq!(chunk_stream.next().await, Some(vec![4]));
assert_eq!(chunk_stream.next().await, Some(vec![5]));
}
-55
View File
@@ -1,55 +0,0 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "time", not(target_os = "wasi")))] // Wasi does not support panic recovery
use parking_lot::{const_mutex, Mutex};
use std::error::Error;
use std::panic;
use std::sync::Arc;
use tokio::time::Duration;
use tokio_stream::{self as stream, StreamExt};
fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<String> {
static PANIC_MUTEX: Mutex<()> = const_mutex(());
{
let _guard = PANIC_MUTEX.lock();
let panic_file: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let prev_hook = panic::take_hook();
{
let panic_file = panic_file.clone();
panic::set_hook(Box::new(move |panic_info| {
let panic_location = panic_info.location().unwrap();
panic_file
.lock()
.clone_from(&Some(panic_location.file().to_string()));
}));
}
let result = panic::catch_unwind(func);
// Return to the previously set panic hook (maybe default) so that we get nice error
// messages in the tests.
panic::set_hook(prev_hook);
if result.is_err() {
panic_file.lock().clone()
} else {
None
}
}
}
#[test]
fn stream_chunks_timeout_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let iter = vec![1, 2, 3].into_iter();
let stream0 = stream::iter(iter);
let _chunk_stream = stream0.chunks_timeout(0, Duration::from_secs(2));
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
-1
View File
@@ -325,7 +325,6 @@ fn one_ready_many_none() {
}
}
#[cfg(not(target_os = "wasi"))]
proptest::proptest! {
#[test]
fn fuzz_pending_complete_mix(kinds: Vec<bool>) {
+1 -1
View File
@@ -19,7 +19,7 @@ categories = ["asynchronous", "testing"]
[dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
tokio-stream = { version = "0.1.1", path = "../tokio-stream" }
async-stream = "0.3.3"
async-stream = "0.3"
bytes = "1.0.0"
futures-core = "0.3.0"
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2023 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+1 -1
View File
@@ -260,7 +260,7 @@ macro_rules! assert_err {
}};
}
/// Asserts that an exact duration has elapsed since the start instant ±1ms.
/// Asserts that an exact duration has elapsed since since the start instant ±1ms.
///
/// ```rust
/// use tokio::time::{self, Instant};
+8 -37
View File
@@ -1,29 +1,4 @@
//! Futures task based helpers to easily test futures and manually written futures.
//!
//! The [`Spawn`] type is used as a mock task harness that allows you to poll futures
//! without needing to setup pinning or context. Any future can be polled but if the
//! future requires the tokio async context you will need to ensure that you poll the
//! [`Spawn`] within a tokio context, this means that as long as you are inside the
//! runtime it will work and you can poll it via [`Spawn`].
//!
//! [`Spawn`] also supports [`Stream`] to call `poll_next` without pinning
//! or context.
//!
//! In addition to circumventing the need for pinning and context, [`Spawn`] also tracks
//! the amount of times the future/task was woken. This can be useful to track if some
//! leaf future notified the root task correctly.
//!
//! # Example
//!
//! ```
//! use tokio_test::task;
//!
//! let fut = async {};
//!
//! let mut task = task::spawn(fut);
//!
//! assert!(task.poll().is_ready(), "Task was not ready!");
//! ```
//! Futures task based helpers
#![allow(clippy::mutex_atomic)]
@@ -36,11 +11,7 @@ use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use tokio_stream::Stream;
/// Spawn a future into a [`Spawn`] which wraps the future in a mocked executor.
///
/// This can be used to spawn a [`Future`] or a [`Stream`].
///
/// For more information, check the module docs.
/// TODO: dox
pub fn spawn<T>(task: T) -> Spawn<T> {
Spawn {
task: MockTask::new(),
@@ -48,14 +19,16 @@ pub fn spawn<T>(task: T) -> Spawn<T> {
}
}
/// Future spawned on a mock task that can be used to poll the future or stream
/// without needing pinning or context types.
/// Future spawned on a mock task
#[derive(Debug)]
pub struct Spawn<T> {
task: MockTask,
future: Pin<Box<T>>,
}
/// Mock task
///
/// A mock task is able to intercept and track wake notifications.
#[derive(Debug, Clone)]
struct MockTask {
waker: Arc<ThreadWaker>,
@@ -118,8 +91,7 @@ impl<T: Unpin> ops::DerefMut for Spawn<T> {
}
impl<T: Future> Spawn<T> {
/// If `T` is a [`Future`] then poll it. This will handle pinning and the context
/// type for the future.
/// Polls a future
pub fn poll(&mut self) -> Poll<T::Output> {
let fut = self.future.as_mut();
self.task.enter(|cx| fut.poll(cx))
@@ -127,8 +99,7 @@ impl<T: Future> Spawn<T> {
}
impl<T: Stream> Spawn<T> {
/// If `T` is a [`Stream`] then poll_next it. This will handle pinning and the context
/// type for the stream.
/// Polls a stream
pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
let stream = self.future.as_mut();
self.task.enter(|cx| stream.poll_next(cx))
-50
View File
@@ -1,47 +1,3 @@
# 0.7.4 (September 8, 2022)
### Added
- io: add `SyncIoBridge::shutdown()` ([#4938])
- task: improve `LocalPoolHandle` ([#4680])
### Fixed
- util: add `track_caller` to public APIs ([#4785])
### Unstable
- task: fix compilation errors in `JoinMap` with Tokio v1.21.0 ([#4755])
- task: remove the unstable, deprecated `JoinMap::join_one` ([#4920])
[#4680]: https://github.com/tokio-rs/tokio/pull/4680
[#4755]: https://github.com/tokio-rs/tokio/pull/4755
[#4785]: https://github.com/tokio-rs/tokio/pull/4785
[#4920]: https://github.com/tokio-rs/tokio/pull/4920
[#4938]: https://github.com/tokio-rs/tokio/pull/4938
# 0.7.3 (June 4, 2022)
### Changed
- tracing: don't require default tracing features ([#4592])
- util: simplify implementation of `ReusableBoxFuture` ([#4675])
### Added (unstable)
- task: add `JoinMap` ([#4640], [#4697])
[#4592]: https://github.com/tokio-rs/tokio/pull/4592
[#4640]: https://github.com/tokio-rs/tokio/pull/4640
[#4675]: https://github.com/tokio-rs/tokio/pull/4675
[#4697]: https://github.com/tokio-rs/tokio/pull/4697
# 0.7.2 (May 14, 2022)
This release contains a rewrite of `CancellationToken` that fixes a memory leak. ([#4652])
[#4652]: https://github.com/tokio-rs/tokio/pull/4652
# 0.7.1 (February 21, 2022)
### Added
@@ -93,12 +49,6 @@ This release contains a rewrite of `CancellationToken` that fixes a memory leak.
[#4214]: https://github.com/tokio-rs/tokio/pull/4214
[#4241]: https://github.com/tokio-rs/tokio/pull/4241
# 0.6.10 (May 14, 2021)
This is a backport for the memory leak in `CancellationToken` that was originally fixed in 0.7.2. ([#4652])
[#4652]: https://github.com/tokio-rs/tokio/pull/4652
# 0.6.9 (October 29, 2021)
### Added
+5 -12
View File
@@ -4,7 +4,7 @@ name = "tokio-util"
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-util-0.7.x" git tag.
version = "0.7.4"
version = "0.7.1"
edition = "2018"
rust-version = "1.49"
authors = ["Tokio Contributors <[email protected]>"]
@@ -29,12 +29,13 @@ codec = ["tracing"]
time = ["tokio/time","slab"]
io = []
io-util = ["io", "tokio/rt", "tokio/io-util"]
rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
rt = ["tokio/rt", "tokio/sync", "futures-util"]
__docs_rs = ["futures-util"]
[dependencies]
tokio = { version = "1.21.0", path = "../tokio", features = ["sync"] }
tokio = { version = "1.6.0", path = "../tokio", features = ["sync"] }
bytes = "1.0.0"
futures-core = "0.3.0"
futures-sink = "0.3.0"
@@ -44,9 +45,6 @@ pin-project-lite = "0.2.0"
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true }
[target.'cfg(tokio_unstable)'.dependencies]
hashbrown = { version = "0.12.0", optional = true }
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
tokio-test = { version = "0.4.0", path = "../tokio-test" }
@@ -55,12 +53,7 @@ tokio-stream = { version = "0.1", path = "../tokio-stream" }
async-stream = "0.3.0"
futures = "0.3.0"
futures-test = "0.3.5"
parking_lot = "0.12.0"
[package.metadata.docs.rs]
all-features = true
# enable unstable features in the documentation
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"]
# it's necessary to _also_ pass `--cfg tokio_unstable` to rustc, or else
# dependencies will not be enabled, and the docs build will fail.
rustc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"]
rustdoc-args = ["--cfg", "docsrs"]
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2023 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+1 -1
View File
@@ -20,7 +20,7 @@ use std::io;
/// it's possible to temporarily read 0 bytes by reaching EOF.
///
/// In these cases `decode_eof` will be called until it signals
/// fulfillment of all closing frames by returning `Ok(None)`.
/// fullfillment of all closing frames by returning `Ok(None)`.
/// After that, repeated attempts to read from the [`Framed`] or [`FramedRead`]
/// will not invoke `decode` or `decode_eof` again, until data can be read
/// during a retry.
+4 -11
View File
@@ -253,16 +253,6 @@ impl<T, U> Framed<T, U> {
&mut self.inner.state.write.buffer
}
/// Returns backpressure boundary
pub fn backpressure_boundary(&self) -> usize {
self.inner.state.write.backpressure_boundary
}
/// Updates backpressure boundary
pub fn set_backpressure_boundary(&mut self, boundary: usize) {
self.inner.state.write.backpressure_boundary = boundary;
}
/// Consumes the `Framed`, returning its underlying I/O stream.
///
/// Note that care should be taken to not tamper with the underlying stream
@@ -368,7 +358,10 @@ pub struct FramedParts<T, U> {
impl<T, U> FramedParts<T, U> {
/// Create a new, default, `FramedParts`
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
pub fn new<I>(io: T, codec: U) -> FramedParts<T, U>
where
U: Encoder<I>,
{
FramedParts {
io,
codec,
+4 -8
View File
@@ -25,6 +25,7 @@ pin_project! {
}
const INITIAL_CAPACITY: usize = 8 * 1024;
const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY;
#[derive(Debug)]
pub(crate) struct ReadFrame {
@@ -36,7 +37,6 @@ pub(crate) struct ReadFrame {
pub(crate) struct WriteFrame {
pub(crate) buffer: BytesMut,
pub(crate) backpressure_boundary: usize,
}
#[derive(Default)]
@@ -60,7 +60,6 @@ impl Default for WriteFrame {
fn default() -> Self {
Self {
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
backpressure_boundary: INITIAL_CAPACITY,
}
}
}
@@ -88,10 +87,7 @@ impl From<BytesMut> for WriteFrame {
buffer.reserve(INITIAL_CAPACITY - size);
}
Self {
buffer,
backpressure_boundary: INITIAL_CAPACITY,
}
Self { buffer }
}
}
@@ -260,7 +256,7 @@ where
type Error = U::Error;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.state.borrow().buffer.len() >= self.state.borrow().backpressure_boundary {
if self.state.borrow().buffer.len() >= BACKPRESSURE_BOUNDARY {
self.as_mut().poll_flush(cx)
} else {
Poll::Ready(Ok(()))
@@ -281,7 +277,7 @@ where
let mut pinned = self.project();
while !pinned.state.borrow_mut().buffer.is_empty() {
let WriteFrame { buffer, .. } = pinned.state.borrow_mut();
let WriteFrame { buffer } = pinned.state.borrow_mut();
trace!(remaining = buffer.len(), "writing;");
let n = ready!(poll_write_buf(pinned.inner.as_mut(), cx, buffer))?;
-10
View File
@@ -123,16 +123,6 @@ impl<T, E> FramedWrite<T, E> {
pub fn write_buffer_mut(&mut self) -> &mut BytesMut {
&mut self.inner.state.buffer
}
/// Returns backpressure boundary
pub fn backpressure_boundary(&self) -> usize {
self.inner.state.backpressure_boundary
}
/// Updates backpressure boundary
pub fn set_backpressure_boundary(&mut self, boundary: usize) {
self.inner.state.backpressure_boundary = boundary;
}
}
// This impl just defers to the underlying FramedImpl
+7 -3
View File
@@ -522,11 +522,15 @@ impl LengthDelimitedCodec {
}
};
src.advance(self.builder.get_num_skip());
let num_skip = self.builder.get_num_skip();
if num_skip > 0 {
src.advance(num_skip);
}
// Ensure that the buffer has enough space to read the incoming
// payload
src.reserve(n.saturating_sub(src.len()));
src.reserve(n);
Ok(Some(n))
}
@@ -564,7 +568,7 @@ impl Decoder for LengthDelimitedCodec {
self.state = DecodeState::Head;
// Make sure the buffer has enough space to read the next head
src.reserve(self.builder.num_head_bytes().saturating_sub(src.len()));
src.reserve(self.builder.num_head_bytes());
Ok(Some(data))
}
-68
View File
@@ -1,68 +0,0 @@
use bytes::Bytes;
use futures_sink::Sink;
use pin_project_lite::pin_project;
use std::pin::Pin;
use std::task::{Context, Poll};
pin_project! {
/// A helper that wraps a [`Sink`]`<`[`Bytes`]`>` and converts it into a
/// [`Sink`]`<&'a [u8]>` by copying each byte slice into an owned [`Bytes`].
///
/// See the documentation for [`SinkWriter`] for an example.
///
/// [`Bytes`]: bytes::Bytes
/// [`SinkWriter`]: crate::io::SinkWriter
/// [`Sink`]: futures_sink::Sink
#[derive(Debug)]
pub struct CopyToBytes<S> {
#[pin]
inner: S,
}
}
impl<S> CopyToBytes<S> {
/// Creates a new [`CopyToBytes`].
pub fn new(inner: S) -> Self {
Self { inner }
}
/// Gets a reference to the underlying sink.
pub fn get_ref(&self) -> &S {
&self.inner
}
/// Gets a mutable reference to the underlying sink.
pub fn get_mut(&mut self) -> &mut S {
&mut self.inner
}
/// Consumes this [`CopyToBytes`], returning the underlying sink.
pub fn into_inner(self) -> S {
self.inner
}
}
impl<'a, S> Sink<&'a [u8]> for CopyToBytes<S>
where
S: Sink<Bytes>,
{
type Error = S::Error;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().inner.poll_ready(cx)
}
fn start_send(self: Pin<&mut Self>, item: &'a [u8]) -> Result<(), Self::Error> {
self.project()
.inner
.start_send(Bytes::copy_from_slice(item))
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().inner.poll_flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().inner.poll_close(cx)
}
}
-134
View File
@@ -1,134 +0,0 @@
use futures_core::ready;
use pin_project_lite::pin_project;
use std::io::{IoSlice, Result};
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
pin_project! {
/// An adapter that lets you inspect the data that's being read.
///
/// This is useful for things like hashing data as it's read in.
pub struct InspectReader<R, F> {
#[pin]
reader: R,
f: F,
}
}
impl<R, F> InspectReader<R, F> {
/// Create a new InspectReader, wrapping `reader` and calling `f` for the
/// new data supplied by each read call.
///
/// The closure will only be called with an empty slice if the inner reader
/// returns without reading data into the buffer. This happens at EOF, or if
/// `poll_read` is called with a zero-size buffer.
pub fn new(reader: R, f: F) -> InspectReader<R, F>
where
R: AsyncRead,
F: FnMut(&[u8]),
{
InspectReader { reader, f }
}
/// Consumes the `InspectReader`, returning the wrapped reader
pub fn into_inner(self) -> R {
self.reader
}
}
impl<R: AsyncRead, F: FnMut(&[u8])> AsyncRead for InspectReader<R, F> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<Result<()>> {
let me = self.project();
let filled_length = buf.filled().len();
ready!(me.reader.poll_read(cx, buf))?;
(me.f)(&buf.filled()[filled_length..]);
Poll::Ready(Ok(()))
}
}
pin_project! {
/// An adapter that lets you inspect the data that's being written.
///
/// This is useful for things like hashing data as it's written out.
pub struct InspectWriter<W, F> {
#[pin]
writer: W,
f: F,
}
}
impl<W, F> InspectWriter<W, F> {
/// Create a new InspectWriter, wrapping `write` and calling `f` for the
/// data successfully written by each write call.
///
/// The closure `f` will never be called with an empty slice. A vectored
/// write can result in multiple calls to `f` - at most one call to `f` per
/// buffer supplied to `poll_write_vectored`.
pub fn new(writer: W, f: F) -> InspectWriter<W, F>
where
W: AsyncWrite,
F: FnMut(&[u8]),
{
InspectWriter { writer, f }
}
/// Consumes the `InspectWriter`, returning the wrapped writer
pub fn into_inner(self) -> W {
self.writer
}
}
impl<W: AsyncWrite, F: FnMut(&[u8])> AsyncWrite for InspectWriter<W, F> {
fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
let me = self.project();
let res = me.writer.poll_write(cx, buf);
if let Poll::Ready(Ok(count)) = res {
if count != 0 {
(me.f)(&buf[..count]);
}
}
res
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
let me = self.project();
me.writer.poll_flush(cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
let me = self.project();
me.writer.poll_shutdown(cx)
}
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<Result<usize>> {
let me = self.project();
let res = me.writer.poll_write_vectored(cx, bufs);
if let Poll::Ready(Ok(mut count)) = res {
for buf in bufs {
if count == 0 {
break;
}
let size = count.min(buf.len());
if size != 0 {
(me.f)(&buf[..size]);
count -= size;
}
}
}
res
}
fn is_write_vectored(&self) -> bool {
self.writer.is_write_vectored()
}
}
-7
View File
@@ -10,22 +10,15 @@
//! [`Body`]: https://docs.rs/hyper/0.13/hyper/struct.Body.html
//! [`AsyncRead`]: tokio::io::AsyncRead
mod copy_to_bytes;
mod inspect;
mod read_buf;
mod reader_stream;
mod sink_writer;
mod stream_reader;
cfg_io_util! {
mod sync_bridge;
pub use self::sync_bridge::SyncIoBridge;
}
pub use self::copy_to_bytes::CopyToBytes;
pub use self::inspect::{InspectReader, InspectWriter};
pub use self::read_buf::read_buf;
pub use self::reader_stream::ReaderStream;
pub use self::sink_writer::SinkWriter;
pub use self::stream_reader::StreamReader;
pub use crate::util::{poll_read_buf, poll_write_buf};
-124
View File
@@ -1,124 +0,0 @@
use futures_sink::Sink;
use pin_project_lite::pin_project;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::AsyncWrite;
pin_project! {
/// Convert a [`Sink`] of byte chunks into an [`AsyncWrite`].
///
/// Whenever you write to this [`SinkWriter`], the supplied bytes are
/// forwarded to the inner [`Sink`]. When `shutdown` is called on this
/// [`SinkWriter`], the inner sink is closed.
///
/// This adapter takes a `Sink<&[u8]>` and provides an [`AsyncWrite`] impl
/// for it. Because of the lifetime, this trait is relatively rarely
/// implemented. The main ways to get a `Sink<&[u8]>` that you can use with
/// this type are:
///
/// * With the codec module by implementing the [`Encoder`]`<&[u8]>` trait.
/// * By wrapping a `Sink<Bytes>` in a [`CopyToBytes`].
/// * Manually implementing `Sink<&[u8]>` directly.
///
/// The opposite conversion of implementing `Sink<_>` for an [`AsyncWrite`]
/// is done using the [`codec`] module.
///
/// # Example
///
/// ```
/// use bytes::Bytes;
/// use futures_util::SinkExt;
/// use std::io::{Error, ErrorKind};
/// use tokio::io::AsyncWriteExt;
/// use tokio_util::io::{SinkWriter, CopyToBytes};
/// use tokio_util::sync::PollSender;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<(), Error> {
/// // We use an mpsc channel as an example of a `Sink<Bytes>`.
/// let (tx, mut rx) = tokio::sync::mpsc::channel::<Bytes>(1);
/// let sink = PollSender::new(tx).sink_map_err(|_| Error::from(ErrorKind::BrokenPipe));
///
/// // Wrap it in `CopyToBytes` to get a `Sink<&[u8]>`.
/// let mut writer = SinkWriter::new(CopyToBytes::new(sink));
///
/// // Write data to our interface...
/// let data: [u8; 4] = [1, 2, 3, 4];
/// let _ = writer.write(&data).await?;
///
/// // ... and receive it.
/// assert_eq!(data.as_slice(), &*rx.recv().await.unwrap());
/// # Ok(())
/// # }
/// ```
///
/// [`AsyncWrite`]: tokio::io::AsyncWrite
/// [`CopyToBytes`]: crate::io::CopyToBytes
/// [`Encoder`]: crate::codec::Encoder
/// [`Sink`]: futures_sink::Sink
/// [`codec`]: tokio_util::codec
#[derive(Debug)]
pub struct SinkWriter<S> {
#[pin]
inner: S,
}
}
impl<S> SinkWriter<S> {
/// Creates a new [`SinkWriter`].
pub fn new(sink: S) -> Self {
Self { inner: sink }
}
/// Gets a reference to the underlying sink.
pub fn get_ref(&self) -> &S {
&self.inner
}
/// Gets a mutable reference to the underlying sink.
pub fn get_mut(&mut self) -> &mut S {
&mut self.inner
}
/// Consumes this [`SinkWriter`], returning the underlying sink.
pub fn into_inner(self) -> S {
self.inner
}
}
impl<S, E> AsyncWrite for SinkWriter<S>
where
for<'a> S: Sink<&'a [u8], Error = E>,
E: Into<io::Error>,
{
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>> {
let mut this = self.project();
match this.inner.as_mut().poll_ready(cx) {
Poll::Ready(Ok(())) => {
if let Err(e) = this.inner.as_mut().start_send(buf) {
Poll::Ready(Err(e.into()))
} else {
Poll::Ready(Ok(buf.len()))
}
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())),
Poll::Pending => {
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
self.project().inner.poll_flush(cx).map_err(Into::into)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
self.project().inner.poll_close(cx).map_err(Into::into)
}
}
+55 -178
View File
@@ -1,162 +1,64 @@
use bytes::Buf;
use futures_core::stream::Stream;
use pin_project_lite::pin_project;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncBufRead, AsyncRead, ReadBuf};
/// Convert a [`Stream`] of byte chunks into an [`AsyncRead`].
///
/// This type performs the inverse operation of [`ReaderStream`].
///
/// This type also implements the [`AsyncBufRead`] trait, so you can use it
/// to read a `Stream` of byte chunks line-by-line. See the examples below.
///
/// # Example
///
/// ```
/// use bytes::Bytes;
/// use tokio::io::{AsyncReadExt, Result};
/// use tokio_util::io::StreamReader;
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
///
/// // Create a stream from an iterator.
/// let stream = tokio_stream::iter(vec![
/// Result::Ok(Bytes::from_static(&[0, 1, 2, 3])),
/// Result::Ok(Bytes::from_static(&[4, 5, 6, 7])),
/// Result::Ok(Bytes::from_static(&[8, 9, 10, 11])),
/// ]);
///
/// // Convert it to an AsyncRead.
/// let mut read = StreamReader::new(stream);
///
/// // Read five bytes from the stream.
/// let mut buf = [0; 5];
/// read.read_exact(&mut buf).await?;
/// assert_eq!(buf, [0, 1, 2, 3, 4]);
///
/// // Read the rest of the current chunk.
/// assert_eq!(read.read(&mut buf).await?, 3);
/// assert_eq!(&buf[..3], [5, 6, 7]);
///
/// // Read the next chunk.
/// assert_eq!(read.read(&mut buf).await?, 4);
/// assert_eq!(&buf[..4], [8, 9, 10, 11]);
///
/// // We have now reached the end.
/// assert_eq!(read.read(&mut buf).await?, 0);
///
/// # Ok(())
/// # }
/// ```
///
/// If the stream produces errors which are not [`std::io::Error`],
/// the errors can be converted using [`StreamExt`] to map each
/// element.
///
/// ```
/// use bytes::Bytes;
/// use tokio::io::AsyncReadExt;
/// use tokio_util::io::StreamReader;
/// use tokio_stream::StreamExt;
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
///
/// // Create a stream from an iterator, including an error.
/// let stream = tokio_stream::iter(vec![
/// Result::Ok(Bytes::from_static(&[0, 1, 2, 3])),
/// Result::Ok(Bytes::from_static(&[4, 5, 6, 7])),
/// Result::Err("Something bad happened!")
/// ]);
///
/// // Use StreamExt to map the stream and error to a std::io::Error
/// let stream = stream.map(|result| result.map_err(|err| {
/// std::io::Error::new(std::io::ErrorKind::Other, err)
/// }));
///
/// // Convert it to an AsyncRead.
/// let mut read = StreamReader::new(stream);
///
/// // Read five bytes from the stream.
/// let mut buf = [0; 5];
/// read.read_exact(&mut buf).await?;
/// assert_eq!(buf, [0, 1, 2, 3, 4]);
///
/// // Read the rest of the current chunk.
/// assert_eq!(read.read(&mut buf).await?, 3);
/// assert_eq!(&buf[..3], [5, 6, 7]);
///
/// // Reading the next chunk will produce an error
/// let error = read.read(&mut buf).await.unwrap_err();
/// assert_eq!(error.kind(), std::io::ErrorKind::Other);
/// assert_eq!(error.into_inner().unwrap().to_string(), "Something bad happened!");
///
/// // We have now reached the end.
/// assert_eq!(read.read(&mut buf).await?, 0);
///
/// # Ok(())
/// # }
/// ```
///
/// Using the [`AsyncBufRead`] impl, you can read a `Stream` of byte chunks
/// line-by-line. Note that you will usually also need to convert the error
/// type when doing this. See the second example for an explanation of how
/// to do this.
///
/// ```
/// use tokio::io::{Result, AsyncBufReadExt};
/// use tokio_util::io::StreamReader;
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
///
/// // Create a stream of byte chunks.
/// let stream = tokio_stream::iter(vec![
/// Result::Ok(b"The first line.\n".as_slice()),
/// Result::Ok(b"The second line.".as_slice()),
/// Result::Ok(b"\nThe third".as_slice()),
/// Result::Ok(b" line.\nThe fourth line.\nThe fifth line.\n".as_slice()),
/// ]);
///
/// // Convert it to an AsyncRead.
/// let mut read = StreamReader::new(stream);
///
/// // Loop through the lines from the `StreamReader`.
/// let mut line = String::new();
/// let mut lines = Vec::new();
/// loop {
/// line.clear();
/// let len = read.read_line(&mut line).await?;
/// if len == 0 { break; }
/// lines.push(line.clone());
/// }
///
/// // Verify that we got the lines we expected.
/// assert_eq!(
/// lines,
/// vec![
/// "The first line.\n",
/// "The second line.\n",
/// "The third line.\n",
/// "The fourth line.\n",
/// "The fifth line.\n",
/// ]
/// );
/// # Ok(())
/// # }
/// ```
///
/// [`AsyncRead`]: tokio::io::AsyncRead
/// [`AsyncBufRead`]: tokio::io::AsyncBufRead
/// [`Stream`]: futures_core::Stream
/// [`ReaderStream`]: crate::io::ReaderStream
/// [`StreamExt`]: https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html
#[derive(Debug)]
pub struct StreamReader<S, B> {
// This field is pinned.
inner: S,
// This field is not pinned.
chunk: Option<B>,
pin_project! {
/// Convert a [`Stream`] of byte chunks into an [`AsyncRead`].
///
/// This type performs the inverse operation of [`ReaderStream`].
///
/// # Example
///
/// ```
/// use bytes::Bytes;
/// use tokio::io::{AsyncReadExt, Result};
/// use tokio_util::io::StreamReader;
/// # #[tokio::main]
/// # async fn main() -> std::io::Result<()> {
///
/// // Create a stream from an iterator.
/// let stream = tokio_stream::iter(vec![
/// Result::Ok(Bytes::from_static(&[0, 1, 2, 3])),
/// Result::Ok(Bytes::from_static(&[4, 5, 6, 7])),
/// Result::Ok(Bytes::from_static(&[8, 9, 10, 11])),
/// ]);
///
/// // Convert it to an AsyncRead.
/// let mut read = StreamReader::new(stream);
///
/// // Read five bytes from the stream.
/// let mut buf = [0; 5];
/// read.read_exact(&mut buf).await?;
/// assert_eq!(buf, [0, 1, 2, 3, 4]);
///
/// // Read the rest of the current chunk.
/// assert_eq!(read.read(&mut buf).await?, 3);
/// assert_eq!(&buf[..3], [5, 6, 7]);
///
/// // Read the next chunk.
/// assert_eq!(read.read(&mut buf).await?, 4);
/// assert_eq!(&buf[..4], [8, 9, 10, 11]);
///
/// // We have now reached the end.
/// assert_eq!(read.read(&mut buf).await?, 0);
///
/// # Ok(())
/// # }
/// ```
///
/// [`AsyncRead`]: tokio::io::AsyncRead
/// [`Stream`]: futures_core::Stream
/// [`ReaderStream`]: crate::io::ReaderStream
#[derive(Debug)]
pub struct StreamReader<S, B> {
#[pin]
inner: S,
chunk: Option<B>,
}
}
impl<S, B, E> StreamReader<S, B>
@@ -191,7 +93,7 @@ where
}
/// Consumes this `StreamReader`, returning a Tuple consisting
/// of the underlying stream and an Option of the internal buffer,
/// of the underlying stream and an Option of the interal buffer,
/// which is Some in case the buffer contains elements.
pub fn into_inner_with_chunk(self) -> (S, Option<B>) {
if self.has_chunk() {
@@ -299,28 +201,3 @@ where
}
}
}
// The code below is a manual expansion of the code that pin-project-lite would
// generate. This is done because pin-project-lite fails by hitting the recusion
// limit on this struct. (Every line of documentation is handled recursively by
// the macro.)
impl<S: Unpin, B> Unpin for StreamReader<S, B> {}
struct StreamReaderProject<'a, S, B> {
inner: Pin<&'a mut S>,
chunk: &'a mut Option<B>,
}
impl<S, B> StreamReader<S, B> {
#[inline]
fn project(self: Pin<&mut Self>) -> StreamReaderProject<'_, S, B> {
// SAFETY: We define that only `inner` should be pinned when `Self` is
// and have an appropriate `impl Unpin` for this.
let me = unsafe { Pin::into_inner_unchecked(self) };
StreamReaderProject {
inner: unsafe { Pin::new_unchecked(&mut me.inner) },
chunk: &mut me.chunk,
}
}
}
+3 -43
View File
@@ -1,7 +1,5 @@
use std::io::{BufRead, Read, Write};
use tokio::io::{
AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt,
};
use std::io::{Read, Write};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
@@ -11,28 +9,6 @@ pub struct SyncIoBridge<T> {
rt: tokio::runtime::Handle,
}
impl<T: AsyncBufRead + Unpin> BufRead for SyncIoBridge<T> {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
let src = &mut self.src;
self.rt.block_on(AsyncBufReadExt::fill_buf(src))
}
fn consume(&mut self, amt: usize) {
let src = &mut self.src;
AsyncBufReadExt::consume(src, amt)
}
fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> std::io::Result<usize> {
let src = &mut self.src;
self.rt
.block_on(AsyncBufReadExt::read_until(src, byte, buf))
}
fn read_line(&mut self, buf: &mut String) -> std::io::Result<usize> {
let src = &mut self.src;
self.rt.block_on(AsyncBufReadExt::read_line(src, buf))
}
}
impl<T: AsyncRead + Unpin> Read for SyncIoBridge<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let src = &mut self.src;
@@ -90,21 +66,6 @@ impl<T: AsyncWrite> SyncIoBridge<T> {
}
}
impl<T: AsyncWrite + Unpin> SyncIoBridge<T> {
/// Shutdown this writer. This method provides a way to call the [`AsyncWriteExt::shutdown`]
/// function of the inner [`tokio::io::AsyncWrite`] instance.
///
/// # Errors
///
/// This method returns the same errors as [`AsyncWriteExt::shutdown`].
///
/// [`AsyncWriteExt::shutdown`]: tokio::io::AsyncWriteExt::shutdown
pub fn shutdown(&mut self) -> std::io::Result<()> {
let src = &mut self.src;
self.rt.block_on(src.shutdown())
}
}
impl<T: Unpin> SyncIoBridge<T> {
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
@@ -124,10 +85,9 @@ impl<T: Unpin> SyncIoBridge<T> {
///
/// Use e.g. `SyncIoBridge::new(Box::pin(src))`.
///
/// # Panics
/// # Panic
///
/// This will panic if called outside the context of a Tokio runtime.
#[track_caller]
pub fn new(src: T) -> Self {
Self::new_with_handle(src, tokio::runtime::Handle::current())
}
-4
View File
@@ -29,7 +29,6 @@ cfg_codec! {
}
cfg_net! {
#[cfg(not(target_arch = "wasm32"))]
pub mod udp;
pub mod net;
}
@@ -116,9 +115,6 @@ mod util {
let n = {
let dst = buf.chunk_mut();
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
// transparent wrapper around `[MaybeUninit<u8>]`.
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
let mut buf = ReadBuf::uninit(dst);
let ptr = buf.filled().as_ptr();
+694 -137
View File
@@ -1,15 +1,18 @@
//! An asynchronously awaitable `CancellationToken`.
//! The token allows to signal a cancellation request to one or more tasks.
pub(crate) mod guard;
mod tree_node;
use crate::loom::sync::Arc;
use crate::loom::sync::atomic::AtomicUsize;
use crate::loom::sync::Mutex;
use crate::sync::intrusive_double_linked_list::{LinkedList, ListNode};
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use core::ptr::NonNull;
use core::sync::atomic::Ordering;
use core::task::{Context, Poll, Waker};
use guard::DropGuard;
use pin_project_lite::pin_project;
/// A token which can be used to signal a cancellation request to one or more
/// tasks.
@@ -52,36 +55,30 @@ use pin_project_lite::pin_project;
/// }
/// ```
pub struct CancellationToken {
inner: Arc<tree_node::TreeNode>,
inner: NonNull<CancellationTokenState>,
}
pin_project! {
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// is cancelled.
#[must_use = "futures do nothing unless polled"]
pub struct WaitForCancellationFuture<'a> {
cancellation_token: &'a CancellationToken,
#[pin]
future: tokio::sync::futures::Notified<'a>,
}
// Safety: The CancellationToken is thread-safe and can be moved between threads,
// since all methods are internally synchronized.
unsafe impl Send for CancellationToken {}
unsafe impl Sync for CancellationToken {}
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// was cancelled
#[must_use = "futures do nothing unless polled"]
pub struct WaitForCancellationFuture<'a> {
/// The CancellationToken that is associated with this WaitForCancellationFuture
cancellation_token: Option<&'a CancellationToken>,
/// Node for waiting at the cancellation_token
wait_node: ListNode<WaitQueueEntry>,
/// Whether this future was registered at the token yet as a waiter
is_registered: bool,
}
pin_project! {
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// is cancelled.
///
/// This is the counterpart to [`WaitForCancellationFuture`] that takes
/// [`CancellationToken`] by value instead of using a reference.
#[must_use = "futures do nothing unless polled"]
pub struct WaitForCancellationFutureOwned {
// Since `future` is the first field, it is dropped before the
// cancellation_token field. This ensures that the reference inside the
// `Notified` remains valid.
#[pin]
future: tokio::sync::futures::Notified<'static>,
cancellation_token: CancellationToken,
}
}
// Safety: Futures can be sent between threads as long as the underlying
// cancellation_token is thread-safe (Sync),
// which allows to poll/register/unregister from a different thread.
unsafe impl<'a> Send for WaitForCancellationFuture<'a> {}
// ===== impl CancellationToken =====
@@ -95,16 +92,43 @@ impl core::fmt::Debug for CancellationToken {
impl Clone for CancellationToken {
fn clone(&self) -> Self {
tree_node::increase_handle_refcount(&self.inner);
CancellationToken {
inner: self.inner.clone(),
}
// Safety: The state inside a `CancellationToken` is always valid, since
// is reference counted
let inner = self.state();
// Tokens are cloned by increasing their refcount
let current_state = inner.snapshot();
inner.increment_refcount(current_state);
CancellationToken { inner: self.inner }
}
}
impl Drop for CancellationToken {
fn drop(&mut self) {
tree_node::decrease_handle_refcount(&self.inner);
let token_state_pointer = self.inner;
// Safety: The state inside a `CancellationToken` is always valid, since
// is reference counted
let inner = unsafe { &mut *self.inner.as_ptr() };
let mut current_state = inner.snapshot();
// We need to safe the parent, since the state might be released by the
// next call
let parent = inner.parent;
// Drop our own refcount
current_state = inner.decrement_refcount(current_state);
// If this was the last reference, unregister from the parent
if current_state.refcount == 0 {
if let Some(mut parent) = parent {
// Safety: Since we still retain a reference on the parent, it must be valid.
let parent = unsafe { parent.as_mut() };
parent.unregister_child(token_state_pointer, current_state);
}
}
}
}
@@ -117,11 +141,29 @@ impl Default for CancellationToken {
impl CancellationToken {
/// Creates a new CancellationToken in the non-cancelled state.
pub fn new() -> CancellationToken {
let state = Box::new(CancellationTokenState::new(
None,
StateSnapshot {
cancel_state: CancellationState::NotCancelled,
has_parent_ref: false,
refcount: 1,
},
));
// Safety: We just created the Box. The pointer is guaranteed to be
// not null
CancellationToken {
inner: Arc::new(tree_node::TreeNode::new()),
inner: unsafe { NonNull::new_unchecked(Box::into_raw(state)) },
}
}
/// Returns a reference to the utilized `CancellationTokenState`.
fn state(&self) -> &CancellationTokenState {
// Safety: The state inside a `CancellationToken` is always valid, since
// is reference counted
unsafe { &*self.inner.as_ptr() }
}
/// Creates a `CancellationToken` which will get cancelled whenever the
/// current token gets cancelled.
///
@@ -161,8 +203,56 @@ impl CancellationToken {
/// }
/// ```
pub fn child_token(&self) -> CancellationToken {
let inner = self.state();
// Increment the refcount of this token. It will be referenced by the
// child, independent of whether the child is immediately cancelled or
// not.
let _current_state = inner.increment_refcount(inner.snapshot());
let mut unpacked_child_state = StateSnapshot {
has_parent_ref: true,
refcount: 1,
cancel_state: CancellationState::NotCancelled,
};
let mut child_token_state = Box::new(CancellationTokenState::new(
Some(self.inner),
unpacked_child_state,
));
{
let mut guard = inner.synchronized.lock().unwrap();
if guard.is_cancelled {
// This task was already cancelled. In this case we should not
// insert the child into the list, since it would never get removed
// from the list.
(*child_token_state.synchronized.lock().unwrap()).is_cancelled = true;
unpacked_child_state.cancel_state = CancellationState::Cancelled;
// Since it's not in the list, the parent doesn't need to retain
// a reference to it.
unpacked_child_state.has_parent_ref = false;
child_token_state
.state
.store(unpacked_child_state.pack(), Ordering::SeqCst);
} else {
if let Some(mut first_child) = guard.first_child {
child_token_state.from_parent.next_peer = Some(first_child);
// Safety: We manipulate other child task inside the Mutex
// and retain a parent reference on it. The child token can't
// get invalidated while the Mutex is held.
unsafe {
first_child.as_mut().from_parent.prev_peer =
Some((&mut *child_token_state).into())
};
}
guard.first_child = Some((&mut *child_token_state).into());
}
};
let child_token_ptr = Box::into_raw(child_token_state);
// Safety: We just created the pointer from a `Box`
CancellationToken {
inner: tree_node::child_node(&self.inner),
inner: unsafe { NonNull::new_unchecked(child_token_ptr) },
}
}
@@ -170,51 +260,24 @@ impl CancellationToken {
/// derived from it.
///
/// This will wake up all tasks which are waiting for cancellation.
///
/// Be aware that cancellation is not an atomic operation. It is possible
/// for another thread running in parallel with a call to `cancel` to first
/// receive `true` from `is_cancelled` on one child node, and then receive
/// `false` from `is_cancelled` on another child node. However, once the
/// call to `cancel` returns, all child nodes have been fully cancelled.
pub fn cancel(&self) {
tree_node::cancel(&self.inner);
self.state().cancel();
}
/// Returns `true` if the `CancellationToken` is cancelled.
/// Returns `true` if the `CancellationToken` had been cancelled
pub fn is_cancelled(&self) -> bool {
tree_node::is_cancelled(&self.inner)
self.state().is_cancelled()
}
/// Returns a `Future` that gets fulfilled when cancellation is requested.
///
/// The future will complete immediately if the token is already cancelled
/// when this method is called.
///
/// # Cancel safety
///
/// This method is cancel safe.
pub fn cancelled(&self) -> WaitForCancellationFuture<'_> {
WaitForCancellationFuture {
cancellation_token: self,
future: self.inner.notified(),
cancellation_token: Some(self),
wait_node: ListNode::new(WaitQueueEntry::new()),
is_registered: false,
}
}
/// Returns a `Future` that gets fulfilled when cancellation is requested.
///
/// The future will complete immediately if the token is already cancelled
/// when this method is called.
///
/// The function takes self by value and returns a future that owns the
/// token.
///
/// # Cancel safety
///
/// This method is cancel safe.
pub fn cancelled_owned(self) -> WaitForCancellationFutureOwned {
WaitForCancellationFutureOwned::new(self)
}
/// Creates a `DropGuard` for this token.
///
/// Returned guard will cancel this token (and all its children) on drop
@@ -222,6 +285,26 @@ impl CancellationToken {
pub fn drop_guard(self) -> DropGuard {
DropGuard { inner: Some(self) }
}
unsafe fn register(
&self,
wait_node: &mut ListNode<WaitQueueEntry>,
cx: &mut Context<'_>,
) -> Poll<()> {
self.state().register(wait_node, cx)
}
fn check_for_cancellation(
&self,
wait_node: &mut ListNode<WaitQueueEntry>,
cx: &mut Context<'_>,
) -> Poll<()> {
self.state().check_for_cancellation(wait_node, cx)
}
fn unregister(&self, wait_node: &mut ListNode<WaitQueueEntry>) {
self.state().unregister(wait_node)
}
}
// ===== impl WaitForCancellationFuture =====
@@ -236,86 +319,560 @@ impl<'a> Future for WaitForCancellationFuture<'a> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let mut this = self.project();
// Safety: We do not move anything out of `WaitForCancellationFuture`
let mut_self: &mut WaitForCancellationFuture<'_> = unsafe { Pin::get_unchecked_mut(self) };
let cancellation_token = mut_self
.cancellation_token
.expect("polled WaitForCancellationFuture after completion");
let poll_res = if !mut_self.is_registered {
// Safety: The `ListNode` is pinned through the Future,
// and we will unregister it in `WaitForCancellationFuture::drop`
// before the Future is dropped and the memory reference is invalidated.
unsafe { cancellation_token.register(&mut mut_self.wait_node, cx) }
} else {
cancellation_token.check_for_cancellation(&mut mut_self.wait_node, cx)
};
if let Poll::Ready(()) = poll_res {
// The cancellation_token was signalled
mut_self.cancellation_token = None;
// A signalled Token means the Waker won't be enqueued anymore
mut_self.is_registered = false;
mut_self.wait_node.task = None;
} else {
// This `Future` and its stored `Waker` stay registered at the
// `CancellationToken`
mut_self.is_registered = true;
}
poll_res
}
}
impl<'a> Drop for WaitForCancellationFuture<'a> {
fn drop(&mut self) {
// If this WaitForCancellationFuture has been polled and it was added to the
// wait queue at the cancellation_token, it must be removed before dropping.
// Otherwise the cancellation_token would access invalid memory.
if let Some(token) = self.cancellation_token {
if self.is_registered {
token.unregister(&mut self.wait_node);
}
}
}
}
/// Tracks how the future had interacted with the [`CancellationToken`]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum PollState {
/// The task has never interacted with the [`CancellationToken`].
New,
/// The task was added to the wait queue at the [`CancellationToken`].
Waiting,
/// The task has been polled to completion.
Done,
}
/// Tracks the WaitForCancellationFuture waiting state.
/// Access to this struct is synchronized through the mutex in the CancellationToken.
struct WaitQueueEntry {
/// The task handle of the waiting task
task: Option<Waker>,
// Current polling state. This state is only updated inside the Mutex of
// the CancellationToken.
state: PollState,
}
impl WaitQueueEntry {
/// Creates a new WaitQueueEntry
fn new() -> WaitQueueEntry {
WaitQueueEntry {
task: None,
state: PollState::New,
}
}
}
struct SynchronizedState {
waiters: LinkedList<WaitQueueEntry>,
first_child: Option<NonNull<CancellationTokenState>>,
is_cancelled: bool,
}
impl SynchronizedState {
fn new() -> Self {
Self {
waiters: LinkedList::new(),
first_child: None,
is_cancelled: false,
}
}
}
/// Information embedded in child tokens which is synchronized through the Mutex
/// in their parent.
struct SynchronizedThroughParent {
next_peer: Option<NonNull<CancellationTokenState>>,
prev_peer: Option<NonNull<CancellationTokenState>>,
}
/// Possible states of a `CancellationToken`
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum CancellationState {
NotCancelled = 0,
Cancelling = 1,
Cancelled = 2,
}
impl CancellationState {
fn pack(self) -> usize {
self as usize
}
fn unpack(value: usize) -> Self {
match value {
0 => CancellationState::NotCancelled,
1 => CancellationState::Cancelling,
2 => CancellationState::Cancelled,
_ => unreachable!("Invalid value"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct StateSnapshot {
/// The amount of references to this particular CancellationToken.
/// `CancellationToken` structs hold these references to a `CancellationTokenState`.
/// Also the state is referenced by the state of each child.
refcount: usize,
/// Whether the state is still referenced by it's parent and can therefore
/// not be freed.
has_parent_ref: bool,
/// Whether the token is cancelled
cancel_state: CancellationState,
}
impl StateSnapshot {
/// Packs the snapshot into a `usize`
fn pack(self) -> usize {
self.refcount << 3 | if self.has_parent_ref { 4 } else { 0 } | self.cancel_state.pack()
}
/// Unpacks the snapshot from a `usize`
fn unpack(value: usize) -> Self {
let refcount = value >> 3;
let has_parent_ref = value & 4 != 0;
let cancel_state = CancellationState::unpack(value & 0x03);
StateSnapshot {
refcount,
has_parent_ref,
cancel_state,
}
}
/// Whether this `CancellationTokenState` is still referenced by any
/// `CancellationToken`.
fn has_refs(&self) -> bool {
self.refcount != 0 || self.has_parent_ref
}
}
/// The maximum permitted amount of references to a CancellationToken. This
/// is derived from the intent to never use more than 32bit in the `Snapshot`.
const MAX_REFS: u32 = (std::u32::MAX - 7) >> 3;
/// Internal state of the `CancellationToken` pair above
struct CancellationTokenState {
state: AtomicUsize,
parent: Option<NonNull<CancellationTokenState>>,
from_parent: SynchronizedThroughParent,
synchronized: Mutex<SynchronizedState>,
}
impl CancellationTokenState {
fn new(
parent: Option<NonNull<CancellationTokenState>>,
state: StateSnapshot,
) -> CancellationTokenState {
CancellationTokenState {
parent,
from_parent: SynchronizedThroughParent {
prev_peer: None,
next_peer: None,
},
state: AtomicUsize::new(state.pack()),
synchronized: Mutex::new(SynchronizedState::new()),
}
}
/// Returns a snapshot of the current atomic state of the token
fn snapshot(&self) -> StateSnapshot {
StateSnapshot::unpack(self.state.load(Ordering::SeqCst))
}
fn atomic_update_state<F>(&self, mut current_state: StateSnapshot, func: F) -> StateSnapshot
where
F: Fn(StateSnapshot) -> StateSnapshot,
{
let mut current_packed_state = current_state.pack();
loop {
if this.cancellation_token.is_cancelled() {
return Poll::Ready(());
let next_state = func(current_state);
match self.state.compare_exchange(
current_packed_state,
next_state.pack(),
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => {
return next_state;
}
Err(actual) => {
current_packed_state = actual;
current_state = StateSnapshot::unpack(actual);
}
}
// No wakeups can be lost here because there is always a call to
// `is_cancelled` between the creation of the future and the call to
// `poll`, and the code that sets the cancelled flag does so before
// waking the `Notified`.
if this.future.as_mut().poll(cx).is_pending() {
return Poll::Pending;
}
this.future.set(this.cancellation_token.inner.notified());
}
}
}
// ===== impl WaitForCancellationFutureOwned =====
impl core::fmt::Debug for WaitForCancellationFutureOwned {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("WaitForCancellationFutureOwned").finish()
}
}
impl WaitForCancellationFutureOwned {
fn new(cancellation_token: CancellationToken) -> Self {
WaitForCancellationFutureOwned {
// cancellation_token holds a heap allocation and is guaranteed to have a
// stable deref, thus it would be ok to move the cancellation_token while
// the future holds a reference to it.
//
// # Safety
//
// cancellation_token is dropped after future due to the field ordering.
future: unsafe { Self::new_future(&cancellation_token) },
cancellation_token,
}
}
/// # Safety
/// The returned future must be destroyed before the cancellation token is
/// destroyed.
unsafe fn new_future(
cancellation_token: &CancellationToken,
) -> tokio::sync::futures::Notified<'static> {
let inner_ptr = Arc::as_ptr(&cancellation_token.inner);
// SAFETY: The `Arc::as_ptr` method guarantees that `inner_ptr` remains
// valid until the strong count of the Arc drops to zero, and the caller
// guarantees that they will drop the future before that happens.
(*inner_ptr).notified()
fn increment_refcount(&self, current_state: StateSnapshot) -> StateSnapshot {
self.atomic_update_state(current_state, |mut state: StateSnapshot| {
if state.refcount >= MAX_REFS as usize {
eprintln!("[ERROR] Maximum reference count for CancellationToken was exceeded");
std::process::abort();
}
state.refcount += 1;
state
})
}
}
impl Future for WaitForCancellationFutureOwned {
type Output = ();
fn decrement_refcount(&self, current_state: StateSnapshot) -> StateSnapshot {
let current_state = self.atomic_update_state(current_state, |mut state: StateSnapshot| {
state.refcount -= 1;
state
});
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let mut this = self.project();
// Drop the State if it is not referenced anymore
if !current_state.has_refs() {
// Safety: `CancellationTokenState` is always stored in refcounted
// Boxes
let _ = unsafe { Box::from_raw(self as *const Self as *mut Self) };
}
loop {
if this.cancellation_token.is_cancelled() {
return Poll::Ready(());
current_state
}
fn remove_parent_ref(&self, current_state: StateSnapshot) -> StateSnapshot {
let current_state = self.atomic_update_state(current_state, |mut state: StateSnapshot| {
state.has_parent_ref = false;
state
});
// Drop the State if it is not referenced anymore
if !current_state.has_refs() {
// Safety: `CancellationTokenState` is always stored in refcounted
// Boxes
let _ = unsafe { Box::from_raw(self as *const Self as *mut Self) };
}
current_state
}
/// Unregisters a child from the parent token.
/// The child tokens state is not exactly known at this point in time.
/// If the parent token is cancelled, the child token gets removed from the
/// parents list, and might therefore already have been freed. If the parent
/// token is not cancelled, the child token is still valid.
fn unregister_child(
&mut self,
mut child_state: NonNull<CancellationTokenState>,
current_child_state: StateSnapshot,
) {
let removed_child = {
// Remove the child toke from the parents linked list
let mut guard = self.synchronized.lock().unwrap();
if !guard.is_cancelled {
// Safety: Since the token was not cancelled, the child must
// still be in the list and valid.
let mut child_state = unsafe { child_state.as_mut() };
debug_assert!(child_state.snapshot().has_parent_ref);
if guard.first_child == Some(child_state.into()) {
guard.first_child = child_state.from_parent.next_peer;
}
// Safety: If peers wouldn't be valid anymore, they would try
// to remove themselves from the list. This would require locking
// the Mutex that we currently own.
unsafe {
if let Some(mut prev_peer) = child_state.from_parent.prev_peer {
prev_peer.as_mut().from_parent.next_peer =
child_state.from_parent.next_peer;
}
if let Some(mut next_peer) = child_state.from_parent.next_peer {
next_peer.as_mut().from_parent.prev_peer =
child_state.from_parent.prev_peer;
}
}
child_state.from_parent.prev_peer = None;
child_state.from_parent.next_peer = None;
// The child is no longer referenced by the parent, since we were able
// to remove its reference from the parents list.
true
} else {
// Do not touch the linked list anymore. If the parent is cancelled
// it will move all childs outside of the Mutex and manipulate
// the pointers there. Manipulating the pointers here too could
// lead to races. Therefore leave them just as as and let the
// parent deal with it. The parent will make sure to retain a
// reference to this state as long as it manipulates the list
// pointers. Therefore the pointers are not dangling.
false
}
};
if removed_child {
// If the token removed itself from the parents list, it can reset
// the parent ref status. If it is isn't able to do so, because the
// parent removed it from the list, there is no need to do this.
// The parent ref acts as as another reference count. Therefore
// removing this reference can free the object.
// Safety: The token was in the list. This means the parent wasn't
// cancelled before, and the token must still be alive.
unsafe { child_state.as_mut().remove_parent_ref(current_child_state) };
}
// Decrement the refcount on the parent and free it if necessary
self.decrement_refcount(self.snapshot());
}
fn cancel(&self) {
// Move the state of the CancellationToken from `NotCancelled` to `Cancelling`
let mut current_state = self.snapshot();
let state_after_cancellation = loop {
if current_state.cancel_state != CancellationState::NotCancelled {
// Another task already initiated the cancellation
return;
}
// No wakeups can be lost here because there is always a call to
// `is_cancelled` between the creation of the future and the call to
// `poll`, and the code that sets the cancelled flag does so before
// waking the `Notified`.
if this.future.as_mut().poll(cx).is_pending() {
return Poll::Pending;
let mut next_state = current_state;
next_state.cancel_state = CancellationState::Cancelling;
match self.state.compare_exchange(
current_state.pack(),
next_state.pack(),
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => break next_state,
Err(actual) => current_state = StateSnapshot::unpack(actual),
}
};
// # Safety
//
// cancellation_token is dropped after future due to the field ordering.
this.future
.set(unsafe { Self::new_future(this.cancellation_token) });
// This task cancelled the token
// Take the task list out of the Token
// We do not want to cancel child token inside this lock. If one of the
// child tasks would have additional child tokens, we would recursively
// take locks.
// Doing this action has an impact if the child token is dropped concurrently:
// It will try to deregister itself from the parent task, but can not find
// itself in the task list anymore. Therefore it needs to assume the parent
// has extracted the list and will process it. It may not modify the list.
// This is OK from a memory safety perspective, since the parent still
// retains a reference to the child task until it finished iterating over
// it.
let mut first_child = {
let mut guard = self.synchronized.lock().unwrap();
// Save the cancellation also inside the Mutex
// This allows child tokens which want to detach themselves to detect
// that this is no longer required since the parent cleared the list.
guard.is_cancelled = true;
// Wakeup all waiters
// This happens inside the lock to make cancellation reliable
// If we would access waiters outside of the lock, the pointers
// may no longer be valid.
// Typically this shouldn't be an issue, since waking a task should
// only move it from the blocked into the ready state and not have
// further side effects.
// Use a reverse iterator, so that the oldest waiter gets
// scheduled first
guard.waiters.reverse_drain(|waiter| {
// We are not allowed to move the `Waker` out of the list node.
// The `Future` relies on the fact that the old `Waker` stays there
// as long as the `Future` has not completed in order to perform
// the `will_wake()` check.
// Therefore `wake_by_ref` is used instead of `wake()`
if let Some(handle) = &mut waiter.task {
handle.wake_by_ref();
}
// Mark the waiter to have been removed from the list.
waiter.state = PollState::Done;
});
guard.first_child.take()
};
while let Some(mut child) = first_child {
// Safety: We know this is a valid pointer since it is in our child pointer
// list. It can't have been freed in between, since we retain a a reference
// to each child.
let mut_child = unsafe { child.as_mut() };
// Get the next child and clean up list pointers
first_child = mut_child.from_parent.next_peer;
mut_child.from_parent.prev_peer = None;
mut_child.from_parent.next_peer = None;
// Cancel the child task
mut_child.cancel();
// Drop the parent reference. This `CancellationToken` is not interested
// in interacting with the child anymore.
// This is ONLY allowed once we promised not to touch the state anymore
// after this interaction.
mut_child.remove_parent_ref(mut_child.snapshot());
}
// The cancellation has completed
// At this point in time tasks which registered a wait node can be sure
// that this wait node already had been dequeued from the list without
// needing to inspect the list.
self.atomic_update_state(state_after_cancellation, |mut state| {
state.cancel_state = CancellationState::Cancelled;
state
});
}
/// Returns `true` if the `CancellationToken` had been cancelled
fn is_cancelled(&self) -> bool {
let current_state = self.snapshot();
current_state.cancel_state != CancellationState::NotCancelled
}
/// Registers a waiting task at the `CancellationToken`.
/// Safety: This method is only safe as long as the waiting waiting task
/// will properly unregister the wait node before it gets moved.
unsafe fn register(
&self,
wait_node: &mut ListNode<WaitQueueEntry>,
cx: &mut Context<'_>,
) -> Poll<()> {
debug_assert_eq!(PollState::New, wait_node.state);
let current_state = self.snapshot();
// Perform an optimistic cancellation check before. This is not strictly
// necessary since we also check for cancellation in the Mutex, but
// reduces the necessary work to be performed for tasks which already
// had been cancelled.
if current_state.cancel_state != CancellationState::NotCancelled {
return Poll::Ready(());
}
// So far the token is not cancelled. However it could be cancelled before
// we get the chance to store the `Waker`. Therefore we need to check
// for cancellation again inside the mutex.
let mut guard = self.synchronized.lock().unwrap();
if guard.is_cancelled {
// Cancellation was signalled
wait_node.state = PollState::Done;
Poll::Ready(())
} else {
// Added the task to the wait queue
wait_node.task = Some(cx.waker().clone());
wait_node.state = PollState::Waiting;
guard.waiters.add_front(wait_node);
Poll::Pending
}
}
fn check_for_cancellation(
&self,
wait_node: &mut ListNode<WaitQueueEntry>,
cx: &mut Context<'_>,
) -> Poll<()> {
debug_assert!(
wait_node.task.is_some(),
"Method can only be called after task had been registered"
);
let current_state = self.snapshot();
if current_state.cancel_state != CancellationState::NotCancelled {
// If the cancellation had been fully completed we know that our `Waker`
// is no longer registered at the `CancellationToken`.
// Otherwise the cancel call may or may not yet have iterated
// through the waiters list and removed the wait nodes.
// If it hasn't yet, we need to remove it. Otherwise an attempt to
// reuse the `wait_node´ might get freed due to the `WaitForCancellationFuture`
// getting dropped before the cancellation had interacted with it.
if current_state.cancel_state != CancellationState::Cancelled {
self.unregister(wait_node);
}
Poll::Ready(())
} else {
// Check if we need to swap the `Waker`. This will make the check more
// expensive, since the `Waker` is synchronized through the Mutex.
// If we don't need to perform a `Waker` update, an atomic check for
// cancellation is sufficient.
let need_waker_update = wait_node
.task
.as_ref()
.map(|waker| !waker.will_wake(cx.waker()))
.unwrap_or(true);
if need_waker_update {
let guard = self.synchronized.lock().unwrap();
if guard.is_cancelled {
// Cancellation was signalled. Since this cancellation signal
// is set inside the Mutex, the old waiter must already have
// been removed from the waiting list
debug_assert_eq!(PollState::Done, wait_node.state);
wait_node.task = None;
Poll::Ready(())
} else {
// The WaitForCancellationFuture is already in the queue.
// The CancellationToken can't have been cancelled,
// since this would change the is_cancelled flag inside the mutex.
// Therefore we just have to update the Waker. A follow-up
// cancellation will always use the new waker.
wait_node.task = Some(cx.waker().clone());
Poll::Pending
}
} else {
// Do nothing. If the token gets cancelled, this task will get
// woken again and can fetch the cancellation.
Poll::Pending
}
}
}
fn unregister(&self, wait_node: &mut ListNode<WaitQueueEntry>) {
debug_assert!(
wait_node.task.is_some(),
"waiter can not be active without task"
);
let mut guard = self.synchronized.lock().unwrap();
// WaitForCancellationFuture only needs to get removed if it has been added to
// the wait queue of the CancellationToken.
// This has happened in the PollState::Waiting case.
if let PollState::Waiting = wait_node.state {
// Safety: Due to the state, we know that the node must be part
// of the waiter list
if !unsafe { guard.waiters.remove(wait_node) } {
// Panic if the address isn't found. This can only happen if the contract was
// violated, e.g. the WaitQueueEntry got moved after the initial poll.
panic!("Future could not be removed from wait queue");
}
wait_node.state = PollState::Done;
}
wait_node.task = None;
}
}
@@ -1,373 +0,0 @@
//! This mod provides the logic for the inner tree structure of the CancellationToken.
//!
//! CancellationTokens are only light handles with references to TreeNode.
//! All the logic is actually implemented in the TreeNode.
//!
//! A TreeNode is part of the cancellation tree and may have one parent and an arbitrary number of
//! children.
//!
//! A TreeNode can receive the request to perform a cancellation through a CancellationToken.
//! This cancellation request will cancel the node and all of its descendants.
//!
//! As soon as a node cannot get cancelled any more (because it was already cancelled or it has no
//! more CancellationTokens pointing to it any more), it gets removed from the tree, to keep the
//! tree as small as possible.
//!
//! # Invariants
//!
//! Those invariants shall be true at any time.
//!
//! 1. A node that has no parents and no handles can no longer be cancelled.
//! This is important during both cancellation and refcounting.
//!
//! 2. If node B *is* or *was* a child of node A, then node B was created *after* node A.
//! This is important for deadlock safety, as it is used for lock order.
//! Node B can only become the child of node A in two ways:
//! - being created with `child_node()`, in which case it is trivially true that
//! node A already existed when node B was created
//! - being moved A->C->B to A->B because node C was removed in `decrease_handle_refcount()`
//! or `cancel()`. In this case the invariant still holds, as B was younger than C, and C
//! was younger than A, therefore B is also younger than A.
//!
//! 3. If two nodes are both unlocked and node A is the parent of node B, then node B is a child of
//! node A. It is important to always restore that invariant before dropping the lock of a node.
//!
//! # Deadlock safety
//!
//! We always lock in the order of creation time. We can prove this through invariant #2.
//! Specifically, through invariant #2, we know that we always have to lock a parent
//! before its child.
//!
use crate::loom::sync::{Arc, Mutex, MutexGuard};
/// A node of the cancellation tree structure
///
/// The actual data it holds is wrapped inside a mutex for synchronization.
pub(crate) struct TreeNode {
inner: Mutex<Inner>,
waker: tokio::sync::Notify,
}
impl TreeNode {
pub(crate) fn new() -> Self {
Self {
inner: Mutex::new(Inner {
parent: None,
parent_idx: 0,
children: vec![],
is_cancelled: false,
num_handles: 1,
}),
waker: tokio::sync::Notify::new(),
}
}
pub(crate) fn notified(&self) -> tokio::sync::futures::Notified<'_> {
self.waker.notified()
}
}
/// The data contained inside a TreeNode.
///
/// This struct exists so that the data of the node can be wrapped
/// in a Mutex.
struct Inner {
parent: Option<Arc<TreeNode>>,
parent_idx: usize,
children: Vec<Arc<TreeNode>>,
is_cancelled: bool,
num_handles: usize,
}
/// Returns whether or not the node is cancelled
pub(crate) fn is_cancelled(node: &Arc<TreeNode>) -> bool {
node.inner.lock().unwrap().is_cancelled
}
/// Creates a child node
pub(crate) fn child_node(parent: &Arc<TreeNode>) -> Arc<TreeNode> {
let mut locked_parent = parent.inner.lock().unwrap();
// Do not register as child if we are already cancelled.
// Cancelled trees can never be uncancelled and therefore
// need no connection to parents or children any more.
if locked_parent.is_cancelled {
return Arc::new(TreeNode {
inner: Mutex::new(Inner {
parent: None,
parent_idx: 0,
children: vec![],
is_cancelled: true,
num_handles: 1,
}),
waker: tokio::sync::Notify::new(),
});
}
let child = Arc::new(TreeNode {
inner: Mutex::new(Inner {
parent: Some(parent.clone()),
parent_idx: locked_parent.children.len(),
children: vec![],
is_cancelled: false,
num_handles: 1,
}),
waker: tokio::sync::Notify::new(),
});
locked_parent.children.push(child.clone());
child
}
/// Disconnects the given parent from all of its children.
///
/// Takes a reference to [Inner] to make sure the parent is already locked.
fn disconnect_children(node: &mut Inner) {
for child in std::mem::take(&mut node.children) {
let mut locked_child = child.inner.lock().unwrap();
locked_child.parent_idx = 0;
locked_child.parent = None;
}
}
/// Figures out the parent of the node and locks the node and its parent atomically.
///
/// The basic principle of preventing deadlocks in the tree is
/// that we always lock the parent first, and then the child.
/// For more info look at *deadlock safety* and *invariant #2*.
///
/// Sadly, it's impossible to figure out the parent of a node without
/// locking it. To then achieve locking order consistency, the node
/// has to be unlocked before the parent gets locked.
/// This leaves a small window where we already assume that we know the parent,
/// but neither the parent nor the node is locked. Therefore, the parent could change.
///
/// To prevent that this problem leaks into the rest of the code, it is abstracted
/// in this function.
///
/// The locked child and optionally its locked parent, if a parent exists, get passed
/// to the `func` argument via (node, None) or (node, Some(parent)).
fn with_locked_node_and_parent<F, Ret>(node: &Arc<TreeNode>, func: F) -> Ret
where
F: FnOnce(MutexGuard<'_, Inner>, Option<MutexGuard<'_, Inner>>) -> Ret,
{
let mut potential_parent = {
let locked_node = node.inner.lock().unwrap();
match locked_node.parent.clone() {
Some(parent) => parent,
// If we locked the node and its parent is `None`, we are in a valid state
// and can return.
None => return func(locked_node, None),
}
};
loop {
// Deadlock safety:
//
// Due to invariant #2, we know that we have to lock the parent first, and then the child.
// This is true even if the potential_parent is no longer the current parent or even its
// sibling, as the invariant still holds.
let locked_parent = potential_parent.inner.lock().unwrap();
let locked_node = node.inner.lock().unwrap();
let actual_parent = match locked_node.parent.clone() {
Some(parent) => parent,
// If we locked the node and its parent is `None`, we are in a valid state
// and can return.
None => {
// Was the wrong parent, so unlock it before calling `func`
drop(locked_parent);
return func(locked_node, None);
}
};
// Loop until we managed to lock both the node and its parent
if Arc::ptr_eq(&actual_parent, &potential_parent) {
return func(locked_node, Some(locked_parent));
}
// Drop locked_parent before reassigning to potential_parent,
// as potential_parent is borrowed in it
drop(locked_node);
drop(locked_parent);
potential_parent = actual_parent;
}
}
/// Moves all children from `node` to `parent`.
///
/// `parent` MUST have been a parent of the node when they both got locked,
/// otherwise there is a potential for a deadlock as invariant #2 would be violated.
///
/// To acquire the locks for node and parent, use [with_locked_node_and_parent].
fn move_children_to_parent(node: &mut Inner, parent: &mut Inner) {
// Pre-allocate in the parent, for performance
parent.children.reserve(node.children.len());
for child in std::mem::take(&mut node.children) {
{
let mut child_locked = child.inner.lock().unwrap();
child_locked.parent = node.parent.clone();
child_locked.parent_idx = parent.children.len();
}
parent.children.push(child);
}
}
/// Removes a child from the parent.
///
/// `parent` MUST be the parent of `node`.
/// To acquire the locks for node and parent, use [with_locked_node_and_parent].
fn remove_child(parent: &mut Inner, mut node: MutexGuard<'_, Inner>) {
// Query the position from where to remove a node
let pos = node.parent_idx;
node.parent = None;
node.parent_idx = 0;
// Unlock node, so that only one child at a time is locked.
// Otherwise we would violate the lock order (see 'deadlock safety') as we
// don't know the creation order of the child nodes
drop(node);
// If `node` is the last element in the list, we don't need any swapping
if parent.children.len() == pos + 1 {
parent.children.pop().unwrap();
} else {
// If `node` is not the last element in the list, we need to
// replace it with the last element
let replacement_child = parent.children.pop().unwrap();
replacement_child.inner.lock().unwrap().parent_idx = pos;
parent.children[pos] = replacement_child;
}
let len = parent.children.len();
if 4 * len <= parent.children.capacity() {
// equal to:
// parent.children.shrink_to(2 * len);
// but shrink_to was not yet stabilized in our minimal compatible version
let old_children = std::mem::replace(&mut parent.children, Vec::with_capacity(2 * len));
parent.children.extend(old_children);
}
}
/// Increases the reference count of handles.
pub(crate) fn increase_handle_refcount(node: &Arc<TreeNode>) {
let mut locked_node = node.inner.lock().unwrap();
// Once no handles are left over, the node gets detached from the tree.
// There should never be a new handle once all handles are dropped.
assert!(locked_node.num_handles > 0);
locked_node.num_handles += 1;
}
/// Decreases the reference count of handles.
///
/// Once no handle is left, we can remove the node from the
/// tree and connect its parent directly to its children.
pub(crate) fn decrease_handle_refcount(node: &Arc<TreeNode>) {
let num_handles = {
let mut locked_node = node.inner.lock().unwrap();
locked_node.num_handles -= 1;
locked_node.num_handles
};
if num_handles == 0 {
with_locked_node_and_parent(node, |mut node, parent| {
// Remove the node from the tree
match parent {
Some(mut parent) => {
// As we want to remove ourselves from the tree,
// we have to move the children to the parent, so that
// they still receive the cancellation event without us.
// Moving them does not violate invariant #1.
move_children_to_parent(&mut node, &mut parent);
// Remove the node from the parent
remove_child(&mut parent, node);
}
None => {
// Due to invariant #1, we can assume that our
// children can no longer be cancelled through us.
// (as we now have neither a parent nor handles)
// Therefore we can disconnect them.
disconnect_children(&mut node);
}
}
});
}
}
/// Cancels a node and its children.
pub(crate) fn cancel(node: &Arc<TreeNode>) {
let mut locked_node = node.inner.lock().unwrap();
if locked_node.is_cancelled {
return;
}
// One by one, adopt grandchildren and then cancel and detach the child
while let Some(child) = locked_node.children.pop() {
// This can't deadlock because the mutex we are already
// holding is the parent of child.
let mut locked_child = child.inner.lock().unwrap();
// Detach the child from node
// No need to modify node.children, as the child already got removed with `.pop`
locked_child.parent = None;
locked_child.parent_idx = 0;
// If child is already cancelled, detaching is enough
if locked_child.is_cancelled {
continue;
}
// Cancel or adopt grandchildren
while let Some(grandchild) = locked_child.children.pop() {
// This can't deadlock because the two mutexes we are already
// holding is the parent and grandparent of grandchild.
let mut locked_grandchild = grandchild.inner.lock().unwrap();
// Detach the grandchild
locked_grandchild.parent = None;
locked_grandchild.parent_idx = 0;
// If grandchild is already cancelled, detaching is enough
if locked_grandchild.is_cancelled {
continue;
}
// For performance reasons, only adopt grandchildren that have children.
// Otherwise, just cancel them right away, no need for another iteration.
if locked_grandchild.children.is_empty() {
// Cancel the grandchild
locked_grandchild.is_cancelled = true;
locked_grandchild.children = Vec::new();
drop(locked_grandchild);
grandchild.waker.notify_waiters();
} else {
// Otherwise, adopt grandchild
locked_grandchild.parent = Some(node.clone());
locked_grandchild.parent_idx = locked_node.children.len();
drop(locked_grandchild);
locked_node.children.push(grandchild);
}
}
// Cancel the child
locked_child.is_cancelled = true;
locked_child.children = Vec::new();
drop(locked_child);
child.waker.notify_waiters();
// Now the child is cancelled and detached and all its children are adopted.
// Just continue until all (including adopted) children are cancelled and detached.
}
// Cancel the node itself.
locked_node.is_cancelled = true;
locked_node.children = Vec::new();
drop(locked_node);
node.waker.notify_waiters();
}
@@ -0,0 +1,788 @@
//! An intrusive double linked list of data
#![allow(dead_code, unreachable_pub)]
use core::{
marker::PhantomPinned,
ops::{Deref, DerefMut},
ptr::NonNull,
};
/// A node which carries data of type `T` and is stored in an intrusive list
#[derive(Debug)]
pub struct ListNode<T> {
/// The previous node in the list. `None` if there is no previous node.
prev: Option<NonNull<ListNode<T>>>,
/// The next node in the list. `None` if there is no previous node.
next: Option<NonNull<ListNode<T>>>,
/// The data which is associated to this list item
data: T,
/// Prevents `ListNode`s from being `Unpin`. They may never be moved, since
/// the list semantics require addresses to be stable.
_pin: PhantomPinned,
}
impl<T> ListNode<T> {
/// Creates a new node with the associated data
pub fn new(data: T) -> ListNode<T> {
Self {
prev: None,
next: None,
data,
_pin: PhantomPinned,
}
}
}
impl<T> Deref for ListNode<T> {
type Target = T;
fn deref(&self) -> &T {
&self.data
}
}
impl<T> DerefMut for ListNode<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.data
}
}
/// An intrusive linked list of nodes, where each node carries associated data
/// of type `T`.
#[derive(Debug)]
pub struct LinkedList<T> {
head: Option<NonNull<ListNode<T>>>,
tail: Option<NonNull<ListNode<T>>>,
}
impl<T> LinkedList<T> {
/// Creates an empty linked list
pub fn new() -> Self {
LinkedList::<T> {
head: None,
tail: None,
}
}
/// Adds a node at the front of the linked list.
/// Safety: This function is only safe as long as `node` is guaranteed to
/// get removed from the list before it gets moved or dropped.
/// In addition to this `node` may not be added to another other list before
/// it is removed from the current one.
pub unsafe fn add_front(&mut self, node: &mut ListNode<T>) {
node.next = self.head;
node.prev = None;
if let Some(mut head) = self.head {
head.as_mut().prev = Some(node.into())
};
self.head = Some(node.into());
if self.tail.is_none() {
self.tail = Some(node.into());
}
}
/// Inserts a node into the list in a way that the list keeps being sorted.
/// Safety: This function is only safe as long as `node` is guaranteed to
/// get removed from the list before it gets moved or dropped.
/// In addition to this `node` may not be added to another other list before
/// it is removed from the current one.
pub unsafe fn add_sorted(&mut self, node: &mut ListNode<T>)
where
T: PartialOrd,
{
if self.head.is_none() {
// First node in the list
self.head = Some(node.into());
self.tail = Some(node.into());
return;
}
let mut prev: Option<NonNull<ListNode<T>>> = None;
let mut current = self.head;
while let Some(mut current_node) = current {
if node.data < current_node.as_ref().data {
// Need to insert before the current node
current_node.as_mut().prev = Some(node.into());
match prev {
Some(mut prev) => {
prev.as_mut().next = Some(node.into());
}
None => {
// We are inserting at the beginning of the list
self.head = Some(node.into());
}
}
node.next = current;
node.prev = prev;
return;
}
prev = current;
current = current_node.as_ref().next;
}
// We looped through the whole list and the nodes data is bigger or equal
// than everything we found up to now.
// Insert at the end. Since we checked before that the list isn't empty,
// tail always has a value.
node.prev = self.tail;
node.next = None;
self.tail.as_mut().unwrap().as_mut().next = Some(node.into());
self.tail = Some(node.into());
}
/// Returns the first node in the linked list without removing it from the list
/// The function is only safe as long as valid pointers are stored inside
/// the linked list.
/// The returned pointer is only guaranteed to be valid as long as the list
/// is not mutated
pub fn peek_first(&self) -> Option<&mut ListNode<T>> {
// Safety: When the node was inserted it was promised that it is alive
// until it gets removed from the list.
// The returned node has a pointer which constrains it to the lifetime
// of the list. This is ok, since the Node is supposed to outlive
// its insertion in the list.
unsafe {
self.head
.map(|mut node| &mut *(node.as_mut() as *mut ListNode<T>))
}
}
/// Returns the last node in the linked list without removing it from the list
/// The function is only safe as long as valid pointers are stored inside
/// the linked list.
/// The returned pointer is only guaranteed to be valid as long as the list
/// is not mutated
pub fn peek_last(&self) -> Option<&mut ListNode<T>> {
// Safety: When the node was inserted it was promised that it is alive
// until it gets removed from the list.
// The returned node has a pointer which constrains it to the lifetime
// of the list. This is ok, since the Node is supposed to outlive
// its insertion in the list.
unsafe {
self.tail
.map(|mut node| &mut *(node.as_mut() as *mut ListNode<T>))
}
}
/// Removes the first node from the linked list
pub fn remove_first(&mut self) -> Option<&mut ListNode<T>> {
#![allow(clippy::debug_assert_with_mut_call)]
// Safety: When the node was inserted it was promised that it is alive
// until it gets removed from the list
unsafe {
let mut head = self.head?;
self.head = head.as_mut().next;
let first_ref = head.as_mut();
match first_ref.next {
None => {
// This was the only node in the list
debug_assert_eq!(Some(first_ref.into()), self.tail);
self.tail = None;
}
Some(mut next) => {
next.as_mut().prev = None;
}
}
first_ref.prev = None;
first_ref.next = None;
Some(&mut *(first_ref as *mut ListNode<T>))
}
}
/// Removes the last node from the linked list and returns it
pub fn remove_last(&mut self) -> Option<&mut ListNode<T>> {
#![allow(clippy::debug_assert_with_mut_call)]
// Safety: When the node was inserted it was promised that it is alive
// until it gets removed from the list
unsafe {
let mut tail = self.tail?;
self.tail = tail.as_mut().prev;
let last_ref = tail.as_mut();
match last_ref.prev {
None => {
// This was the last node in the list
debug_assert_eq!(Some(last_ref.into()), self.head);
self.head = None;
}
Some(mut prev) => {
prev.as_mut().next = None;
}
}
last_ref.prev = None;
last_ref.next = None;
Some(&mut *(last_ref as *mut ListNode<T>))
}
}
/// Returns whether the linked list does not contain any node
pub fn is_empty(&self) -> bool {
if self.head.is_some() {
return false;
}
debug_assert!(self.tail.is_none());
true
}
/// Removes the given `node` from the linked list.
/// Returns whether the `node` was removed.
/// It is also only safe if it is known that the `node` is either part of this
/// list, or of no list at all. If `node` is part of another list, the
/// behavior is undefined.
pub unsafe fn remove(&mut self, node: &mut ListNode<T>) -> bool {
#![allow(clippy::debug_assert_with_mut_call)]
match node.prev {
None => {
// This might be the first node in the list. If it is not, the
// node is not in the list at all. Since our precondition is that
// the node must either be in this list or in no list, we check that
// the node is really in no list.
if self.head != Some(node.into()) {
debug_assert!(node.next.is_none());
return false;
}
self.head = node.next;
}
Some(mut prev) => {
debug_assert_eq!(prev.as_ref().next, Some(node.into()));
prev.as_mut().next = node.next;
}
}
match node.next {
None => {
// This must be the last node in our list. Otherwise the list
// is inconsistent.
debug_assert_eq!(self.tail, Some(node.into()));
self.tail = node.prev;
}
Some(mut next) => {
debug_assert_eq!(next.as_mut().prev, Some(node.into()));
next.as_mut().prev = node.prev;
}
}
node.next = None;
node.prev = None;
true
}
/// Drains the list iby calling a callback on each list node
///
/// The method does not return an iterator since stopping or deferring
/// draining the list is not permitted. If the method would push nodes to
/// an iterator we could not guarantee that the nodes do not get utilized
/// after having been removed from the list anymore.
pub fn drain<F>(&mut self, mut func: F)
where
F: FnMut(&mut ListNode<T>),
{
let mut current = self.head;
self.head = None;
self.tail = None;
while let Some(mut node) = current {
// Safety: The nodes have not been removed from the list yet and must
// therefore contain valid data. The nodes can also not be added to
// the list again during iteration, since the list is mutably borrowed.
unsafe {
let node_ref = node.as_mut();
current = node_ref.next;
node_ref.next = None;
node_ref.prev = None;
// Note: We do not reset the pointers from the next element in the
// list to the current one since we will iterate over the whole
// list anyway, and therefore clean up all pointers.
func(node_ref);
}
}
}
/// Drains the list in reverse order by calling a callback on each list node
///
/// The method does not return an iterator since stopping or deferring
/// draining the list is not permitted. If the method would push nodes to
/// an iterator we could not guarantee that the nodes do not get utilized
/// after having been removed from the list anymore.
pub fn reverse_drain<F>(&mut self, mut func: F)
where
F: FnMut(&mut ListNode<T>),
{
let mut current = self.tail;
self.head = None;
self.tail = None;
while let Some(mut node) = current {
// Safety: The nodes have not been removed from the list yet and must
// therefore contain valid data. The nodes can also not be added to
// the list again during iteration, since the list is mutably borrowed.
unsafe {
let node_ref = node.as_mut();
current = node_ref.prev;
node_ref.next = None;
node_ref.prev = None;
// Note: We do not reset the pointers from the next element in the
// list to the current one since we will iterate over the whole
// list anyway, and therefore clean up all pointers.
func(node_ref);
}
}
}
}
#[cfg(all(test, feature = "std"))] // Tests make use of Vec at the moment
mod tests {
use super::*;
fn collect_list<T: Copy>(mut list: LinkedList<T>) -> Vec<T> {
let mut result = Vec::new();
list.drain(|node| {
result.push(**node);
});
result
}
fn collect_reverse_list<T: Copy>(mut list: LinkedList<T>) -> Vec<T> {
let mut result = Vec::new();
list.reverse_drain(|node| {
result.push(**node);
});
result
}
unsafe fn add_nodes(list: &mut LinkedList<i32>, nodes: &mut [&mut ListNode<i32>]) {
for node in nodes.iter_mut() {
list.add_front(node);
}
}
unsafe fn assert_clean<T>(node: &mut ListNode<T>) {
assert!(node.next.is_none());
assert!(node.prev.is_none());
}
#[test]
fn insert_and_iterate() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut setup = |list: &mut LinkedList<i32>| {
assert_eq!(true, list.is_empty());
list.add_front(&mut c);
assert_eq!(31, **list.peek_first().unwrap());
assert_eq!(false, list.is_empty());
list.add_front(&mut b);
assert_eq!(7, **list.peek_first().unwrap());
list.add_front(&mut a);
assert_eq!(5, **list.peek_first().unwrap());
};
let mut list = LinkedList::new();
setup(&mut list);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31].to_vec(), items);
let mut list = LinkedList::new();
setup(&mut list);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([31, 7, 5].to_vec(), items);
}
}
#[test]
fn add_sorted() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut d = ListNode::new(99);
let mut list = LinkedList::new();
list.add_sorted(&mut a);
let items: Vec<i32> = collect_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
list.add_sorted(&mut a);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut c, &mut b]);
list.add_sorted(&mut a);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31, 99].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut c, &mut b]);
list.add_sorted(&mut a);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([99, 31, 7, 5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut c, &mut a]);
list.add_sorted(&mut b);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31, 99].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut c, &mut a]);
list.add_sorted(&mut b);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([99, 31, 7, 5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut b, &mut a]);
list.add_sorted(&mut c);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31, 99].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut b, &mut a]);
list.add_sorted(&mut c);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([99, 31, 7, 5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
list.add_sorted(&mut d);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31, 99].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
list.add_sorted(&mut d);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([99, 31, 7, 5].to_vec(), items);
}
}
#[test]
fn drain_and_collect() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let taken_items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31].to_vec(), taken_items);
}
}
#[test]
fn peek_last() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let last = list.peek_last();
assert_eq!(31, **last.unwrap());
list.remove_last();
let last = list.peek_last();
assert_eq!(7, **last.unwrap());
list.remove_last();
let last = list.peek_last();
assert_eq!(5, **last.unwrap());
list.remove_last();
let last = list.peek_last();
assert!(last.is_none());
}
}
#[test]
fn remove_first() {
unsafe {
// We iterate forward and backwards through the manipulated lists
// to make sure pointers in both directions are still ok.
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_list(list);
assert_eq!([7, 31].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([31, 7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_list(list);
assert_eq!([7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(list.is_empty());
let items: Vec<i32> = collect_list(list);
assert!(items.is_empty());
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert!(items.is_empty());
}
}
#[test]
fn remove_last() {
unsafe {
// We iterate forward and backwards through the manipulated lists
// to make sure pointers in both directions are still ok.
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([7, 5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(list.is_empty());
let items: Vec<i32> = collect_list(list);
assert!(items.is_empty());
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert!(items.is_empty());
}
}
#[test]
fn remove_by_address() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
{
// Remove first
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
// a should be no longer there and can't be removed twice
assert_eq!(false, list.remove(&mut a));
assert_eq!(Some((&mut b).into()), list.head);
assert_eq!(Some((&mut c).into()), b.next);
assert_eq!(Some((&mut b).into()), c.prev);
let items: Vec<i32> = collect_list(list);
assert_eq!([7, 31].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
// a should be no longer there and can't be removed twice
assert_eq!(false, list.remove(&mut a));
assert_eq!(Some((&mut c).into()), b.next);
assert_eq!(Some((&mut b).into()), c.prev);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([31, 7].to_vec(), items);
}
{
// Remove middle
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut b));
assert_clean((&mut b).into());
assert_eq!(Some((&mut c).into()), a.next);
assert_eq!(Some((&mut a).into()), c.prev);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 31].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut b));
assert_clean((&mut b).into());
assert_eq!(Some((&mut c).into()), a.next);
assert_eq!(Some((&mut a).into()), c.prev);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([31, 5].to_vec(), items);
}
{
// Remove last
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut c));
assert_clean((&mut c).into());
assert!(b.next.is_none());
assert_eq!(Some((&mut b).into()), list.tail);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut c));
assert_clean((&mut c).into());
assert!(b.next.is_none());
assert_eq!(Some((&mut b).into()), list.tail);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([7, 5].to_vec(), items);
}
{
// Remove first of two
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
// a should be no longer there and can't be removed twice
assert_eq!(false, list.remove(&mut a));
assert_eq!(Some((&mut b).into()), list.head);
assert_eq!(Some((&mut b).into()), list.tail);
assert!(b.next.is_none());
assert!(b.prev.is_none());
let items: Vec<i32> = collect_list(list);
assert_eq!([7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
// a should be no longer there and can't be removed twice
assert_eq!(false, list.remove(&mut a));
assert_eq!(Some((&mut b).into()), list.head);
assert_eq!(Some((&mut b).into()), list.tail);
assert!(b.next.is_none());
assert!(b.prev.is_none());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([7].to_vec(), items);
}
{
// Remove last of two
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
assert_eq!(true, list.remove(&mut b));
assert_clean((&mut b).into());
assert_eq!(Some((&mut a).into()), list.head);
assert_eq!(Some((&mut a).into()), list.tail);
assert!(a.next.is_none());
assert!(a.prev.is_none());
let items: Vec<i32> = collect_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
assert_eq!(true, list.remove(&mut b));
assert_clean((&mut b).into());
assert_eq!(Some((&mut a).into()), list.head);
assert_eq!(Some((&mut a).into()), list.tail);
assert!(a.next.is_none());
assert!(a.prev.is_none());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([5].to_vec(), items);
}
{
// Remove last item
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
assert!(list.head.is_none());
assert!(list.tail.is_none());
let items: Vec<i32> = collect_list(list);
assert!(items.is_empty());
}
{
// Remove missing
let mut list = LinkedList::new();
list.add_front(&mut b);
list.add_front(&mut a);
assert_eq!(false, list.remove(&mut c));
}
}
}
}
+3 -3
View File
@@ -1,9 +1,9 @@
//! Synchronization primitives
mod cancellation_token;
pub use cancellation_token::{
guard::DropGuard, CancellationToken, WaitForCancellationFuture, WaitForCancellationFutureOwned,
};
pub use cancellation_token::{guard::DropGuard, CancellationToken, WaitForCancellationFuture};
mod intrusive_double_linked_list;
mod mpsc;
pub use mpsc::{PollSendError, PollSender};
-1
View File
@@ -136,7 +136,6 @@ impl<T: Send + 'static> PollSender<T> {
///
/// If `poll_reserve` was not successfully called prior to calling `send_item`, then this method
/// will panic.
#[track_caller]
pub fn send_item(&mut self, value: T) -> Result<(), PollSendError<T>> {
let (result, next_state) = match self.take_state() {
State::Idle(_) | State::Acquiring => {
+9 -44
View File
@@ -12,10 +12,7 @@ use super::ReusableBoxFuture;
/// [`Semaphore`]: tokio::sync::Semaphore
pub struct PollSemaphore {
semaphore: Arc<Semaphore>,
permit_fut: Option<(
u32, // The number of permits requested.
ReusableBoxFuture<'static, Result<OwnedSemaphorePermit, AcquireError>>,
)>,
permit_fut: Option<ReusableBoxFuture<'static, Result<OwnedSemaphorePermit, AcquireError>>>,
}
impl PollSemaphore {
@@ -56,57 +53,25 @@ impl PollSemaphore {
/// the `Waker` from the `Context` passed to the most recent call is
/// scheduled to receive a wakeup.
pub fn poll_acquire(&mut self, cx: &mut Context<'_>) -> Poll<Option<OwnedSemaphorePermit>> {
self.poll_acquire_many(cx, 1)
}
/// Poll to acquire many permits from the semaphore.
///
/// This can return the following values:
///
/// - `Poll::Pending` if a permit is not currently available.
/// - `Poll::Ready(Some(permit))` if a permit was acquired.
/// - `Poll::Ready(None)` if the semaphore has been closed.
///
/// When this method returns `Poll::Pending`, the current task is scheduled
/// to receive a wakeup when the permits become available, or when the
/// semaphore is closed. Note that on multiple calls to `poll_acquire`, only
/// the `Waker` from the `Context` passed to the most recent call is
/// scheduled to receive a wakeup.
pub fn poll_acquire_many(
&mut self,
cx: &mut Context<'_>,
permits: u32,
) -> Poll<Option<OwnedSemaphorePermit>> {
let permit_future = match self.permit_fut.as_mut() {
Some((prev_permits, fut)) if *prev_permits == permits => fut,
Some((old_permits, fut_box)) => {
// We're requesting a different number of permits, so replace the future
// and record the new amount.
let fut = Arc::clone(&self.semaphore).acquire_many_owned(permits);
fut_box.set(fut);
*old_permits = permits;
fut_box
}
Some(fut) => fut,
None => {
// avoid allocations completely if we can grab a permit immediately
match Arc::clone(&self.semaphore).try_acquire_many_owned(permits) {
match Arc::clone(&self.semaphore).try_acquire_owned() {
Ok(permit) => return Poll::Ready(Some(permit)),
Err(TryAcquireError::Closed) => return Poll::Ready(None),
Err(TryAcquireError::NoPermits) => {}
}
let next_fut = Arc::clone(&self.semaphore).acquire_many_owned(permits);
&mut self
.permit_fut
.get_or_insert((permits, ReusableBoxFuture::new(next_fut)))
.1
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
self.permit_fut
.get_or_insert(ReusableBoxFuture::new(next_fut))
}
};
let result = ready!(permit_future.poll(cx));
// Assume we'll request the same amount of permits in a subsequent call.
let next_fut = Arc::clone(&self.semaphore).acquire_many_owned(permits);
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
permit_future.set(next_fut);
match result {
@@ -130,7 +95,7 @@ impl PollSemaphore {
/// Adds `n` new permits to the semaphore.
///
/// The maximum number of permits is [`Semaphore::MAX_PERMITS`], and this function
/// The maximum number of permits is `usize::MAX >> 3`, and this function
/// will panic if the limit is exceeded.
///
/// This is equivalent to the [`Semaphore::add_permits`] method on the
@@ -166,6 +131,6 @@ impl fmt::Debug for PollSemaphore {
impl AsRef<Semaphore> for PollSemaphore {
fn as_ref(&self) -> &Semaphore {
&self.semaphore
&*self.semaphore
}
}
+74 -97
View File
@@ -1,18 +1,17 @@
use std::alloc::Layout;
use std::fmt;
use std::future::Future;
use std::marker::PhantomData;
use std::mem::{self, ManuallyDrop};
use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::ptr;
use std::ptr::{self, NonNull};
use std::task::{Context, Poll};
use std::{fmt, panic};
/// A reusable `Pin<Box<dyn Future<Output = T> + Send + 'a>>`.
///
/// This type lets you replace the future stored in the box without
/// reallocating when the size and alignment permits this.
pub struct ReusableBoxFuture<'a, T> {
boxed: Pin<Box<dyn Future<Output = T> + Send + 'a>>,
boxed: NonNull<dyn Future<Output = T> + Send + 'a>,
}
impl<'a, T> ReusableBoxFuture<'a, T> {
@@ -21,9 +20,11 @@ impl<'a, T> ReusableBoxFuture<'a, T> {
where
F: Future<Output = T> + Send + 'a,
{
Self {
boxed: Box::pin(future),
}
let boxed: Box<dyn Future<Output = T> + Send + 'a> = Box::new(future);
let boxed = NonNull::from(Box::leak(boxed));
Self { boxed }
}
/// Replace the future currently stored in this box.
@@ -48,29 +49,62 @@ impl<'a, T> ReusableBoxFuture<'a, T> {
where
F: Future<Output = T> + Send + 'a,
{
// If we try to inline the contents of this function, the type checker complains because
// the bound `T: 'a` is not satisfied in the call to `pending()`. But by putting it in an
// inner function that doesn't have `T` as a generic parameter, we implicitly get the bound
// `F::Output: 'a` transitively through `F: 'a`, allowing us to call `pending()`.
#[inline(always)]
fn real_try_set<'a, F>(
this: &mut ReusableBoxFuture<'a, F::Output>,
future: F,
) -> Result<(), F>
where
F: Future + Send + 'a,
{
// future::Pending<T> is a ZST so this never allocates.
let boxed = mem::replace(&mut this.boxed, Box::pin(Pending(PhantomData)));
reuse_pin_box(boxed, future, |boxed| this.boxed = Pin::from(boxed))
}
// SAFETY: The pointer is not dangling.
let self_layout = {
let dyn_future: &(dyn Future<Output = T> + Send) = unsafe { self.boxed.as_ref() };
Layout::for_value(dyn_future)
};
real_try_set(self, future)
if Layout::new::<F>() == self_layout {
// SAFETY: We just checked that the layout of F is correct.
unsafe {
self.set_same_layout(future);
}
Ok(())
} else {
Err(future)
}
}
/// Set the current future.
///
/// # Safety
///
/// This function requires that the layout of the provided future is the
/// same as `self.layout`.
unsafe fn set_same_layout<F>(&mut self, future: F)
where
F: Future<Output = T> + Send + 'a,
{
// Drop the existing future, catching any panics.
let result = panic::catch_unwind(AssertUnwindSafe(|| {
ptr::drop_in_place(self.boxed.as_ptr());
}));
// Overwrite the future behind the pointer. This is safe because the
// allocation was allocated with the same size and alignment as the type F.
let self_ptr: *mut F = self.boxed.as_ptr() as *mut F;
ptr::write(self_ptr, future);
// Update the vtable of self.boxed. The pointer is not null because we
// just got it from self.boxed, which is not null.
self.boxed = NonNull::new_unchecked(self_ptr);
// If the old future's destructor panicked, resume unwinding.
match result {
Ok(()) => {}
Err(payload) => {
panic::resume_unwind(payload);
}
}
}
/// Get a pinned reference to the underlying future.
pub fn get_pin(&mut self) -> Pin<&mut (dyn Future<Output = T> + Send)> {
self.boxed.as_mut()
// SAFETY: The user of this box cannot move the box, and we do not move it
// either.
unsafe { Pin::new_unchecked(self.boxed.as_mut()) }
}
/// Poll the future stored inside this box.
@@ -88,84 +122,27 @@ impl<T> Future for ReusableBoxFuture<'_, T> {
}
}
// The future stored inside ReusableBoxFuture<'_, T> must be Send.
unsafe impl<T> Send for ReusableBoxFuture<'_, T> {}
// The only method called on self.boxed is poll, which takes &mut self, so this
// struct being Sync does not permit any invalid access to the Future, even if
// the future is not Sync.
unsafe impl<T> Sync for ReusableBoxFuture<'_, T> {}
// Just like a Pin<Box<dyn Future>> is always Unpin, so is this type.
impl<T> Unpin for ReusableBoxFuture<'_, T> {}
impl<T> Drop for ReusableBoxFuture<'_, T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.boxed.as_ptr()));
}
}
}
impl<T> fmt::Debug for ReusableBoxFuture<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReusableBoxFuture").finish()
}
}
fn reuse_pin_box<T: ?Sized, U, O, F>(boxed: Pin<Box<T>>, new_value: U, callback: F) -> Result<O, U>
where
F: FnOnce(Box<U>) -> O,
{
let layout = Layout::for_value::<T>(&*boxed);
if layout != Layout::new::<U>() {
return Err(new_value);
}
// SAFETY: We don't ever construct a non-pinned reference to the old `T` from now on, and we
// always drop the `T`.
let raw: *mut T = Box::into_raw(unsafe { Pin::into_inner_unchecked(boxed) });
// When dropping the old value panics, we still want to call `callback` — so move the rest of
// the code into a guard type.
let guard = CallOnDrop::new(|| {
let raw: *mut U = raw.cast::<U>();
unsafe { raw.write(new_value) };
// SAFETY:
// - `T` and `U` have the same layout.
// - `raw` comes from a `Box` that uses the same allocator as this one.
// - `raw` points to a valid instance of `U` (we just wrote it in).
let boxed = unsafe { Box::from_raw(raw) };
callback(boxed)
});
// Drop the old value.
unsafe { ptr::drop_in_place(raw) };
// Run the rest of the code.
Ok(guard.call())
}
struct CallOnDrop<O, F: FnOnce() -> O> {
f: ManuallyDrop<F>,
}
impl<O, F: FnOnce() -> O> CallOnDrop<O, F> {
fn new(f: F) -> Self {
let f = ManuallyDrop::new(f);
Self { f }
}
fn call(self) -> O {
let mut this = ManuallyDrop::new(self);
let f = unsafe { ManuallyDrop::take(&mut this.f) };
f()
}
}
impl<O, F: FnOnce() -> O> Drop for CallOnDrop<O, F> {
fn drop(&mut self) {
let f = unsafe { ManuallyDrop::take(&mut self.f) };
f();
}
}
/// The same as `std::future::Pending<T>`; we can't use that type directly because on rustc
/// versions <1.60 it didn't unconditionally implement `Send`.
// FIXME: use `std::future::Pending<T>` once the MSRV is >=1.60
struct Pending<T>(PhantomData<fn() -> T>);
impl<T> Future for Pending<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Pending
}
}
@@ -24,27 +24,6 @@ fn cancel_token() {
});
}
#[test]
fn cancel_token_owned() {
loom::model(|| {
let token = CancellationToken::new();
let token1 = token.clone();
let th1 = thread::spawn(move || {
block_on(async {
token1.cancelled_owned().await;
});
});
let th2 = thread::spawn(move || {
token.cancel();
});
assert_ok!(th1.join());
assert_ok!(th2.join());
});
}
#[test]
fn cancel_with_child() {
loom::model(|| {
@@ -101,7 +80,7 @@ fn drop_token_no_child() {
}
#[test]
fn drop_token_with_children() {
fn drop_token_with_childs() {
loom::model(|| {
let token1 = CancellationToken::new();
let child_token1 = token1.child_token();
-807
View File
@@ -1,807 +0,0 @@
use hashbrown::hash_map::RawEntryMut;
use hashbrown::HashMap;
use std::borrow::Borrow;
use std::collections::hash_map::RandomState;
use std::fmt;
use std::future::Future;
use std::hash::{BuildHasher, Hash, Hasher};
use tokio::runtime::Handle;
use tokio::task::{AbortHandle, Id, JoinError, JoinSet, LocalSet};
/// A collection of tasks spawned on a Tokio runtime, associated with hash map
/// keys.
///
/// This type is very similar to the [`JoinSet`] type in `tokio::task`, with the
/// addition of a set of keys associated with each task. These keys allow
/// [cancelling a task][abort] or [multiple tasks][abort_matching] in the
/// `JoinMap` based on their keys, or [test whether a task corresponding to a
/// given key exists][contains] in the `JoinMap`.
///
/// In addition, when tasks in the `JoinMap` complete, they will return the
/// associated key along with the value returned by the task, if any.
///
/// A `JoinMap` can be used to await the completion of some or all of the tasks
/// in the map. The map is not ordered, and the tasks will be returned in the
/// order they complete.
///
/// All of the tasks must have the same return type `V`.
///
/// When the `JoinMap` is dropped, all tasks in the `JoinMap` are immediately aborted.
///
/// **Note**: This type depends on Tokio's [unstable API][unstable]. See [the
/// documentation on unstable features][unstable] for details on how to enable
/// Tokio's unstable features.
///
/// # Examples
///
/// Spawn multiple tasks and wait for them:
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// #[tokio::main]
/// async fn main() {
/// let mut map = JoinMap::new();
///
/// for i in 0..10 {
/// // Spawn a task on the `JoinMap` with `i` as its key.
/// map.spawn(i, async move { /* ... */ });
/// }
///
/// let mut seen = [false; 10];
///
/// // When a task completes, `join_next` returns the task's key along
/// // with its output.
/// while let Some((key, res)) = map.join_next().await {
/// seen[key] = true;
/// assert!(res.is_ok(), "task {} completed successfully!", key);
/// }
///
/// for i in 0..10 {
/// assert!(seen[i]);
/// }
/// }
/// ```
///
/// Cancel tasks based on their keys:
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// #[tokio::main]
/// async fn main() {
/// let mut map = JoinMap::new();
///
/// map.spawn("hello world", async move { /* ... */ });
/// map.spawn("goodbye world", async move { /* ... */});
///
/// // Look up the "goodbye world" task in the map and abort it.
/// let aborted = map.abort("goodbye world");
///
/// // `JoinMap::abort` returns `true` if a task existed for the
/// // provided key.
/// assert!(aborted);
///
/// while let Some((key, res)) = map.join_next().await {
/// if key == "goodbye world" {
/// // The aborted task should complete with a cancelled `JoinError`.
/// assert!(res.unwrap_err().is_cancelled());
/// } else {
/// // Other tasks should complete normally.
/// assert!(res.is_ok());
/// }
/// }
/// }
/// ```
///
/// [`JoinSet`]: tokio::task::JoinSet
/// [unstable]: tokio#unstable-features
/// [abort]: fn@Self::abort
/// [abort_matching]: fn@Self::abort_matching
/// [contains]: fn@Self::contains_key
#[cfg_attr(docsrs, doc(cfg(all(feature = "rt", tokio_unstable))))]
pub struct JoinMap<K, V, S = RandomState> {
/// A map of the [`AbortHandle`]s of the tasks spawned on this `JoinMap`,
/// indexed by their keys and task IDs.
///
/// The [`Key`] type contains both the task's `K`-typed key provided when
/// spawning tasks, and the task's IDs. The IDs are stored here to resolve
/// hash collisions when looking up tasks based on their pre-computed hash
/// (as stored in the `hashes_by_task` map).
tasks_by_key: HashMap<Key<K>, AbortHandle, S>,
/// A map from task IDs to the hash of the key associated with that task.
///
/// This map is used to perform reverse lookups of tasks in the
/// `tasks_by_key` map based on their task IDs. When a task terminates, the
/// ID is provided to us by the `JoinSet`, so we can look up the hash value
/// of that task's key, and then remove it from the `tasks_by_key` map using
/// the raw hash code, resolving collisions by comparing task IDs.
hashes_by_task: HashMap<Id, u64, S>,
/// The [`JoinSet`] that awaits the completion of tasks spawned on this
/// `JoinMap`.
tasks: JoinSet<V>,
}
/// A [`JoinMap`] key.
///
/// This holds both a `K`-typed key (the actual key as seen by the user), _and_
/// a task ID, so that hash collisions between `K`-typed keys can be resolved
/// using either `K`'s `Eq` impl *or* by checking the task IDs.
///
/// This allows looking up a task using either an actual key (such as when the
/// user queries the map with a key), *or* using a task ID and a hash (such as
/// when removing completed tasks from the map).
#[derive(Debug)]
struct Key<K> {
key: K,
id: Id,
}
impl<K, V> JoinMap<K, V> {
/// Creates a new empty `JoinMap`.
///
/// The `JoinMap` is initially created with a capacity of 0, so it will not
/// allocate until a task is first spawned on it.
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
/// let map: JoinMap<&str, i32> = JoinMap::new();
/// ```
#[inline]
#[must_use]
pub fn new() -> Self {
Self::with_hasher(RandomState::new())
}
/// Creates an empty `JoinMap` with the specified capacity.
///
/// The `JoinMap` will be able to hold at least `capacity` tasks without
/// reallocating.
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
/// let map: JoinMap<&str, i32> = JoinMap::with_capacity(10);
/// ```
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
JoinMap::with_capacity_and_hasher(capacity, Default::default())
}
}
impl<K, V, S: Clone> JoinMap<K, V, S> {
/// Creates an empty `JoinMap` which will use the given hash builder to hash
/// keys.
///
/// The created map has the default initial capacity.
///
/// Warning: `hash_builder` is normally randomly generated, and
/// is designed to allow `JoinMap` to be resistant to attacks that
/// cause many collisions and very poor performance. Setting it
/// manually using this function can expose a DoS attack vector.
///
/// The `hash_builder` passed should implement the [`BuildHasher`] trait for
/// the `JoinMap` to be useful, see its documentation for details.
#[inline]
#[must_use]
pub fn with_hasher(hash_builder: S) -> Self {
Self::with_capacity_and_hasher(0, hash_builder)
}
/// Creates an empty `JoinMap` with the specified capacity, using `hash_builder`
/// to hash the keys.
///
/// The `JoinMap` will be able to hold at least `capacity` elements without
/// reallocating. If `capacity` is 0, the `JoinMap` will not allocate.
///
/// Warning: `hash_builder` is normally randomly generated, and
/// is designed to allow HashMaps to be resistant to attacks that
/// cause many collisions and very poor performance. Setting it
/// manually using this function can expose a DoS attack vector.
///
/// The `hash_builder` passed should implement the [`BuildHasher`] trait for
/// the `JoinMap`to be useful, see its documentation for details.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio_util::task::JoinMap;
/// use std::collections::hash_map::RandomState;
///
/// let s = RandomState::new();
/// let mut map = JoinMap::with_capacity_and_hasher(10, s);
/// map.spawn(1, async move { "hello world!" });
/// # }
/// ```
#[inline]
#[must_use]
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
Self {
tasks_by_key: HashMap::with_capacity_and_hasher(capacity, hash_builder.clone()),
hashes_by_task: HashMap::with_capacity_and_hasher(capacity, hash_builder),
tasks: JoinSet::new(),
}
}
/// Returns the number of tasks currently in the `JoinMap`.
pub fn len(&self) -> usize {
let len = self.tasks_by_key.len();
debug_assert_eq!(len, self.hashes_by_task.len());
len
}
/// Returns whether the `JoinMap` is empty.
pub fn is_empty(&self) -> bool {
let empty = self.tasks_by_key.is_empty();
debug_assert_eq!(empty, self.hashes_by_task.is_empty());
empty
}
/// Returns the number of tasks the map can hold without reallocating.
///
/// This number is a lower bound; the `JoinMap` might be able to hold
/// more, but is guaranteed to be able to hold at least this many.
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// let map: JoinMap<i32, i32> = JoinMap::with_capacity(100);
/// assert!(map.capacity() >= 100);
/// ```
#[inline]
pub fn capacity(&self) -> usize {
let capacity = self.tasks_by_key.capacity();
debug_assert_eq!(capacity, self.hashes_by_task.capacity());
capacity
}
}
impl<K, V, S> JoinMap<K, V, S>
where
K: Hash + Eq,
V: 'static,
S: BuildHasher,
{
/// Spawn the provided task and store it in this `JoinMap` with the provided
/// key.
///
/// If a task previously existed in the `JoinMap` for this key, that task
/// will be cancelled and replaced with the new one. The previous task will
/// be removed from the `JoinMap`; a subsequent call to [`join_next`] will
/// *not* return a cancelled [`JoinError`] for that task.
///
/// # Panics
///
/// This method panics if called outside of a Tokio runtime.
///
/// [`join_next`]: Self::join_next
#[track_caller]
pub fn spawn<F>(&mut self, key: K, task: F)
where
F: Future<Output = V>,
F: Send + 'static,
V: Send,
{
let task = self.tasks.spawn(task);
self.insert(key, task)
}
/// Spawn the provided task on the provided runtime and store it in this
/// `JoinMap` with the provided key.
///
/// If a task previously existed in the `JoinMap` for this key, that task
/// will be cancelled and replaced with the new one. The previous task will
/// be removed from the `JoinMap`; a subsequent call to [`join_next`] will
/// *not* return a cancelled [`JoinError`] for that task.
///
/// [`join_next`]: Self::join_next
#[track_caller]
pub fn spawn_on<F>(&mut self, key: K, task: F, handle: &Handle)
where
F: Future<Output = V>,
F: Send + 'static,
V: Send,
{
let task = self.tasks.spawn_on(task, handle);
self.insert(key, task);
}
/// Spawn the provided task on the current [`LocalSet`] and store it in this
/// `JoinMap` with the provided key.
///
/// If a task previously existed in the `JoinMap` for this key, that task
/// will be cancelled and replaced with the new one. The previous task will
/// be removed from the `JoinMap`; a subsequent call to [`join_next`] will
/// *not* return a cancelled [`JoinError`] for that task.
///
/// # Panics
///
/// This method panics if it is called outside of a `LocalSet`.
///
/// [`LocalSet`]: tokio::task::LocalSet
/// [`join_next`]: Self::join_next
#[track_caller]
pub fn spawn_local<F>(&mut self, key: K, task: F)
where
F: Future<Output = V>,
F: 'static,
{
let task = self.tasks.spawn_local(task);
self.insert(key, task);
}
/// Spawn the provided task on the provided [`LocalSet`] and store it in
/// this `JoinMap` with the provided key.
///
/// If a task previously existed in the `JoinMap` for this key, that task
/// will be cancelled and replaced with the new one. The previous task will
/// be removed from the `JoinMap`; a subsequent call to [`join_next`] will
/// *not* return a cancelled [`JoinError`] for that task.
///
/// [`LocalSet`]: tokio::task::LocalSet
/// [`join_next`]: Self::join_next
#[track_caller]
pub fn spawn_local_on<F>(&mut self, key: K, task: F, local_set: &LocalSet)
where
F: Future<Output = V>,
F: 'static,
{
let task = self.tasks.spawn_local_on(task, local_set);
self.insert(key, task)
}
fn insert(&mut self, key: K, abort: AbortHandle) {
let hash = self.hash(&key);
let id = abort.id();
let map_key = Key { id, key };
// Insert the new key into the map of tasks by keys.
let entry = self
.tasks_by_key
.raw_entry_mut()
.from_hash(hash, |k| k.key == map_key.key);
match entry {
RawEntryMut::Occupied(mut occ) => {
// There was a previous task spawned with the same key! Cancel
// that task, and remove its ID from the map of hashes by task IDs.
let Key { id: prev_id, .. } = occ.insert_key(map_key);
occ.insert(abort).abort();
let _prev_hash = self.hashes_by_task.remove(&prev_id);
debug_assert_eq!(Some(hash), _prev_hash);
}
RawEntryMut::Vacant(vac) => {
vac.insert(map_key, abort);
}
};
// Associate the key's hash with this task's ID, for looking up tasks by ID.
let _prev = self.hashes_by_task.insert(id, hash);
debug_assert!(_prev.is_none(), "no prior task should have had the same ID");
}
/// Waits until one of the tasks in the map completes and returns its
/// output, along with the key corresponding to that task.
///
/// Returns `None` if the map is empty.
///
/// # Cancel Safety
///
/// This method is cancel safe. If `join_next` is used as the event in a [`tokio::select!`]
/// statement and some other branch completes first, it is guaranteed that no tasks were
/// removed from this `JoinMap`.
///
/// # 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 ask, 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 the `JoinMap` is empty.
///
/// [`tokio::select!`]: tokio::select
pub async fn join_next(&mut self) -> Option<(K, Result<V, JoinError>)> {
let (res, id) = match self.tasks.join_next_with_id().await {
Some(Ok((id, output))) => (Ok(output), id),
Some(Err(e)) => {
let id = e.id();
(Err(e), id)
}
None => return None,
};
let key = self.remove_by_id(id)?;
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
/// a loop until it returns `None`.
///
/// This method ignores any panics in the tasks shutting down. When this call returns, the
/// `JoinMap` will be empty.
///
/// [`abort_all`]: fn@Self::abort_all
/// [`join_next`]: fn@Self::join_next
pub async fn shutdown(&mut self) {
self.abort_all();
while self.join_next().await.is_some() {}
}
/// Abort the task corresponding to the provided `key`.
///
/// If this `JoinMap` contains a task corresponding to `key`, this method
/// will abort that task and return `true`. Otherwise, if no task exists for
/// `key`, this method returns `false`.
///
/// # Examples
///
/// Aborting a task by key:
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut map = JoinMap::new();
///
/// map.spawn("hello world", async move { /* ... */ });
/// map.spawn("goodbye world", async move { /* ... */});
///
/// // Look up the "goodbye world" task in the map and abort it.
/// map.abort("goodbye world");
///
/// while let Some((key, res)) = map.join_next().await {
/// if key == "goodbye world" {
/// // The aborted task should complete with a cancelled `JoinError`.
/// assert!(res.unwrap_err().is_cancelled());
/// } else {
/// // Other tasks should complete normally.
/// assert!(res.is_ok());
/// }
/// }
/// # }
/// ```
///
/// `abort` returns `true` if a task was aborted:
/// ```
/// use tokio_util::task::JoinMap;
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut map = JoinMap::new();
///
/// map.spawn("hello world", async move { /* ... */ });
/// map.spawn("goodbye world", async move { /* ... */});
///
/// // A task for the key "goodbye world" should exist in the map:
/// assert!(map.abort("goodbye world"));
///
/// // Aborting a key that does not exist will return `false`:
/// assert!(!map.abort("goodbye universe"));
/// # }
/// ```
pub fn abort<Q: ?Sized>(&mut self, key: &Q) -> bool
where
Q: Hash + Eq,
K: Borrow<Q>,
{
match self.get_by_key(key) {
Some((_, handle)) => {
handle.abort();
true
}
None => false,
}
}
/// Aborts all tasks with keys matching `predicate`.
///
/// `predicate` is a function called with a reference to each key in the
/// map. If it returns `true` for a given key, the corresponding task will
/// be cancelled.
///
/// # Examples
/// ```
/// use tokio_util::task::JoinMap;
///
/// # // use the current thread rt so that spawned tasks don't
/// # // complete in the background before they can be aborted.
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let mut map = JoinMap::new();
///
/// map.spawn("hello world", async move {
/// // ...
/// # tokio::task::yield_now().await; // don't complete immediately, get aborted!
/// });
/// map.spawn("goodbye world", async move {
/// // ...
/// # tokio::task::yield_now().await; // don't complete immediately, get aborted!
/// });
/// map.spawn("hello san francisco", async move {
/// // ...
/// # tokio::task::yield_now().await; // don't complete immediately, get aborted!
/// });
/// map.spawn("goodbye universe", async move {
/// // ...
/// # tokio::task::yield_now().await; // don't complete immediately, get aborted!
/// });
///
/// // Abort all tasks whose keys begin with "goodbye"
/// map.abort_matching(|key| key.starts_with("goodbye"));
///
/// let mut seen = 0;
/// while let Some((key, res)) = map.join_next().await {
/// seen += 1;
/// if key.starts_with("goodbye") {
/// // The aborted task should complete with a cancelled `JoinError`.
/// assert!(res.unwrap_err().is_cancelled());
/// } else {
/// // Other tasks should complete normally.
/// assert!(key.starts_with("hello"));
/// assert!(res.is_ok());
/// }
/// }
///
/// // All spawned tasks should have completed.
/// assert_eq!(seen, 4);
/// # }
/// ```
pub fn abort_matching(&mut self, mut predicate: impl FnMut(&K) -> bool) {
// Note: this method iterates over the tasks and keys *without* removing
// any entries, so that the keys from aborted tasks can still be
// returned when calling `join_next` in the future.
for (Key { ref key, .. }, task) in &self.tasks_by_key {
if predicate(key) {
task.abort();
}
}
}
/// Returns `true` if this `JoinMap` contains a task for the provided key.
///
/// If the task has completed, but its output hasn't yet been consumed by a
/// call to [`join_next`], this method will still return `true`.
///
/// [`join_next`]: fn@Self::join_next
pub fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool
where
Q: Hash + Eq,
K: Borrow<Q>,
{
self.get_by_key(key).is_some()
}
/// Returns `true` if this `JoinMap` contains a task with the provided
/// [task ID].
///
/// If the task has completed, but its output hasn't yet been consumed by a
/// call to [`join_next`], this method will still return `true`.
///
/// [`join_next`]: fn@Self::join_next
/// [task ID]: tokio::task::Id
pub fn contains_task(&self, task: &Id) -> bool {
self.get_by_id(task).is_some()
}
/// Reserves capacity for at least `additional` more tasks to be spawned
/// on this `JoinMap` without reallocating for the map of task keys. The
/// collection may reserve more space to avoid frequent reallocations.
///
/// Note that spawning a task will still cause an allocation for the task
/// itself.
///
/// # Panics
///
/// Panics if the new allocation size overflows [`usize`].
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// let mut map: JoinMap<&str, i32> = JoinMap::new();
/// map.reserve(10);
/// ```
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.tasks_by_key.reserve(additional);
self.hashes_by_task.reserve(additional);
}
/// Shrinks the capacity of the `JoinMap` as much as possible. It will drop
/// down as much as possible while maintaining the internal rules
/// and possibly leaving some space in accordance with the resize policy.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio_util::task::JoinMap;
///
/// let mut map: JoinMap<i32, i32> = JoinMap::with_capacity(100);
/// map.spawn(1, async move { 2 });
/// map.spawn(3, async move { 4 });
/// assert!(map.capacity() >= 100);
/// map.shrink_to_fit();
/// assert!(map.capacity() >= 2);
/// # }
/// ```
#[inline]
pub fn shrink_to_fit(&mut self) {
self.hashes_by_task.shrink_to_fit();
self.tasks_by_key.shrink_to_fit();
}
/// Shrinks the capacity of the map with a lower limit. It will drop
/// down no lower than the supplied limit while maintaining the internal rules
/// and possibly leaving some space in accordance with the resize policy.
///
/// If the current capacity is less than the lower limit, this is a no-op.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio_util::task::JoinMap;
///
/// let mut map: JoinMap<i32, i32> = JoinMap::with_capacity(100);
/// map.spawn(1, async move { 2 });
/// map.spawn(3, async move { 4 });
/// assert!(map.capacity() >= 100);
/// map.shrink_to(10);
/// assert!(map.capacity() >= 10);
/// map.shrink_to(0);
/// assert!(map.capacity() >= 2);
/// # }
/// ```
#[inline]
pub fn shrink_to(&mut self, min_capacity: usize) {
self.hashes_by_task.shrink_to(min_capacity);
self.tasks_by_key.shrink_to(min_capacity)
}
/// Look up a task in the map by its key, returning the key and abort handle.
fn get_by_key<'map, Q: ?Sized>(&'map self, key: &Q) -> Option<(&'map Key<K>, &'map AbortHandle)>
where
Q: Hash + Eq,
K: Borrow<Q>,
{
let hash = self.hash(key);
self.tasks_by_key
.raw_entry()
.from_hash(hash, |k| k.key.borrow() == key)
}
/// Look up a task in the map by its task ID, returning the key and abort handle.
fn get_by_id<'map>(&'map self, id: &Id) -> Option<(&'map Key<K>, &'map AbortHandle)> {
let hash = self.hashes_by_task.get(id)?;
self.tasks_by_key
.raw_entry()
.from_hash(*hash, |k| &k.id == id)
}
/// Remove a task from the map by ID, returning the key for that task.
fn remove_by_id(&mut self, id: Id) -> Option<K> {
// Get the hash for the given ID.
let hash = self.hashes_by_task.remove(&id)?;
// Remove the entry for that hash.
let entry = self
.tasks_by_key
.raw_entry_mut()
.from_hash(hash, |k| k.id == id);
let (Key { id: _key_id, key }, handle) = match entry {
RawEntryMut::Occupied(entry) => entry.remove_entry(),
_ => return None,
};
debug_assert_eq!(_key_id, id);
debug_assert_eq!(id, handle.id());
self.hashes_by_task.remove(&id);
Some(key)
}
/// Returns the hash for a given key.
#[inline]
fn hash<Q: ?Sized>(&self, key: &Q) -> u64
where
Q: Hash,
{
let mut hasher = self.tasks_by_key.hasher().build_hasher();
key.hash(&mut hasher);
hasher.finish()
}
}
impl<K, V, S> JoinMap<K, V, S>
where
V: 'static,
{
/// Aborts all tasks on this `JoinMap`.
///
/// This does not remove the tasks from the `JoinMap`. To wait for the tasks to complete
/// cancellation, you should call `join_next` in a loop until the `JoinMap` is empty.
pub fn abort_all(&mut self) {
self.tasks.abort_all()
}
/// Removes all tasks from this `JoinMap` without aborting them.
///
/// The tasks removed by this call will continue to run in the background even if the `JoinMap`
/// is dropped. They may still be aborted by key.
pub fn detach_all(&mut self) {
self.tasks.detach_all();
self.tasks_by_key.clear();
self.hashes_by_task.clear();
}
}
// Hand-written `fmt::Debug` implementation in order to avoid requiring `V:
// Debug`, since no value is ever actually stored in the map.
impl<K: fmt::Debug, V, S> fmt::Debug for JoinMap<K, V, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// format the task keys and abort handles a little nicer by just
// printing the key and task ID pairs, without format the `Key` struct
// itself or the `AbortHandle`, which would just format the task's ID
// again.
struct KeySet<'a, K: fmt::Debug, S>(&'a HashMap<Key<K>, AbortHandle, S>);
impl<K: fmt::Debug, S> fmt::Debug for KeySet<'_, K, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.0.keys().map(|Key { key, id }| (key, id)))
.finish()
}
}
f.debug_struct("JoinMap")
// The `tasks_by_key` map is the only one that contains information
// that's really worth formatting for the user, since it contains
// the tasks' keys and IDs. The other fields are basically
// implementation details.
.field("tasks", &KeySet(&self.tasks_by_key))
.finish()
}
}
impl<K, V> Default for JoinMap<K, V> {
fn default() -> Self {
Self::new()
}
}
// === impl Key ===
impl<K: Hash> Hash for Key<K> {
// Don't include the task ID in the hash.
#[inline]
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.key.hash(hasher);
}
}
// Because we override `Hash` for this type, we must also override the
// `PartialEq` impl, so that all instances with the same hash are equal.
impl<K: PartialEq> PartialEq for Key<K> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl<K: Eq> Eq for Key<K> {}
-8
View File
@@ -1,12 +1,4 @@
//! Extra utilities for spawning tasks
#[cfg(tokio_unstable)]
mod join_map;
#[cfg(not(target_os = "wasi"))]
mod spawn_pinned;
#[cfg(not(target_os = "wasi"))]
pub use spawn_pinned::LocalPoolHandle;
#[cfg(tokio_unstable)]
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt"))))]
pub use join_map::JoinMap;
+5 -134
View File
@@ -9,44 +9,7 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot;
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::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::task::LocalSet`]: tokio::task::LocalSet
/// [`tokio::task::spawn_local`]: tokio::task::spawn_local
///
/// # Examples
///
/// ```
/// use std::rc::Rc;
/// use tokio::{self, task };
/// use tokio_util::task::LocalPoolHandle;
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() {
/// let pool = LocalPoolHandle::new(5);
///
/// let output = pool.spawn_pinned(|| {
/// // `data` is !Send + !Sync
/// let data = Rc::new("local data");
/// let data_clone = data.clone();
///
/// async move {
/// task::spawn_local(async move {
/// println!("{}", data_clone);
/// });
///
/// data.to_string()
/// }
/// }).await.unwrap();
/// println!("output: {}", output);
/// }
/// ```
///
/// A handle to a local pool, used for spawning `!Send` tasks.
#[derive(Clone)]
pub struct LocalPoolHandle {
pool: Arc<LocalPool>,
@@ -57,9 +20,7 @@ impl LocalPoolHandle {
/// pool via [`LocalPoolHandle::spawn_pinned`].
///
/// # Panics
///
/// Panics if the pool size is less than one.
#[track_caller]
pub fn new(pool_size: usize) -> LocalPoolHandle {
assert!(pool_size > 0);
@@ -72,22 +33,6 @@ impl LocalPoolHandle {
LocalPoolHandle { pool }
}
/// Returns the number of threads of the Pool.
#[inline]
pub fn num_threads(&self) -> usize {
self.pool.workers.len()
}
/// Returns the number of tasks scheduled on each worker. The indices of the
/// worker threads correspond to the indices of the returned `Vec`.
pub fn get_task_loads_for_each_worker(&self) -> Vec<usize> {
self.pool
.workers
.iter()
.map(|worker| worker.task_count.load(Ordering::SeqCst))
.collect::<Vec<_>>()
}
/// Spawn a task onto a worker thread and pin it there so it can't be moved
/// off of the thread. Note that the future is not [`Send`], but the
/// [`FnOnce`] which creates it is.
@@ -124,61 +69,7 @@ impl LocalPoolHandle {
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
self.pool
.spawn_pinned(create_task, WorkerChoice::LeastBurdened)
}
/// Differs from `spawn_pinned` only in that you can choose a specific worker thread
/// of the pool, whereas `spawn_pinned` chooses the worker with the smallest
/// number of tasks scheduled.
///
/// A worker thread is chosen by index. Indices are 0 based and the largest index
/// is given by `num_threads() - 1`
///
/// # Panics
///
/// This method panics if the index is out of bounds.
///
/// # Examples
///
/// This method can be used to spawn a task on all worker threads of the pool:
///
/// ```
/// use tokio_util::task::LocalPoolHandle;
///
/// #[tokio::main]
/// async fn main() {
/// const NUM_WORKERS: usize = 3;
/// let pool = LocalPoolHandle::new(NUM_WORKERS);
/// let handles = (0..pool.num_threads())
/// .map(|worker_idx| {
/// pool.spawn_pinned_by_idx(
/// || {
/// async {
/// "test"
/// }
/// },
/// worker_idx,
/// )
/// })
/// .collect::<Vec<_>>();
///
/// for handle in handles {
/// handle.await.unwrap();
/// }
/// }
/// ```
///
#[track_caller]
pub fn spawn_pinned_by_idx<F, Fut>(&self, create_task: F, idx: usize) -> JoinHandle<Fut::Output>
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
self.pool
.spawn_pinned(create_task, WorkerChoice::ByIdx(idx))
self.pool.spawn_pinned(create_task)
}
}
@@ -188,23 +79,13 @@ impl Debug for LocalPoolHandle {
}
}
enum WorkerChoice {
LeastBurdened,
ByIdx(usize),
}
struct LocalPool {
workers: Vec<LocalWorkerHandle>,
}
impl LocalPool {
/// Spawn a `?Send` future onto a worker
#[track_caller]
fn spawn_pinned<F, Fut>(
&self,
create_task: F,
worker_choice: WorkerChoice,
) -> JoinHandle<Fut::Output>
fn spawn_pinned<F, Fut>(&self, create_task: F) -> JoinHandle<Fut::Output>
where
F: FnOnce() -> Fut,
F: Send + 'static,
@@ -212,10 +93,8 @@ impl LocalPool {
Fut::Output: Send + 'static,
{
let (sender, receiver) = oneshot::channel();
let (worker, job_guard) = match worker_choice {
WorkerChoice::LeastBurdened => self.find_and_incr_least_burdened_worker(),
WorkerChoice::ByIdx(idx) => self.find_worker_by_idx(idx),
};
let (worker, job_guard) = self.find_and_incr_least_burdened_worker();
let worker_spawner = worker.spawner.clone();
// Spawn a future onto the worker's runtime so we can immediately return
@@ -327,14 +206,6 @@ impl LocalPool {
}
}
}
#[track_caller]
fn find_worker_by_idx(&self, idx: usize) -> (&LocalWorkerHandle, JobCountGuard) {
let worker = &self.workers[idx];
worker.task_count.fetch_add(1, Ordering::SeqCst);
(worker, JobCountGuard(Arc::clone(&worker.task_count)))
}
}
/// Automatically decrements a worker's job count when a job finishes (when
+2 -52
View File
@@ -190,7 +190,7 @@ impl<T> SlabStorage<T> {
let key_contained = self.key_map.contains_key(&key.into());
if key_contained {
// It's possible that a `compact` call creates capacity in `self.inner` in
// It's possible that a `compact` call creates capacitiy in `self.inner` in
// such a way that a `self.inner.insert` call creates a `key` which was
// previously given out during an `insert` call prior to the `compact` call.
// If `key` is contained in `self.key_map`, we have encountered this exact situation,
@@ -275,7 +275,7 @@ impl<T> SlabStorage<T> {
fn remap_key(&self, key: &Key) -> Option<KeyInternal> {
let key_map = &self.key_map;
if self.compact_called {
key_map.get(key).copied()
key_map.get(&*key).copied()
} else {
Some((*key).into())
}
@@ -531,7 +531,6 @@ impl<T> DelayQueue<T> {
/// [`reset`]: method@Self::reset
/// [`Key`]: struct@Key
/// [type]: #
#[track_caller]
pub fn insert_at(&mut self, value: T, when: Instant) -> Key {
assert!(self.slab.len() < MAX_ENTRIES, "max entries exceeded");
@@ -650,12 +649,10 @@ impl<T> DelayQueue<T> {
/// [`reset`]: method@Self::reset
/// [`Key`]: struct@Key
/// [type]: #
#[track_caller]
pub fn insert(&mut self, value: T, timeout: Duration) -> Key {
self.insert_at(value, Instant::now() + timeout)
}
#[track_caller]
fn insert_idx(&mut self, when: u64, key: Key) {
use self::wheel::{InsertError, Stack};
@@ -677,7 +674,6 @@ impl<T> DelayQueue<T> {
/// # Panics
///
/// Panics if the key is not contained in the expired queue or the wheel.
#[track_caller]
fn remove_key(&mut self, key: &Key) {
use crate::time::wheel::Stack;
@@ -717,7 +713,6 @@ impl<T> DelayQueue<T> {
/// assert_eq!(*item.get_ref(), "foo");
/// # }
/// ```
#[track_caller]
pub fn remove(&mut self, key: &Key) -> Expired<T> {
let prev_deadline = self.next_deadline();
@@ -740,43 +735,6 @@ impl<T> DelayQueue<T> {
}
}
/// Attempts to remove the item associated with `key` from the queue.
///
/// Removes the item associated with `key`, and returns it along with the
/// `Instant` at which it would have expired, if it exists.
///
/// Returns `None` if `key` is not in the queue.
///
/// # Examples
///
/// Basic usage
///
/// ```rust
/// use tokio_util::time::DelayQueue;
/// use std::time::Duration;
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::new();
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
///
/// // The item is in the queue, `try_remove` returns `Some(Expired("foo"))`.
/// let item = delay_queue.try_remove(&key);
/// assert_eq!(item.unwrap().into_inner(), "foo");
///
/// // The item is not in the queue anymore, `try_remove` returns `None`.
/// let item = delay_queue.try_remove(&key);
/// assert!(item.is_none());
/// # }
/// ```
pub fn try_remove(&mut self, key: &Key) -> Option<Expired<T>> {
if self.slab.contains(key) {
Some(self.remove(key))
} else {
None
}
}
/// Sets the delay of the item associated with `key` to expire at `when`.
///
/// This function is identical to `reset` but takes an `Instant` instead of
@@ -811,7 +769,6 @@ impl<T> DelayQueue<T> {
/// // "foo" is now scheduled to be returned in 10 seconds
/// # }
/// ```
#[track_caller]
pub fn reset_at(&mut self, key: &Key, when: Instant) {
self.remove_key(key);
@@ -916,7 +873,6 @@ impl<T> DelayQueue<T> {
/// // "foo"is now scheduled to be returned in 10 seconds
/// # }
/// ```
#[track_caller]
pub fn reset(&mut self, key: &Key, timeout: Duration) {
self.reset_at(key, Instant::now() + timeout);
}
@@ -1022,12 +978,7 @@ impl<T> DelayQueue<T> {
/// assert!(delay_queue.capacity() >= 11);
/// # }
/// ```
#[track_caller]
pub fn reserve(&mut self, additional: usize) {
assert!(
self.slab.capacity() + additional <= MAX_ENTRIES,
"max queue capacity exceeded"
);
self.slab.reserve(additional);
}
@@ -1166,7 +1117,6 @@ impl<T> wheel::Stack for Stack<T> {
}
}
#[track_caller]
fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store) {
let key = *item;
assert!(store.contains(item));
-1
View File
@@ -118,7 +118,6 @@ where
}
/// Remove `item` from the timing wheel.
#[track_caller]
pub(crate) fn remove(&mut self, item: &T::Borrowed, store: &mut T::Store) {
let when = T::when(item, store);
+5 -9
View File
@@ -81,21 +81,17 @@ where
}
// We're out of data. Try and fetch more data to decode
let addr = {
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
// transparent wrapper around `[MaybeUninit<u8>]`.
let buf = unsafe { &mut *(pin.rd.chunk_mut() as *mut _ as *mut [MaybeUninit<u8>]) };
let addr = unsafe {
// Convert `&mut [MaybeUnit<u8>]` to `&mut [u8]` because we will be
// writing to it via `poll_recv_from` and therefore initializing the memory.
let buf = &mut *(pin.rd.chunk_mut() as *mut _ as *mut [MaybeUninit<u8>]);
let mut read = ReadBuf::uninit(buf);
let ptr = read.filled().as_ptr();
let res = ready!(pin.socket.borrow().poll_recv_from(cx, &mut read));
assert_eq!(ptr, read.filled().as_ptr());
let addr = res?;
// Safety: This is guaranteed to be the number of initialized (and read) bytes due
// to the invariants provided by `ReadBuf::filled`.
unsafe { pin.rd.advance_mut(read.filled().len()) };
pin.rd.advance_mut(read.filled().len());
addr
};
+1 -2
View File
@@ -1,5 +1,4 @@
#![cfg(feature = "rt")]
#![cfg(not(target_os = "wasi"))] // Wasi doesn't support threads
#![warn(rust_2018_idioms)]
use tokio::runtime::Builder;
@@ -21,5 +20,5 @@ fn tokio_context_with_another_runtime() {
// Without the `HandleExt.wrap()` there would be a panic because there is
// no timer running, since it would be referencing runtime r1.
rt1.block_on(rt2.wrap(async move { sleep(Duration::from_millis(2)).await }));
let _ = rt1.block_on(rt2.wrap(async move { sleep(Duration::from_millis(2)).await }));
}
+9 -10
View File
@@ -109,12 +109,12 @@ fn write_hits_backpressure() {
const ITER: usize = 2 * 1024;
let mut mock = mock! {
// Block the `ITER*2`th write
// Block the `ITER`th write
Err(io::Error::new(io::ErrorKind::WouldBlock, "not ready")),
Ok(b"".to_vec()),
};
for i in 0..=ITER * 2 {
for i in 0..=ITER {
let mut b = BytesMut::with_capacity(4);
b.put_u32(i as u32);
@@ -130,18 +130,17 @@ fn write_hits_backpressure() {
_ => unreachable!(),
}
// Push a new chunk
// Push a new new chunk
mock.calls.push_back(Ok(b[..].to_vec()));
}
// 1 'wouldblock', 8 * 2KB buffers, 1 b-byte buffer
assert_eq!(mock.calls.len(), 10);
// 1 'wouldblock', 4 * 2KB buffers, 1 b-byte buffer
assert_eq!(mock.calls.len(), 6);
let mut task = task::spawn(());
let mut framed = FramedWrite::new(mock, U32Encoder);
framed.set_backpressure_boundary(ITER * 8);
task.enter(|cx, _| {
// Send 16KB. This fills up FramedWrite buffer
for i in 0..ITER * 2 {
// Send 8KB. This fills up FramedWrite2 buffer
for i in 0..ITER {
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
assert!(pin!(framed).start_send(i as u32).is_ok());
}
@@ -151,11 +150,11 @@ fn write_hits_backpressure() {
assert!(pin!(framed).poll_ready(cx).is_pending());
// We poll again, forcing another flush, which this time succeeds
// The whole 16KB buffer is flushed
// The whole 8KB buffer is flushed
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
// Send more data. This matches the final message expected by the mock
assert!(pin!(framed).start_send((ITER * 2) as u32).is_ok());
assert!(pin!(framed).start_send(ITER as u32).is_ok());
// Flush the rest of the buffer
assert!(assert_ready!(pin!(framed).poll_flush(cx)).is_ok());
-194
View File
@@ -1,194 +0,0 @@
use futures::future::poll_fn;
use std::{
io::IoSlice,
pin::Pin,
task::{Context, Poll},
};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
use tokio_util::io::{InspectReader, InspectWriter};
/// An AsyncRead implementation that works byte-by-byte, to catch out callers
/// who don't allow for `buf` being part-filled before the call
struct SmallReader {
contents: Vec<u8>,
}
impl Unpin for SmallReader {}
impl AsyncRead for SmallReader {
fn poll_read(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
if let Some(byte) = self.contents.pop() {
buf.put_slice(&[byte])
}
Poll::Ready(Ok(()))
}
}
#[tokio::test]
async fn read_tee() {
let contents = b"This could be really long, you know".to_vec();
let reader = SmallReader {
contents: contents.clone(),
};
let mut altout: Vec<u8> = Vec::new();
let mut teeout = Vec::new();
{
let mut tee = InspectReader::new(reader, |bytes| altout.extend(bytes));
tee.read_to_end(&mut teeout).await.unwrap();
}
assert_eq!(teeout, altout);
assert_eq!(altout.len(), contents.len());
}
/// An AsyncWrite implementation that works byte-by-byte for poll_write, and
/// that reads the whole of the first buffer plus one byte from the second in
/// poll_write_vectored.
///
/// This is designed to catch bugs in handling partially written buffers
#[derive(Debug)]
struct SmallWriter {
contents: Vec<u8>,
}
impl Unpin for SmallWriter {}
impl AsyncWrite for SmallWriter {
fn poll_write(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
// Just write one byte at a time
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
self.contents.push(buf[0]);
Poll::Ready(Ok(1))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
Poll::Ready(Ok(()))
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<Result<usize, std::io::Error>> {
// Write all of the first buffer, then one byte from the second buffer
// This should trip up anything that doesn't correctly handle multiple
// buffers.
if bufs.is_empty() {
return Poll::Ready(Ok(0));
}
let mut written_len = bufs[0].len();
self.contents.extend_from_slice(&bufs[0]);
if bufs.len() > 1 {
let buf = bufs[1];
if !buf.is_empty() {
written_len += 1;
self.contents.push(buf[0]);
}
}
Poll::Ready(Ok(written_len))
}
fn is_write_vectored(&self) -> bool {
true
}
}
#[tokio::test]
async fn write_tee() {
let mut altout: Vec<u8> = Vec::new();
let mut writeout = SmallWriter {
contents: Vec::new(),
};
{
let mut tee = InspectWriter::new(&mut writeout, |bytes| altout.extend(bytes));
tee.write_all(b"A testing string, very testing")
.await
.unwrap();
}
assert_eq!(altout, writeout.contents);
}
// This is inefficient, but works well enough for test use.
// If you want something similar for real code, you'll want to avoid all the
// fun of manipulating `bufs` - ideally, by the time you read this,
// IoSlice::advance_slices will be stable, and you can use that.
async fn write_all_vectored<W: AsyncWrite + Unpin>(
mut writer: W,
mut bufs: Vec<Vec<u8>>,
) -> Result<usize, std::io::Error> {
let mut res = 0;
while !bufs.is_empty() {
let mut written = poll_fn(|cx| {
let bufs: Vec<IoSlice> = bufs.iter().map(|v| IoSlice::new(v)).collect();
Pin::new(&mut writer).poll_write_vectored(cx, &bufs)
})
.await?;
res += written;
while written > 0 {
let buf_len = bufs[0].len();
if buf_len <= written {
bufs.remove(0);
written -= buf_len;
} else {
let buf = &mut bufs[0];
let drain_len = written.min(buf.len());
buf.drain(..drain_len);
written -= drain_len;
}
}
}
Ok(res)
}
#[tokio::test]
async fn write_tee_vectored() {
let mut altout: Vec<u8> = Vec::new();
let mut writeout = SmallWriter {
contents: Vec::new(),
};
let original = b"A very long string split up";
let bufs: Vec<Vec<u8>> = original
.split(|b| b.is_ascii_whitespace())
.map(Vec::from)
.collect();
assert!(bufs.len() > 1);
let expected: Vec<u8> = {
let mut out = Vec::new();
for item in &bufs {
out.extend_from_slice(item)
}
out
};
{
let mut bufcount = 0;
let tee = InspectWriter::new(&mut writeout, |bytes| {
bufcount += 1;
altout.extend(bytes)
});
assert!(tee.is_write_vectored());
write_all_vectored(tee, bufs.clone()).await.unwrap();
assert!(bufcount >= bufs.len());
}
assert_eq!(altout, writeout.contents);
assert_eq!(writeout.contents, expected);
}
-72
View File
@@ -1,72 +0,0 @@
#![warn(rust_2018_idioms)]
use bytes::Bytes;
use futures_util::SinkExt;
use std::io::{self, Error, ErrorKind};
use tokio::io::AsyncWriteExt;
use tokio_util::codec::{Encoder, FramedWrite};
use tokio_util::io::{CopyToBytes, SinkWriter};
use tokio_util::sync::PollSender;
#[tokio::test]
async fn test_copied_sink_writer() -> Result<(), Error> {
// Construct a channel pair to send data across and wrap a pollable sink.
// Note that the sink must mimic a writable object, e.g. have `std::io::Error`
// as its error type.
// As `PollSender` requires an owned copy of the buffer, we wrap it additionally
// with a `CopyToBytes` helper.
let (tx, mut rx) = tokio::sync::mpsc::channel::<Bytes>(1);
let mut writer = SinkWriter::new(CopyToBytes::new(
PollSender::new(tx).sink_map_err(|_| io::Error::from(ErrorKind::BrokenPipe)),
));
// Write data to our interface...
let data: [u8; 4] = [1, 2, 3, 4];
let _ = writer.write(&data).await;
// ... and receive it.
assert_eq!(data.to_vec(), rx.recv().await.unwrap().to_vec());
Ok(())
}
/// A trivial encoder.
struct SliceEncoder;
impl SliceEncoder {
fn new() -> Self {
Self {}
}
}
impl<'a> Encoder<&'a [u8]> for SliceEncoder {
type Error = Error;
fn encode(&mut self, item: &'a [u8], dst: &mut bytes::BytesMut) -> Result<(), Self::Error> {
// This is where we'd write packet headers, lengths, etc. in a real encoder.
// For simplicity and demonstration purposes, we just pack a copy of
// the slice at the end of a buffer.
dst.extend_from_slice(item);
Ok(())
}
}
#[tokio::test]
async fn test_direct_sink_writer() -> Result<(), Error> {
// We define a framed writer which accepts byte slices
// and 'reverse' this construction immediately.
let framed_byte_lc = FramedWrite::new(Vec::new(), SliceEncoder::new());
let mut writer = SinkWriter::new(framed_byte_lc);
// Write multiple slices to the sink...
let _ = writer.write(&[1, 2, 3]).await;
let _ = writer.write(&[4, 5, 6]).await;
// ... and compare it with the buffer.
assert_eq!(
writer.into_inner().write_buffer().to_vec().as_slice(),
&[1, 2, 3, 4, 5, 6]
);
Ok(())
}
+2 -21
View File
@@ -1,9 +1,8 @@
#![cfg(feature = "io-util")]
#![cfg(not(target_os = "wasi"))] // Wasi doesn't support threads
use std::error::Error;
use std::io::{Cursor, Read, Result as IoResult, Write};
use tokio::io::{AsyncRead, AsyncReadExt};
use std::io::{Cursor, Read, Result as IoResult};
use tokio::io::AsyncRead;
use tokio_util::io::SyncIoBridge;
async fn test_reader_len(
@@ -42,21 +41,3 @@ async fn test_async_write_to_sync() -> Result<(), Box<dyn Error>> {
assert_eq!(dest.as_slice(), src);
Ok(())
}
#[tokio::test]
async fn test_shutdown() -> Result<(), Box<dyn Error>> {
let (s1, mut s2) = tokio::io::duplex(1024);
let (_rh, wh) = tokio::io::split(s1);
tokio::task::spawn_blocking(move || -> std::io::Result<_> {
let mut wh = SyncIoBridge::new(wh);
wh.write_all(b"hello")?;
wh.shutdown()?;
assert!(wh.write_all(b" world").is_err());
Ok(())
})
.await??;
let mut buf = vec![];
s2.read_to_end(&mut buf).await?;
assert_eq!(buf, b"hello");
Ok(())
}
-226
View File
@@ -1,226 +0,0 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support panic recovery
use parking_lot::{const_mutex, Mutex};
use std::error::Error;
use std::panic;
use std::sync::Arc;
use tokio::runtime::Runtime;
use tokio::sync::mpsc::channel;
use tokio::time::{Duration, Instant};
use tokio_test::task;
use tokio_util::io::SyncIoBridge;
use tokio_util::sync::PollSender;
use tokio_util::task::LocalPoolHandle;
use tokio_util::time::DelayQueue;
// Taken from tokio-util::time::wheel, if that changes then
const MAX_DURATION_MS: u64 = (1 << (36)) - 1;
fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<String> {
static PANIC_MUTEX: Mutex<()> = const_mutex(());
{
let _guard = PANIC_MUTEX.lock();
let panic_file: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let prev_hook = panic::take_hook();
{
let panic_file = panic_file.clone();
panic::set_hook(Box::new(move |panic_info| {
let panic_location = panic_info.location().unwrap();
panic_file
.lock()
.clone_from(&Some(panic_location.file().to_string()));
}));
}
let result = panic::catch_unwind(func);
// Return to the previously set panic hook (maybe default) so that we get nice error
// messages in the tests.
panic::set_hook(prev_hook);
if result.is_err() {
panic_file.lock().clone()
} else {
None
}
}
}
#[test]
fn sync_bridge_new_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let _ = SyncIoBridge::new(tokio::io::empty());
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn poll_sender_send_item_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let (send, _) = channel::<u32>(3);
let mut send = PollSender::new(send);
let _ = send.send_item(42);
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn local_pool_handle_new_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let _ = LocalPoolHandle::new(0);
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn local_pool_handle_spawn_pinned_by_idx_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let rt = basic();
rt.block_on(async {
let handle = LocalPoolHandle::new(2);
handle.spawn_pinned_by_idx(|| async { "test" }, 3);
});
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn delay_queue_insert_at_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let rt = basic();
rt.block_on(async {
let mut queue = task::spawn(DelayQueue::with_capacity(3));
//let st = std::time::Instant::from(SystemTime::UNIX_EPOCH);
let _k = queue.insert_at(
"1",
Instant::now() + Duration::from_millis(MAX_DURATION_MS + 1),
);
});
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn delay_queue_insert_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let rt = basic();
rt.block_on(async {
let mut queue = task::spawn(DelayQueue::with_capacity(3));
let _k = queue.insert("1", Duration::from_millis(MAX_DURATION_MS + 1));
});
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn delay_queue_remove_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let rt = basic();
rt.block_on(async {
let mut queue = task::spawn(DelayQueue::with_capacity(3));
let key = queue.insert_at("1", Instant::now());
queue.remove(&key);
queue.remove(&key);
});
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn delay_queue_reset_at_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let rt = basic();
rt.block_on(async {
let mut queue = task::spawn(DelayQueue::with_capacity(3));
let key = queue.insert_at("1", Instant::now());
queue.reset_at(
&key,
Instant::now() + Duration::from_millis(MAX_DURATION_MS + 1),
);
});
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn delay_queue_reset_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let rt = basic();
rt.block_on(async {
let mut queue = task::spawn(DelayQueue::with_capacity(3));
let key = queue.insert_at("1", Instant::now());
queue.reset(&key, Duration::from_millis(MAX_DURATION_MS + 1));
});
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
#[test]
fn delay_queue_reserve_panic_caller() -> Result<(), Box<dyn Error>> {
let panic_location_file = test_panic(|| {
let rt = basic();
rt.block_on(async {
let mut queue = task::spawn(DelayQueue::<u32>::with_capacity(3));
queue.reserve((1 << 30) as usize);
});
});
// The panic location should be in this file
assert_eq!(&panic_location_file.unwrap(), file!());
Ok(())
}
fn basic() -> Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
}
-48
View File
@@ -13,14 +13,6 @@ fn semaphore_poll(
tokio_test::task::spawn(fut)
}
fn semaphore_poll_many(
sem: &mut PollSemaphore,
permits: u32,
) -> tokio_test::task::Spawn<impl Future<Output = SemRet> + '_> {
let fut = futures::future::poll_fn(move |cx| sem.poll_acquire_many(cx, permits));
tokio_test::task::spawn(fut)
}
#[tokio::test]
async fn it_works() {
let sem = Arc::new(Semaphore::new(1));
@@ -42,43 +34,3 @@ async fn it_works() {
assert!(semaphore_poll(&mut poll_sem).await.is_none());
assert!(semaphore_poll(&mut poll_sem).await.is_none());
}
#[tokio::test]
async fn can_acquire_many_permits() {
let sem = Arc::new(Semaphore::new(4));
let mut poll_sem = PollSemaphore::new(sem.clone());
let permit1 = semaphore_poll(&mut poll_sem).poll();
assert!(matches!(permit1, Poll::Ready(Some(_))));
let permit2 = semaphore_poll_many(&mut poll_sem, 2).poll();
assert!(matches!(permit2, Poll::Ready(Some(_))));
assert_eq!(sem.available_permits(), 1);
drop(permit2);
let mut permit4 = semaphore_poll_many(&mut poll_sem, 4);
assert!(permit4.poll().is_pending());
drop(permit1);
let permit4 = permit4.poll();
assert!(matches!(permit4, Poll::Ready(Some(_))));
assert_eq!(sem.available_permits(), 0);
}
#[tokio::test]
async fn can_poll_different_amounts_of_permits() {
let sem = Arc::new(Semaphore::new(4));
let mut poll_sem = PollSemaphore::new(sem.clone());
assert!(semaphore_poll_many(&mut poll_sem, 5).poll().is_pending());
assert!(semaphore_poll_many(&mut poll_sem, 4).poll().is_ready());
let permit = sem.acquire_many(4).await.unwrap();
assert!(semaphore_poll_many(&mut poll_sem, 5).poll().is_pending());
assert!(semaphore_poll_many(&mut poll_sem, 4).poll().is_pending());
drop(permit);
assert!(semaphore_poll_many(&mut poll_sem, 5).poll().is_pending());
assert!(semaphore_poll_many(&mut poll_sem, 4).poll().is_ready());
}
-13
View File
@@ -1,23 +1,10 @@
use futures::future::FutureExt;
use std::alloc::Layout;
use std::future::Future;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use tokio_util::sync::ReusableBoxFuture;
#[test]
// Clippy false positive; it's useful to be able to test the trait impls for any lifetime
#[allow(clippy::extra_unused_lifetimes)]
fn traits<'a>() {
fn assert_traits<T: Send + Sync + Unpin>() {}
// Use a type that is !Unpin
assert_traits::<ReusableBoxFuture<'a, PhantomPinned>>();
// Use a type that is !Send + !Sync
assert_traits::<ReusableBoxFuture<'a, Rc<()>>>();
}
#[test]
fn test_different_futures() {
let fut = async move { 10 };
+4 -48
View File
@@ -1,9 +1,7 @@
#![warn(rust_2018_idioms)]
#![cfg(not(target_os = "wasi"))] // Wasi doesn't support threads
use std::rc::Rc;
use std::sync::Arc;
use tokio::sync::Barrier;
use tokio_util::task;
/// Simple test of running a !Send future via spawn_pinned
@@ -82,8 +80,8 @@ async fn task_panic_propagates() {
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.is_panic());
let panic_str = error.into_panic().downcast::<&'static str>().unwrap();
assert_eq!(*panic_str, "Test panic");
let panic_str: &str = *error.into_panic().downcast().unwrap();
assert_eq!(panic_str, "Test panic");
// Trying again with a "safe" task still works
let join_handle = pool.spawn_pinned(|| async { "test" });
@@ -108,8 +106,8 @@ async fn callback_panic_does_not_kill_worker() {
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.is_panic());
let panic_str = error.into_panic().downcast::<&'static str>().unwrap();
assert_eq!(*panic_str, "Test panic");
let panic_str: &str = *error.into_panic().downcast().unwrap();
assert_eq!(panic_str, "Test panic");
// Trying again with a "safe" callback works
let join_handle = pool.spawn_pinned(|| async { "test" });
@@ -193,45 +191,3 @@ async fn tasks_are_balanced() {
// be on separate workers/threads.
assert_ne!(thread_id1, thread_id2);
}
#[tokio::test]
async fn spawn_by_idx() {
let pool = task::LocalPoolHandle::new(3);
let barrier = Arc::new(Barrier::new(4));
let barrier1 = barrier.clone();
let barrier2 = barrier.clone();
let barrier3 = barrier.clone();
let handle1 = pool.spawn_pinned_by_idx(
|| async move {
barrier1.wait().await;
std::thread::current().id()
},
0,
);
let _ = pool.spawn_pinned_by_idx(
|| async move {
barrier2.wait().await;
std::thread::current().id()
},
0,
);
let handle2 = pool.spawn_pinned_by_idx(
|| async move {
barrier3.wait().await;
std::thread::current().id()
},
1,
);
let loads = pool.get_task_loads_for_each_worker();
barrier.wait().await;
assert_eq!(loads[0], 2);
assert_eq!(loads[1], 1);
assert_eq!(loads[2], 0);
let thread_id1 = handle1.await.unwrap();
let thread_id2 = handle2.await.unwrap();
assert_ne!(thread_id1, thread_id2);
}
+4 -231
View File
@@ -1,7 +1,7 @@
#![warn(rust_2018_idioms)]
use tokio::pin;
use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
use tokio_util::sync::CancellationToken;
use core::future::Future;
use core::task::{Context, Poll};
@@ -39,56 +39,6 @@ fn cancel_token() {
);
}
#[test]
fn cancel_token_owned() {
let (waker, wake_counter) = new_count_waker();
let token = CancellationToken::new();
assert!(!token.is_cancelled());
let wait_fut = token.clone().cancelled_owned();
pin!(wait_fut);
assert_eq!(
Poll::Pending,
wait_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(wake_counter, 0);
let wait_fut_2 = token.clone().cancelled_owned();
pin!(wait_fut_2);
token.cancel();
assert_eq!(wake_counter, 1);
assert!(token.is_cancelled());
assert_eq!(
Poll::Ready(()),
wait_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Ready(()),
wait_fut_2.as_mut().poll(&mut Context::from_waker(&waker))
);
}
#[test]
fn cancel_token_owned_drop_test() {
let (waker, wake_counter) = new_count_waker();
let token = CancellationToken::new();
let future = token.cancelled_owned();
pin!(future);
assert_eq!(
Poll::Pending,
future.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(wake_counter, 0);
// let future be dropped while pinned and under pending state to
// find potential memory related bugs.
}
#[test]
fn cancel_child_token_through_parent() {
let (waker, wake_counter) = new_count_waker();
@@ -127,46 +77,6 @@ fn cancel_child_token_through_parent() {
);
}
#[test]
fn cancel_grandchild_token_through_parent_if_child_was_dropped() {
let (waker, wake_counter) = new_count_waker();
let token = CancellationToken::new();
let intermediate_token = token.child_token();
let child_token = intermediate_token.child_token();
drop(intermediate_token);
assert!(!child_token.is_cancelled());
let child_fut = child_token.cancelled();
pin!(child_fut);
let parent_fut = token.cancelled();
pin!(parent_fut);
assert_eq!(
Poll::Pending,
child_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Pending,
parent_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(wake_counter, 0);
token.cancel();
assert_eq!(wake_counter, 2);
assert!(token.is_cancelled());
assert!(child_token.is_cancelled());
assert_eq!(
Poll::Ready(()),
child_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Ready(()),
parent_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
}
#[test]
fn cancel_child_token_without_parent() {
let (waker, wake_counter) = new_count_waker();
@@ -256,6 +166,9 @@ fn create_child_token_after_parent_was_cancelled() {
parent_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(wake_counter, 0);
drop(child_fut);
drop(parent_fut);
}
if drop_child_first {
@@ -293,134 +206,6 @@ fn drop_multiple_child_tokens() {
}
}
#[test]
fn cancel_only_all_descendants() {
// ARRANGE
let (waker, wake_counter) = new_count_waker();
let parent_token = CancellationToken::new();
let token = parent_token.child_token();
let sibling_token = parent_token.child_token();
let child1_token = token.child_token();
let child2_token = token.child_token();
let grandchild_token = child1_token.child_token();
let grandchild2_token = child1_token.child_token();
let great_grandchild_token = grandchild_token.child_token();
assert!(!parent_token.is_cancelled());
assert!(!token.is_cancelled());
assert!(!sibling_token.is_cancelled());
assert!(!child1_token.is_cancelled());
assert!(!child2_token.is_cancelled());
assert!(!grandchild_token.is_cancelled());
assert!(!grandchild2_token.is_cancelled());
assert!(!great_grandchild_token.is_cancelled());
let parent_fut = parent_token.cancelled();
let fut = token.cancelled();
let sibling_fut = sibling_token.cancelled();
let child1_fut = child1_token.cancelled();
let child2_fut = child2_token.cancelled();
let grandchild_fut = grandchild_token.cancelled();
let grandchild2_fut = grandchild2_token.cancelled();
let great_grandchild_fut = great_grandchild_token.cancelled();
pin!(parent_fut);
pin!(fut);
pin!(sibling_fut);
pin!(child1_fut);
pin!(child2_fut);
pin!(grandchild_fut);
pin!(grandchild2_fut);
pin!(great_grandchild_fut);
assert_eq!(
Poll::Pending,
parent_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Pending,
fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Pending,
sibling_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Pending,
child1_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Pending,
child2_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Pending,
grandchild_fut
.as_mut()
.poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Pending,
grandchild2_fut
.as_mut()
.poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Pending,
great_grandchild_fut
.as_mut()
.poll(&mut Context::from_waker(&waker))
);
assert_eq!(wake_counter, 0);
// ACT
token.cancel();
// ASSERT
assert_eq!(wake_counter, 6);
assert!(!parent_token.is_cancelled());
assert!(token.is_cancelled());
assert!(!sibling_token.is_cancelled());
assert!(child1_token.is_cancelled());
assert!(child2_token.is_cancelled());
assert!(grandchild_token.is_cancelled());
assert!(grandchild2_token.is_cancelled());
assert!(great_grandchild_token.is_cancelled());
assert_eq!(
Poll::Ready(()),
fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Ready(()),
child1_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Ready(()),
child2_fut.as_mut().poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Ready(()),
grandchild_fut
.as_mut()
.poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Ready(()),
grandchild2_fut
.as_mut()
.poll(&mut Context::from_waker(&waker))
);
assert_eq!(
Poll::Ready(()),
great_grandchild_fut
.as_mut()
.poll(&mut Context::from_waker(&waker))
);
assert_eq!(wake_counter, 6);
}
#[test]
fn drop_parent_before_child_tokens() {
let token = CancellationToken::new();
@@ -433,15 +218,3 @@ fn drop_parent_before_child_tokens() {
drop(child1);
drop(child2);
}
#[test]
fn derives_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<CancellationToken>();
assert_sync::<CancellationToken>();
assert_send::<WaitForCancellationFuture<'static>>();
assert_sync::<WaitForCancellationFuture<'static>>();
}

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