mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89a87d5836 |
@@ -0,0 +1,8 @@
|
||||
# See https://github.com/rustsec/rustsec/blob/59e1d2ad0b9cbc6892c26de233d4925074b4b97b/cargo-audit/audit.toml.example for example.
|
||||
|
||||
[advisories]
|
||||
ignore = [
|
||||
# We depend on nix 0.22 only via mio-aio, a dev-dependency.
|
||||
# https://github.com/tokio-rs/tokio/pull/4255#issuecomment-974786349
|
||||
"RUSTSEC-2021-0119",
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
# [build]
|
||||
# rustflags = ["--cfg", "tokio_unstable"]
|
||||
@@ -2,7 +2,7 @@ version: 2.1
|
||||
jobs:
|
||||
test-arm:
|
||||
machine:
|
||||
image: default
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.medium
|
||||
environment:
|
||||
# Change to pin rust version
|
||||
@@ -22,4 +22,4 @@ jobs:
|
||||
workflows:
|
||||
ci:
|
||||
jobs:
|
||||
- test-arm
|
||||
- test-arm
|
||||
+8
-7
@@ -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-14-1
|
||||
image: freebsd-12-3-release-amd64
|
||||
env:
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2024-05-05
|
||||
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
|
||||
@@ -13,8 +11,9 @@ env:
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 64-bit
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
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
|
||||
@@ -27,11 +26,12 @@ task:
|
||||
|
||||
task:
|
||||
name: FreeBSD docs
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
env:
|
||||
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
|
||||
@@ -44,8 +44,9 @@ task:
|
||||
|
||||
task:
|
||||
name: FreeBSD 32-bit
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
msrv = "1.49"
|
||||
+5
-25
@@ -1,28 +1,8 @@
|
||||
|
||||
R-loom-sync:
|
||||
R-loom:
|
||||
- tokio/src/sync/*
|
||||
- tokio/src/sync/**/*
|
||||
|
||||
R-loom-time-driver:
|
||||
- tokio/src/runtime/time/*
|
||||
- tokio/src/runtime/time/**/*
|
||||
|
||||
R-loom-current-thread:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/current_thread/*
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-multi-thread:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/*
|
||||
- tokio/src/runtime/scheduler/multi_thread/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-multi-thread-alt:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread_alt/*
|
||||
- tokio/src/runtime/scheduler/multi_thread_alt/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
- tokio-util/src/sync/*
|
||||
- tokio-util/src/sync/**/*
|
||||
- tokio/src/runtime/*
|
||||
- tokio/src/runtime/**/*
|
||||
|
||||
@@ -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@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- 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 }}
|
||||
|
||||
+173
-735
File diff suppressed because it is too large
Load Diff
@@ -4,18 +4,8 @@ on:
|
||||
|
||||
# See .github/labeler.yml file
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
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:
|
||||
|
||||
+18
-95
@@ -7,116 +7,39 @@ on:
|
||||
|
||||
name: Loom
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings --cfg loom --cfg tokio_unstable -C debug_assertions
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
LOOM_MAX_BRANCHES: 10000
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
loom-sync:
|
||||
name: loom tokio::sync
|
||||
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-sync') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture sync::tests
|
||||
working-directory: tokio
|
||||
|
||||
loom-time-driver:
|
||||
name: loom time driver
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture runtime::time::tests
|
||||
working-directory: tokio
|
||||
|
||||
loom-current-thread:
|
||||
name: loom current-thread scheduler
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: run tests
|
||||
run: cargo test --lib --release --features full -- --nocapture loom_current_thread
|
||||
working-directory: tokio
|
||||
|
||||
loom-multi-thread:
|
||||
name: loom multi-thread scheduler
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread') || (github.base_ref == null))
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- scope: loom_multi_thread::group_a
|
||||
- scope: loom_multi_thread::group_b
|
||||
- scope: loom_multi_thread::group_c
|
||||
- scope: loom_multi_thread::group_d
|
||||
scope:
|
||||
- --skip loom_pool
|
||||
- loom_pool::group_a
|
||||
- loom_pool::group_b
|
||||
- loom_pool::group_c
|
||||
- loom_pool::group_d
|
||||
- time::driver
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- 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 -- $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
loom-multi-thread-alt:
|
||||
name: loom ALT multi-thread scheduler
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread-alt') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- scope: loom_multi_thread_alt::group_a
|
||||
- scope: loom_multi_thread_alt::group_b
|
||||
- scope: loom_multi_thread_alt::group_c
|
||||
- scope: loom_multi_thread_alt::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
@@ -8,25 +8,25 @@ on:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- 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
|
||||
|
||||
@@ -5,19 +5,12 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
stress-test:
|
||||
name: Stress Test
|
||||
@@ -27,19 +20,20 @@ jobs:
|
||||
stress-test:
|
||||
- simple_echo_tcp
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install Valgrind
|
||||
uses: taiki-e/install-action@valgrind
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Compiles each of the stress test examples.
|
||||
- name: Compile stress test examples
|
||||
run: cargo build -p stress-test --release --example ${{ matrix.stress-test }}
|
||||
|
||||
# Runs each of the examples using Valgrind. Detects leaks and displays them.
|
||||
- name: Run valgrind
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/release/examples/${{ matrix.stress-test }}
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/release/examples/${{ matrix.stress-test }}
|
||||
|
||||
@@ -2,4 +2,3 @@ target
|
||||
Cargo.lock
|
||||
|
||||
.cargo/config.toml
|
||||
.cargo/config
|
||||
|
||||
+23
-122
@@ -131,11 +131,8 @@ cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
Ideally, you should use the same version of clippy as the one used in CI
|
||||
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
|
||||
might have new lints:
|
||||
|
||||
[ci.yml]: .github/workflows/ci.yml
|
||||
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:
|
||||
@@ -149,31 +146,23 @@ When updating this, also update:
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.77 clippy --all --tests --all-features
|
||||
cargo +1.49.0 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation, a simple `cargo doc` is not sufficient. To produce
|
||||
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
|
||||
docs, please use:
|
||||
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 --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
|
||||
RUSTDOCFLAGS="--cfg docsrs" RUSTFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
||||
```
|
||||
|
||||
This turns on indicators to display the Cargo features required for
|
||||
conditionally compiled APIs in Tokio, and it enables documentation of unstable
|
||||
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
|
||||
*and* rustc.
|
||||
|
||||
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
|
||||
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
|
||||
as docs.rs itself does.
|
||||
|
||||
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
|
||||
To build documentation including Tokio's unstable features, it is necessary to
|
||||
pass `--cfg tokio_unstable` to both RustDoc *and* rustc. To build the
|
||||
documentation for unstable features, use this command:
|
||||
|
||||
```
|
||||
cargo install --locked cargo-docs-rs
|
||||
cargo +nightly docs-rs [--open]
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features
|
||||
```
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
@@ -181,10 +170,10 @@ command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
rustfmt --check --edition 2018 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
@@ -192,58 +181,24 @@ it, `rustfmt` will update your files locally instead.
|
||||
You can run loom tests with
|
||||
```
|
||||
cd tokio # tokio crate in workspace
|
||||
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
|
||||
LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
|
||||
run loom tests that test unstable features.
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" \
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" PROPTEST_CASES=10 \
|
||||
cargo +nightly miri test --features full --lib
|
||||
```
|
||||
|
||||
### Performing spellcheck on tokio codebase
|
||||
|
||||
You can perform spell-check on tokio codebase. For details of how to use the spellcheck tool, feel free to visit
|
||||
https://github.com/drahnr/cargo-spellcheck
|
||||
```
|
||||
# First install the spell-check plugin
|
||||
cargo install --locked cargo-spellcheck
|
||||
|
||||
# Then run the cargo spell check command
|
||||
cargo spellcheck check
|
||||
```
|
||||
|
||||
if the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
|
||||
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
|
||||
the total number of words included in the file
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: [integration tests][integration-tests]
|
||||
and [documentation tests][documentation-tests].
|
||||
(Tokio avoids [unit tests][unit-tests] as much as possible).
|
||||
|
||||
Tokio uses [conditional compilation attributes][conditional-compilation]
|
||||
throughout the codebase, to modify rustc's behavior. Code marked with such
|
||||
attributes can be enabled using RUSTFLAGS and RUSTDOCFLAGS environment
|
||||
variables. One of the most prevalent flags passed in these variables is
|
||||
the `--cfg` option. To run tests in a particular file, check first what
|
||||
options #![cfg] declaration defines for that file.
|
||||
|
||||
For instance, to run a test marked with the 'tokio_unstable' cfg option,
|
||||
you must pass this flag to the compiler when running the test.
|
||||
```
|
||||
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
|
||||
```
|
||||
There are two ways to write tests: integration tests and documentation tests
|
||||
(Tokio avoids unit tests as much as possible).
|
||||
|
||||
#### Integration tests
|
||||
|
||||
@@ -254,31 +209,6 @@ utilities available to use in tests, no matter the crate being tested.
|
||||
The best strategy for writing a new integration test is to look at existing
|
||||
integration tests in the crate and follow the style.
|
||||
|
||||
#### Fuzz tests
|
||||
|
||||
Some of our crates include a set of fuzz tests, this will be marked by a
|
||||
directory `fuzz`. It is a good idea to run fuzz tests after each change.
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install --locked cargo-fuzz`
|
||||
|
||||
To list the available fuzzing harnesses you can run;
|
||||
|
||||
```bash
|
||||
$ cd tokio
|
||||
$ cargo fuzz list
|
||||
fuzz_linked_list
|
||||
````
|
||||
|
||||
Running a fuzz test is as simple as;
|
||||
|
||||
`cargo fuzz run fuzz_linked_list`
|
||||
|
||||
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
|
||||
harness will run forever and will only exit if you `ctrl-c` or it finds
|
||||
a bug.
|
||||
|
||||
#### Documentation tests
|
||||
|
||||
Ideally, every API has at least one [documentation test] that demonstrates how to
|
||||
@@ -355,29 +285,6 @@ example would explicitly use `Timeout::new`. For example:
|
||||
/// # }
|
||||
```
|
||||
|
||||
### Benchmarks
|
||||
|
||||
You can run benchmarks locally for the changes you've made to the tokio codebase.
|
||||
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
|
||||
against the changes you have made, for example, you can run;
|
||||
|
||||
```bash
|
||||
cd benches
|
||||
|
||||
# Run all benchmarks.
|
||||
cargo bench
|
||||
|
||||
# Run all tests in the `benches/fs.rs` file
|
||||
cargo bench --bench fs
|
||||
|
||||
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
|
||||
cargo bench async_read_buf
|
||||
|
||||
# After running benches, you can check the statistics under `tokio/target/criterion/`
|
||||
```
|
||||
|
||||
You can also refer to Criterion docs for additional options and details.
|
||||
|
||||
### Commits
|
||||
|
||||
It is a recommended best practice to keep your changes as logically grouped as
|
||||
@@ -401,16 +308,14 @@ A good commit message should describe what changed and why.
|
||||
and no more than 72 characters)
|
||||
* be entirely in lowercase with the exception of proper nouns, acronyms, and
|
||||
the words that refer to code, like function/variable names
|
||||
* start with an imperative verb
|
||||
* not have a period at the end
|
||||
* be prefixed with the name of the module being changed; usually this is the
|
||||
same as the M-* label on the PR
|
||||
* be prefixed with the name of the sub crate being changed (without the `tokio-`
|
||||
prefix) and start with an imperative verb. If modifying `tokio` proper,
|
||||
omit the crate prefix.
|
||||
|
||||
Examples:
|
||||
|
||||
* time: introduce `Timeout` and deprecate `Deadline`
|
||||
* codec: export `Encoder`, `Decoder`, `Framed*`
|
||||
* ci: fix the FreeBSD ci configuration
|
||||
* timer: introduce `Timeout` and deprecate `Deadline`
|
||||
* export `Encoder`, `Decoder`, `Framed*` from tokio_codec
|
||||
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns (except for long URLs).
|
||||
@@ -427,7 +332,7 @@ A good commit message should describe what changed and why.
|
||||
Sample complete commit message:
|
||||
|
||||
```txt
|
||||
module: explain the commit in one line
|
||||
subcrate: explain the commit in one line
|
||||
|
||||
Body of commit message is a few lines of text, explaining things
|
||||
in more detail, possibly giving some background about the issue
|
||||
@@ -728,7 +633,3 @@ When releasing a new version of a crate, follow these steps:
|
||||
entry for that release version into your editor and close the window.
|
||||
|
||||
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog/blob/master/CHANGELOG.md
|
||||
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
|
||||
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
|
||||
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
|
||||
+1
-4
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
@@ -14,6 +14,3 @@ members = [
|
||||
"tests-build",
|
||||
"tests-integration",
|
||||
]
|
||||
|
||||
[workspace.metadata.spellcheck]
|
||||
config = "spellcheck.toml"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[build.env]
|
||||
passthrough = [
|
||||
"RUSTFLAGS",
|
||||
"RUST_BACKTRACE",
|
||||
]
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
MIT License
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Copyright (c) Tokio Contributors
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
tokio = { version = "1.21.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -132,8 +132,6 @@ project.
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`axum`]: A web application framework that focuses on ergonomics and modularity.
|
||||
|
||||
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
|
||||
|
||||
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
|
||||
@@ -144,18 +142,21 @@ several other libraries, including:
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers `tokio`.
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
* [`loom`]: A testing tool for concurrent Rust code.
|
||||
* [`loom`]: A testing tool for concurrent Rust code
|
||||
|
||||
[`axum`]: https://github.com/tokio-rs/axum
|
||||
[`warp`]: https://github.com/seanmonstar/warp
|
||||
[`hyper`]: https://github.com/hyperium/hyper
|
||||
[`tonic`]: https://github.com/hyperium/tonic
|
||||
[`tower`]: https://github.com/tower-rs/tower
|
||||
[`loom`]: https://github.com/tokio-rs/loom
|
||||
[`rdbc`]: https://github.com/tokio-rs/rdbc
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
@@ -186,22 +187,7 @@ When updating this, also update:
|
||||
|
||||
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
|
||||
least** 6 months. When increasing the MSRV, the new Rust version must have been
|
||||
released at least six months ago. The current MSRV is 1.70.
|
||||
|
||||
Note that the MSRV is not increased automatically, and only as part of a minor
|
||||
release. The MSRV history for past minor releases can be found below:
|
||||
|
||||
* 1.39 to now - Rust 1.70
|
||||
* 1.30 to 1.38 - Rust 1.63
|
||||
* 1.27 to 1.29 - Rust 1.56
|
||||
* 1.17 to 1.26 - Rust 1.49
|
||||
* 1.15 to 1.16 - Rust 1.46
|
||||
* 1.0 to 1.14 - Rust 1.45
|
||||
|
||||
Note that although we try to avoid the situation where a dependency transitively
|
||||
increases the MSRV of Tokio, we do not guarantee that this does not happen.
|
||||
However, every minor release will have some set of versions of dependencies that
|
||||
works with the MSRV of that minor release.
|
||||
released at least six months ago. The current MSRV is 1.49.0.
|
||||
|
||||
## Release schedule
|
||||
|
||||
@@ -216,29 +202,20 @@ 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.32.x` - LTS release until September 2024. (MSRV 1.63)
|
||||
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
|
||||
* `1.38.x` - LTS release until July 2025. (MSRV 1.63)
|
||||
* `1.18.x` - LTS release until June 2023
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
use an LTS release.
|
||||
|
||||
To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.32.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.18.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.32", features = [...] }
|
||||
tokio = { version = "~1.18", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
|
||||
* `1.8.x` - LTS release until February 2022.
|
||||
* `1.14.x` - LTS release until June 2022.
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
* `1.25.x` - LTS release until March 2024.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
+2
-46
@@ -2,16 +2,11 @@
|
||||
name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
test-util = ["tokio/test-util"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
criterion = "0.5.1"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
bencher = "0.1.5"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
|
||||
@@ -25,40 +20,16 @@ name = "spawn"
|
||||
path = "spawn.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_broadcast"
|
||||
path = "sync_broadcast.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_mpsc"
|
||||
path = "sync_mpsc.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_mpsc_oneshot"
|
||||
path = "sync_mpsc_oneshot.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_watch"
|
||||
path = "sync_watch.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_current_thread"
|
||||
path = "rt_current_thread.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_multi_threaded"
|
||||
path = "rt_multi_threaded.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_notify"
|
||||
path = "sync_notify.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_rwlock"
|
||||
@@ -79,18 +50,3 @@ harness = false
|
||||
name = "fs"
|
||||
path = "fs.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "copy"
|
||||
path = "copy.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "time_now"
|
||||
path = "time_now.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "time_timeout"
|
||||
path = "time_timeout.rs"
|
||||
harness = false
|
||||
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
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 behavior under heavy load.
|
||||
///
|
||||
/// There is a limited buffer, which is fully drained on the next write after
|
||||
/// a time limit is reached. Flush waits for the time limit to be reached
|
||||
/// 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 res.is_ready() {
|
||||
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.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(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("copy_mem_to_mem", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_mem_to_slow_hdd(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("copy_mem_to_slow_hdd", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_chunk_to_mem(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("copy_chunk_to_mem", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source =
|
||||
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_chunk_to_slow_hdd(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("copy_chunk_to_slow_hdd", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source =
|
||||
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
copy_bench,
|
||||
copy_mem_to_mem,
|
||||
copy_mem_to_slow_hdd,
|
||||
copy_chunk_to_mem,
|
||||
copy_chunk_to_slow_hdd,
|
||||
);
|
||||
criterion_main!(copy_bench);
|
||||
+57
-66
@@ -6,7 +6,7 @@ use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead /*FramedWrite*/};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
|
||||
use std::fs::File as StdFile;
|
||||
use std::io::Read as StdRead;
|
||||
@@ -23,90 +23,81 @@ const BLOCK_COUNT: usize = 1_000;
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
const DEV_ZERO: &str = "/dev/zero";
|
||||
|
||||
fn async_read_codec(c: &mut Criterion) {
|
||||
fn async_read_codec(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("async_read_codec", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut input_stream =
|
||||
FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut input_stream = FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let _bytes = input_stream.next().await.unwrap();
|
||||
}
|
||||
};
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let _bytes = input_stream.next().await.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
rt.block_on(task());
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_buf(c: &mut Criterion) {
|
||||
fn async_read_buf(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("async_read_buf", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let count = file.read(&mut buffer).await.unwrap();
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_std_file(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("async_read_std_file", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file =
|
||||
tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
let mut file_ref = file.as_mut();
|
||||
|
||||
tokio::task::block_in_place(move || {
|
||||
file_ref.read_exact(&mut buffer).unwrap();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn sync_read(c: &mut Criterion) {
|
||||
c.bench_function("sync_read", |b| {
|
||||
b.iter(|| {
|
||||
let mut file = StdFile::open(DEV_ZERO).unwrap();
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
file.read_exact(&mut buffer).unwrap();
|
||||
let count = file.read(&mut buffer).await.unwrap();
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
fn async_read_std_file(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
let task = || async {
|
||||
let mut file = tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
let mut file_ref = file.as_mut();
|
||||
|
||||
tokio::task::block_in_place(move || {
|
||||
file_ref.read_exact(&mut buffer).unwrap();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(task());
|
||||
});
|
||||
}
|
||||
|
||||
fn sync_read(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let mut file = StdFile::open(DEV_ZERO).unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
file.read_exact(&mut buffer).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
file,
|
||||
async_read_std_file,
|
||||
async_read_buf,
|
||||
async_read_codec,
|
||||
sync_read
|
||||
);
|
||||
criterion_main!(file);
|
||||
|
||||
benchmark_main!(file);
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
//! Benchmark implementation details of the threaded scheduler. These benches are
|
||||
//! intended to be used as a form of regression testing and not as a general
|
||||
//! purpose benchmark demonstrating real-world performance.
|
||||
|
||||
use tokio::runtime::{self, Runtime};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
fn rt_curr_spawn_many_local(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
c.bench_function("spawn_many_local", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(tokio::spawn(async move {}));
|
||||
}
|
||||
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn rt_curr_spawn_many_remote_idle(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
c.bench_function("spawn_many_remote_idle", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn rt_curr_spawn_many_remote_busy(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
rt.spawn(async {
|
||||
fn iter() {
|
||||
tokio::spawn(async { iter() });
|
||||
}
|
||||
|
||||
iter()
|
||||
});
|
||||
|
||||
c.bench_function("spawn_many_remote_busy", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_current_thread().build().unwrap()
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
rt_curr_scheduler,
|
||||
rt_curr_spawn_many_local,
|
||||
rt_curr_spawn_many_remote_idle,
|
||||
rt_curr_spawn_many_remote_busy
|
||||
);
|
||||
|
||||
criterion_main!(rt_curr_scheduler);
|
||||
+82
-207
@@ -5,173 +5,67 @@
|
||||
use tokio::runtime::{self, Runtime};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
fn spawn_many(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
|
||||
const NUM_WORKERS: usize = 4;
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const STALL_DUR: Duration = Duration::from_micros(10);
|
||||
|
||||
fn rt_multi_spawn_many_local(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
c.bench_function("spawn_many_local", |b| {
|
||||
b.iter(|| {
|
||||
rem.store(NUM_SPAWN, Relaxed);
|
||||
b.iter(|| {
|
||||
rem.store(NUM_SPAWN, Relaxed);
|
||||
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rx.recv().unwrap();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn rt_multi_spawn_many_remote_idle(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
c.bench_function("spawn_many_remote_idle", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and yield. Yielding is a
|
||||
// lower notification priority than spawning / regular notification.
|
||||
fn rt_multi_spawn_many_remote_busy1(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
let flag = Arc::new(AtomicBool::new(true));
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(2 * NUM_WORKERS) {
|
||||
let flag = flag.clone();
|
||||
rt.spawn(async move {
|
||||
while flag.load(Relaxed) {
|
||||
tokio::task::yield_now().await;
|
||||
stall();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
c.bench_function("spawn_many_remote_busy1", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and spawn new high-CPU
|
||||
// tasks. Spawning goes via a higher notification priority than yielding.
|
||||
fn rt_multi_spawn_many_remote_busy2(c: &mut Criterion) {
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
let flag = Arc::new(AtomicBool::new(true));
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(NUM_WORKERS) {
|
||||
let flag = flag.clone();
|
||||
fn iter(flag: Arc<AtomicBool>) {
|
||||
tokio::spawn(async {
|
||||
if flag.load(Relaxed) {
|
||||
stall();
|
||||
iter(flag);
|
||||
}
|
||||
});
|
||||
}
|
||||
rt.spawn(async {
|
||||
iter(flag);
|
||||
});
|
||||
}
|
||||
|
||||
c.bench_function("spawn_many_remote_busy2", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
fn rt_multi_yield_many(c: &mut Criterion) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS: usize = 200;
|
||||
|
||||
c.bench_function("yield_many", |b| {
|
||||
let rt = rt();
|
||||
let (tx, rx) = mpsc::sync_channel(TASKS);
|
||||
|
||||
b.iter(move || {
|
||||
for _ in 0..TASKS {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
|
||||
rt.spawn(async move {
|
||||
for _ in 0..NUM_YIELD {
|
||||
tokio::task::yield_now().await;
|
||||
tokio::spawn(async move {
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..TASKS {
|
||||
rx.recv().unwrap();
|
||||
}
|
||||
})
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn rt_multi_ping_pong(c: &mut Criterion) {
|
||||
fn yield_many(b: &mut Bencher) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS: usize = 200;
|
||||
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(TASKS);
|
||||
|
||||
b.iter(move || {
|
||||
for _ in 0..TASKS {
|
||||
let tx = tx.clone();
|
||||
|
||||
rt.spawn(async move {
|
||||
for _ in 0..NUM_YIELD {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..TASKS {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn ping_pong(b: &mut Bencher) {
|
||||
const NUM_PINGS: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
@@ -179,46 +73,46 @@ fn rt_multi_ping_pong(c: &mut Criterion) {
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
c.bench_function("ping_pong", |b| {
|
||||
b.iter(|| {
|
||||
let done_tx = done_tx.clone();
|
||||
let rem = rem.clone();
|
||||
rem.store(NUM_PINGS, Relaxed);
|
||||
b.iter(|| {
|
||||
let done_tx = done_tx.clone();
|
||||
let rem = rem.clone();
|
||||
rem.store(NUM_PINGS, Relaxed);
|
||||
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..NUM_PINGS {
|
||||
let rem = rem.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..NUM_PINGS {
|
||||
let rem = rem.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
rx1.await.unwrap();
|
||||
tx2.send(()).unwrap();
|
||||
});
|
||||
|
||||
tx1.send(()).unwrap();
|
||||
rx2.await.unwrap();
|
||||
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
done_tx.send(()).unwrap();
|
||||
}
|
||||
rx1.await.unwrap();
|
||||
tx2.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
tx1.send(()).unwrap();
|
||||
rx2.await.unwrap();
|
||||
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
done_tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn rt_multi_chained_spawn(c: &mut Criterion) {
|
||||
fn chained_spawn(b: &mut Bencher) {
|
||||
const ITER: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
|
||||
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
done_tx.send(()).unwrap();
|
||||
@@ -229,48 +123,29 @@ fn rt_multi_chained_spawn(c: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
c.bench_function("chained_spawn", |b| {
|
||||
let rt = rt();
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
|
||||
b.iter(move || {
|
||||
let done_tx = done_tx.clone();
|
||||
b.iter(move || {
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
iter(done_tx, ITER);
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
iter(done_tx, ITER);
|
||||
});
|
||||
})
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread()
|
||||
.worker_threads(NUM_WORKERS)
|
||||
.worker_threads(4)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn stall() {
|
||||
let now = Instant::now();
|
||||
while now.elapsed() < STALL_DUR {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
benchmark_group!(scheduler, spawn_many, ping_pong, yield_many, chained_spawn,);
|
||||
|
||||
criterion_group!(
|
||||
rt_multi_scheduler,
|
||||
rt_multi_spawn_many_local,
|
||||
rt_multi_spawn_many_remote_idle,
|
||||
rt_multi_spawn_many_remote_busy1,
|
||||
rt_multi_spawn_many_remote_busy2,
|
||||
rt_multi_ping_pong,
|
||||
rt_multi_yield_many,
|
||||
rt_multi_chained_spawn,
|
||||
);
|
||||
|
||||
criterion_main!(rt_multi_scheduler);
|
||||
benchmark_main!(scheduler);
|
||||
|
||||
+15
-17
@@ -1,7 +1,7 @@
|
||||
//! Benchmark the delay in propagating OS signals to any listeners.
|
||||
#![cfg(unix)]
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -41,7 +41,7 @@ pub fn send_signal(signal: libc::c_int) {
|
||||
}
|
||||
}
|
||||
|
||||
fn many_signals(c: &mut Criterion) {
|
||||
fn many_signals(bench: &mut Bencher) {
|
||||
let num_signals = 10;
|
||||
let (tx, mut rx) = mpsc::channel(num_signals);
|
||||
|
||||
@@ -75,23 +75,21 @@ fn many_signals(c: &mut Criterion) {
|
||||
// tasks have been polled at least once
|
||||
rt.block_on(Spinner::new());
|
||||
|
||||
c.bench_function("many_signals", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
send_signal(libc::SIGCHLD);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
bench.iter(|| {
|
||||
rt.block_on(async {
|
||||
send_signal(libc::SIGCHLD);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
|
||||
send_signal(libc::SIGIO);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
});
|
||||
})
|
||||
send_signal(libc::SIGIO);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(signal_group, many_signals);
|
||||
benchmark_group!(signal_group, many_signals,);
|
||||
|
||||
criterion_main!(signal_group);
|
||||
benchmark_main!(signal_group);
|
||||
|
||||
+40
-47
@@ -2,7 +2,10 @@
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
#[macro_use]
|
||||
extern crate bencher;
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
|
||||
async fn work() -> usize {
|
||||
let val = 1 + 1;
|
||||
@@ -10,77 +13,67 @@ async fn work() -> usize {
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
fn basic_scheduler_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("basic_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
})
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
fn basic_scheduler_spawn10(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("basic_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
})
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
fn threaded_scheduler_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
c.bench_function("threaded_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
})
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn10(c: &mut Criterion) {
|
||||
fn threaded_scheduler_spawn10(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
c.bench_function("threaded_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
})
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
bencher::benchmark_group!(
|
||||
spawn,
|
||||
basic_scheduler_spawn,
|
||||
basic_scheduler_spawn10,
|
||||
@@ -88,4 +81,4 @@ criterion_group!(
|
||||
threaded_scheduler_spawn10,
|
||||
);
|
||||
|
||||
criterion_main!(spawn);
|
||||
bencher::benchmark_main!(spawn);
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
use rand::{Rng, RngCore, SeedableRng};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, Notify};
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.map(|&c| c as u32)
|
||||
.fold(0, u32::wrapping_add)
|
||||
}
|
||||
|
||||
fn contention_impl<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
let (tx, _rx) = broadcast::channel::<usize>(1000);
|
||||
let wg = Arc::new((AtomicUsize::new(0), Notify::new()));
|
||||
|
||||
for n in 0..N_TASKS {
|
||||
let wg = wg.clone();
|
||||
let mut rx = tx.subscribe();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
|
||||
rt.spawn(async move {
|
||||
while (rx.recv().await).is_ok() {
|
||||
let r = do_work(&mut rng);
|
||||
let _ = black_box(r);
|
||||
if wg.0.fetch_sub(1, Ordering::Relaxed) == 1 {
|
||||
wg.1.notify_one();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 100;
|
||||
|
||||
g.bench_function(N_TASKS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on({
|
||||
let wg = wg.clone();
|
||||
let tx = tx.clone();
|
||||
async move {
|
||||
for i in 0..N_ITERS {
|
||||
assert_eq!(wg.0.fetch_add(N_TASKS, Ordering::Relaxed), 0);
|
||||
tx.send(i).unwrap();
|
||||
while wg.0.load(Ordering::Relaxed) > 0 {
|
||||
wg.1.notified().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
contention_impl::<10>(&mut group);
|
||||
contention_impl::<100>(&mut group);
|
||||
contention_impl::<500>(&mut group);
|
||||
contention_impl::<1000>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention);
|
||||
|
||||
criterion_main!(contention);
|
||||
+109
-261
@@ -1,23 +1,8 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Medium(#[allow(dead_code)] [usize; 64]);
|
||||
impl Default for Medium {
|
||||
fn default() -> Self {
|
||||
Medium([0; 64])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Large(#[allow(dead_code)] [Medium; 64]);
|
||||
impl Default for Large {
|
||||
fn default() -> Self {
|
||||
Large([Medium::default(); 64])
|
||||
}
|
||||
}
|
||||
type Medium = [usize; 64];
|
||||
type Large = [Medium; 64];
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
@@ -26,306 +11,169 @@ fn rt() -> tokio::runtime::Runtime {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create_medium<const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
g.bench_function(SIZE.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(SIZE));
|
||||
})
|
||||
fn create_1_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(1));
|
||||
});
|
||||
}
|
||||
|
||||
fn send_data<T: Default, const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>, prefix: &str) {
|
||||
fn create_100_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_000_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100_000));
|
||||
});
|
||||
}
|
||||
|
||||
fn send_medium(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function(format!("{}_{}", prefix, SIZE), |b| {
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<T>(SIZE);
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
|
||||
let _ = rt.block_on(tx.send(T::default()));
|
||||
let _ = rt.block_on(tx.send([0; 64]));
|
||||
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
})
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn send_large(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
let _ = rt.block_on(tx.send([[0; 64]; 64]));
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn contention_bounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded_full(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn contention_bounded_full(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded_full", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded_full_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn contention_unbounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded_full_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn uncontented_bounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn uncontented_unbounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
bencher::benchmark_group!(
|
||||
create,
|
||||
create_1_medium,
|
||||
create_100_medium,
|
||||
create_100_000_medium
|
||||
);
|
||||
|
||||
g.bench_function("bounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
bencher::benchmark_group!(send, send_medium, send_large);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
bencher::benchmark_group!(
|
||||
contention,
|
||||
contention_bounded,
|
||||
contention_bounded_full,
|
||||
contention_unbounded,
|
||||
uncontented_bounded,
|
||||
uncontented_unbounded
|
||||
);
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_create_medium(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("create_medium");
|
||||
create_medium::<1>(&mut group);
|
||||
create_medium::<100>(&mut group);
|
||||
create_medium::<100_000>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_send(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("send");
|
||||
send_data::<Medium, 1000>(&mut group, "medium");
|
||||
send_data::<Large, 1000>(&mut group, "large");
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
contention_bounded(&mut group);
|
||||
contention_bounded_recv_many(&mut group);
|
||||
contention_bounded_full(&mut group);
|
||||
contention_bounded_full_recv_many(&mut group);
|
||||
contention_unbounded(&mut group);
|
||||
contention_unbounded_recv_many(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
uncontented_bounded(&mut group);
|
||||
uncontented_bounded_recv_many(&mut group);
|
||||
uncontented_unbounded(&mut group);
|
||||
uncontented_unbounded_recv_many(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(create, bench_create_medium);
|
||||
criterion_group!(send, bench_send);
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(create, send, contention, uncontented);
|
||||
bencher::benchmark_main!(create, send, contention);
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
sync::{mpsc, oneshot},
|
||||
};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
fn request_reply_current_thread(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(c, rt);
|
||||
}
|
||||
|
||||
fn request_reply_multi_threaded(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(c, rt);
|
||||
}
|
||||
|
||||
fn request_reply(b: &mut Criterion, rt: Runtime) {
|
||||
let tx = rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<oneshot::Sender<()>>(10);
|
||||
tokio::spawn(async move {
|
||||
while let Some(reply) = rx.recv().await {
|
||||
reply.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
tx
|
||||
});
|
||||
|
||||
b.bench_function("request_reply", |b| {
|
||||
b.iter(|| {
|
||||
let task_tx = tx.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..1_000 {
|
||||
let (o_tx, o_rx) = oneshot::channel();
|
||||
task_tx.send(o_tx).await.unwrap();
|
||||
let _ = o_rx.await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
sync_mpsc_oneshot_group,
|
||||
request_reply_current_thread,
|
||||
request_reply_multi_threaded,
|
||||
);
|
||||
|
||||
criterion_main!(sync_mpsc_oneshot_group);
|
||||
@@ -1,104 +0,0 @@
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn notify_waiters<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
for _ in 0..N_WAITERS {
|
||||
rt.spawn({
|
||||
let notify = notify.clone();
|
||||
let counter = counter.clone();
|
||||
async move {
|
||||
loop {
|
||||
notify.notified().await;
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
g.bench_function(N_WAITERS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_waiters();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn notify_one<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
for _ in 0..N_WAITERS {
|
||||
rt.spawn({
|
||||
let notify = notify.clone();
|
||||
let counter = counter.clone();
|
||||
async move {
|
||||
loop {
|
||||
notify.notified().await;
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
g.bench_function(N_WAITERS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_one();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_notify_one(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("notify_one");
|
||||
notify_one::<10>(&mut group);
|
||||
notify_one::<50>(&mut group);
|
||||
notify_one::<100>(&mut group);
|
||||
notify_one::<200>(&mut group);
|
||||
notify_one::<500>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_notify_waiters(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("notify_waiters");
|
||||
notify_waiters::<10>(&mut group);
|
||||
notify_waiters::<50>(&mut group);
|
||||
notify_waiters::<100>(&mut group);
|
||||
notify_waiters::<200>(&mut group);
|
||||
notify_waiters::<500>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
notify_waiters_simple,
|
||||
bench_notify_one,
|
||||
bench_notify_waiters
|
||||
);
|
||||
|
||||
criterion_main!(notify_waiters_simple);
|
||||
+70
-91
@@ -1,30 +1,26 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use std::sync::Arc;
|
||||
use tokio::{sync::RwLock, task};
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn read_uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn read_uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
g.bench_function("read", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let read = lock.read().await;
|
||||
let _read = black_box(read);
|
||||
}
|
||||
})
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let read = lock.read().await;
|
||||
let _read = black_box(read);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
@@ -36,25 +32,23 @@ fn read_concurrent_uncontended_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
g.bench_function("read_concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
@@ -65,24 +59,22 @@ fn read_concurrent_uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
g.bench_function("read_concurrent", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone())
|
||||
};
|
||||
})
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone())
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_contended_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
@@ -94,26 +86,24 @@ fn read_concurrent_contended_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
g.bench_function("read_concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
let j = tokio::try_join! {
|
||||
async move { drop(write); Ok(()) },
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
let j = tokio::try_join! {
|
||||
async move { drop(write); Ok(()) },
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_contended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn read_concurrent_contended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
@@ -124,40 +114,29 @@ fn read_concurrent_contended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
g.bench_function("read_concurrent", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
tokio::join! {
|
||||
async move { drop(write) },
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
};
|
||||
})
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
tokio::join! {
|
||||
async move { drop(write) },
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
read_concurrent_contended(&mut group);
|
||||
read_concurrent_contended_multi(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
bencher::benchmark_group!(
|
||||
sync_rwlock,
|
||||
read_uncontended,
|
||||
read_concurrent_uncontended,
|
||||
read_concurrent_uncontended_multi,
|
||||
read_concurrent_contended,
|
||||
read_concurrent_contended_multi
|
||||
);
|
||||
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
read_uncontended(&mut group);
|
||||
read_concurrent_uncontended(&mut group);
|
||||
read_concurrent_uncontended_multi(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(contention, uncontented);
|
||||
bencher::benchmark_main!(sync_rwlock);
|
||||
|
||||
+84
-106
@@ -1,36 +1,21 @@
|
||||
use bencher::Bencher;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::{sync::Semaphore, task};
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn single_rt() -> Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn multi_rt() -> Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
fn uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
g.bench_function("multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let permit = s.acquire().await;
|
||||
drop(permit);
|
||||
}
|
||||
})
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let permit = s.acquire().await;
|
||||
drop(permit);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -40,108 +25,101 @@ async fn task(s: Arc<Semaphore>) {
|
||||
drop(permit);
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
fn uncontended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
g.bench_function("concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = single_rt();
|
||||
fn uncontended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
g.bench_function("concurrent_single", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
fn contended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(5));
|
||||
g.bench_function("concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = single_rt();
|
||||
fn contended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(5));
|
||||
g.bench_function("concurrent_single", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
contended_concurrent_multi(&mut group);
|
||||
contended_concurrent_single(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
bencher::benchmark_group!(
|
||||
sync_semaphore,
|
||||
uncontended,
|
||||
uncontended_concurrent_multi,
|
||||
uncontended_concurrent_single,
|
||||
contended_concurrent_multi,
|
||||
contended_concurrent_single
|
||||
);
|
||||
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
uncontended(&mut group);
|
||||
uncontended_concurrent_multi(&mut group);
|
||||
uncontended_concurrent_single(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(contention, uncontented);
|
||||
bencher::benchmark_main!(sync_semaphore);
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
use rand::prelude::*;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{watch, Notify};
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.map(|&c| c as u32)
|
||||
.fold(0, u32::wrapping_add)
|
||||
}
|
||||
|
||||
fn contention_resubscribe<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let (snd, _) = watch::channel(0i32);
|
||||
let snd = Arc::new(snd);
|
||||
let wg = Arc::new((AtomicU64::new(0), Notify::new()));
|
||||
for n in 0..N_TASKS {
|
||||
let mut rcv = snd.subscribe();
|
||||
let wg = wg.clone();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
|
||||
rt.spawn(async move {
|
||||
while rcv.changed().await.is_ok() {
|
||||
let _ = *rcv.borrow(); // contend on rwlock
|
||||
let r = do_work(&mut rng);
|
||||
let _ = black_box(r);
|
||||
if wg.0.fetch_sub(1, Ordering::Release) == 1 {
|
||||
wg.1.notify_one();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 100;
|
||||
g.bench_function(N_TASKS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on({
|
||||
let snd = snd.clone();
|
||||
let wg = wg.clone();
|
||||
async move {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..N_ITERS {
|
||||
assert_eq!(wg.0.fetch_add(N_TASKS as u64, Ordering::Relaxed), 0);
|
||||
let _ = snd.send(black_box(42));
|
||||
while wg.0.load(Ordering::Acquire) > 0 {
|
||||
wg.1.notified().await;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_contention_resubscribe(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention_resubscribe");
|
||||
contention_resubscribe::<10>(&mut group);
|
||||
contention_resubscribe::<100>(&mut group);
|
||||
contention_resubscribe::<500>(&mut group);
|
||||
contention_resubscribe::<1000>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention_resubscribe);
|
||||
|
||||
criterion_main!(contention);
|
||||
@@ -1,24 +0,0 @@
|
||||
//! Benchmark spawning a task onto the basic and threaded Tokio executors.
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
fn time_now_current_thread(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("time_now_current_thread", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
black_box(tokio::time::Instant::now());
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(time_now, time_now_current_thread);
|
||||
|
||||
criterion_main!(time_now);
|
||||
@@ -1,109 +0,0 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
time::{sleep, timeout},
|
||||
};
|
||||
|
||||
// a very quick async task, but might timeout
|
||||
async fn quick_job() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn build_run_time(workers: usize) -> Runtime {
|
||||
if workers == 1 {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
} else {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.worker_threads(workers)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn single_thread_scheduler_timeout(c: &mut Criterion) {
|
||||
do_timeout_test(c, 1, "single_thread_timeout");
|
||||
}
|
||||
|
||||
fn multi_thread_scheduler_timeout(c: &mut Criterion) {
|
||||
do_timeout_test(c, 8, "multi_thread_timeout-8");
|
||||
}
|
||||
|
||||
fn do_timeout_test(c: &mut Criterion, workers: usize, name: &str) {
|
||||
let runtime = build_run_time(workers);
|
||||
c.bench_function(name, |b| {
|
||||
b.iter_custom(|iters| {
|
||||
let start = Instant::now();
|
||||
runtime.block_on(async {
|
||||
black_box(spawn_timeout_job(iters as usize, workers)).await;
|
||||
});
|
||||
start.elapsed()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async fn spawn_timeout_job(iters: usize, procs: usize) {
|
||||
let mut handles = Vec::with_capacity(procs);
|
||||
for _ in 0..procs {
|
||||
handles.push(tokio::spawn(async move {
|
||||
for _ in 0..iters / procs {
|
||||
let h = timeout(Duration::from_secs(1), quick_job());
|
||||
assert_eq!(black_box(h.await.unwrap()), 1);
|
||||
}
|
||||
}));
|
||||
}
|
||||
for handle in handles {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn single_thread_scheduler_sleep(c: &mut Criterion) {
|
||||
do_sleep_test(c, 1, "single_thread_sleep");
|
||||
}
|
||||
|
||||
fn multi_thread_scheduler_sleep(c: &mut Criterion) {
|
||||
do_sleep_test(c, 8, "multi_thread_sleep-8");
|
||||
}
|
||||
|
||||
fn do_sleep_test(c: &mut Criterion, workers: usize, name: &str) {
|
||||
let runtime = build_run_time(workers);
|
||||
|
||||
c.bench_function(name, |b| {
|
||||
b.iter_custom(|iters| {
|
||||
let start = Instant::now();
|
||||
runtime.block_on(async {
|
||||
black_box(spawn_sleep_job(iters as usize, workers)).await;
|
||||
});
|
||||
start.elapsed()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async fn spawn_sleep_job(iters: usize, procs: usize) {
|
||||
let mut handles = Vec::with_capacity(procs);
|
||||
for _ in 0..procs {
|
||||
handles.push(tokio::spawn(async move {
|
||||
for _ in 0..iters / procs {
|
||||
let _h = black_box(sleep(Duration::from_secs(1)));
|
||||
}
|
||||
}));
|
||||
}
|
||||
for handle in handles {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
timeout_benchmark,
|
||||
single_thread_scheduler_timeout,
|
||||
multi_thread_scheduler_timeout,
|
||||
single_thread_scheduler_sleep,
|
||||
multi_thread_scheduler_sleep
|
||||
);
|
||||
|
||||
criterion_main!(timeout_benchmark);
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Publish a new release of a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-v, --verbose Use verbose Cargo output
|
||||
-d, --dry-run Perform a dry run (do not publish or tag the release)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
DRY_RUN=""
|
||||
VERBOSE=""
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
verify() {
|
||||
status "Verifying" "if $CRATE v$VERSION can be released"
|
||||
ACTUAL=$(cargo pkgid | sed -n 's/.*#\(.*\)/\1/p')
|
||||
|
||||
if [ "$ACTUAL" != "$VERSION" ]; then
|
||||
err "expected to release version $VERSION, but Cargo.toml contained $ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git tag -l | grep -Fxq "$TAG" ; then
|
||||
err "git tag \`$TAG\` already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATH_DEPS=$(grep -F "path = \"" Cargo.toml | sed -e 's/^/ /')
|
||||
if [ -n "$PATH_DEPS" ]; then
|
||||
err "crate \`$CRATE\` contained path dependencies:\n$PATH_DEPS"
|
||||
echo "path dependencies must be removed prior to release"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
release() {
|
||||
status "Releasing" "$CRATE v$VERSION"
|
||||
cargo package $VERBOSE
|
||||
cargo publish $VERBOSE $DRY_RUN
|
||||
|
||||
status "Tagging" "$TAG"
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
echo "# git tag $TAG && git push --tags"
|
||||
else
|
||||
git tag "$TAG" && git push --tags
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE="--verbose"
|
||||
set +x
|
||||
shift
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
(cd "$CRATE" && verify && release )
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Update links to docs.rs in a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-d, --dry-run Perform a dry run (do not modify any file)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
c1grep() { grep "$@" || test $? = 1; }
|
||||
|
||||
update_versions_in_doc() {
|
||||
# Print what is being/would be done
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
local MSG="Would change:"
|
||||
else
|
||||
local MSG="Updating:"
|
||||
fi
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed --quiet \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|gp" \
|
||||
| sed -e "s/^/$MSG /"
|
||||
|
||||
# Apply changes if not in dry run
|
||||
if [ -z "$DRY_RUN" ]; then
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed -i \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|g"
|
||||
fi
|
||||
}
|
||||
|
||||
update() {
|
||||
update_versions_in_doc
|
||||
}
|
||||
|
||||
show_outdated() {
|
||||
OUTDATED=$(git grep -rn "docs.rs/$CRATE/" \
|
||||
| c1grep -v "$VERSION" \
|
||||
| sed -e 's/^/ - /')
|
||||
if [[ -n "$OUTDATED" ]]; then
|
||||
echo "Found the following links to docs.rs with an outdated version:"
|
||||
echo "$OUTDATED"
|
||||
echo
|
||||
else
|
||||
echo "Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
# Does not cd in order to update everywhere
|
||||
show_outdated && update
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+4
-7
@@ -2,7 +2,7 @@
|
||||
name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead, and delete the **path**.
|
||||
@@ -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.52"
|
||||
[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"
|
||||
@@ -90,7 +91,3 @@ path = "named-pipe-ready.rs"
|
||||
[[example]]
|
||||
name = "named-pipe-multi-client"
|
||||
path = "named-pipe-multi-client.rs"
|
||||
|
||||
[[example]]
|
||||
name = "dump"
|
||||
path = "dump.rs"
|
||||
|
||||
@@ -23,7 +23,7 @@ fn main() {
|
||||
|
||||
// Without the `HandleExt.wrap()` there would be a panic because there is
|
||||
// no timer running, since it would be referencing runtime r1.
|
||||
rt1.block_on(rt2.wrap(async move {
|
||||
let _ = rt1.block_on(rt2.wrap(async move {
|
||||
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
|
||||
println!("addr: {:?}", listener.local_addr());
|
||||
tx.send(()).unwrap();
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
|
||||
//! This example demonstrates tokio's experimental task dumping functionality.
|
||||
//! This application deadlocks. Input CTRL+C to display traces of each task, or
|
||||
//! input CTRL+C twice within 1 second to quit.
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Barrier;
|
||||
|
||||
#[inline(never)]
|
||||
async fn a(barrier: Arc<Barrier>) {
|
||||
b(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn b(barrier: Arc<Barrier>) {
|
||||
c(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn c(barrier: Arc<Barrier>) {
|
||||
barrier.wait().await;
|
||||
}
|
||||
|
||||
// Prints a task dump upon receipt of CTRL+C, or returns if CTRL+C is
|
||||
// inputted twice within a second.
|
||||
async fn dump_or_quit() {
|
||||
use tokio::time::{timeout, Duration, Instant};
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
let mut last_signal: Option<Instant> = None;
|
||||
// wait for CTRL+C
|
||||
while let Ok(_) = tokio::signal::ctrl_c().await {
|
||||
// exit if a CTRL+C is inputted twice within 1 second
|
||||
if let Some(time_since_last_signal) = last_signal.map(|i| i.elapsed()) {
|
||||
if time_since_last_signal < Duration::from_secs(1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
last_signal = Some(Instant::now());
|
||||
|
||||
// capture a dump, and print each trace
|
||||
println!("{:-<80}", "");
|
||||
if let Ok(dump) = timeout(Duration::from_secs(2), handle.dump()).await {
|
||||
for task in dump.tasks().iter() {
|
||||
let id = task.id();
|
||||
let trace = task.trace();
|
||||
println!("TASK {id}:");
|
||||
println!("{trace}\n");
|
||||
}
|
||||
} else {
|
||||
println!("Task dumping timed out. Use a native debugger (like gdb) to debug the deadlock.");
|
||||
}
|
||||
println!("{:-<80}", "");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
}
|
||||
}
|
||||
|
||||
println!("This program has a deadlock.");
|
||||
println!("Input CTRL+C to print a task dump.");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
|
||||
// oops! this barrier waits for one more task than will ever come.
|
||||
let barrier = Arc::new(Barrier::new(3));
|
||||
|
||||
let task_1 = tokio::spawn(a(barrier.clone()));
|
||||
let task_2 = tokio::spawn(a(barrier));
|
||||
|
||||
tokio::select!(
|
||||
_ = dump_or_quit() => {},
|
||||
_ = task_1 => {},
|
||||
_ = task_2 => {},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
)))]
|
||||
fn main() {
|
||||
println!("task dumps are not available")
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
+30
-12
@@ -22,7 +22,8 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::copy_bidirectional;
|
||||
use tokio::io;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use futures::FutureExt;
|
||||
@@ -43,19 +44,36 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Ok((mut inbound, _)) = listener.accept().await {
|
||||
let mut outbound = TcpStream::connect(server_addr.clone()).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
copy_bidirectional(&mut inbound, &mut outbound)
|
||||
.map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
}
|
||||
})
|
||||
.await
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(transfer);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<dyn Error>> {
|
||||
let mut outbound = TcpStream::connect(proxy_addr).await?;
|
||||
|
||||
let (mut ri, mut wi) = inbound.split();
|
||||
let (mut ro, mut wo) = outbound.split();
|
||||
|
||||
let client_to_server = async {
|
||||
io::copy(&mut ri, &mut wo).await?;
|
||||
wo.shutdown().await
|
||||
};
|
||||
|
||||
let server_to_client = async {
|
||||
io::copy(&mut ro, &mut wi).await?;
|
||||
wi.shutdown().await
|
||||
};
|
||||
|
||||
tokio::try_join!(client_to_server, server_to_client)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -180,11 +180,8 @@ impl Decoder for Http {
|
||||
headers[i] = Some((k, v));
|
||||
}
|
||||
|
||||
let method = http::Method::try_from(r.method.unwrap())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
(
|
||||
method,
|
||||
toslice(r.method.unwrap().as_bytes()),
|
||||
toslice(r.path.unwrap().as_bytes()),
|
||||
r.version.unwrap(),
|
||||
amt,
|
||||
@@ -198,7 +195,7 @@ impl Decoder for Http {
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
ret = ret.method(method);
|
||||
ret = ret.method(&data[method.0..method.1]);
|
||||
let s = data.slice(path.0..path.1);
|
||||
let s = unsafe { String::from_utf8_unchecked(Vec::from(s.as_ref())) };
|
||||
ret = ret.uri(s);
|
||||
@@ -259,11 +256,11 @@ mod date {
|
||||
unix_date: u64,
|
||||
}
|
||||
|
||||
thread_local!(static LAST: RefCell<LastRenderedNow> = const { RefCell::new(LastRenderedNow {
|
||||
thread_local!(static LAST: RefCell<LastRenderedNow> = RefCell::new(LastRenderedNow {
|
||||
bytes: [0; 128],
|
||||
amt: 0,
|
||||
unix_date: 0,
|
||||
}) });
|
||||
}));
|
||||
|
||||
impl fmt::Display for Now {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
||||
+1
-2
@@ -8,9 +8,8 @@
|
||||
RUSTDOCFLAGS="""
|
||||
--cfg docsrs \
|
||||
--cfg tokio_unstable \
|
||||
--cfg tokio_taskdump \
|
||||
"""
|
||||
RUSTFLAGS="--cfg tokio_unstable --cfg tokio_taskdump --cfg docsrs"
|
||||
RUSTFLAGS="--cfg tokio_unstable --cfg docsrs"
|
||||
|
||||
[[redirects]]
|
||||
from = "/"
|
||||
|
||||
-285
@@ -1,285 +0,0 @@
|
||||
285
|
||||
&
|
||||
+
|
||||
<
|
||||
=
|
||||
>
|
||||
\
|
||||
~
|
||||
—
|
||||
0o777
|
||||
0s
|
||||
0xA
|
||||
0xD
|
||||
100ms
|
||||
10ms
|
||||
~12
|
||||
±1m
|
||||
±1ms
|
||||
1ms
|
||||
1s
|
||||
250ms
|
||||
2x
|
||||
~4
|
||||
443
|
||||
450ms
|
||||
50ms
|
||||
8MB
|
||||
adaptor
|
||||
adaptors
|
||||
Adaptors
|
||||
AIO
|
||||
ambiant
|
||||
amongst
|
||||
api
|
||||
APIs
|
||||
async
|
||||
atomics
|
||||
awaitable
|
||||
backend
|
||||
backpressure
|
||||
backtrace
|
||||
backtraces
|
||||
backtracing
|
||||
binded
|
||||
bitfield
|
||||
bitfields
|
||||
Blockingly
|
||||
boolean
|
||||
broadcasted
|
||||
cancelled
|
||||
cancelling
|
||||
Cancelling
|
||||
CLI
|
||||
cloneable
|
||||
codebase
|
||||
codec
|
||||
codecs
|
||||
combinator
|
||||
combinators
|
||||
config
|
||||
Config
|
||||
connectionless
|
||||
coroutines
|
||||
cpu
|
||||
cpus
|
||||
Customizable
|
||||
datagram
|
||||
Datagram
|
||||
datagrams
|
||||
deallocate
|
||||
deallocated
|
||||
Deallocates
|
||||
decrementing
|
||||
dequeued
|
||||
deregister
|
||||
deregistered
|
||||
deregistering
|
||||
deregisters
|
||||
Deregisters
|
||||
deregistration
|
||||
descriptor's
|
||||
destructor
|
||||
destructors
|
||||
destructure
|
||||
Destructures
|
||||
Dev
|
||||
dns
|
||||
DNS
|
||||
DoS
|
||||
dwOpenMode
|
||||
endian
|
||||
enqueue
|
||||
enqueued
|
||||
EntryInner
|
||||
enum
|
||||
eof
|
||||
Erlang's
|
||||
errored
|
||||
EWMA
|
||||
expirations
|
||||
fcntl
|
||||
fd's
|
||||
FIFOs
|
||||
filename
|
||||
filesystem
|
||||
filesystems
|
||||
fn
|
||||
fns
|
||||
FreeBSD
|
||||
frontend
|
||||
fs
|
||||
functionalities
|
||||
getters
|
||||
GID
|
||||
goroutines
|
||||
Growable
|
||||
gzip
|
||||
hashmaps
|
||||
HashMaps
|
||||
hashsets
|
||||
ie
|
||||
Illumos
|
||||
impl
|
||||
implementers
|
||||
implementor
|
||||
implementors
|
||||
incrementing
|
||||
interoperate
|
||||
invariants
|
||||
Invariants
|
||||
io
|
||||
IOCP
|
||||
iOS
|
||||
IOs
|
||||
io_uring
|
||||
IP
|
||||
IPv4
|
||||
IPv6
|
||||
iteratively
|
||||
Kotlin's
|
||||
latencies
|
||||
Lauck
|
||||
libc
|
||||
lifecycle
|
||||
lifo
|
||||
lookups
|
||||
macOS
|
||||
MacOS
|
||||
Marsaglia's
|
||||
metadata
|
||||
mio
|
||||
Mio
|
||||
mio's
|
||||
misconfigured
|
||||
mock's
|
||||
mpmc
|
||||
mpsc
|
||||
multi
|
||||
Multi
|
||||
multicast
|
||||
Multithreaded
|
||||
mut
|
||||
mutex
|
||||
Mutex
|
||||
Nagle
|
||||
nonblocking
|
||||
nondecreasing
|
||||
noop
|
||||
ntasks
|
||||
ok
|
||||
oneshot
|
||||
ORed
|
||||
os
|
||||
overweighing
|
||||
parker
|
||||
parsers
|
||||
peekable
|
||||
PGID
|
||||
PID
|
||||
plaintext
|
||||
poller
|
||||
POSIX
|
||||
proxied
|
||||
qos
|
||||
RAII
|
||||
reallocations
|
||||
recv's
|
||||
refactors
|
||||
refcount
|
||||
refcounting
|
||||
repo
|
||||
repr
|
||||
representable
|
||||
reqwest
|
||||
reregistering
|
||||
resize
|
||||
resized
|
||||
RMW
|
||||
runtime
|
||||
runtime's
|
||||
runtimes
|
||||
rwlock
|
||||
rx
|
||||
scalability
|
||||
scheduler's
|
||||
semver
|
||||
setpgid
|
||||
sharded
|
||||
signalled
|
||||
signalling
|
||||
SmallCrush
|
||||
Solaris
|
||||
spawner
|
||||
Splitter
|
||||
spmc
|
||||
spsc
|
||||
src
|
||||
stabilised
|
||||
startup
|
||||
stateful
|
||||
stderr
|
||||
stdin
|
||||
stdout
|
||||
stealable
|
||||
stealer
|
||||
stealers
|
||||
struct
|
||||
structs
|
||||
subfield
|
||||
suboptimal
|
||||
subprocess
|
||||
superset
|
||||
symlink
|
||||
symlinks
|
||||
sys
|
||||
syscall
|
||||
syscalls
|
||||
tcp
|
||||
TCP
|
||||
TestU01
|
||||
threadpool
|
||||
timestamp
|
||||
timestamps
|
||||
TLS
|
||||
TOCTOU
|
||||
TODO
|
||||
tokio
|
||||
Tokio
|
||||
tokio's
|
||||
Tokio's
|
||||
tuple
|
||||
Tuple
|
||||
tx
|
||||
udp
|
||||
UDP
|
||||
UID
|
||||
unhandled
|
||||
unix
|
||||
unlink
|
||||
unpark
|
||||
Unpark
|
||||
unparked
|
||||
unparking
|
||||
unparks
|
||||
Unparks
|
||||
unreceived
|
||||
unsafety
|
||||
Unsets
|
||||
unsynchronized
|
||||
untrusted
|
||||
usecases
|
||||
Valgrind
|
||||
Varghese
|
||||
vec
|
||||
versa
|
||||
versioned
|
||||
versioning
|
||||
vtable
|
||||
waker
|
||||
wakers
|
||||
Wakers
|
||||
wakeup
|
||||
wakeups
|
||||
workstealing
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
dev_comments = false
|
||||
skip_readme = false
|
||||
|
||||
[Hunspell]
|
||||
lang = "en_US"
|
||||
search_dirs = ["."]
|
||||
extra_dictionaries = ["spellcheck.dic"]
|
||||
skip_os_lookups = true
|
||||
use_builtin = true
|
||||
|
||||
[Hunspell.quirks]
|
||||
allow_concatenation = true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "stress-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -42,7 +42,7 @@ fn main() {
|
||||
.write_all(one_mega_random_bytes.as_slice())
|
||||
.await
|
||||
.unwrap();
|
||||
let _ = stream.read(&mut buff).await.unwrap();
|
||||
stream.read(&mut buff).await.unwrap();
|
||||
}
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
This is used for the `no-atomic-u64-test` ci check that verifies that Tokio
|
||||
works even if the `AtomicU64` type is missing.
|
||||
|
||||
When increasing the nightly compiler version, you may need to regenerate this
|
||||
target using the following command:
|
||||
```
|
||||
rustc +nightly -Z unstable-options --print target-spec-json --target i686-unknown-linux-gnu | grep -v 'is-builtin' | sed 's/"max-atomic-width": 64/"max-atomic-width": 32/' > target-specs/i686-unknown-linux-gnu.json
|
||||
```
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"arch": "x86",
|
||||
"cpu": "pentium4",
|
||||
"crt-objects-fallback": "false",
|
||||
"crt-static-respected": true,
|
||||
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
|
||||
"dynamic-linking": true,
|
||||
"env": "gnu",
|
||||
"has-rpath": true,
|
||||
"has-thread-local": true,
|
||||
"linker-flavor": "gnu-cc",
|
||||
"llvm-target": "i686-unknown-linux-gnu",
|
||||
"max-atomic-width": 32,
|
||||
"metadata": {
|
||||
"description": null,
|
||||
"host_tools": null,
|
||||
"std": null,
|
||||
"tier": null
|
||||
},
|
||||
"os": "linux",
|
||||
"position-independent-executables": true,
|
||||
"pre-link-args": {
|
||||
"gnu-cc": [
|
||||
"-m32"
|
||||
],
|
||||
"gnu-lld-cc": [
|
||||
"-m32"
|
||||
]
|
||||
},
|
||||
"relro-level": "full",
|
||||
"stack-probes": {
|
||||
"kind": "inline"
|
||||
},
|
||||
"supported-sanitizers": [
|
||||
"address"
|
||||
],
|
||||
"supported-split-debuginfo": [
|
||||
"packed",
|
||||
"unpacked",
|
||||
"off"
|
||||
],
|
||||
"target-family": [
|
||||
"unix"
|
||||
],
|
||||
"target-pointer-width": "32"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tests-build"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
|
||||
@@ -36,36 +36,16 @@ async fn test_worker_threads_not_int() {}
|
||||
async fn test_worker_threads_and_current_thread() {}
|
||||
|
||||
#[tokio::test(crate = 456)]
|
||||
async fn test_crate_not_path_int() {}
|
||||
async fn test_crate_not_ident_int() {}
|
||||
|
||||
#[tokio::test(crate = "456")]
|
||||
async fn test_crate_not_path_invalid() {}
|
||||
async fn test_crate_not_ident_invalid() {}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", unhandled_panic = "shutdown_runtime")]
|
||||
async fn test_multi_thread_with_unhandled_panic() {}
|
||||
#[tokio::test(crate = "abc::edf")]
|
||||
async fn test_crate_not_ident_path() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[test]
|
||||
async fn test_has_second_test_attr() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[::core::prelude::v1::test]
|
||||
async fn test_has_second_test_attr_v1() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[core::prelude::rust_2015::test]
|
||||
async fn test_has_second_test_attr_rust_2015() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[::std::prelude::rust_2018::test]
|
||||
async fn test_has_second_test_attr_rust_2018() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[std::prelude::rust_2021::test]
|
||||
async fn test_has_second_test_attr_rust_2021() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test]
|
||||
async fn test_has_generated_second_test_attr() {}
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -1,121 +1,101 @@
|
||||
error: the `async` keyword is missing from the function declaration
|
||||
--> tests/fail/macros_invalid_input.rs:6:1
|
||||
--> $DIR/macros_invalid_input.rs:6:1
|
||||
|
|
||||
6 | fn main_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
|
||||
--> tests/fail/macros_invalid_input.rs:8:15
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
|
||||
--> $DIR/macros_invalid_input.rs:8:15
|
||||
|
|
||||
8 | #[tokio::main(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Must have specified ident
|
||||
--> tests/fail/macros_invalid_input.rs:11:15
|
||||
--> $DIR/macros_invalid_input.rs:11:15
|
||||
|
|
||||
11 | #[tokio::main(threadpool::bar)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: the `async` keyword is missing from the function declaration
|
||||
--> tests/fail/macros_invalid_input.rs:15:1
|
||||
--> $DIR/macros_invalid_input.rs:15:1
|
||||
|
|
||||
15 | fn test_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
|
||||
--> tests/fail/macros_invalid_input.rs:17:15
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
|
||||
--> $DIR/macros_invalid_input.rs:17:15
|
||||
|
|
||||
17 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`
|
||||
--> tests/fail/macros_invalid_input.rs:20:15
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
|
||||
--> $DIR/macros_invalid_input.rs:20:15
|
||||
|
|
||||
20 | #[tokio::test(foo = 123)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: Failed to parse value of `flavor` as string.
|
||||
--> tests/fail/macros_invalid_input.rs:23:24
|
||||
--> $DIR/macros_invalid_input.rs:23:24
|
||||
|
|
||||
23 | #[tokio::test(flavor = 123)]
|
||||
| ^^^
|
||||
|
||||
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
|
||||
--> tests/fail/macros_invalid_input.rs:26:24
|
||||
--> $DIR/macros_invalid_input.rs:26:24
|
||||
|
|
||||
26 | #[tokio::test(flavor = "foo")]
|
||||
| ^^^^^
|
||||
|
||||
error: The `start_paused` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
|
||||
--> tests/fail/macros_invalid_input.rs:29:55
|
||||
--> $DIR/macros_invalid_input.rs:29:55
|
||||
|
|
||||
29 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
|
||||
| ^^^^^
|
||||
|
||||
error: Failed to parse value of `worker_threads` as integer.
|
||||
--> tests/fail/macros_invalid_input.rs:32:57
|
||||
--> $DIR/macros_invalid_input.rs:32:57
|
||||
|
|
||||
32 | #[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")]`
|
||||
--> tests/fail/macros_invalid_input.rs:35:59
|
||||
--> $DIR/macros_invalid_input.rs:35:59
|
||||
|
|
||||
35 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
|
||||
| ^
|
||||
|
||||
error: Failed to parse value of `crate` as path.
|
||||
--> tests/fail/macros_invalid_input.rs:38:23
|
||||
error: Failed to parse value of `crate` as ident.
|
||||
--> $DIR/macros_invalid_input.rs:38:23
|
||||
|
|
||||
38 | #[tokio::test(crate = 456)]
|
||||
| ^^^
|
||||
|
||||
error: Failed to parse value of `crate` as path: "456"
|
||||
--> tests/fail/macros_invalid_input.rs:41:23
|
||||
error: Failed to parse value of `crate` as ident: "456"
|
||||
--> $DIR/macros_invalid_input.rs:41:23
|
||||
|
|
||||
41 | #[tokio::test(crate = "456")]
|
||||
| ^^^^^
|
||||
|
||||
error: The `unhandled_panic` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
|
||||
--> tests/fail/macros_invalid_input.rs:44:58
|
||||
error: Failed to parse value of `crate` as ident: "abc::edf"
|
||||
--> $DIR/macros_invalid_input.rs:44:23
|
||||
|
|
||||
44 | #[tokio::test(flavor = "multi_thread", unhandled_panic = "shutdown_runtime")]
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
44 | #[tokio::test(crate = "abc::edf")]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> tests/fail/macros_invalid_input.rs:48:1
|
||||
error: second test attribute is supplied
|
||||
--> $DIR/macros_invalid_input.rs:48:1
|
||||
|
|
||||
48 | #[test]
|
||||
| ^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> tests/fail/macros_invalid_input.rs:52:1
|
||||
error: duplicated attribute
|
||||
--> $DIR/macros_invalid_input.rs:48:1
|
||||
|
|
||||
52 | #[::core::prelude::v1::test]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> tests/fail/macros_invalid_input.rs:56:1
|
||||
48 | #[test]
|
||||
| ^^^^^^^
|
||||
|
|
||||
56 | #[core::prelude::rust_2015::test]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> tests/fail/macros_invalid_input.rs:60:1
|
||||
note: the lint level is defined here
|
||||
--> $DIR/macros_invalid_input.rs:1:9
|
||||
|
|
||||
60 | #[::std::prelude::rust_2018::test]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> tests/fail/macros_invalid_input.rs:64:1
|
||||
|
|
||||
64 | #[std::prelude::rust_2021::test]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied, consider removing or changing the order of your test attributes
|
||||
--> tests/fail/macros_invalid_input.rs:67:1
|
||||
|
|
||||
67 | #[tokio::test]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `tokio::test` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
1 | #![deny(duplicate_macro_attributes)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1,45 +1,33 @@
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:5:5
|
||||
--> $DIR/macros_type_mismatch.rs:5:5
|
||||
|
|
||||
4 | async fn missing_semicolon_or_return_type() {
|
||||
| - help: a return type might be missing here: `-> _`
|
||||
5 | Ok(())
|
||||
| ^^^^^^ expected `()`, found `Result<(), _>`
|
||||
| ^^^^^^ expected `()`, found enum `Result`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
help: a return type might be missing here
|
||||
|
|
||||
4 | async fn missing_semicolon_or_return_type() -> _ {
|
||||
| ++++
|
||||
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
||||
|
|
||||
5 | Ok(()).expect("REASON")
|
||||
| +++++++++++++++++
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:10:5
|
||||
--> $DIR/macros_type_mismatch.rs:10:5
|
||||
|
|
||||
9 | async fn missing_return_type() {
|
||||
| - help: a return type might be missing here: `-> _`
|
||||
10 | return Ok(());
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found enum `Result`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
help: a return type might be missing here
|
||||
|
|
||||
9 | async fn missing_return_type() -> _ {
|
||||
| ++++
|
||||
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
||||
|
|
||||
10 | return Ok(());.expect("REASON")
|
||||
| +++++++++++++++++
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:23:5
|
||||
--> $DIR/macros_type_mismatch.rs:23:5
|
||||
|
|
||||
14 | async fn extra_semicolon() -> Result<(), ()> {
|
||||
| -------------- expected `Result<(), ()>` because of return type
|
||||
...
|
||||
23 | Ok(());
|
||||
| ^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
| ^^^^^^^ expected enum `Result`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
@@ -50,10 +38,10 @@ help: try adding an expression at the end of the block
|
||||
|
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:32:5
|
||||
--> $DIR/macros_type_mismatch.rs:32:5
|
||||
|
|
||||
30 | async fn issue_4635() {
|
||||
| - help: try adding a return type: `-> i32`
|
||||
| - help: try adding a return type: `-> i32`
|
||||
31 | return 1;
|
||||
32 | ;
|
||||
| ^ expected `()`, found integer
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
@@ -39,7 +39,6 @@ rt-process-signal = ["rt-net", "tokio/process", "tokio/signal"]
|
||||
# This is an explicit feature so we can use `cargo hack` testing single features
|
||||
# instead of all possible permutations.
|
||||
wasi-rt = ["rt", "macros", "sync"]
|
||||
wasi-threads-rt = ["wasi-rt", "rt-multi-thread"]
|
||||
|
||||
full = [
|
||||
"macros",
|
||||
@@ -59,4 +58,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,4 +1,8 @@
|
||||
#![cfg(all(feature = "macros", feature = "rt-multi-thread"))]
|
||||
#![cfg(all(
|
||||
feature = "macros",
|
||||
feature = "rt-multi-thread",
|
||||
not(target_os = "wasi")
|
||||
))]
|
||||
|
||||
#[tokio::main]
|
||||
async fn basic_main() -> usize {
|
||||
|
||||
@@ -4,10 +4,7 @@ use futures::channel::oneshot;
|
||||
use futures::executor::block_on;
|
||||
use std::thread;
|
||||
|
||||
#[cfg_attr(
|
||||
not(feature = "rt-multi-thread"),
|
||||
ignore = "WASI: std::thread::spawn not supported"
|
||||
)]
|
||||
#[cfg_attr(target_os = "wasi", ignore = "WASI: std::thread::spawn not supported")]
|
||||
#[test]
|
||||
fn join_with_select() {
|
||||
block_on(async {
|
||||
|
||||
@@ -7,6 +7,7 @@ use tokio::process::{Child, Command};
|
||||
use tokio_test::assert_ok;
|
||||
|
||||
use futures::future::{self, FutureExt};
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
@@ -189,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");
|
||||
}
|
||||
|
||||
@@ -1,66 +1,3 @@
|
||||
# 2.4.0 (July 22nd, 2024)
|
||||
|
||||
- msrv: increase MSRV to 1.70 ([#6645])
|
||||
- macros: allow `unhandled_panic` behavior for `#[tokio::main]` and `#[tokio::test]` ([#6593])
|
||||
|
||||
[#6593]: https://github.com/tokio-rs/tokio/pull/6593
|
||||
[#6645]: https://github.com/tokio-rs/tokio/pull/6645
|
||||
|
||||
# 2.3.0 (May 30th, 2024)
|
||||
|
||||
- macros: make `#[tokio::test]` append `#[test]` at the end of the attribute list ([#6497])
|
||||
|
||||
[#6497]: https://github.com/tokio-rs/tokio/pull/6497
|
||||
|
||||
# 2.2.0 (November 19th, 2023)
|
||||
|
||||
### Changed
|
||||
|
||||
- use `::core` qualified imports instead of `::std` inside `tokio::test` macro ([#5973])
|
||||
|
||||
[#5973]: https://github.com/tokio-rs/tokio/pull/5973
|
||||
|
||||
# 2.1.0 (April 25th, 2023)
|
||||
|
||||
- macros: fix typo in `#[tokio::test]` docs ([#5636])
|
||||
- macros: make entrypoints more efficient ([#5621])
|
||||
|
||||
[#5621]: https://github.com/tokio-rs/tokio/pull/5621
|
||||
[#5636]: https://github.com/tokio-rs/tokio/pull/5636
|
||||
|
||||
# 2.0.0 (March 24th, 2023)
|
||||
|
||||
This major release updates the dependency on the syn crate to 2.0.0, and
|
||||
increases the MSRV to 1.56.
|
||||
|
||||
As part of this release, we are adopting a policy of depending on a specific minor
|
||||
release of tokio-macros. This prevents Tokio from being able to pull in many different
|
||||
versions of tokio-macros.
|
||||
|
||||
- macros: update `syn` ([#5572])
|
||||
- macros: accept path as crate rename ([#5557])
|
||||
|
||||
[#5572]: https://github.com/tokio-rs/tokio/pull/5572
|
||||
[#5557]: https://github.com/tokio-rs/tokio/pull/5557
|
||||
|
||||
# 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])
|
||||
|
||||
@@ -3,10 +3,10 @@ name = "tokio-macros"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.x.y" git tag.
|
||||
version = "2.4.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
# - Create "tokio-macros-1.0.x" git tag.
|
||||
version = "1.8.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -22,9 +22,9 @@ proc-macro = true
|
||||
[features]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.60"
|
||||
proc-macro2 = "1.0.7"
|
||||
quote = "1"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
syn = { version = "1.0.56", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
+27
-2
@@ -1,7 +1,32 @@
|
||||
MIT License
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Yoshua Wuyts
|
||||
Copyright (c) Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+90
-318
@@ -1,10 +1,10 @@
|
||||
use proc_macro2::{Span, TokenStream, TokenTree};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::parse::{Parse, ParseStream, Parser};
|
||||
use syn::{braced, Attribute, Ident, Path, Signature, Visibility};
|
||||
use syn::parse::Parser;
|
||||
|
||||
// syn::AttributeArgs does not implement syn::Parse
|
||||
type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
|
||||
type AttributeArgs = syn::punctuated::Punctuated<syn::NestedMeta, syn::Token![,]>;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum RuntimeFlavor {
|
||||
@@ -25,37 +25,11 @@ impl RuntimeFlavor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum UnhandledPanic {
|
||||
Ignore,
|
||||
ShutdownRuntime,
|
||||
}
|
||||
|
||||
impl UnhandledPanic {
|
||||
fn from_str(s: &str) -> Result<UnhandledPanic, String> {
|
||||
match s {
|
||||
"ignore" => Ok(UnhandledPanic::Ignore),
|
||||
"shutdown_runtime" => Ok(UnhandledPanic::ShutdownRuntime),
|
||||
_ => Err(format!("No such unhandled panic behavior `{}`. The unhandled panic behaviors are `ignore` and `shutdown_runtime`.", s)),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_tokens(self, crate_path: &TokenStream) -> TokenStream {
|
||||
match self {
|
||||
UnhandledPanic::Ignore => quote! { #crate_path::runtime::UnhandledPanic::Ignore },
|
||||
UnhandledPanic::ShutdownRuntime => {
|
||||
quote! { #crate_path::runtime::UnhandledPanic::ShutdownRuntime }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FinalConfig {
|
||||
flavor: RuntimeFlavor,
|
||||
worker_threads: Option<usize>,
|
||||
start_paused: Option<bool>,
|
||||
crate_name: Option<Path>,
|
||||
unhandled_panic: Option<UnhandledPanic>,
|
||||
crate_name: Option<String>,
|
||||
}
|
||||
|
||||
/// Config used in case of the attribute not being able to build a valid config
|
||||
@@ -64,7 +38,6 @@ const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
|
||||
worker_threads: None,
|
||||
start_paused: None,
|
||||
crate_name: None,
|
||||
unhandled_panic: None,
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
@@ -74,8 +47,7 @@ struct Configuration {
|
||||
worker_threads: Option<(usize, Span)>,
|
||||
start_paused: Option<(bool, Span)>,
|
||||
is_test: bool,
|
||||
crate_name: Option<Path>,
|
||||
unhandled_panic: Option<(UnhandledPanic, Span)>,
|
||||
crate_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
@@ -91,7 +63,6 @@ impl Configuration {
|
||||
start_paused: None,
|
||||
is_test,
|
||||
crate_name: None,
|
||||
unhandled_panic: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,27 +112,8 @@ impl Configuration {
|
||||
if self.crate_name.is_some() {
|
||||
return Err(syn::Error::new(span, "`crate` set multiple times."));
|
||||
}
|
||||
let name_path = parse_path(name, span, "crate")?;
|
||||
self.crate_name = Some(name_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_unhandled_panic(
|
||||
&mut self,
|
||||
unhandled_panic: syn::Lit,
|
||||
span: Span,
|
||||
) -> Result<(), syn::Error> {
|
||||
if self.unhandled_panic.is_some() {
|
||||
return Err(syn::Error::new(
|
||||
span,
|
||||
"`unhandled_panic` set multiple times.",
|
||||
));
|
||||
}
|
||||
|
||||
let unhandled_panic = parse_string(unhandled_panic, span, "unhandled_panic")?;
|
||||
let unhandled_panic =
|
||||
UnhandledPanic::from_str(&unhandled_panic).map_err(|err| syn::Error::new(span, err))?;
|
||||
self.unhandled_panic = Some((unhandled_panic, span));
|
||||
let name_ident = parse_ident(name, span, "crate")?;
|
||||
self.crate_name = Some(name_ident.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -174,22 +126,22 @@ impl Configuration {
|
||||
}
|
||||
|
||||
fn build(&self) -> Result<FinalConfig, syn::Error> {
|
||||
use RuntimeFlavor as F;
|
||||
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
use RuntimeFlavor::*;
|
||||
|
||||
let worker_threads = match (flavor, self.worker_threads) {
|
||||
(F::CurrentThread, Some((_, worker_threads_span))) => {
|
||||
(CurrentThread, Some((_, worker_threads_span))) => {
|
||||
let msg = format!(
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(worker_threads_span, msg));
|
||||
}
|
||||
(F::CurrentThread, None) => None,
|
||||
(F::Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
(CurrentThread, None) => None,
|
||||
(Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
worker_threads.map(|(val, _span)| val)
|
||||
}
|
||||
(F::Threaded, _) => {
|
||||
(Threaded, _) => {
|
||||
let msg = if self.flavor.is_none() {
|
||||
"The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled."
|
||||
} else {
|
||||
@@ -200,26 +152,14 @@ impl Configuration {
|
||||
};
|
||||
|
||||
let start_paused = match (flavor, self.start_paused) {
|
||||
(F::Threaded, Some((_, start_paused_span))) => {
|
||||
(Threaded, Some((_, start_paused_span))) => {
|
||||
let msg = format!(
|
||||
"The `start_paused` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(start_paused_span, msg));
|
||||
}
|
||||
(F::CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
let unhandled_panic = match (flavor, self.unhandled_panic) {
|
||||
(F::Threaded, Some((_, unhandled_panic_span))) => {
|
||||
let msg = format!(
|
||||
"The `unhandled_panic` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(unhandled_panic_span, msg));
|
||||
}
|
||||
(F::CurrentThread, Some((unhandled_panic, _))) => Some(unhandled_panic),
|
||||
(CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
@@ -228,7 +168,6 @@ impl Configuration {
|
||||
flavor,
|
||||
worker_threads,
|
||||
start_paused,
|
||||
unhandled_panic,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -260,22 +199,23 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_path(lit: syn::Lit, span: Span, field: &str) -> Result<Path, syn::Error> {
|
||||
fn parse_ident(lit: syn::Lit, span: Span, field: &str) -> Result<Ident, syn::Error> {
|
||||
match lit {
|
||||
syn::Lit::Str(s) => {
|
||||
let err = syn::Error::new(
|
||||
span,
|
||||
format!(
|
||||
"Failed to parse value of `{}` as path: \"{}\"",
|
||||
"Failed to parse value of `{}` as ident: \"{}\"",
|
||||
field,
|
||||
s.value()
|
||||
),
|
||||
);
|
||||
s.parse::<syn::Path>().map_err(|_| err.clone())
|
||||
let path = s.parse::<syn::Path>().map_err(|_| err.clone())?;
|
||||
path.get_ident().cloned().ok_or(err)
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as path.", field),
|
||||
format!("Failed to parse value of `{}` as ident.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -291,7 +231,7 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
|
||||
}
|
||||
|
||||
fn build_config(
|
||||
input: &ItemFn,
|
||||
input: syn::ItemFn,
|
||||
args: AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_multi_thread: bool,
|
||||
@@ -306,7 +246,7 @@ fn build_config(
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
syn::Meta::NameValue(namevalue) => {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
|
||||
let ident = namevalue
|
||||
.path
|
||||
.get_ident()
|
||||
@@ -315,41 +255,45 @@ fn build_config(
|
||||
})?
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
let lit = match &namevalue.value {
|
||||
syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit,
|
||||
expr => return Err(syn::Error::new_spanned(expr, "Must be a literal")),
|
||||
};
|
||||
match ident.as_str() {
|
||||
"worker_threads" => {
|
||||
config.set_worker_threads(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
config.set_worker_threads(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
}
|
||||
"flavor" => {
|
||||
config.set_flavor(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
config.set_flavor(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
}
|
||||
"start_paused" => {
|
||||
config.set_start_paused(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
config.set_start_paused(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
}
|
||||
"core_threads" => {
|
||||
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
"crate" => {
|
||||
config.set_crate_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"unhandled_panic" => {
|
||||
config
|
||||
.set_unhandled_panic(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
config.set_crate_name(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
}
|
||||
name => {
|
||||
let msg = format!(
|
||||
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
|
||||
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`",
|
||||
name,
|
||||
);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::Meta::Path(path) => {
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
||||
let name = path
|
||||
.get_ident()
|
||||
.ok_or_else(|| syn::Error::new_spanned(&path, "Must have specified ident"))?
|
||||
@@ -368,11 +312,11 @@ fn build_config(
|
||||
macro_name
|
||||
)
|
||||
}
|
||||
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
|
||||
"flavor" | "worker_threads" | "start_paused" => {
|
||||
format!("The `{}` attribute requires an argument.", name)
|
||||
}
|
||||
name => {
|
||||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.", name)
|
||||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`", name)
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
@@ -389,13 +333,18 @@ fn build_config(
|
||||
config.build()
|
||||
}
|
||||
|
||||
fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenStream {
|
||||
fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> TokenStream {
|
||||
input.sig.asyncness = None;
|
||||
|
||||
// If type mismatch occurs, the current rustc points to the last statement.
|
||||
let (last_stmt_start_span, last_stmt_end_span) = {
|
||||
let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter();
|
||||
|
||||
let mut last_stmt = input
|
||||
.block
|
||||
.stmts
|
||||
.last()
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_default()
|
||||
.into_iter();
|
||||
// `Span` on stable Rust has a limitation that only points to the first
|
||||
// token, not the whole tokens. We can work around this limitation by
|
||||
// using the first/last span of the tokens like
|
||||
@@ -405,31 +354,26 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
(start, end)
|
||||
};
|
||||
|
||||
let crate_path = config
|
||||
.crate_name
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream());
|
||||
let crate_name = config.crate_name.as_deref().unwrap_or("tokio");
|
||||
|
||||
let crate_ident = Ident::new(crate_name, last_stmt_start_span);
|
||||
|
||||
let mut rt = match config.flavor {
|
||||
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_path::runtime::Builder::new_current_thread()
|
||||
#crate_ident::runtime::Builder::new_current_thread()
|
||||
},
|
||||
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_path::runtime::Builder::new_multi_thread()
|
||||
#crate_ident::runtime::Builder::new_multi_thread()
|
||||
},
|
||||
};
|
||||
if let Some(v) = config.worker_threads {
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) };
|
||||
rt = quote! { #rt.worker_threads(#v) };
|
||||
}
|
||||
if let Some(v) = config.start_paused {
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) };
|
||||
}
|
||||
if let Some(v) = config.unhandled_panic {
|
||||
let unhandled_panic = v.into_tokens(&crate_path);
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) };
|
||||
rt = quote! { #rt.start_paused(#v) };
|
||||
}
|
||||
|
||||
let generated_attrs = if is_test {
|
||||
let header = if is_test {
|
||||
quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
}
|
||||
@@ -437,61 +381,43 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let body_ident = quote! { body };
|
||||
let last_block = quote_spanned! {last_stmt_end_span=>
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
let body = &input.block;
|
||||
let brace_token = input.block.brace_token;
|
||||
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
|
||||
{
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(#body_ident);
|
||||
let body = async #body;
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
{
|
||||
return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(body);
|
||||
}
|
||||
}
|
||||
})
|
||||
.expect("Parsing failure");
|
||||
input.block.brace_token = brace_token;
|
||||
|
||||
let result = quote! {
|
||||
#header
|
||||
#input
|
||||
};
|
||||
|
||||
let body = input.body();
|
||||
|
||||
// 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_path::pin!(body);
|
||||
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let body = async #body;
|
||||
}
|
||||
};
|
||||
|
||||
input.into_tokens(generated_attrs, body, last_block)
|
||||
result.into()
|
||||
}
|
||||
|
||||
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||
tokens.extend(error.into_compile_error());
|
||||
tokens.extend(TokenStream::from(error.into_compile_error()));
|
||||
tokens
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let input: ItemFn = match syn::parse2(item.clone()) {
|
||||
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
@@ -501,8 +427,8 @@ pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
Err(syn::Error::new_spanned(&input.sig.ident, msg))
|
||||
} else {
|
||||
AttributeArgs::parse_terminated
|
||||
.parse2(args)
|
||||
.and_then(|args| build_config(&input, args, false, rt_multi_thread))
|
||||
.parse(args)
|
||||
.and_then(|args| build_config(input.clone(), args, false, rt_multi_thread))
|
||||
};
|
||||
|
||||
match config {
|
||||
@@ -511,50 +437,21 @@ pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether given attribute is a test attribute of forms:
|
||||
// * `#[test]`
|
||||
// * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]`
|
||||
// * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]`
|
||||
fn is_test_attribute(attr: &Attribute) -> bool {
|
||||
let path = match &attr.meta {
|
||||
syn::Meta::Path(path) => path,
|
||||
_ => return false,
|
||||
};
|
||||
let candidates = [
|
||||
["core", "prelude", "*", "test"],
|
||||
["std", "prelude", "*", "test"],
|
||||
];
|
||||
if path.leading_colon.is_none()
|
||||
&& path.segments.len() == 1
|
||||
&& path.segments[0].arguments.is_none()
|
||||
&& path.segments[0].ident == "test"
|
||||
{
|
||||
return true;
|
||||
} else if path.segments.len() != candidates[0].len() {
|
||||
return false;
|
||||
}
|
||||
candidates.into_iter().any(|segments| {
|
||||
path.segments.iter().zip(segments).all(|(segment, path)| {
|
||||
segment.arguments.is_none() && (path == "*" || segment.ident == path)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let input: ItemFn = match syn::parse2(item.clone()) {
|
||||
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
let config = if let Some(attr) = input.attrs().find(|attr| is_test_attribute(attr)) {
|
||||
let msg = "second test attribute is supplied, consider removing or changing the order of your test attributes";
|
||||
Err(syn::Error::new_spanned(attr, msg))
|
||||
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))
|
||||
} else {
|
||||
AttributeArgs::parse_terminated
|
||||
.parse2(args)
|
||||
.and_then(|args| build_config(&input, args, true, rt_multi_thread))
|
||||
.parse(args)
|
||||
.and_then(|args| build_config(input.clone(), args, true, rt_multi_thread))
|
||||
};
|
||||
|
||||
match config {
|
||||
@@ -562,128 +459,3 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e),
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemFn {
|
||||
outer_attrs: Vec<Attribute>,
|
||||
vis: Visibility,
|
||||
sig: Signature,
|
||||
brace_token: syn::token::Brace,
|
||||
inner_attrs: Vec<Attribute>,
|
||||
stmts: Vec<proc_macro2::TokenStream>,
|
||||
}
|
||||
|
||||
impl ItemFn {
|
||||
/// Access all attributes of the function item.
|
||||
fn attrs(&self) -> impl Iterator<Item = &Attribute> {
|
||||
self.outer_attrs.iter().chain(self.inner_attrs.iter())
|
||||
}
|
||||
|
||||
/// Get the body of the function item in a manner so that it can be
|
||||
/// conveniently used with the `quote!` macro.
|
||||
fn body(&self) -> Body<'_> {
|
||||
Body {
|
||||
brace_token: self.brace_token,
|
||||
stmts: &self.stmts,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert our local function item into a token stream.
|
||||
fn into_tokens(
|
||||
self,
|
||||
generated_attrs: proc_macro2::TokenStream,
|
||||
body: proc_macro2::TokenStream,
|
||||
last_block: proc_macro2::TokenStream,
|
||||
) -> TokenStream {
|
||||
let mut tokens = proc_macro2::TokenStream::new();
|
||||
// Outer attributes are simply streamed as-is.
|
||||
for attr in self.outer_attrs {
|
||||
attr.to_tokens(&mut tokens);
|
||||
}
|
||||
|
||||
// Inner attributes require extra care, since they're not supported on
|
||||
// blocks (which is what we're expanded into) we instead lift them
|
||||
// outside of the function. This matches the behavior of `syn`.
|
||||
for mut attr in self.inner_attrs {
|
||||
attr.style = syn::AttrStyle::Outer;
|
||||
attr.to_tokens(&mut tokens);
|
||||
}
|
||||
|
||||
// Add generated macros at the end, so macros processed later are aware of them.
|
||||
generated_attrs.to_tokens(&mut tokens);
|
||||
|
||||
self.vis.to_tokens(&mut tokens);
|
||||
self.sig.to_tokens(&mut tokens);
|
||||
|
||||
self.brace_token.surround(&mut tokens, |tokens| {
|
||||
body.to_tokens(tokens);
|
||||
last_block.to_tokens(tokens);
|
||||
});
|
||||
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ItemFn {
|
||||
#[inline]
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
// This parse implementation has been largely lifted from `syn`, with
|
||||
// the exception of:
|
||||
// * We don't have access to the plumbing necessary to parse inner
|
||||
// attributes in-place.
|
||||
// * We do our own statements parsing to avoid recursively parsing
|
||||
// entire statements and only look for the parts we're interested in.
|
||||
|
||||
let outer_attrs = input.call(Attribute::parse_outer)?;
|
||||
let vis: Visibility = input.parse()?;
|
||||
let sig: Signature = input.parse()?;
|
||||
|
||||
let content;
|
||||
let brace_token = braced!(content in input);
|
||||
let inner_attrs = Attribute::parse_inner(&content)?;
|
||||
|
||||
let mut buf = proc_macro2::TokenStream::new();
|
||||
let mut stmts = Vec::new();
|
||||
|
||||
while !content.is_empty() {
|
||||
if let Some(semi) = content.parse::<Option<syn::Token![;]>>()? {
|
||||
semi.to_tokens(&mut buf);
|
||||
stmts.push(buf);
|
||||
buf = proc_macro2::TokenStream::new();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse a single token tree and extend our current buffer with it.
|
||||
// This avoids parsing the entire content of the sub-tree.
|
||||
buf.extend([content.parse::<TokenTree>()?]);
|
||||
}
|
||||
|
||||
if !buf.is_empty() {
|
||||
stmts.push(buf);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
outer_attrs,
|
||||
vis,
|
||||
sig,
|
||||
brace_token,
|
||||
inner_attrs,
|
||||
stmts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Body<'a> {
|
||||
brace_token: syn::token::Brace,
|
||||
// Statements, with terminating `;`.
|
||||
stmts: &'a [TokenStream],
|
||||
}
|
||||
|
||||
impl ToTokens for Body<'_> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
self.brace_token.surround(tokens, |tokens| {
|
||||
for stmt in self.stmts {
|
||||
stmt.to_tokens(tokens);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+10
-112
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -40,13 +39,6 @@ use proc_macro::TokenStream;
|
||||
/// function is called often, it is preferable to create the runtime using the
|
||||
/// runtime builder so the runtime can be reused across calls.
|
||||
///
|
||||
/// # Non-worker async function
|
||||
///
|
||||
/// Note that the async function marked with this macro does not run as a
|
||||
/// worker. The expectation is that other tasks are spawned by the function here.
|
||||
/// Awaiting on other futures from the function provided here will not
|
||||
/// perform as fast as those spawned as workers.
|
||||
///
|
||||
/// # Multi-threaded runtime
|
||||
///
|
||||
/// To use the multi-threaded runtime, the macro can be configured using
|
||||
@@ -202,62 +194,15 @@ use proc_macro::TokenStream;
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Configure unhandled panic behavior
|
||||
///
|
||||
/// Available options are `shutdown_runtime` and `ignore`. For more details, see
|
||||
/// [`Builder::unhandled_panic`].
|
||||
///
|
||||
/// This option is only compatible with the `current_thread` runtime.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![allow(unknown_lints, unexpected_cfgs)]
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[tokio::main(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
|
||||
/// async fn main() {
|
||||
/// let _ = tokio::spawn(async {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![allow(unknown_lints, unexpected_cfgs)]
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// let _ = tokio::spawn(async {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// })
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// **Note**: This option depends on Tokio's [unstable API][unstable]. See [the
|
||||
/// documentation on unstable features][unstable] for details on how to enable
|
||||
/// Tokio's unstable features.
|
||||
///
|
||||
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
|
||||
/// [unstable]: ../tokio/index.html#unstable-features
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args.into(), item.into(), true).into()
|
||||
entry::main(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime. 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.
|
||||
/// [Builder](../tokio/runtime/struct.builder.html) directly.
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
@@ -315,8 +260,9 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args.into(), item.into(), false).into()
|
||||
entry::main(args, item, false)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment.
|
||||
@@ -342,7 +288,8 @@ 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.
|
||||
/// defaults to the number of cpus on the system. This is the default
|
||||
/// flavor.
|
||||
///
|
||||
/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
|
||||
/// flag.
|
||||
@@ -412,7 +359,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ### Set number of worker threads
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
/// #[tokio::test(flavor ="multi_thread", worker_threads = 2)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
@@ -471,58 +418,9 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Configure unhandled panic behavior
|
||||
///
|
||||
/// Available options are `shutdown_runtime` and `ignore`. For more details, see
|
||||
/// [`Builder::unhandled_panic`].
|
||||
///
|
||||
/// This option is only compatible with the `current_thread` runtime.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![allow(unknown_lints, unexpected_cfgs)]
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[tokio::test(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
|
||||
/// async fn my_test() {
|
||||
/// let _ = tokio::spawn(async {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::test]`
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![allow(unknown_lints, unexpected_cfgs)]
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[test]
|
||||
/// fn my_test() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// let _ = tokio::spawn(async {
|
||||
/// panic!("This panic will shutdown the runtime.");
|
||||
/// }).await;
|
||||
/// })
|
||||
/// }
|
||||
/// # #[cfg(not(tokio_unstable))]
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// **Note**: This option depends on Tokio's [unstable API][unstable]. See [the
|
||||
/// documentation on unstable features][unstable] for details on how to enable
|
||||
/// Tokio's unstable features.
|
||||
///
|
||||
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
|
||||
/// [unstable]: ../tokio/index.html#unstable-features
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args.into(), item.into(), true).into()
|
||||
entry::test(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
@@ -537,7 +435,7 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args.into(), item.into(), false).into()
|
||||
entry::test(args, item, false)
|
||||
}
|
||||
|
||||
/// Always fails with the error message below.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{parse::Parser, Ident};
|
||||
use syn::Ident;
|
||||
|
||||
pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
// passed in is: `(_ _ _)` with one `_` per branch
|
||||
@@ -46,7 +46,7 @@ pub(crate) fn clean_pattern_macro(input: TokenStream) -> TokenStream {
|
||||
// If this isn't a pattern, we return the token stream as-is. The select!
|
||||
// macro is using it in a location requiring a pattern, so an error will be
|
||||
// emitted there.
|
||||
let mut input: syn::Pat = match syn::Pat::parse_single.parse(input.clone()) {
|
||||
let mut input: syn::Pat = match syn::parse(input.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(_) => return input,
|
||||
};
|
||||
@@ -58,6 +58,7 @@ pub(crate) fn clean_pattern_macro(input: TokenStream) -> TokenStream {
|
||||
// Removes any occurrences of ref or mut in the provided pattern.
|
||||
fn clean_pattern(pat: &mut syn::Pat) {
|
||||
match pat {
|
||||
syn::Pat::Box(_box) => {}
|
||||
syn::Pat::Lit(_literal) => {}
|
||||
syn::Pat::Macro(_macro) => {}
|
||||
syn::Pat::Path(_path) => {}
|
||||
@@ -73,36 +74,36 @@ fn clean_pattern(pat: &mut syn::Pat) {
|
||||
}
|
||||
}
|
||||
syn::Pat::Or(or) => {
|
||||
for case in &mut or.cases {
|
||||
for case in or.cases.iter_mut() {
|
||||
clean_pattern(case);
|
||||
}
|
||||
}
|
||||
syn::Pat::Slice(slice) => {
|
||||
for elem in &mut slice.elems {
|
||||
for elem in slice.elems.iter_mut() {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::Struct(struct_pat) => {
|
||||
for field in &mut struct_pat.fields {
|
||||
for field in struct_pat.fields.iter_mut() {
|
||||
clean_pattern(&mut field.pat);
|
||||
}
|
||||
}
|
||||
syn::Pat::Tuple(tuple) => {
|
||||
for elem in &mut tuple.elems {
|
||||
for elem in tuple.elems.iter_mut() {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::TupleStruct(tuple) => {
|
||||
for elem in &mut tuple.elems {
|
||||
for elem in tuple.pat.elems.iter_mut() {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,3 @@
|
||||
# 0.1.15 (March 14th, 2024)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.63.
|
||||
|
||||
- docs: fix typo in argument name ([#6389])
|
||||
- docs: fix typo in peekable docs ([#6130])
|
||||
- docs: link to latest version of tokio-util docs ([#5694])
|
||||
- docs: typographic improvements ([#6262])
|
||||
- stream: add `StreamExt::peekable` ([#6095])
|
||||
|
||||
[#5694]: https://github.com/tokio-rs/tokio/pull/5694
|
||||
[#6095]: https://github.com/tokio-rs/tokio/pull/6095
|
||||
[#6130]: https://github.com/tokio-rs/tokio/pull/6130
|
||||
[#6262]: https://github.com/tokio-rs/tokio/pull/6262
|
||||
[#6389]: https://github.com/tokio-rs/tokio/pull/6389
|
||||
|
||||
# 0.1.14 (April 26th, 2023)
|
||||
|
||||
This bugfix release bumps the minimum version of Tokio to 1.15, which is
|
||||
necessary for `timeout_repeating` to compile. ([#5657])
|
||||
|
||||
[#5657]: https://github.com/tokio-rs/tokio/pull/5657
|
||||
|
||||
# 0.1.13 (April 25th, 2023)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.56.
|
||||
|
||||
- stream: add "full" feature flag ([#5639])
|
||||
- stream: add `StreamExt::timeout_repeating` ([#5577])
|
||||
- stream: add `StreamNotifyClose` ([#4851])
|
||||
|
||||
[#4851]: https://github.com/tokio-rs/tokio/pull/4851
|
||||
[#5577]: https://github.com/tokio-rs/tokio/pull/5577
|
||||
[#5639]: https://github.com/tokio-rs/tokio/pull/5639
|
||||
|
||||
# 0.1.12 (January 20, 2023)
|
||||
|
||||
- time: remove `Unpin` bound on `Throttle` methods ([#5105])
|
||||
- time: document that `throttle` operates on ms granularity ([#5101])
|
||||
- sync: add `WatchStream::from_changes` ([#5432])
|
||||
|
||||
[#5105]: https://github.com/tokio-rs/tokio/pull/5105
|
||||
[#5101]: https://github.com/tokio-rs/tokio/pull/5101
|
||||
[#5432]: https://github.com/tokio-rs/tokio/pull/5432
|
||||
|
||||
# 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])
|
||||
|
||||
+8
-15
@@ -4,9 +4,9 @@ name = "tokio-stream"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.15"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
version = "0.1.10"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -18,16 +18,6 @@ categories = ["asynchronous"]
|
||||
|
||||
[features]
|
||||
default = ["time"]
|
||||
|
||||
full = [
|
||||
"time",
|
||||
"net",
|
||||
"io-util",
|
||||
"fs",
|
||||
"sync",
|
||||
"signal"
|
||||
]
|
||||
|
||||
time = ["tokio/time"]
|
||||
net = ["tokio/net"]
|
||||
io-util = ["tokio/io-util"]
|
||||
@@ -37,8 +27,8 @@ signal = ["tokio/signal"]
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.11"
|
||||
tokio = { version = "1.15.0", path = "../tokio", features = ["sync"] }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.8.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -48,6 +38,9 @@ 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]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
+22
-18
@@ -1,21 +1,25 @@
|
||||
MIT License
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Copyright (c) Tokio Contributors
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# tokio-stream
|
||||
|
||||
Utilities to work with `Stream` and `tokio`.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -1,4 +0,0 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
@@ -1,29 +0,0 @@
|
||||
[package]
|
||||
name = "tokio-stream-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
tokio-test = { path = "../../tokio-test" }
|
||||
|
||||
[dependencies.tokio-stream]
|
||||
path = ".."
|
||||
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_stream_map"
|
||||
path = "fuzz_targets/fuzz_stream_map.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -1,72 +0,0 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::pin::Pin;
|
||||
|
||||
use tokio_stream::{self as stream, Stream, StreamMap};
|
||||
use tokio_test::{assert_pending, assert_ready, task};
|
||||
|
||||
macro_rules! assert_ready_none {
|
||||
($($t:tt)*) => {
|
||||
match assert_ready!($($t)*) {
|
||||
None => {}
|
||||
Some(v) => panic!("expected `None`, got `Some({:?})`", v),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
|
||||
Box::pin(s)
|
||||
}
|
||||
|
||||
fuzz_target!(|data: [bool; 64]| {
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct DidPoll<T> {
|
||||
did_poll: bool,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: Stream + Unpin> Stream for DidPoll<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T::Item>> {
|
||||
self.did_poll = true;
|
||||
Pin::new(&mut self.inner).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// Try the test with each possible length.
|
||||
for len in 0..data.len() {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let mut expect = 0;
|
||||
|
||||
for (i, is_empty) in data[..len].iter().copied().enumerate() {
|
||||
let inner = if is_empty {
|
||||
pin_box(stream::empty::<()>())
|
||||
} else {
|
||||
expect += 1;
|
||||
pin_box(stream::pending::<()>())
|
||||
};
|
||||
|
||||
let stream = DidPoll {
|
||||
did_poll: false,
|
||||
inner,
|
||||
};
|
||||
|
||||
map.insert(i, stream);
|
||||
}
|
||||
|
||||
if expect == 0 {
|
||||
assert_ready_none!(map.poll_next());
|
||||
} else {
|
||||
assert_pending!(map.poll_next());
|
||||
|
||||
assert_eq!(expect, map.values().count());
|
||||
|
||||
for stream in map.values() {
|
||||
assert!(stream.did_poll);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
+8
-29
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
#![allow(
|
||||
clippy::cognitive_complexity,
|
||||
clippy::large_enum_variant,
|
||||
@@ -55,7 +54,7 @@
|
||||
//!
|
||||
//! [async-stream]: https://docs.rs/async-stream
|
||||
//!
|
||||
//! # Conversion to and from `AsyncRead`/`AsyncWrite`
|
||||
//! # Conversion to and from AsyncRead/AsyncWrite
|
||||
//!
|
||||
//! It is often desirable to convert a `Stream` into an [`AsyncRead`],
|
||||
//! especially when dealing with plaintext formats streamed over the network.
|
||||
@@ -64,39 +63,22 @@
|
||||
//! [`tokio-util`] provides the [`StreamReader`] and [`ReaderStream`]
|
||||
//! types when the io feature is enabled.
|
||||
//!
|
||||
//! [`tokio-util`]: https://docs.rs/tokio-util/latest/tokio_util/codec/index.html
|
||||
//! [`tokio::io`]: https://docs.rs/tokio/latest/tokio/io/index.html
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html
|
||||
//! [`ReaderStream`]: https://docs.rs/tokio-util/latest/tokio_util/io/struct.ReaderStream.html
|
||||
//! [`StreamReader`]: https://docs.rs/tokio-util/latest/tokio_util/io/struct.StreamReader.html
|
||||
//! [`tokio-util`]: https://docs.rs/tokio-util/0.4/tokio_util/codec/index.html
|
||||
//! [`tokio::io`]: https://docs.rs/tokio/1.0/tokio/io/index.html
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/1.0/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/1.0/tokio/io/trait.AsyncWrite.html
|
||||
//! [`ReaderStream`]: https://docs.rs/tokio-util/0.4/tokio_util/io/struct.ReaderStream.html
|
||||
//! [`StreamReader`]: https://docs.rs/tokio-util/0.4/tokio_util/io/struct.StreamReader.html
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod poll_fn;
|
||||
pub(crate) use poll_fn::poll_fn;
|
||||
|
||||
pub mod wrappers;
|
||||
|
||||
mod stream_ext;
|
||||
pub use stream_ext::{collect::FromStream, StreamExt};
|
||||
/// Adapters for [`Stream`]s created by methods in [`StreamExt`].
|
||||
pub mod adapters {
|
||||
pub use crate::stream_ext::{
|
||||
Chain, Filter, FilterMap, Fuse, Map, MapWhile, Merge, Peekable, Skip, SkipWhile, Take,
|
||||
TakeWhile, Then,
|
||||
};
|
||||
cfg_time! {
|
||||
pub use crate::stream_ext::{ChunksTimeout, Timeout, TimeoutRepeating};
|
||||
}
|
||||
}
|
||||
|
||||
cfg_time! {
|
||||
#[deprecated = "Import those symbols from adapters instead"]
|
||||
#[doc(hidden)]
|
||||
pub use stream_ext::timeout::Timeout;
|
||||
pub use stream_ext::timeout::Elapsed;
|
||||
pub use stream_ext::timeout::{Elapsed, Timeout};
|
||||
}
|
||||
|
||||
mod empty;
|
||||
@@ -114,8 +96,5 @@ pub use pending::{pending, Pending};
|
||||
mod stream_map;
|
||||
pub use stream_map::StreamMap;
|
||||
|
||||
mod stream_close;
|
||||
pub use stream_close::StreamNotifyClose;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use futures_core::Stream;
|
||||
|
||||
@@ -35,7 +35,7 @@ impl<I> Unpin for Once<I> {}
|
||||
/// ```
|
||||
pub fn once<T>(value: T) -> Once<T> {
|
||||
Once {
|
||||
iter: crate::iter(Some(value)),
|
||||
iter: crate::iter(Some(value).into_iter()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub(crate) struct PollFn<F> {
|
||||
f: F,
|
||||
}
|
||||
|
||||
pub(crate) fn poll_fn<T, F>(f: F) -> PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
PollFn { f }
|
||||
}
|
||||
|
||||
impl<T, F> Future for PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
|
||||
// Safety: We never construct a `Pin<&mut F>` anywhere, so accessing `f`
|
||||
// mutably in an unpinned way is sound.
|
||||
//
|
||||
// This use of unsafe cannot be replaced with the pin-project macro
|
||||
// because:
|
||||
// * If we put `#[pin]` on the field, then it gives us a `Pin<&mut F>`,
|
||||
// which we can't use to call the closure.
|
||||
// * If we don't put `#[pin]` on the field, then it makes `PollFn` be
|
||||
// unconditionally `Unpin`, which we also don't want.
|
||||
let me = unsafe { Pin::into_inner_unchecked(self) };
|
||||
(me.f)(cx)
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
use crate::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
/// A `Stream` that wraps the values in an `Option`.
|
||||
///
|
||||
/// Whenever the wrapped stream yields an item, this stream yields that item
|
||||
/// wrapped in `Some`. When the inner stream ends, then this stream first
|
||||
/// yields a `None` item, and then this stream will also end.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Using `StreamNotifyClose` to handle closed streams with `StreamMap`.
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct StreamNotifyClose<S> {
|
||||
#[pin]
|
||||
inner: Option<S>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StreamNotifyClose<S> {
|
||||
/// Create a new `StreamNotifyClose`.
|
||||
pub fn new(stream: S) -> Self {
|
||||
Self {
|
||||
inner: Some(stream),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get back the inner `Stream`.
|
||||
///
|
||||
/// Returns `None` if the stream has reached its end.
|
||||
pub fn into_inner(self) -> Option<S> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Stream for StreamNotifyClose<S>
|
||||
where
|
||||
S: Stream,
|
||||
{
|
||||
type Item = Option<S::Item>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// We can't invoke poll_next after it ended, so we unset the inner stream as a marker.
|
||||
match self
|
||||
.as_mut()
|
||||
.project()
|
||||
.inner
|
||||
.as_pin_mut()
|
||||
.map(|stream| S::poll_next(stream, cx))
|
||||
{
|
||||
Some(Poll::Ready(Some(item))) => Poll::Ready(Some(Some(item))),
|
||||
Some(Poll::Ready(None)) => {
|
||||
self.project().inner.set(None);
|
||||
Poll::Ready(Some(None))
|
||||
}
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if let Some(inner) = &self.inner {
|
||||
// We always return +1 because when there's stream there's atleast one more item.
|
||||
let (l, u) = inner.size_hint();
|
||||
(l.saturating_add(1), u.and_then(|u| u.checked_add(1)))
|
||||
} else {
|
||||
(0, Some(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-160
@@ -8,66 +8,61 @@ mod any;
|
||||
use any::AnyFuture;
|
||||
|
||||
mod chain;
|
||||
pub use chain::Chain;
|
||||
use chain::Chain;
|
||||
|
||||
pub(crate) mod collect;
|
||||
use collect::{Collect, FromStream};
|
||||
|
||||
mod filter;
|
||||
pub use filter::Filter;
|
||||
use filter::Filter;
|
||||
|
||||
mod filter_map;
|
||||
pub use filter_map::FilterMap;
|
||||
use filter_map::FilterMap;
|
||||
|
||||
mod fold;
|
||||
use fold::FoldFuture;
|
||||
|
||||
mod fuse;
|
||||
pub use fuse::Fuse;
|
||||
use fuse::Fuse;
|
||||
|
||||
mod map;
|
||||
pub use map::Map;
|
||||
use map::Map;
|
||||
|
||||
mod map_while;
|
||||
pub use map_while::MapWhile;
|
||||
use map_while::MapWhile;
|
||||
|
||||
mod merge;
|
||||
pub use merge::Merge;
|
||||
use merge::Merge;
|
||||
|
||||
mod next;
|
||||
use next::Next;
|
||||
|
||||
mod skip;
|
||||
pub use skip::Skip;
|
||||
use skip::Skip;
|
||||
|
||||
mod skip_while;
|
||||
pub use skip_while::SkipWhile;
|
||||
use skip_while::SkipWhile;
|
||||
|
||||
mod take;
|
||||
pub use take::Take;
|
||||
use take::Take;
|
||||
|
||||
mod take_while;
|
||||
pub use take_while::TakeWhile;
|
||||
use take_while::TakeWhile;
|
||||
|
||||
mod then;
|
||||
pub use then::Then;
|
||||
use then::Then;
|
||||
|
||||
mod try_next;
|
||||
use try_next::TryNext;
|
||||
|
||||
mod peekable;
|
||||
pub use peekable::Peekable;
|
||||
|
||||
cfg_time! {
|
||||
pub(crate) mod timeout;
|
||||
pub(crate) mod timeout_repeating;
|
||||
pub use timeout::Timeout;
|
||||
pub use timeout_repeating::TimeoutRepeating;
|
||||
use tokio::time::{Duration, Interval};
|
||||
use timeout::Timeout;
|
||||
use tokio::time::Duration;
|
||||
mod throttle;
|
||||
use throttle::{throttle, Throttle};
|
||||
mod chunks_timeout;
|
||||
pub use chunks_timeout::ChunksTimeout;
|
||||
use chunks_timeout::ChunksTimeout;
|
||||
}
|
||||
|
||||
/// An extension trait for the [`Stream`] trait that provides a variety of
|
||||
@@ -851,7 +846,8 @@ pub trait StreamExt: Stream {
|
||||
///
|
||||
/// `collect` streams all values, awaiting as needed. Values are pushed into
|
||||
/// a collection. A number of different target collection types are
|
||||
/// supported, including [`Vec`], [`String`], and [`Bytes`].
|
||||
/// supported, including [`Vec`](std::vec::Vec),
|
||||
/// [`String`](std::string::String), and [`Bytes`].
|
||||
///
|
||||
/// [`Bytes`]: https://docs.rs/bytes/0.6.0/bytes/struct.Bytes.html
|
||||
///
|
||||
@@ -928,9 +924,7 @@ pub trait StreamExt: Stream {
|
||||
/// If the wrapped stream yields a value before the deadline is reached, the
|
||||
/// value is returned. Otherwise, an error is returned. The caller may decide
|
||||
/// to continue consuming the stream and will eventually get the next source
|
||||
/// stream value once it becomes available. See
|
||||
/// [`timeout_repeating`](StreamExt::timeout_repeating) for an alternative
|
||||
/// where the timeouts will repeat.
|
||||
/// stream value once it becomes available.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
@@ -977,26 +971,7 @@ pub trait StreamExt: Stream {
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Once a timeout error is received, no further events will be received
|
||||
/// unless the wrapped stream yields a value (timeouts do not repeat).
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::IntervalStream};
|
||||
/// use std::time::Duration;
|
||||
/// let interval_stream = IntervalStream::new(tokio::time::interval(Duration::from_millis(100)));
|
||||
/// let timeout_stream = interval_stream.timeout(Duration::from_millis(10));
|
||||
/// tokio::pin!(timeout_stream);
|
||||
///
|
||||
/// // Only one timeout will be received between values in the source stream.
|
||||
/// assert!(timeout_stream.try_next().await.is_ok());
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected one timeout");
|
||||
/// assert!(timeout_stream.try_next().await.is_ok(), "expected no more timeouts");
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg(all(feature = "time"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn timeout(self, duration: Duration) -> Timeout<Self>
|
||||
where
|
||||
@@ -1005,98 +980,8 @@ pub trait StreamExt: Stream {
|
||||
Timeout::new(self, duration)
|
||||
}
|
||||
|
||||
/// Applies a per-item timeout to the passed stream.
|
||||
///
|
||||
/// `timeout_repeating()` takes an [`Interval`] that controls the time each
|
||||
/// element of the stream has to complete before timing out.
|
||||
///
|
||||
/// If the wrapped stream yields a value before the deadline is reached, the
|
||||
/// value is returned. Otherwise, an error is returned. The caller may decide
|
||||
/// to continue consuming the stream and will eventually get the next source
|
||||
/// stream value once it becomes available. Unlike `timeout()`, if no value
|
||||
/// becomes available before the deadline is reached, additional errors are
|
||||
/// returned at the specified interval. See [`timeout`](StreamExt::timeout)
|
||||
/// for an alternative where the timeouts do not repeat.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This function consumes the stream passed into it and returns a
|
||||
/// wrapped version of it.
|
||||
///
|
||||
/// Polling the returned stream will continue to poll the inner stream even
|
||||
/// if one or more items time out.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// use std::time::Duration;
|
||||
/// # let int_stream = stream::iter(1..=3);
|
||||
///
|
||||
/// let int_stream = int_stream.timeout_repeating(tokio::time::interval(Duration::from_secs(1)));
|
||||
/// tokio::pin!(int_stream);
|
||||
///
|
||||
/// // When no items time out, we get the 3 elements in succession:
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(2)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(3)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
///
|
||||
/// // If the second item times out, we get an error and continue polling the stream:
|
||||
/// # let mut int_stream = stream::iter(vec![Ok(1), Err(()), Ok(2), Ok(3)]);
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert!(int_stream.try_next().await.is_err());
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(2)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(3)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
///
|
||||
/// // If we want to stop consuming the source stream the first time an
|
||||
/// // element times out, we can use the `take_while` operator:
|
||||
/// # let int_stream = stream::iter(vec![Ok(1), Err(()), Ok(2), Ok(3)]);
|
||||
/// let mut int_stream = int_stream.take_while(Result::is_ok);
|
||||
///
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Timeout errors will be continuously produced at the specified interval
|
||||
/// until the wrapped stream yields a value.
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::IntervalStream};
|
||||
/// use std::time::Duration;
|
||||
/// let interval_stream = IntervalStream::new(tokio::time::interval(Duration::from_millis(23)));
|
||||
/// let timeout_stream = interval_stream.timeout_repeating(tokio::time::interval(Duration::from_millis(9)));
|
||||
/// tokio::pin!(timeout_stream);
|
||||
///
|
||||
/// // Multiple timeouts will be received between values in the source stream.
|
||||
/// assert!(timeout_stream.try_next().await.is_ok());
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected one timeout");
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected a second timeout");
|
||||
/// // Will eventually receive another value from the source stream...
|
||||
/// assert!(timeout_stream.try_next().await.is_ok(), "expected non-timeout");
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn timeout_repeating(self, interval: Interval) -> TimeoutRepeating<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
TimeoutRepeating::new(self, interval)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -1114,7 +999,7 @@ pub trait StreamExt: Stream {
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg(all(feature = "time"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn throttle(self, duration: Duration) -> Throttle<Self>
|
||||
where
|
||||
@@ -1179,31 +1064,6 @@ pub trait StreamExt: Stream {
|
||||
assert!(max_size > 0, "`max_size` must be non-zero.");
|
||||
ChunksTimeout::new(self, max_size, duration)
|
||||
}
|
||||
|
||||
/// Turns the stream into a peekable stream, whose next element can be peeked at without being
|
||||
/// consumed.
|
||||
/// ```rust
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// # async fn _unused() {}
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// async fn main() {
|
||||
/// let iter = vec![1, 2, 3, 4].into_iter();
|
||||
/// let mut stream = stream::iter(iter).peekable();
|
||||
///
|
||||
/// assert_eq!(*stream.peek().await.unwrap(), 1);
|
||||
/// assert_eq!(*stream.peek().await.unwrap(), 1);
|
||||
/// assert_eq!(stream.next().await.unwrap(), 1);
|
||||
/// assert_eq!(*stream.peek().await.unwrap(), 2);
|
||||
/// }
|
||||
/// ```
|
||||
fn peekable(self) -> Peekable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Peekable::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<St: ?Sized> StreamExt for St where St: Stream {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::stream_ext::Fuse;
|
||||
use crate::Stream;
|
||||
use tokio::time::{sleep, Sleep};
|
||||
use tokio::time::{sleep, Instant, Sleep};
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
@@ -16,7 +16,7 @@ pin_project! {
|
||||
#[pin]
|
||||
stream: Fuse<S>,
|
||||
#[pin]
|
||||
deadline: Option<Sleep>,
|
||||
deadline: Sleep,
|
||||
duration: Duration,
|
||||
items: Vec<S::Item>,
|
||||
cap: usize, // https://github.com/rust-lang/futures-rs/issues/1475
|
||||
@@ -27,7 +27,7 @@ impl<S: Stream> ChunksTimeout<S> {
|
||||
pub(super) fn new(stream: S, max_size: usize, duration: Duration) -> Self {
|
||||
ChunksTimeout {
|
||||
stream: Fuse::new(stream),
|
||||
deadline: None,
|
||||
deadline: sleep(duration),
|
||||
duration,
|
||||
items: Vec::with_capacity(max_size),
|
||||
cap: max_size,
|
||||
@@ -45,7 +45,7 @@ impl<S: Stream> Stream for ChunksTimeout<S> {
|
||||
Poll::Pending => break,
|
||||
Poll::Ready(Some(item)) => {
|
||||
if me.items.is_empty() {
|
||||
me.deadline.set(Some(sleep(*me.duration)));
|
||||
me.deadline.as_mut().reset(Instant::now() + *me.duration);
|
||||
me.items.reserve_exact(*me.cap);
|
||||
}
|
||||
me.items.push(item);
|
||||
@@ -67,9 +67,7 @@ impl<S: Stream> Stream for ChunksTimeout<S> {
|
||||
}
|
||||
|
||||
if !me.items.is_empty() {
|
||||
if let Some(deadline) = me.deadline.as_pin_mut() {
|
||||
ready!(deadline.poll(cx));
|
||||
}
|
||||
ready!(me.deadline.poll(cx));
|
||||
return Poll::Ready(Some(std::mem::take(me.items)));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from a [`Stream`].
|
||||
/// Convert from a [`Stream`](crate::Stream).
|
||||
///
|
||||
/// This trait is not intended to be used directly. Instead, call
|
||||
/// [`StreamExt::collect()`](super::StreamExt::collect).
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,23 +66,25 @@ where
|
||||
T: Stream,
|
||||
U: Stream<Item = T::Item>,
|
||||
{
|
||||
use Poll::*;
|
||||
|
||||
let mut done = true;
|
||||
|
||||
match first.poll_next(cx) {
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some(val)),
|
||||
Poll::Ready(None) => {}
|
||||
Poll::Pending => done = false,
|
||||
Ready(Some(val)) => return Ready(Some(val)),
|
||||
Ready(None) => {}
|
||||
Pending => done = false,
|
||||
}
|
||||
|
||||
match second.poll_next(cx) {
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some(val)),
|
||||
Poll::Ready(None) => {}
|
||||
Poll::Pending => done = false,
|
||||
Ready(Some(val)) => return Ready(Some(val)),
|
||||
Ready(None) => {}
|
||||
Pending => done = false,
|
||||
}
|
||||
|
||||
if done {
|
||||
Poll::Ready(None)
|
||||
Ready(None)
|
||||
} else {
|
||||
Poll::Pending
|
||||
Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures_core::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::stream_ext::Fuse;
|
||||
use crate::StreamExt;
|
||||
|
||||
pin_project! {
|
||||
/// Stream returned by the [`chain`](super::StreamExt::peekable) method.
|
||||
pub struct Peekable<T: Stream> {
|
||||
peek: Option<T::Item>,
|
||||
#[pin]
|
||||
stream: Fuse<T>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Stream> Peekable<T> {
|
||||
pub(crate) fn new(stream: T) -> Self {
|
||||
let stream = stream.fuse();
|
||||
Self { peek: None, stream }
|
||||
}
|
||||
|
||||
/// Peek at the next item in the stream.
|
||||
pub async fn peek(&mut self) -> Option<&T::Item>
|
||||
where
|
||||
T: Unpin,
|
||||
{
|
||||
if let Some(ref it) = self.peek {
|
||||
Some(it)
|
||||
} else {
|
||||
self.peek = self.next().await;
|
||||
self.peek.as_ref()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Stream> Stream for Peekable<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let this = self.project();
|
||||
if let Some(it) = this.peek.take() {
|
||||
Poll::Ready(Some(it))
|
||||
} else {
|
||||
this.stream.poll_next(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,11 +64,11 @@ where
|
||||
|
||||
let (lower, upper) = self.stream.size_hint();
|
||||
|
||||
let lower = cmp::min(lower, self.remaining);
|
||||
let lower = cmp::min(lower, self.remaining as usize);
|
||||
|
||||
let upper = match upper {
|
||||
Some(x) if x < self.remaining => Some(x),
|
||||
_ => Some(self.remaining),
|
||||
Some(x) if x < self.remaining as usize => Some(x),
|
||||
_ => Some(self.remaining as usize),
|
||||
};
|
||||
|
||||
(lower, upper)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -23,8 +23,8 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by `Timeout` and `TimeoutRepeating`.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
/// Error returned by `Timeout`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Elapsed(());
|
||||
|
||||
impl<S: Stream> Timeout<S> {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
use crate::stream_ext::Fuse;
|
||||
use crate::{Elapsed, Stream};
|
||||
use tokio::time::Interval;
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Stream returned by the [`timeout_repeating`](super::StreamExt::timeout_repeating) method.
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
pub struct TimeoutRepeating<S> {
|
||||
#[pin]
|
||||
stream: Fuse<S>,
|
||||
#[pin]
|
||||
interval: Interval,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> TimeoutRepeating<S> {
|
||||
pub(super) fn new(stream: S, interval: Interval) -> Self {
|
||||
TimeoutRepeating {
|
||||
stream: Fuse::new(stream),
|
||||
interval,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for TimeoutRepeating<S> {
|
||||
type Item = Result<S::Item, Elapsed>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut me = self.project();
|
||||
|
||||
match me.stream.poll_next(cx) {
|
||||
Poll::Ready(v) => {
|
||||
if v.is_some() {
|
||||
me.interval.reset();
|
||||
}
|
||||
return Poll::Ready(v.map(Ok));
|
||||
}
|
||||
Poll::Pending => {}
|
||||
};
|
||||
|
||||
ready!(me.interval.poll_tick(cx));
|
||||
Poll::Ready(Some(Err(Elapsed::new())))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (lower, _) = self.stream.size_hint();
|
||||
|
||||
// The timeout stream may insert an error an infinite number of times.
|
||||
(lower, None)
|
||||
}
|
||||
}
|
||||
+20
-152
@@ -1,4 +1,4 @@
|
||||
use crate::{poll_fn, Stream};
|
||||
use crate::Stream;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::hash::Hash;
|
||||
@@ -42,18 +42,10 @@ use std::task::{Context, Poll};
|
||||
/// to be merged, it may be advisable to use tasks sending values on a shared
|
||||
/// [`mpsc`] channel.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// `StreamMap` removes finished streams automatically, without alerting the user.
|
||||
/// In some scenarios, the caller would want to know on closed streams.
|
||||
/// To do this, use [`StreamNotifyClose`] as a wrapper to your stream.
|
||||
/// It will return None when the stream is closed.
|
||||
///
|
||||
/// [`StreamExt::merge`]: crate::StreamExt::merge
|
||||
/// [`mpsc`]: https://docs.rs/tokio/1.0/tokio/sync/mpsc/index.html
|
||||
/// [`pin!`]: https://docs.rs/tokio/1.0/tokio/macro.pin.html
|
||||
/// [`Box::pin`]: std::boxed::Box::pin
|
||||
/// [`StreamNotifyClose`]: crate::StreamNotifyClose
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -178,28 +170,6 @@ use std::task::{Context, Poll};
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Using `StreamNotifyClose` to handle closed streams with `StreamMap`.
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StreamMap<K, V> {
|
||||
/// Streams stored in the map
|
||||
@@ -209,7 +179,7 @@ pub struct StreamMap<K, V> {
|
||||
impl<K, V> StreamMap<K, V> {
|
||||
/// An iterator visiting all key-value pairs in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is `&'a (K, V)`.
|
||||
/// The iterator element type is &'a (K, V).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -232,7 +202,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
|
||||
/// An iterator visiting all key-value pairs mutably in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is `&'a mut (K, V)`.
|
||||
/// The iterator element type is &'a mut (K, V).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -289,7 +259,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
|
||||
/// Returns an iterator visiting all keys in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is `&'a K`.
|
||||
/// The iterator element type is &'a K.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -312,7 +282,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
|
||||
/// An iterator visiting all values in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is `&'a V`.
|
||||
/// The iterator element type is &'a V.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -335,7 +305,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
|
||||
/// An iterator visiting all values mutably in arbitrary order.
|
||||
///
|
||||
/// The iterator element type is `&'a mut V`.
|
||||
/// The iterator element type is &'a mut V.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -467,10 +437,10 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// assert!(map.remove(&1).is_some());
|
||||
/// assert!(map.remove(&1).is_none());
|
||||
/// ```
|
||||
pub fn remove<Q>(&mut self, k: &Q) -> Option<V>
|
||||
pub fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
Q: Hash + Eq,
|
||||
{
|
||||
for i in 0..self.entries.len() {
|
||||
if self.entries[i].0.borrow() == k {
|
||||
@@ -496,10 +466,10 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// assert_eq!(map.contains_key(&1), true);
|
||||
/// assert_eq!(map.contains_key(&2), false);
|
||||
/// ```
|
||||
pub fn contains_key<Q>(&self, k: &Q) -> bool
|
||||
pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
Q: Hash + Eq,
|
||||
{
|
||||
for i in 0..self.entries.len() {
|
||||
if self.entries[i].0.borrow() == k {
|
||||
@@ -518,6 +488,8 @@ where
|
||||
{
|
||||
/// Polls the next value, includes the vec entry index
|
||||
fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<Option<(usize, V::Item)>> {
|
||||
use Poll::*;
|
||||
|
||||
let start = self::rand::thread_rng_n(self.entries.len() as u32) as usize;
|
||||
let mut idx = start;
|
||||
|
||||
@@ -525,8 +497,8 @@ where
|
||||
let (_, stream) = &mut self.entries[idx];
|
||||
|
||||
match Pin::new(stream).poll_next(cx) {
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some((idx, val))),
|
||||
Poll::Ready(None) => {
|
||||
Ready(Some(val)) => return Ready(Some((idx, val))),
|
||||
Ready(None) => {
|
||||
// Remove the entry
|
||||
self.entries.swap_remove(idx);
|
||||
|
||||
@@ -540,7 +512,7 @@ where
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
Poll::Pending => {
|
||||
Pending => {
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
@@ -548,9 +520,9 @@ where
|
||||
|
||||
// If the map is empty, then the stream is complete.
|
||||
if self.entries.is_empty() {
|
||||
Poll::Ready(None)
|
||||
Ready(None)
|
||||
} else {
|
||||
Poll::Pending
|
||||
Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,110 +533,6 @@ impl<K, V> Default for StreamMap<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> StreamMap<K, V>
|
||||
where
|
||||
K: Clone + Unpin,
|
||||
V: Stream + Unpin,
|
||||
{
|
||||
/// Receives multiple items on this [`StreamMap`], extending the provided `buffer`.
|
||||
///
|
||||
/// This method returns the number of items that is appended to the `buffer`.
|
||||
///
|
||||
/// Note that this method does not guarantee that exactly `limit` items
|
||||
/// are received. Rather, if at least one item is available, it returns
|
||||
/// as many items as it can up to the given limit. This method returns
|
||||
/// zero only if the `StreamMap` is empty (or if `limit` is zero).
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. If `next_many` is used as the event in a
|
||||
/// [`tokio::select!`](tokio::select) statement and some other branch
|
||||
/// completes first, it is guaranteed that no items were received on any of
|
||||
/// the underlying streams.
|
||||
pub async fn next_many(&mut self, buffer: &mut Vec<(K, V::Item)>, limit: usize) -> usize {
|
||||
poll_fn(|cx| self.poll_next_many(cx, buffer, limit)).await
|
||||
}
|
||||
|
||||
/// Polls to receive multiple items on this `StreamMap`, extending the provided `buffer`.
|
||||
///
|
||||
/// This method returns:
|
||||
/// * `Poll::Pending` if no items are available but the `StreamMap` is not empty.
|
||||
/// * `Poll::Ready(count)` where `count` is the number of items successfully received and
|
||||
/// stored in `buffer`. This can be less than, or equal to, `limit`.
|
||||
/// * `Poll::Ready(0)` if `limit` is set to zero or when the `StreamMap` is empty.
|
||||
///
|
||||
/// Note that this method does not guarantee that exactly `limit` items
|
||||
/// are received. Rather, if at least one item is available, it returns
|
||||
/// as many items as it can up to the given limit. This method returns
|
||||
/// zero only if the `StreamMap` is empty (or if `limit` is zero).
|
||||
pub fn poll_next_many(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
buffer: &mut Vec<(K, V::Item)>,
|
||||
limit: usize,
|
||||
) -> Poll<usize> {
|
||||
if limit == 0 || self.entries.is_empty() {
|
||||
return Poll::Ready(0);
|
||||
}
|
||||
|
||||
let mut added = 0;
|
||||
|
||||
let start = self::rand::thread_rng_n(self.entries.len() as u32) as usize;
|
||||
let mut idx = start;
|
||||
|
||||
while added < limit {
|
||||
// Indicates whether at least one stream returned a value when polled or not
|
||||
let mut should_loop = false;
|
||||
|
||||
for _ in 0..self.entries.len() {
|
||||
let (_, stream) = &mut self.entries[idx];
|
||||
|
||||
match Pin::new(stream).poll_next(cx) {
|
||||
Poll::Ready(Some(val)) => {
|
||||
added += 1;
|
||||
|
||||
let key = self.entries[idx].0.clone();
|
||||
buffer.push((key, val));
|
||||
|
||||
should_loop = true;
|
||||
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
// Remove the entry
|
||||
self.entries.swap_remove(idx);
|
||||
|
||||
// Check if this was the last entry, if so the cursor needs
|
||||
// to wrap
|
||||
if idx == self.entries.len() {
|
||||
idx = 0;
|
||||
} else if idx < start && start <= self.entries.len() {
|
||||
// The stream being swapped into the current index has
|
||||
// already been polled, so skip it.
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
Poll::Pending => {
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !should_loop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if added > 0 {
|
||||
Poll::Ready(added)
|
||||
} else if self.entries.is_empty() {
|
||||
Poll::Ready(0)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Stream for StreamMap<K, V>
|
||||
where
|
||||
K: Clone + Unpin,
|
||||
@@ -700,7 +568,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> FromIterator<(K, V)> for StreamMap<K, V>
|
||||
impl<K, V> std::iter::FromIterator<(K, V)> for StreamMap<K, V>
|
||||
where
|
||||
K: Hash + Eq,
|
||||
{
|
||||
@@ -762,9 +630,9 @@ mod rand {
|
||||
|
||||
/// Fast random number generate
|
||||
///
|
||||
/// Implement `xorshift64+`: 2 32-bit `xorshift` sequences added together.
|
||||
/// Implement xorshift64+: 2 32-bit xorshift sequences added together.
|
||||
/// Shift triplet `[17,7,16]` was calculated as indicated in Marsaglia's
|
||||
/// `Xorshift` paper: <https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf>
|
||||
/// Xorshift paper: <https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf>
|
||||
/// This generator passes the SmallCrush suite, part of TestU01 framework:
|
||||
/// <http://simul.iro.umontreal.ca/testu01/tu01.html>
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Stream for IntervalStream {
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(usize::MAX, None)
|
||||
(std::usize::MAX, None)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ impl<R> LinesStream<R> {
|
||||
}
|
||||
|
||||
/// Obtain a pinned reference to the inner `Lines<R>`.
|
||||
#[allow(clippy::wrong_self_convention)] // https://github.com/rust-lang/rust-clippy/issues/4546
|
||||
pub fn as_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Lines<R>> {
|
||||
self.project().inner
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ impl<T> ReceiverStream<T> {
|
||||
///
|
||||
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close();
|
||||
self.inner.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ impl<T> UnboundedReceiverStream<T> {
|
||||
/// This prevents any further messages from being sent on the channel while
|
||||
/// still enabling the receiver to drain messages that are buffered.
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close();
|
||||
self.inner.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ pub struct SignalStream {
|
||||
|
||||
impl SignalStream {
|
||||
/// Create a new `SignalStream`.
|
||||
pub fn new(signal: Signal) -> Self {
|
||||
Self { inner: signal }
|
||||
pub fn new(interval: Signal) -> Self {
|
||||
Self { inner: interval }
|
||||
}
|
||||
|
||||
/// Get back the inner `Signal`.
|
||||
|
||||
@@ -30,6 +30,7 @@ impl<R> SplitStream<R> {
|
||||
}
|
||||
|
||||
/// Obtain a pinned reference to the inner `Split<R>`.
|
||||
#[allow(clippy::wrong_self_convention)] // https://github.com/rust-lang/rust-clippy/issues/4546
|
||||
pub fn as_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Split<R>> {
|
||||
self.project().inner
|
||||
}
|
||||
|
||||
@@ -10,9 +10,8 @@ use tokio::sync::watch::error::RecvError;
|
||||
|
||||
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// This stream will start by yielding the current value when the `WatchStream` is polled,
|
||||
/// regardless of whether it was the initial value or sent afterwards,
|
||||
/// unless you use [`WatchStream<T>::from_changes`].
|
||||
/// This stream will always start by yielding the current value when the WatchStream is polled,
|
||||
/// regardless of whether it was the initial value or sent afterwards.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -41,28 +40,6 @@ use tokio::sync::watch::error::RecvError;
|
||||
/// let (tx, rx) = watch::channel("hello");
|
||||
/// let mut rx = WatchStream::new(rx);
|
||||
///
|
||||
/// // existing rx output with "hello" is ignored here
|
||||
///
|
||||
/// tx.send("goodbye").unwrap();
|
||||
/// assert_eq!(rx.next().await, Some("goodbye"));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Example with [`WatchStream<T>::from_changes`]:
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use futures::future::FutureExt;
|
||||
/// use tokio::sync::watch;
|
||||
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
|
||||
///
|
||||
/// let (tx, rx) = watch::channel("hello");
|
||||
/// let mut rx = WatchStream::from_changes(rx);
|
||||
///
|
||||
/// // no output from rx is available at this point - let's check this:
|
||||
/// assert!(rx.next().now_or_never().is_none());
|
||||
///
|
||||
/// tx.send("goodbye").unwrap();
|
||||
/// assert_eq!(rx.next().await, Some("goodbye"));
|
||||
/// # }
|
||||
@@ -89,13 +66,6 @@ impl<T: 'static + Clone + Send + Sync> WatchStream<T> {
|
||||
inner: ReusableBoxFuture::new(async move { (Ok(()), rx) }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `WatchStream` that waits for the value to be changed.
|
||||
pub fn from_changes(rx: Receiver<T>) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(make_future(rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static + Send + Sync> Stream for WatchStream<T> {
|
||||
|
||||
@@ -15,21 +15,18 @@ fn require_unpin<T: Unpin>(_t: &T) {}
|
||||
#[allow(dead_code)]
|
||||
struct Invalid;
|
||||
|
||||
#[allow(unused)]
|
||||
trait AmbiguousIfSend<A> {
|
||||
fn some_item(&self) {}
|
||||
}
|
||||
impl<T: ?Sized> AmbiguousIfSend<()> for T {}
|
||||
impl<T: ?Sized + Send> AmbiguousIfSend<Invalid> for T {}
|
||||
|
||||
#[allow(unused)]
|
||||
trait AmbiguousIfSync<A> {
|
||||
fn some_item(&self) {}
|
||||
}
|
||||
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
|
||||
impl<T: ?Sized + Sync> AmbiguousIfSync<Invalid> for T {}
|
||||
|
||||
#[allow(unused)]
|
||||
trait AmbiguousIfUnpin<A> {
|
||||
fn some_item(&self) {}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ mod support {
|
||||
}
|
||||
|
||||
use support::mpsc;
|
||||
use tokio_stream::adapters::Chain;
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_usage() {
|
||||
let one = stream::iter(vec![1, 2, 3]);
|
||||
let two = stream::iter(vec![4, 5, 6]);
|
||||
|
||||
let mut stream = visibility_test(one, two);
|
||||
let mut stream = one.chain(two);
|
||||
|
||||
assert_eq!(stream.size_hint(), (6, Some(6)));
|
||||
assert_eq!(stream.next().await, Some(1));
|
||||
@@ -40,14 +39,6 @@ async fn basic_usage() {
|
||||
assert_eq!(stream.next().await, None);
|
||||
}
|
||||
|
||||
fn visibility_test<I, S1, S2>(s1: S1, s2: S2) -> Chain<S1, S2>
|
||||
where
|
||||
S1: Stream<Item = I>,
|
||||
S2: Stream<Item = I>,
|
||||
{
|
||||
s1.chain(s2)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pending_first() {
|
||||
let (tx1, rx1) = mpsc::unbounded_channel_stream();
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
use tokio_stream::{StreamExt, StreamNotifyClose};
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_usage() {
|
||||
let mut stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
|
||||
assert_eq!(stream.next().await, Some(Some(0)));
|
||||
assert_eq!(stream.next().await, Some(Some(1)));
|
||||
assert_eq!(stream.next().await, Some(None));
|
||||
assert_eq!(stream.next().await, None);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(all(feature = "time", not(target_os = "wasi")))] // Wasi does not support panic recovery
|
||||
#![cfg(panic = "unwind")]
|
||||
|
||||
use parking_lot::{const_mutex, Mutex};
|
||||
use std::error::Error;
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
use futures::stream::iter;
|
||||
use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap};
|
||||
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
|
||||
|
||||
use std::future::{poll_fn, Future};
|
||||
use std::pin::{pin, Pin};
|
||||
use std::task::Poll;
|
||||
|
||||
mod support {
|
||||
pub(crate) mod mpsc;
|
||||
}
|
||||
|
||||
use support::mpsc;
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
macro_rules! assert_ready_some {
|
||||
($($t:tt)*) => {
|
||||
match assert_ready!($($t)*) {
|
||||
@@ -328,236 +325,63 @@ fn one_ready_many_none() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn fuzz_pending_complete_mix(kinds: Vec<bool>) {
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct DidPoll<T> {
|
||||
did_poll: bool,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: Stream + Unpin> Stream for DidPoll<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
|
||||
-> Poll<Option<T::Item>>
|
||||
{
|
||||
self.did_poll = true;
|
||||
Pin::new(&mut self.inner).poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let mut expect = 0;
|
||||
|
||||
for (i, &is_empty) in kinds.iter().enumerate() {
|
||||
let inner = if is_empty {
|
||||
pin_box(stream::empty::<()>())
|
||||
} else {
|
||||
expect += 1;
|
||||
pin_box(stream::pending::<()>())
|
||||
};
|
||||
|
||||
let stream = DidPoll {
|
||||
did_poll: false,
|
||||
inner,
|
||||
};
|
||||
|
||||
map.insert(i, stream);
|
||||
}
|
||||
|
||||
if expect == 0 {
|
||||
assert_ready_none!(map.poll_next());
|
||||
} else {
|
||||
assert_pending!(map.poll_next());
|
||||
|
||||
assert_eq!(expect, map.values().count());
|
||||
|
||||
for stream in map.values() {
|
||||
assert!(stream.did_poll);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
|
||||
Box::pin(s)
|
||||
}
|
||||
|
||||
type UsizeStream = Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_zero() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut vec![], 0)).await;
|
||||
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_empty() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut vec![], 1)).await;
|
||||
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_pending() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
|
||||
|
||||
let mut is_pending = false;
|
||||
poll_fn(|cx| {
|
||||
let poll = stream_map.poll_next_many(cx, &mut vec![], 1);
|
||||
|
||||
is_pending = poll.is_pending();
|
||||
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
|
||||
assert!(is_pending);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_not_enough() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 3)).await;
|
||||
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(buffer.len(), 2);
|
||||
assert!(buffer.contains(&(0, 0)));
|
||||
assert!(buffer.contains(&(1, 1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_enough() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 2)).await;
|
||||
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(buffer.len(), 2);
|
||||
assert!(buffer.contains(&(0, 0)));
|
||||
assert!(buffer.contains(&(1, 1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn poll_next_many_correctly_loops_around() {
|
||||
for _ in 0..10 {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([0usize, 1].into_iter())) as UsizeStream);
|
||||
stream_map.insert(2, Box::pin(iter([0usize, 1, 2].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 3)).await;
|
||||
assert_eq!(n, 3);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![0, 0, 0]
|
||||
);
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 2)).await;
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![1, 1]
|
||||
);
|
||||
|
||||
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 1)).await;
|
||||
assert_eq!(n, 1);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![2]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_zero() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
|
||||
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut vec![], 0)).poll(cx)).await;
|
||||
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_empty() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
let n = stream_map.next_many(&mut vec![], 1).await;
|
||||
|
||||
assert_eq!(n, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_pending() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
|
||||
|
||||
let mut is_pending = false;
|
||||
poll_fn(|cx| {
|
||||
let poll = pin!(stream_map.next_many(&mut vec![], 1)).poll(cx);
|
||||
|
||||
is_pending = poll.is_pending();
|
||||
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
|
||||
assert!(is_pending);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_not_enough() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 3)).poll(cx)).await;
|
||||
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(buffer.len(), 2);
|
||||
assert!(buffer.contains(&(0, 0)));
|
||||
assert!(buffer.contains(&(1, 1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_enough() {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 2)).poll(cx)).await;
|
||||
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(buffer.len(), 2);
|
||||
assert!(buffer.contains(&(0, 0)));
|
||||
assert!(buffer.contains(&(1, 1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn next_many_correctly_loops_around() {
|
||||
for _ in 0..10 {
|
||||
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
|
||||
|
||||
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
|
||||
stream_map.insert(1, Box::pin(iter([0usize, 1].into_iter())) as UsizeStream);
|
||||
stream_map.insert(2, Box::pin(iter([0usize, 1, 2].into_iter())) as UsizeStream);
|
||||
|
||||
let mut buffer = vec![];
|
||||
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 3)).poll(cx)).await;
|
||||
assert_eq!(n, 3);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![0, 0, 0]
|
||||
);
|
||||
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 2)).poll(cx)).await;
|
||||
assert_eq!(n, 2);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![1, 1]
|
||||
);
|
||||
|
||||
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 1)).poll(cx)).await;
|
||||
assert_eq!(n, 1);
|
||||
assert_eq!(
|
||||
std::mem::take(&mut buffer)
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![2]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![cfg(all(feature = "time", feature = "sync", feature = "io-util"))]
|
||||
|
||||
use tokio::time::{self, sleep, Duration};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_stream::{self, StreamExt};
|
||||
use tokio_test::*;
|
||||
|
||||
use futures::stream;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user