mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
243
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91b9850505 | ||
|
|
3c467056e9 | ||
|
|
afd2189eec | ||
|
|
986b88b3f1 | ||
|
|
257053e40b | ||
|
|
5af9e0db2b | ||
|
|
8f77ee8609 | ||
|
|
2747043f6f | ||
|
|
2a5071fc2d | ||
|
|
621790e165 | ||
|
|
7aad428994 | ||
|
|
9e38ebcaa9 | ||
|
|
9a57a6a7c4 | ||
|
|
24f4ee31f0 | ||
|
|
4eed411519 | ||
|
|
16a8404967 | ||
|
|
089eeae24b | ||
|
|
e255a265d3 | ||
|
|
e951d55720 | ||
|
|
1d698b5a90 | ||
|
|
867f137dc9 | ||
|
|
aea26b322c | ||
|
|
bcb968af84 | ||
|
|
cec1bc151e | ||
|
|
1601de1196 | ||
|
|
c800deaacc | ||
|
|
ac2343d984 | ||
|
|
553cc3b194 | ||
|
|
cb9a68eb1a | ||
|
|
cc8ad367a0 | ||
|
|
25e5141c36 | ||
|
|
4a12163d7c | ||
|
|
12dd06336d | ||
|
|
c301f6d83a | ||
|
|
fb35c83944 | ||
|
|
43cdb2cb50 | ||
|
|
49a9dc6743 | ||
|
|
96370ba4ce | ||
|
|
a9d9bde068 | ||
|
|
0190831ec1 | ||
|
|
ee0e811a36 | ||
|
|
47feaa7a89 | ||
|
|
dda8da75d0 | ||
|
|
dc1894105b | ||
|
|
78e0f0b42a | ||
|
|
e55f3d4398 | ||
|
|
8582363b4e | ||
|
|
c3fbaba1f9 | ||
|
|
22e6aef6e7 | ||
|
|
f64673580d | ||
|
|
54e6693dff | ||
|
|
4e3268d222 | ||
|
|
4b6bb1d9a7 | ||
|
|
eb1af7f29c | ||
|
|
0bc9160e25 | ||
|
|
4c571b55b1 | ||
|
|
60ba634d60 | ||
|
|
f73ed1fdba | ||
|
|
ee4b2ede83 | ||
|
|
64da914d17 | ||
|
|
d764ba5816 | ||
|
|
65fb0210d5 | ||
|
|
a77b2fbab2 | ||
|
|
347c0cdaba | ||
|
|
2c0e5c9704 | ||
|
|
2a614fba0d | ||
|
|
3b339024f0 | ||
|
|
1a423b3322 | ||
|
|
a8b662f643 | ||
|
|
cf3206842c | ||
|
|
8943e8aeef | ||
|
|
fe770dc509 | ||
|
|
8b6542fc3e | ||
|
|
b1afd95994 | ||
|
|
095b5dcf93 | ||
|
|
623c09c52c | ||
|
|
884a9a4b18 | ||
|
|
e0be45e49b | ||
|
|
26f0938bf3 | ||
|
|
4b78ed4d68 | ||
|
|
79d7a625d0 | ||
|
|
ccf855ec24 | ||
|
|
579f61106d | ||
|
|
f45320a9c0 | ||
|
|
1f8105588c | ||
|
|
94ee305741 | ||
|
|
669bc4476e | ||
|
|
1265d0c5dc | ||
|
|
09b770c5db | ||
|
|
d1a400912e | ||
|
|
aa03622cf3 | ||
|
|
ac89d8926d | ||
|
|
e184205421 | ||
|
|
44a1aad8df | ||
|
|
75c07770bf | ||
|
|
268ed5e73e | ||
|
|
0c68b89452 | ||
|
|
827694a9e3 | ||
|
|
d15e5fad16 | ||
|
|
9cb495cdb8 | ||
|
|
e7d3e0c93c | ||
|
|
e04b5be1f5 | ||
|
|
2734fa9a85 | ||
|
|
83aeae8610 | ||
|
|
cf550b2183 | ||
|
|
e1c8b5a159 | ||
|
|
eb7a615c96 | ||
|
|
44e9013f64 | ||
|
|
03969cdae7 | ||
|
|
095012b03b | ||
|
|
b5c1fb4012 | ||
|
|
d4848a9e2e | ||
|
|
bb6a292d0a | ||
|
|
eb2106f87e | ||
|
|
fadd0190da | ||
|
|
f0cb360d70 | ||
|
|
d047584e86 | ||
|
|
c5d37204dc | ||
|
|
7f26ad85c2 | ||
|
|
d39c9ed9dc | ||
|
|
44cfe10ee5 | ||
|
|
1073f6e8be | ||
|
|
dee3236c97 | ||
|
|
d9ca1517c6 | ||
|
|
6c1a1d9b07 | ||
|
|
d32acd97eb | ||
|
|
7875f26586 | ||
|
|
7ce8f05cff | ||
|
|
ea19606bc4 | ||
|
|
b9b59e4f15 | ||
|
|
cdc46a9ded | ||
|
|
b9834f6d8b | ||
|
|
1ed89aa5cf | ||
|
|
d9b2dc81ca | ||
|
|
8e54145c8b | ||
|
|
279e8b001a | ||
|
|
e9f6faee67 | ||
|
|
f1b89675eb | ||
|
|
ddd33f2b05 | ||
|
|
8e92f05795 | ||
|
|
957ed3eac0 | ||
|
|
ab34805849 | ||
|
|
8b298d9ed4 | ||
|
|
57563e218b | ||
|
|
33f0a1fd2e | ||
|
|
4c9b469562 | ||
|
|
7af0f32751 | ||
|
|
3fe1662e4b | ||
|
|
a73428252b | ||
|
|
b99eedc2ea | ||
|
|
6ebd0575e4 | ||
|
|
7e51b44a20 | ||
|
|
bd1e4aaea6 | ||
|
|
fd22164f5d | ||
|
|
98e78a6f7b | ||
|
|
01a6feb0dc | ||
|
|
e31e06c5c2 | ||
|
|
d0dd74a058 | ||
|
|
6778a7def6 | ||
|
|
9a97eb36bc | ||
|
|
1409041525 | ||
|
|
23b0aee5dd | ||
|
|
909d3ec0ff | ||
|
|
b67d46403f | ||
|
|
98578a6f4a | ||
|
|
8a097d27b5 | ||
|
|
1e2e38b7cd | ||
|
|
80bda3bf5f | ||
|
|
51f4f0594c | ||
|
|
897fed1609 | ||
|
|
fd52f9f66b | ||
|
|
dd060b16f5 | ||
|
|
4152918a39 | ||
|
|
7e474640dd | ||
|
|
84f6845bf2 | ||
|
|
2bc9a42d2b | ||
|
|
8aa2bfe23e | ||
|
|
5ac32934b4 | ||
|
|
d0305d57e5 | ||
|
|
5c19b5a162 | ||
|
|
f478647a8e | ||
|
|
c0974bad94 | ||
|
|
b67534a20b | ||
|
|
c4e56232ff | ||
|
|
84c4a6d89f | ||
|
|
b501f25202 | ||
|
|
032c55e77f | ||
|
|
1e95d6994a | ||
|
|
362df5a317 | ||
|
|
106bb94896 | ||
|
|
8198ef3881 | ||
|
|
e66217575b | ||
|
|
175d84e2b1 | ||
|
|
69a6585429 | ||
|
|
cf02b3f32d | ||
|
|
d01bda86a4 | ||
|
|
e60d7a474b | ||
|
|
0d9430b99c | ||
|
|
f51676891f | ||
|
|
3340ae6aa9 | ||
|
|
f957f7f9a7 | ||
|
|
8b447649bb | ||
|
|
4d8cc28b76 | ||
|
|
f2a06bff1b | ||
|
|
0de05422ce | ||
|
|
2b731c0e01 | ||
|
|
8f27c04a9e | ||
|
|
c85a0e524e | ||
|
|
df10b68d47 | ||
|
|
b280c6dcd7 | ||
|
|
998dc5a2eb | ||
|
|
ced7992f65 | ||
|
|
0cefa85bfa | ||
|
|
2087f3e0eb | ||
|
|
5ae2855eaf | ||
|
|
252004811f | ||
|
|
549e89e9cd | ||
|
|
c8fc492748 | ||
|
|
7c4183a45d | ||
|
|
aef2d64b0a | ||
|
|
8f10d81613 | ||
|
|
7a11cfd77a | ||
|
|
6610ba9bd6 | ||
|
|
3fd88dac64 | ||
|
|
ccd495647d | ||
|
|
80d8d40a34 | ||
|
|
3b38ebd7f5 | ||
|
|
127983e5b4 | ||
|
|
c306bf853a | ||
|
|
c6fbb9aeb1 | ||
|
|
5d61c997e9 | ||
|
|
c505a2f81a | ||
|
|
e2589a0e40 | ||
|
|
51fad066e2 | ||
|
|
ae233c1e9f | ||
|
|
be26ca7625 | ||
|
|
2d41f0303e | ||
|
|
4818c2ed05 | ||
|
|
80f0801e19 | ||
|
|
8148b2107c | ||
|
|
2e7de1ae1d | ||
|
|
37e60fc7f9 | ||
|
|
8170e2787c |
@@ -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,22 @@
|
||||
version: 2.1
|
||||
jobs:
|
||||
test-arm:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.medium
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install Rust
|
||||
command: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o rustup.sh
|
||||
chmod +x rustup.sh
|
||||
./rustup.sh -y
|
||||
source "$HOME"/.cargo/env
|
||||
# Only run Tokio tests
|
||||
- run: cargo test --all-features -p tokio
|
||||
|
||||
workflows:
|
||||
ci:
|
||||
jobs:
|
||||
- test-arm
|
||||
+34
-9
@@ -1,15 +1,45 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-2-release-amd64
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD
|
||||
name: FreeBSD 64-bit
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all --all-features
|
||||
|
||||
task:
|
||||
name: FreeBSD docs
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUSTFLAGS: --cfg docsrs
|
||||
RUSTDOCFLAGS: --cfg docsrs -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain nightly-2022-01-12
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo doc --lib --no-deps --all-features --document-private-items
|
||||
|
||||
task:
|
||||
name: FreeBSD 32-bit
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
@@ -21,9 +51,4 @@ task:
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all --all-features
|
||||
- cargo doc --all --no-deps
|
||||
i686_test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
cargo test --all --all-features --target i686-unknown-freebsd
|
||||
- cargo test --all --all-features --target i686-unknown-freebsd
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
msrv = "1.45"
|
||||
msrv = "1.46"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
R-loom:
|
||||
- tokio/src/sync/*
|
||||
- tokio/src/sync/**/*
|
||||
- tokio-util/src/sync/*
|
||||
- tokio-util/src/sync/**/*
|
||||
- tokio/src/runtime/*
|
||||
- tokio/src/runtime/**/*
|
||||
@@ -1,55 +0,0 @@
|
||||
name: Benchmark
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
bench:
|
||||
- rt_multi_threaded
|
||||
- sync_mpsc
|
||||
- sync_rwlock
|
||||
- sync_semaphore
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
# Run benchmark with `go test -bench` and stores the output to a file
|
||||
- name: Run benchmark
|
||||
run: cargo bench --bench ${{ matrix.bench }} | tee ../output.txt
|
||||
working-directory: benches
|
||||
|
||||
# Download previous benchmark result from cache (if exists)
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
|
||||
# Run `github-action-benchmark` action
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
name: ${{ matrix.bench }}
|
||||
# What benchmark tool the output.txt came from
|
||||
tool: 'cargo'
|
||||
# Where the output from the benchmark tool is stored
|
||||
output-file-path: output.txt
|
||||
# # Where the previous data file is stored
|
||||
# external-data-json-path: ./cache/benchmark-data.json
|
||||
# Workflow will fail when an alert happens
|
||||
fail-on-alert: true
|
||||
# GitHub API token to make a commit comment
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Enable alert commit comment
|
||||
comment-on-alert: true
|
||||
alert-comment-cc-users: '@tokio-rs/maintainers'
|
||||
auto-push: true
|
||||
|
||||
# Upload the updated cache file for the next job by actions/cache
|
||||
+122
-28
@@ -9,8 +9,12 @@ name: CI
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
nightly: nightly-2021-04-25
|
||||
minrust: 1.45.2
|
||||
nightly: nightly-2022-01-12
|
||||
minrust: 1.46
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
# Depends on all action sthat are required for a "successful" CI run.
|
||||
@@ -20,6 +24,7 @@ jobs:
|
||||
needs:
|
||||
- test
|
||||
- test-unstable
|
||||
- test-parking_lot
|
||||
- miri
|
||||
- cross
|
||||
- features
|
||||
@@ -27,8 +32,11 @@ jobs:
|
||||
- fmt
|
||||
- clippy
|
||||
- docs
|
||||
- loom
|
||||
- valgrind
|
||||
- loom-compile
|
||||
- check-readme
|
||||
- test-hyper
|
||||
- wasm32-unknown-unknown
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
@@ -45,6 +53,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
|
||||
@@ -75,6 +84,27 @@ jobs:
|
||||
# bench.yml workflow runs benchmarks only on linux.
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
|
||||
test-parking_lot:
|
||||
# The parking_lot crate has a feature called send_guard which changes when
|
||||
# some of its types are Send. Tokio has some measures in place to prevent
|
||||
# this from affecting when Tokio types are Send, and this test exists to
|
||||
# ensure that those measures are working.
|
||||
#
|
||||
# This relies on the potentially affected Tokio type being listed in
|
||||
# `tokio/tokio/tests/async_send_sync.rs`.
|
||||
name: compile tests with parking lot send guards
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Enable parking_lot send_guard feature
|
||||
# Inserts the line "plsend = ["parking_lot/send_guard"]" right after [features]
|
||||
run: sed -i '/\[features\]/a plsend = ["parking_lot/send_guard"]' tokio/Cargo.toml
|
||||
- name: Compile tests with all features enabled
|
||||
run: cargo build --workspace --all-features --tests
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
runs-on: ubuntu-latest
|
||||
@@ -82,6 +112,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
- name: Install Valgrind
|
||||
run: |
|
||||
@@ -95,7 +126,7 @@ jobs:
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind test-mem
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
|
||||
|
||||
# Compile tests
|
||||
- name: cargo build test-process-signal
|
||||
@@ -104,7 +135,7 @@ jobs:
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind test-process-signal
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-process-signal
|
||||
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/test-process-signal
|
||||
|
||||
test-unstable:
|
||||
name: test tokio full --unstable
|
||||
@@ -119,6 +150,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
# Run `tokio` with "unstable" cfg flag.
|
||||
- name: test tokio full --cfg unstable
|
||||
@@ -136,6 +168,7 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install Miri
|
||||
run: |
|
||||
set -e
|
||||
@@ -155,6 +188,7 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: asan
|
||||
run: cargo test --all-features --target x86_64-unknown-linux-gnu --lib -- --test-threads 1
|
||||
working-directory: tokio
|
||||
@@ -180,6 +214,7 @@ jobs:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
@@ -195,6 +230,7 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
|
||||
@@ -216,7 +252,7 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ env.minrust }}
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: "test --workspace --all-features"
|
||||
run: cargo check --workspace --all-features
|
||||
|
||||
@@ -229,6 +265,7 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
- name: "check --all-features -Z minimal-versions"
|
||||
@@ -239,6 +276,16 @@ jobs:
|
||||
# Update Cargo.lock to minimal version dependencies.
|
||||
cargo update -Z minimal-versions
|
||||
cargo hack check --all-features --ignore-private
|
||||
- name: "check --all-features --unstable -Z minimal-versions"
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
cargo hack --remove-dev-deps --workspace
|
||||
# Update Cargo.lock to minimal version dependencies.
|
||||
cargo update -Z minimal-versions
|
||||
cargo hack check --all-features --ignore-private
|
||||
|
||||
fmt:
|
||||
name: fmt
|
||||
@@ -247,15 +294,14 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- name: Install rustfmt
|
||||
run: rustup component add rustfmt
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
# Check fmt
|
||||
- name: "rustfmt --check"
|
||||
# Workaround for rust-lang/cargo#7732
|
||||
run: |
|
||||
if ! rustfmt --check --edition 2018 $(find . -name '*.rs' -print); then
|
||||
printf "Please run \`rustfmt --edition 2018 \$(find . -name '*.rs' -print)\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
if ! rustfmt --check --edition 2018 $(git ls-files '*.rs'); then
|
||||
printf "Please run \`rustfmt --edition 2018 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -265,7 +311,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update 1.52.1 && rustup default 1.52.1
|
||||
run: rustup update 1.57 && rustup default 1.57
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install clippy
|
||||
run: rustup component add clippy
|
||||
|
||||
@@ -282,33 +329,80 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: "doc --lib --all-features"
|
||||
run: cargo doc --lib --no-deps --all-features --document-private-items
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs
|
||||
RUSTDOCFLAGS: --cfg docsrs -Dwarnings
|
||||
|
||||
loom:
|
||||
name: loom
|
||||
loom-compile:
|
||||
name: build loom tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
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@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: build --cfg loom
|
||||
run: cargo test --no-run --lib --features full
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
check-readme:
|
||||
name: Check README
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Verify that both READMEs are identical
|
||||
run: diff README.md tokio/README.md
|
||||
|
||||
- name: Verify that Tokio version is up to date in README
|
||||
working-directory: tokio
|
||||
run: grep -q "$(sed '/^version = /!d' Cargo.toml | head -n1)" README.md
|
||||
|
||||
test-hyper:
|
||||
name: Test hyper
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Test hyper
|
||||
run: |
|
||||
set -x
|
||||
git clone https://github.com/hyperium/hyper.git
|
||||
cd hyper
|
||||
# checkout the latest release because HEAD maybe contains breakage.
|
||||
tag=$(git describe --abbrev=0 --tags)
|
||||
git checkout "${tag}"
|
||||
echo '[workspace]' >>Cargo.toml
|
||||
echo '[patch.crates-io]' >>Cargo.toml
|
||||
echo 'tokio = { path = "../tokio" }' >>Cargo.toml
|
||||
echo 'tokio-util = { path = "../tokio-util" }' >>Cargo.toml
|
||||
echo 'tokio-stream = { path = "../tokio-stream" }' >>Cargo.toml
|
||||
echo 'tokio-test = { path = "../tokio-test" }' >>Cargo.toml
|
||||
git diff
|
||||
cargo test --features full
|
||||
|
||||
wasm32-unknown-unknown:
|
||||
name: test tokio for wasm32-unknown-unknown
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: test tokio
|
||||
run: wasm-pack test --node -- --features "macros sync"
|
||||
working-directory: tokio
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
# See .github/labeler.yml file
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
@@ -0,0 +1,40 @@
|
||||
on:
|
||||
push:
|
||||
branches: ["master", "tokio-*.x"]
|
||||
pull_request:
|
||||
types: [labeled, opened, synchronize, reopened]
|
||||
branches: ["master", "tokio-*.x"]
|
||||
|
||||
name: Loom
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
loom:
|
||||
name: loom
|
||||
# base_ref is null when it's not a pull request
|
||||
if: contains(github.event.pull_request.labels.*.name, 'R-loom') || (github.base_ref == null)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
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@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install Valgrind
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
|
||||
+9
-1
@@ -139,6 +139,14 @@ correctly, use this command:
|
||||
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg tokio_unstable" cargo +nightly doc --all-features
|
||||
```
|
||||
|
||||
There is currently a [bug in cargo] that means documentation cannot be built
|
||||
from the root of the workspace. If you `cd` into the `tokio` subdirectory the
|
||||
command shown above will work.
|
||||
@@ -150,7 +158,7 @@ command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2018 $(find . -name '*.rs' -print)
|
||||
rustfmt --check --edition 2018 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.8.0", features = ["full"] }
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -140,8 +140,7 @@ several other libraries, including:
|
||||
|
||||
* [`tower`]: A library of modular and reusable components for building robust networking clients and servers.
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level
|
||||
tracing and async-aware diagnostics.
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
@@ -164,9 +163,36 @@ several other libraries, including:
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.45.
|
||||
The current Tokio version is not guaranteed to build on Rust versions earlier than the
|
||||
minimum supported version.
|
||||
Tokio is built against the latest stable release. The minimum supported version
|
||||
is 1.46. The current Tokio version is not guaranteed to build on Rust versions
|
||||
earlier than the minimum supported version.
|
||||
|
||||
## Release schedule
|
||||
|
||||
Tokio doesn't follow a fixed release schedule, but we typically make one to two
|
||||
new minor releases each month. We make patch releases for bugfixes as necessary.
|
||||
|
||||
## Bug patching policy
|
||||
|
||||
For the purposes of making patch releases with bugfixes, we have designated
|
||||
certain minor releases as LTS (long term support) releases. Whenever a bug
|
||||
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.8.x` - LTS release until February 2022.
|
||||
* `1.14.x` - LTS release until June 2022.
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least half a
|
||||
year. If you wish to use a fixed minor release in your project, we recommend
|
||||
that you use an LTS release.
|
||||
|
||||
To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.8.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.8", features = [...] }
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-util = { version = "0.6.6", path = "../tokio-util", features = ["full"] }
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
|
||||
tokio-stream = { path = "../tokio-stream" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ fn rt() -> tokio::runtime::Runtime {
|
||||
const BLOCK_COUNT: usize = 1_000;
|
||||
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
const DEV_ZERO: &'static str = "/dev/zero";
|
||||
const DEV_ZERO: &str = "/dev/zero";
|
||||
|
||||
fn async_read_codec(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
+57
-37
@@ -2,63 +2,83 @@
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bencher;
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
|
||||
async fn work() -> usize {
|
||||
let val = 1 + 1;
|
||||
tokio::task::yield_now().await;
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
fn basic_scheduler_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
bench.iter(|| {
|
||||
let h = tokio::spawn(work());
|
||||
black_box(h);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
bench.iter(|| {
|
||||
let h = tokio::spawn(work());
|
||||
black_box(h);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn basic_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
bench.iter(|| {
|
||||
let h = runtime.spawn(work());
|
||||
black_box(h);
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap();
|
||||
|
||||
fn basic_scheduler_spawn10(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
let h = runtime.spawn(work());
|
||||
black_box(h);
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn10(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
spawn,
|
||||
basic_scheduler_local_spawn,
|
||||
threaded_scheduler_local_spawn,
|
||||
basic_scheduler_remote_spawn,
|
||||
threaded_scheduler_remote_spawn
|
||||
basic_scheduler_spawn,
|
||||
basic_scheduler_spawn10,
|
||||
threaded_scheduler_spawn,
|
||||
threaded_scheduler_spawn10,
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(spawn);
|
||||
|
||||
+5
-5
@@ -5,14 +5,14 @@ publish = false
|
||||
edition = "2018"
|
||||
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead.
|
||||
# [dependencies] instead, and delete the **path**.
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio",features = ["full", "tracing"] }
|
||||
tokio-util = { version = "0.6.3", path = "../tokio-util",features = ["full"] }
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing"] }
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
|
||||
tracing-subscriber = { version = "0.3.1", default-features = false, features = ["fmt", "ansi", "env-filter", "tracing-log"] }
|
||||
bytes = "1.0.0"
|
||||
futures = { version = "0.3.0", features = ["thread-pool"]}
|
||||
http = "0.2"
|
||||
@@ -20,7 +20,7 @@ serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
httparse = "1.0"
|
||||
time = "0.1"
|
||||
httpdate = "1.0"
|
||||
once_cell = "1.5.2"
|
||||
rand = "0.8.3"
|
||||
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
fn handle_request(line: &str, db: &Arc<Database>) -> Response {
|
||||
let request = match Request::parse(&line) {
|
||||
let request = match Request::parse(line) {
|
||||
Ok(req) => req,
|
||||
Err(e) => return Response::Error { msg: e },
|
||||
};
|
||||
|
||||
+14
-10
@@ -221,8 +221,9 @@ mod date {
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::{self, Write};
|
||||
use std::str;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use time::{self, Duration};
|
||||
use httpdate::HttpDate;
|
||||
|
||||
pub struct Now(());
|
||||
|
||||
@@ -252,22 +253,26 @@ mod date {
|
||||
struct LastRenderedNow {
|
||||
bytes: [u8; 128],
|
||||
amt: usize,
|
||||
next_update: time::Timespec,
|
||||
unix_date: u64,
|
||||
}
|
||||
|
||||
thread_local!(static LAST: RefCell<LastRenderedNow> = RefCell::new(LastRenderedNow {
|
||||
bytes: [0; 128],
|
||||
amt: 0,
|
||||
next_update: time::Timespec::new(0, 0),
|
||||
unix_date: 0,
|
||||
}));
|
||||
|
||||
impl fmt::Display for Now {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
LAST.with(|cache| {
|
||||
let mut cache = cache.borrow_mut();
|
||||
let now = time::get_time();
|
||||
if now >= cache.next_update {
|
||||
cache.update(now);
|
||||
let now = SystemTime::now();
|
||||
let now_unix = now
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|since_epoch| since_epoch.as_secs())
|
||||
.unwrap_or(0);
|
||||
if cache.unix_date != now_unix {
|
||||
cache.update(now, now_unix);
|
||||
}
|
||||
f.write_str(cache.buffer())
|
||||
})
|
||||
@@ -279,11 +284,10 @@ mod date {
|
||||
str::from_utf8(&self.bytes[..self.amt]).unwrap()
|
||||
}
|
||||
|
||||
fn update(&mut self, now: time::Timespec) {
|
||||
fn update(&mut self, now: SystemTime, now_unix: u64) {
|
||||
self.amt = 0;
|
||||
write!(LocalBuffer(self), "{}", time::at(now).rfc822()).unwrap();
|
||||
self.next_update = now + Duration::seconds(1);
|
||||
self.next_update.nsec = 0;
|
||||
self.unix_date = now_unix;
|
||||
write!(LocalBuffer(self), "{}", HttpDate::from(now)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@ error: The default runtime flavor is `multi_thread`, but the `rt-multi-thread` f
|
||||
3 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
@@ -7,12 +7,6 @@ async fn missing_semicolon_or_return_type() {
|
||||
|
||||
#[tokio::main]
|
||||
async fn missing_return_type() {
|
||||
/* TODO(taiki-e): one of help messages still wrong
|
||||
help: consider using a semicolon here
|
||||
|
|
||||
16 | return Ok(());;
|
||||
|
|
||||
*/
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -21,9 +15,9 @@ async fn extra_semicolon() -> Result<(), ()> {
|
||||
/* TODO(taiki-e): help message still wrong
|
||||
help: try using a variant of the expected enum
|
||||
|
|
||||
29 | Ok(Ok(());)
|
||||
23 | Ok(Ok(());)
|
||||
|
|
||||
29 | Err(Ok(());)
|
||||
23 | Err(Ok(());)
|
||||
|
|
||||
*/
|
||||
Ok(());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:5:5
|
||||
--> tests/fail/macros_type_mismatch.rs:5:5
|
||||
|
|
||||
5 | Ok(())
|
||||
| ^^^^^^ expected `()`, found enum `Result`
|
||||
@@ -9,43 +9,36 @@ error[E0308]: mismatched types
|
||||
help: consider using a semicolon here
|
||||
|
|
||||
5 | Ok(());
|
||||
| ^
|
||||
| +
|
||||
help: try adding a return type
|
||||
|
|
||||
4 | async fn missing_semicolon_or_return_type() -> Result<(), _> {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
| ++++++++++++++++
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:16:5
|
||||
--> tests/fail/macros_type_mismatch.rs:10:5
|
||||
|
|
||||
16 | return Ok(());
|
||||
9 | async fn missing_return_type() {
|
||||
| - help: try adding a return type: `-> Result<(), _>`
|
||||
10 | return Ok(());
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found enum `Result`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
help: consider using a semicolon here
|
||||
|
|
||||
16 | return Ok(());;
|
||||
| ^
|
||||
help: try adding a return type
|
||||
|
|
||||
9 | async fn missing_return_type() -> Result<(), _> {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:29:5
|
||||
--> tests/fail/macros_type_mismatch.rs:23:5
|
||||
|
|
||||
20 | async fn extra_semicolon() -> Result<(), ()> {
|
||||
14 | async fn extra_semicolon() -> Result<(), ()> {
|
||||
| -------------- expected `Result<(), ()>` because of return type
|
||||
...
|
||||
29 | Ok(());
|
||||
23 | Ok(());
|
||||
| ^^^^^^^ expected enum `Result`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
help: try using a variant of the expected enum
|
||||
help: try adding an expression at the end of the block
|
||||
|
|
||||
29 | Ok(Ok(());)
|
||||
|
|
||||
29 | Err(Ok(());)
|
||||
23 ~ Ok(());;
|
||||
24 + Ok(())
|
||||
|
|
||||
|
||||
@@ -5,6 +5,12 @@ fn compile_fail_full() {
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/forward_args_and_output.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/macros_main_return.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/macros_main_loop.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#[cfg(feature = "full")]
|
||||
#[tokio::test]
|
||||
async fn test_with_semicolon_without_return_type() {
|
||||
#![deny(clippy::semicolon_if_nothing_returned)]
|
||||
|
||||
dbg!(0);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), ()> {
|
||||
loop {
|
||||
if !never() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn never() -> bool {
|
||||
std::time::Instant::now() > std::time::Instant::now()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), ()> {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -1,3 +1,54 @@
|
||||
# 1.7.0 (December 15th, 2021)
|
||||
|
||||
- macros: address remainging clippy::semicolon_if_nothing_returned warning ([#4252])
|
||||
|
||||
[#4252]: https://github.com/tokio-rs/tokio/pull/4252
|
||||
|
||||
# 1.6.0 (November 16th, 2021)
|
||||
|
||||
- macros: fix mut patterns in `select!` macro ([#4211])
|
||||
|
||||
[#4211]: https://github.com/tokio-rs/tokio/pull/4211
|
||||
|
||||
# 1.5.1 (October 29th, 2021)
|
||||
|
||||
- macros: fix type resolution error in `#[tokio::main]` ([#4176])
|
||||
|
||||
[#4176]: https://github.com/tokio-rs/tokio/pull/4176
|
||||
|
||||
# 1.5.0 (October 13th, 2021)
|
||||
|
||||
- macros: make tokio-macros attributes more IDE friendly ([#4162])
|
||||
|
||||
[#4162]: https://github.com/tokio-rs/tokio/pull/4162
|
||||
|
||||
# 1.4.1 (September 30th, 2021)
|
||||
|
||||
Reverted: run `current_thread` inside `LocalSet` ([#4027])
|
||||
|
||||
# 1.4.0 (September 29th, 2021)
|
||||
|
||||
(yanked)
|
||||
|
||||
### Changed
|
||||
|
||||
- macros: run `current_thread` inside `LocalSet` ([#4027])
|
||||
- macros: explicitly relaxed clippy lint for `.expect()` in runtime entry macro ([#4030])
|
||||
|
||||
### Fixed
|
||||
|
||||
- macros: fix invalid error messages in functions wrapped with `#[main]` or `#[test]` ([#4067])
|
||||
|
||||
[#4027]: https://github.com/tokio-rs/tokio/pull/4027
|
||||
[#4030]: https://github.com/tokio-rs/tokio/pull/4030
|
||||
[#4067]: https://github.com/tokio-rs/tokio/pull/4067
|
||||
|
||||
# 1.3.0 (July 7, 2021)
|
||||
|
||||
- macros: don't trigger `clippy::unwrap_used` ([#3926])
|
||||
|
||||
[#3926]: https://github.com/tokio-rs/tokio/pull/3926
|
||||
|
||||
# 1.2.0 (May 14, 2021)
|
||||
|
||||
- macros: forward input arguments in `#[tokio::test]` ([#3691])
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
name = "tokio-macros"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.0.x" git tag.
|
||||
version = "1.2.0"
|
||||
version = "1.7.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.46"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-macros/1.2.0/tokio_macros"
|
||||
description = """
|
||||
Tokio's proc macros.
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
+97
-42
@@ -1,6 +1,10 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::parse::Parser;
|
||||
|
||||
// syn::AttributeArgs does not implement syn::Parse
|
||||
type AttributeArgs = syn::punctuated::Punctuated<syn::NestedMeta, syn::Token![,]>;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum RuntimeFlavor {
|
||||
@@ -27,6 +31,13 @@ struct FinalConfig {
|
||||
start_paused: Option<bool>,
|
||||
}
|
||||
|
||||
/// Config used in case of the attribute not being able to build a valid config
|
||||
const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
|
||||
flavor: RuntimeFlavor::CurrentThread,
|
||||
worker_threads: None,
|
||||
start_paused: None,
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
rt_multi_thread_available: bool,
|
||||
default_flavor: RuntimeFlavor,
|
||||
@@ -184,13 +195,13 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_knobs(
|
||||
mut input: syn::ItemFn,
|
||||
args: syn::AttributeArgs,
|
||||
fn build_config(
|
||||
input: syn::ItemFn,
|
||||
args: AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_multi_thread: bool,
|
||||
) -> Result<TokenStream, syn::Error> {
|
||||
if input.sig.asyncness.take().is_none() {
|
||||
) -> Result<FinalConfig, syn::Error> {
|
||||
if input.sig.asyncness.is_none() {
|
||||
let msg = "the `async` keyword is missing from the function declaration";
|
||||
return Err(syn::Error::new_spanned(input.sig.fn_token, msg));
|
||||
}
|
||||
@@ -201,12 +212,15 @@ fn parse_knobs(
|
||||
for arg in args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
|
||||
let ident = namevalue.path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
let ident = namevalue
|
||||
.path
|
||||
.get_ident()
|
||||
.ok_or_else(|| {
|
||||
syn::Error::new_spanned(&namevalue, "Must have specified ident")
|
||||
})?
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
match ident.as_str() {
|
||||
"worker_threads" => {
|
||||
config.set_worker_threads(
|
||||
namevalue.lit.clone(),
|
||||
@@ -239,12 +253,11 @@ fn parse_knobs(
|
||||
}
|
||||
}
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
let name = ident.unwrap().to_string().to_lowercase();
|
||||
let name = path
|
||||
.get_ident()
|
||||
.ok_or_else(|| syn::Error::new_spanned(&path, "Must have specified ident"))?
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
let msg = match name.as_str() {
|
||||
"threaded_scheduler" | "multi_thread" => {
|
||||
format!(
|
||||
@@ -276,7 +289,11 @@ fn parse_knobs(
|
||||
}
|
||||
}
|
||||
|
||||
let config = config.build()?;
|
||||
config.build()
|
||||
}
|
||||
|
||||
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) = {
|
||||
@@ -321,16 +338,32 @@ fn parse_knobs(
|
||||
|
||||
let body = &input.block;
|
||||
let brace_token = input.block.brace_token;
|
||||
let (tail_return, tail_semicolon) = match body.stmts.last() {
|
||||
Some(syn::Stmt::Semi(syn::Expr::Return(_), _)) => (quote! { return }, quote! { ; }),
|
||||
Some(syn::Stmt::Semi(..)) | Some(syn::Stmt::Local(..)) | None => {
|
||||
match &input.sig.output {
|
||||
syn::ReturnType::Type(_, ty) if matches!(&**ty, syn::Type::Tuple(ty) if ty.elems.is_empty()) =>
|
||||
{
|
||||
(quote! {}, quote! { ; }) // unit
|
||||
}
|
||||
syn::ReturnType::Default => (quote! {}, quote! { ; }), // unit
|
||||
syn::ReturnType::Type(..) => (quote! {}, quote! {}), // ! or another
|
||||
}
|
||||
}
|
||||
_ => (quote! {}, quote! {}),
|
||||
};
|
||||
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
|
||||
{
|
||||
#rt
|
||||
let body = async #body;
|
||||
#[allow(clippy::expect_used)]
|
||||
#tail_return #rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async #body)
|
||||
.expect("Failed building the Runtime")
|
||||
.block_on(body)#tail_semicolon
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
.expect("Parsing failure");
|
||||
input.block.brace_token = brace_token;
|
||||
|
||||
let result = quote! {
|
||||
@@ -338,36 +371,58 @@ fn parse_knobs(
|
||||
#input
|
||||
};
|
||||
|
||||
Ok(result.into())
|
||||
result.into()
|
||||
}
|
||||
|
||||
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||
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 {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
// 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: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
|
||||
if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
|
||||
let config = if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.ident, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
Err(syn::Error::new_spanned(&input.sig.ident, msg))
|
||||
} else {
|
||||
AttributeArgs::parse_terminated
|
||||
.parse(args)
|
||||
.and_then(|args| build_config(input.clone(), args, false, rt_multi_thread))
|
||||
};
|
||||
|
||||
parse_knobs(input, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
match config {
|
||||
Ok(config) => parse_knobs(input, false, config),
|
||||
Err(e) => token_stream_with_error(parse_knobs(input, false, DEFAULT_ERROR_CONFIG), e),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
// 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: 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.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
|
||||
.parse(args)
|
||||
.and_then(|args| build_config(input.clone(), args, true, rt_multi_thread))
|
||||
};
|
||||
|
||||
for attr in &input.attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
match config {
|
||||
Ok(config) => parse_knobs(input, true, config),
|
||||
Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e),
|
||||
}
|
||||
|
||||
parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
@@ -329,3 +328,11 @@ pub fn test_fail(_args: TokenStream, _item: TokenStream) -> TokenStream {
|
||||
pub fn select_priv_declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
select::declare_output_enum(input)
|
||||
}
|
||||
|
||||
/// Implementation detail of the `select!` macro. This macro is **not** intended
|
||||
/// to be used as part of the public API and is permitted to change.
|
||||
#[proc_macro]
|
||||
#[doc(hidden)]
|
||||
pub fn select_priv_clean_pattern(input: TokenStream) -> TokenStream {
|
||||
select::clean_pattern_macro(input)
|
||||
}
|
||||
|
||||
@@ -41,3 +41,70 @@ pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
pub(super) type Mask = #mask;
|
||||
})
|
||||
}
|
||||
|
||||
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::parse(input.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(_) => return input,
|
||||
};
|
||||
|
||||
clean_pattern(&mut input);
|
||||
quote::ToTokens::into_token_stream(input).into()
|
||||
}
|
||||
|
||||
// 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) => {}
|
||||
syn::Pat::Range(_range) => {}
|
||||
syn::Pat::Rest(_rest) => {}
|
||||
syn::Pat::Verbatim(_tokens) => {}
|
||||
syn::Pat::Wild(_underscore) => {}
|
||||
syn::Pat::Ident(ident) => {
|
||||
ident.by_ref = None;
|
||||
ident.mutability = None;
|
||||
if let Some((_at, pat)) = &mut ident.subpat {
|
||||
clean_pattern(&mut *pat);
|
||||
}
|
||||
}
|
||||
syn::Pat::Or(or) => {
|
||||
for case in or.cases.iter_mut() {
|
||||
clean_pattern(case);
|
||||
}
|
||||
}
|
||||
syn::Pat::Slice(slice) => {
|
||||
for elem in slice.elems.iter_mut() {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::Struct(struct_pat) => {
|
||||
for field in struct_pat.fields.iter_mut() {
|
||||
clean_pattern(&mut field.pat);
|
||||
}
|
||||
}
|
||||
syn::Pat::Tuple(tuple) => {
|
||||
for elem in tuple.elems.iter_mut() {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::TupleStruct(tuple) => {
|
||||
for elem in tuple.pat.elems.iter_mut() {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::Reference(reference) => {
|
||||
reference.mutability = None;
|
||||
clean_pattern(&mut *reference.pat);
|
||||
}
|
||||
syn::Pat::Type(type_pat) => {
|
||||
clean_pattern(&mut *type_pat.pat);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
# 0.1.8 (October 29, 2021)
|
||||
|
||||
- stream: add `From<Receiver<T>>` impl for receiver streams ([#4080])
|
||||
- stream: impl `FromIterator` for `StreamMap` ([#4052])
|
||||
- signal: make windows docs for signal module show up on unix builds ([#3770])
|
||||
|
||||
[#3770]: https://github.com/tokio-rs/tokio/pull/3770
|
||||
[#4052]: https://github.com/tokio-rs/tokio/pull/4052
|
||||
[#4080]: https://github.com/tokio-rs/tokio/pull/4080
|
||||
|
||||
# 0.1.7 (July 7, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: fix watch wrapper ([#3914])
|
||||
- time: fix `Timeout::size_hint` ([#3902])
|
||||
|
||||
[#3902]: https://github.com/tokio-rs/tokio/pull/3902
|
||||
[#3914]: https://github.com/tokio-rs/tokio/pull/3914
|
||||
|
||||
# 0.1.6 (May 14, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
name = "tokio-stream"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
edition = "2018"
|
||||
rust-version = "1.46"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-stream/0.1.6/tokio_stream"
|
||||
description = """
|
||||
Utilities to work with `Stream` and `tokio`.
|
||||
"""
|
||||
@@ -30,8 +28,8 @@ signal = ["tokio/signal"]
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.6.3", path = "../tokio-util", optional = true }
|
||||
tokio = { version = "1.8.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
|
||||
@@ -44,3 +42,8 @@ proptest = "1"
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
# Issue #3770
|
||||
#
|
||||
# This should allow `docsrs` to be read across projects, so that `tokio-stream`
|
||||
# can pick up stubbed types exported by `tokio`.
|
||||
rustc-args = ["--cfg", "docsrs"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
unreachable_pub
|
||||
)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#. Once `None` is returned,
|
||||
/// the underlying stream will not be polled again.
|
||||
///
|
||||
/// Note that this function consumes the stream passed into it and returns a
|
||||
/// wrapped version of it, similar to the [`Iterator::map_while`] method in the
|
||||
/// standard library.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let stream = stream::iter(1..=10);
|
||||
/// let mut stream = stream.map_while(|x| {
|
||||
/// if x < 4 {
|
||||
/// Some(x + 3)
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// });
|
||||
/// assert_eq!(stream.next().await, Some(4));
|
||||
/// assert_eq!(stream.next().await, Some(5));
|
||||
/// assert_eq!(stream.next().await, Some(6));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// # }
|
||||
/// ```
|
||||
fn map_while<T, F>(self, f: F) -> MapWhile<Self, F>
|
||||
where
|
||||
F: FnMut(Self::Item) -> Option<T>,
|
||||
Self: Sized,
|
||||
{
|
||||
MapWhile::new(self, f)
|
||||
}
|
||||
|
||||
/// Maps this stream's items asynchronously to a different type, returning a
|
||||
/// new stream of the resulting type.
|
||||
///
|
||||
/// The provided closure is executed over all elements of this stream as
|
||||
/// they are made available, and the returned future is executed. Only one
|
||||
/// future is executed at the time.
|
||||
///
|
||||
/// Note that this function consumes the stream passed into it and returns a
|
||||
/// wrapped version of it, similar to the existing `then` methods in the
|
||||
/// standard library.
|
||||
///
|
||||
/// Be aware that if the future is not `Unpin`, then neither is the `Stream`
|
||||
/// returned by this method. To handle this, you can use `tokio::pin!` as in
|
||||
/// the example below or put the stream in a `Box` with `Box::pin(stream)`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// async fn do_async_work(value: i32) -> i32 {
|
||||
/// value + 3
|
||||
/// }
|
||||
///
|
||||
/// let stream = stream::iter(1..=3);
|
||||
/// let stream = stream.then(do_async_work);
|
||||
///
|
||||
/// tokio::pin!(stream);
|
||||
///
|
||||
/// assert_eq!(stream.next().await, Some(4));
|
||||
/// assert_eq!(stream.next().await, Some(5));
|
||||
/// assert_eq!(stream.next().await, Some(6));
|
||||
/// # }
|
||||
/// ```
|
||||
fn then<F, Fut>(self, f: F) -> Then<Self, Fut, F>
|
||||
where
|
||||
F: FnMut(Self::Item) -> Fut,
|
||||
Fut: Future,
|
||||
Self: Sized,
|
||||
{
|
||||
Then::new(self, f)
|
||||
}
|
||||
|
||||
/// Combine two streams into one by interleaving the output of both as it
|
||||
/// is produced.
|
||||
///
|
||||
|
||||
@@ -66,17 +66,17 @@ where
|
||||
use Poll::Ready;
|
||||
|
||||
loop {
|
||||
let mut me = self.as_mut().project();
|
||||
let me = self.as_mut().project();
|
||||
|
||||
let item = match ready!(me.stream.poll_next(cx)) {
|
||||
Some(item) => item,
|
||||
None => {
|
||||
return Ready(U::finalize(sealed::Internal, &mut me.collection));
|
||||
return Ready(U::finalize(sealed::Internal, me.collection));
|
||||
}
|
||||
};
|
||||
|
||||
if !U::extend(sealed::Internal, &mut me.collection, item) {
|
||||
return Ready(U::finalize(sealed::Internal, &mut me.collection));
|
||||
if !U::extend(sealed::Internal, me.collection, item) {
|
||||
return Ready(U::finalize(sealed::Internal, me.collection));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ impl<T: AsRef<str>> sealed::FromStreamPriv<T> for String {
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut String) -> String {
|
||||
mem::replace(collection, String::new())
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ impl<T> sealed::FromStreamPriv<T> for Vec<T> {
|
||||
}
|
||||
|
||||
fn finalize(_: sealed::Internal, collection: &mut Vec<T>) -> Vec<T> {
|
||||
mem::replace(collection, vec![])
|
||||
mem::take(collection)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Stream for the [`map_while`](super::StreamExt::map_while) method.
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct MapWhile<St, F> {
|
||||
#[pin]
|
||||
stream: St,
|
||||
f: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F> fmt::Debug for MapWhile<St, F>
|
||||
where
|
||||
St: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MapWhile")
|
||||
.field("stream", &self.stream)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F> MapWhile<St, F> {
|
||||
pub(super) fn new(stream: St, f: F) -> Self {
|
||||
MapWhile { stream, f }
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F, T> Stream for MapWhile<St, F>
|
||||
where
|
||||
St: Stream,
|
||||
F: FnMut(St::Item) -> Option<T>,
|
||||
{
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
|
||||
let me = self.project();
|
||||
let f = me.f;
|
||||
me.stream.poll_next(cx).map(|opt| opt.and_then(f))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (_, upper) = self.stream.size_hint();
|
||||
(0, upper)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Stream for the [`then`](super::StreamExt::then) method.
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct Then<St, Fut, F> {
|
||||
#[pin]
|
||||
stream: St,
|
||||
#[pin]
|
||||
future: Option<Fut>,
|
||||
f: F,
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, Fut, F> fmt::Debug for Then<St, Fut, F>
|
||||
where
|
||||
St: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Then")
|
||||
.field("stream", &self.stream)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, Fut, F> Then<St, Fut, F> {
|
||||
pub(super) fn new(stream: St, f: F) -> Self {
|
||||
Then {
|
||||
stream,
|
||||
future: None,
|
||||
f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F, Fut> Stream for Then<St, Fut, F>
|
||||
where
|
||||
St: Stream,
|
||||
Fut: Future,
|
||||
F: FnMut(St::Item) -> Fut,
|
||||
{
|
||||
type Item = Fut::Output;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Fut::Output>> {
|
||||
let mut me = self.project();
|
||||
|
||||
loop {
|
||||
if let Some(future) = me.future.as_mut().as_pin_mut() {
|
||||
match future.poll(cx) {
|
||||
Poll::Ready(item) => {
|
||||
me.future.set(None);
|
||||
return Poll::Ready(Some(item));
|
||||
}
|
||||
Poll::Pending => return Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
match me.stream.as_mut().poll_next(cx) {
|
||||
Poll::Ready(Some(item)) => {
|
||||
me.future.set(Some((me.f)(item)));
|
||||
}
|
||||
Poll::Ready(None) => return Poll::Ready(None),
|
||||
Poll::Pending => return Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
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);
|
||||
let upper = upper.and_then(|upper| upper.checked_add(future_len));
|
||||
|
||||
(lower, upper)
|
||||
}
|
||||
}
|
||||
@@ -364,11 +364,11 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::collections::HashMap;
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut a = HashMap::new();
|
||||
/// let mut a = StreamMap::new();
|
||||
/// assert!(a.is_empty());
|
||||
/// a.insert(1, "a");
|
||||
/// a.insert(1, pending::<i32>());
|
||||
/// assert!(!a.is_empty());
|
||||
/// ```
|
||||
pub fn is_empty(&self) -> bool {
|
||||
@@ -568,6 +568,32 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> std::iter::FromIterator<(K, V)> for StreamMap<K, V>
|
||||
where
|
||||
K: Hash + Eq,
|
||||
{
|
||||
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
|
||||
let iterator = iter.into_iter();
|
||||
let (lower_bound, _) = iterator.size_hint();
|
||||
let mut stream_map = Self::with_capacity(lower_bound);
|
||||
|
||||
for (key, value) in iterator {
|
||||
stream_map.insert(key, value);
|
||||
}
|
||||
|
||||
stream_map
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Extend<(K, V)> for StreamMap<K, V> {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where
|
||||
T: IntoIterator<Item = (K, V)>,
|
||||
{
|
||||
self.entries.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
mod rand {
|
||||
use std::cell::Cell;
|
||||
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
//! Wrappers for Tokio types that implement `Stream`.
|
||||
//!
|
||||
#![cfg_attr(
|
||||
unix,
|
||||
doc = "You are viewing documentation built under unix. To view windows-specific wrappers, change to the `x86_64-pc-windows-msvc` platform."
|
||||
)]
|
||||
#![cfg_attr(
|
||||
windows,
|
||||
doc = "You are viewing documentation built under windows. To view unix-specific wrappers, change to the `x86_64-unknown-linux-gnu` platform."
|
||||
)]
|
||||
|
||||
/// Error types for the wrappers.
|
||||
pub mod errors {
|
||||
@@ -36,9 +27,9 @@ cfg_signal! {
|
||||
#[cfg(unix)]
|
||||
pub use signal_unix::SignalStream;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(windows, docsrs))]
|
||||
mod signal_windows;
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(windows, docsrs))]
|
||||
pub use signal_windows::{CtrlCStream, CtrlBreakStream};
|
||||
}
|
||||
|
||||
|
||||
@@ -71,3 +71,9 @@ impl<T> fmt::Debug for BroadcastStream<T> {
|
||||
f.debug_struct("BroadcastStream").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone + Send> From<Receiver<T>> for BroadcastStream<T> {
|
||||
fn from(recv: Receiver<T>) -> Self {
|
||||
Self::new(recv)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,3 +57,9 @@ impl<T> AsMut<Receiver<T>> for ReceiverStream<T> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Receiver<T>> for ReceiverStream<T> {
|
||||
fn from(recv: Receiver<T>) -> Self {
|
||||
Self::new(recv)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,3 +51,9 @@ impl<T> AsMut<UnboundedReceiver<T>> for UnboundedReceiverStream<T> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<UnboundedReceiver<T>> for UnboundedReceiverStream<T> {
|
||||
fn from(recv: UnboundedReceiver<T>) -> Self {
|
||||
Self::new(recv)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ async fn make_future<T: Clone + Send + Sync>(
|
||||
(result, rx)
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone + Unpin + Send + Sync> WatchStream<T> {
|
||||
impl<T: 'static + Clone + Send + Sync> WatchStream<T> {
|
||||
/// Create a new `WatchStream`.
|
||||
pub fn new(rx: Receiver<T>) -> Self {
|
||||
Self {
|
||||
@@ -72,10 +72,10 @@ impl<T: Clone + 'static + Send + Sync> Stream for WatchStream<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let (result, rx) = ready!(self.inner.poll(cx));
|
||||
let (result, mut rx) = ready!(self.inner.poll(cx));
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let received = (*rx.borrow()).clone();
|
||||
let received = (*rx.borrow_and_update()).clone();
|
||||
self.inner.set(make_future(rx));
|
||||
Poll::Ready(Some(received))
|
||||
}
|
||||
@@ -94,3 +94,9 @@ impl<T> fmt::Debug for WatchStream<T> {
|
||||
f.debug_struct("WatchStream").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone + Send + Sync> From<Receiver<T>> for WatchStream<T> {
|
||||
fn from(recv: Receiver<T>) -> Self {
|
||||
Self::new(recv)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#![cfg(feature = "sync")]
|
||||
|
||||
use tokio::sync::watch;
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn message_not_twice() {
|
||||
let (tx, rx) = watch::channel("hello");
|
||||
|
||||
let mut counter = 0;
|
||||
let mut stream = WatchStream::new(rx).map(move |payload| {
|
||||
println!("{}", payload);
|
||||
if payload == "goodbye" {
|
||||
counter += 1;
|
||||
}
|
||||
if counter >= 2 {
|
||||
panic!("too many goodbyes");
|
||||
}
|
||||
});
|
||||
|
||||
let task = tokio::spawn(async move { while stream.next().await.is_some() {} });
|
||||
|
||||
// Send goodbye just once
|
||||
tx.send("goodbye").unwrap();
|
||||
|
||||
drop(tx);
|
||||
task.await.unwrap();
|
||||
}
|
||||
@@ -2,17 +2,15 @@
|
||||
name = "tokio-test"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.2"
|
||||
edition = "2018"
|
||||
rust-version = "1.46"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-test/0.4.2/tokio_test"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
@@ -20,7 +18,7 @@ categories = ["asynchronous", "testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
tokio-stream = { version = "0.1.1", path = "../tokio-stream" }
|
||||
async-stream = "0.3"
|
||||
|
||||
bytes = "1.0.0"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
|
||||
@@ -1,3 +1,39 @@
|
||||
# 0.6.9 (October 29, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- codec: implement `Clone` for `LengthDelimitedCodec` ([#4089])
|
||||
- io: add `SyncIoBridge` ([#4146])
|
||||
|
||||
### Fixed
|
||||
|
||||
- time: update deadline on removal in `DelayQueue` ([#4178])
|
||||
- codec: Update stream impl for Framed to return None after Err ([#4166])
|
||||
|
||||
[#4089]: https://github.com/tokio-rs/tokio/pull/4089
|
||||
[#4146]: https://github.com/tokio-rs/tokio/pull/4146
|
||||
[#4166]: https://github.com/tokio-rs/tokio/pull/4166
|
||||
[#4178]: https://github.com/tokio-rs/tokio/pull/4178
|
||||
|
||||
# 0.6.8 (September 3, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- sync: add drop guard for `CancellationToken` ([#3839])
|
||||
- compact: added `AsyncSeek` compat ([#4078])
|
||||
- time: expose `Key` used in `DelayQueue`'s `Expired` ([#4081])
|
||||
- io: add `with_capacity` to `ReaderStream` ([#4086])
|
||||
|
||||
### Fixed
|
||||
|
||||
- codec: remove unnecessary `doc(cfg(...))` ([#3989])
|
||||
|
||||
[#3839]: https://github.com/tokio-rs/tokio/pull/3839
|
||||
[#4078]: https://github.com/tokio-rs/tokio/pull/4078
|
||||
[#4081]: https://github.com/tokio-rs/tokio/pull/4081
|
||||
[#4086]: https://github.com/tokio-rs/tokio/pull/4086
|
||||
[#3989]: https://github.com/tokio-rs/tokio/pull/3989
|
||||
|
||||
# 0.6.7 (May 14, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -2,35 +2,35 @@
|
||||
name = "tokio-util"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.6.x" git tag.
|
||||
version = "0.6.7"
|
||||
# - Create "tokio-util-0.7.x" git tag.
|
||||
version = "0.7.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.46"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-util/0.6.7/tokio_util"
|
||||
description = """
|
||||
Additional utilities for working with Tokio.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
# No features on by default
|
||||
default = []
|
||||
|
||||
# Shorthand for enabling everything
|
||||
full = ["codec", "compat", "io", "time", "net", "rt"]
|
||||
full = ["codec", "compat", "io-util", "time", "net", "rt"]
|
||||
|
||||
net = ["tokio/net"]
|
||||
compat = ["futures-io",]
|
||||
codec = []
|
||||
time = ["tokio/time","slab"]
|
||||
io = []
|
||||
rt = ["tokio/rt"]
|
||||
io-util = ["io", "tokio/rt", "tokio/io-util"]
|
||||
rt = ["tokio/rt", "tokio/sync", "futures-util"]
|
||||
|
||||
__docs_rs = ["futures-util"]
|
||||
|
||||
@@ -44,7 +44,7 @@ futures-io = { version = "0.3.0", optional = true }
|
||||
futures-util = { version = "0.3.0", optional = true }
|
||||
log = "0.4"
|
||||
pin-project-lite = "0.2.0"
|
||||
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
|
||||
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tokio-util
|
||||
|
||||
Utilities for encoding and decoding frames.
|
||||
Utilities for working with Tokio.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -38,6 +38,18 @@ macro_rules! cfg_io {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_io! {
|
||||
macro_rules! cfg_io_util {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "io-util")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_rt {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
|
||||
@@ -106,6 +106,7 @@ where
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(capacity),
|
||||
has_errored: false,
|
||||
},
|
||||
write: WriteFrame::default(),
|
||||
},
|
||||
@@ -203,6 +204,35 @@ impl<T, U> Framed<T, U> {
|
||||
&mut self.inner.codec
|
||||
}
|
||||
|
||||
/// Maps the codec `U` to `C`, preserving the read and write buffers
|
||||
/// wrapped by `Framed`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn map_codec<C, F>(self, map: F) -> Framed<T, C>
|
||||
where
|
||||
F: FnOnce(U) -> C,
|
||||
{
|
||||
// This could be potentially simplified once rust-lang/rust#86555 hits stable
|
||||
let parts = self.into_parts();
|
||||
Framed::from_parts(FramedParts {
|
||||
io: parts.io,
|
||||
codec: map(parts.codec),
|
||||
read_buf: parts.read_buf,
|
||||
write_buf: parts.write_buf,
|
||||
_priv: (),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying codec wrapped by
|
||||
/// `Framed`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec_pin_mut(self: Pin<&mut Self>) -> &mut U {
|
||||
self.project().inner.project().codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the read buffer.
|
||||
pub fn read_buffer(&self) -> &BytesMut {
|
||||
&self.inner.state.read.buffer
|
||||
|
||||
@@ -27,10 +27,12 @@ pin_project! {
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ReadFrame {
|
||||
pub(crate) eof: bool,
|
||||
pub(crate) is_readable: bool,
|
||||
pub(crate) buffer: BytesMut,
|
||||
pub(crate) has_errored: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct WriteFrame {
|
||||
@@ -49,6 +51,7 @@ impl Default for ReadFrame {
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
has_errored: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +75,7 @@ impl From<BytesMut> for ReadFrame {
|
||||
buffer,
|
||||
is_readable: size > 0,
|
||||
eof: false,
|
||||
has_errored: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,30 +130,42 @@ where
|
||||
//
|
||||
// The initial state is `reading`.
|
||||
//
|
||||
// | state | eof | is_readable |
|
||||
// |---------|-------|-------------|
|
||||
// | reading | false | false |
|
||||
// | framing | false | true |
|
||||
// | pausing | true | true |
|
||||
// | paused | true | false |
|
||||
//
|
||||
// `decode_eof`
|
||||
// returns `Some` read 0 bytes
|
||||
// │ │ │ │
|
||||
// │ ▼ │ ▼
|
||||
// ┌───────┐ `decode_eof` ┌──────┐
|
||||
// ┌──read 0 bytes──▶│pausing│─returns `None`─▶│paused│──┐
|
||||
// │ └───────┘ └──────┘ │
|
||||
// pending read┐ │ ┌──────┐ │ ▲ │
|
||||
// │ │ │ │ │ │ │ │
|
||||
// │ ▼ │ │ `decode` returns `Some`│ pending read
|
||||
// │ ╔═══════╗ ┌───────┐◀─┘ │
|
||||
// └──║reading║─read n>0 bytes─▶│framing│ │
|
||||
// ╚═══════╝ └───────┘◀──────read n>0 bytes┘
|
||||
// ▲ │
|
||||
// │ │
|
||||
// └─`decode` returns `None`─┘
|
||||
// | state | eof | is_readable | has_errored |
|
||||
// |---------|-------|-------------|-------------|
|
||||
// | reading | false | false | false |
|
||||
// | framing | false | true | false |
|
||||
// | pausing | true | true | false |
|
||||
// | paused | true | false | false |
|
||||
// | errored | <any> | <any> | true |
|
||||
// `decode_eof` returns Err
|
||||
// ┌────────────────────────────────────────────────────────┐
|
||||
// `decode_eof` returns │ │
|
||||
// `Ok(Some)` │ │
|
||||
// ┌─────┐ │ `decode_eof` returns After returning │
|
||||
// Read 0 bytes ├─────▼──┴┐ `Ok(None)` ┌────────┐ ◄───┐ `None` ┌───▼─────┐
|
||||
// ┌────────────────►│ Pausing ├───────────────────────►│ Paused ├─┐ └───────────┤ Errored │
|
||||
// │ └─────────┘ └─┬──▲───┘ │ └───▲───▲─┘
|
||||
// Pending read │ │ │ │ │ │
|
||||
// ┌──────┐ │ `decode` returns `Some` │ └─────┘ │ │
|
||||
// │ │ │ ┌──────┐ │ Pending │ │
|
||||
// │ ┌────▼──┴─┐ Read n>0 bytes ┌┴──────▼─┐ read n>0 bytes │ read │ │
|
||||
// └─┤ Reading ├───────────────►│ Framing │◄────────────────────────┘ │ │
|
||||
// └──┬─▲────┘ └─────┬──┬┘ │ │
|
||||
// │ │ │ │ `decode` returns Err │ │
|
||||
// │ └───decode` returns `None`──┘ └───────────────────────────────────────────────────────┘ │
|
||||
// │ read returns Err │
|
||||
// └────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
loop {
|
||||
// Return `None` if we have encountered an error from the underlying decoder
|
||||
// See: https://github.com/tokio-rs/tokio/issues/3976
|
||||
if state.has_errored {
|
||||
// preparing has_errored -> paused
|
||||
trace!("Returning None and setting paused");
|
||||
state.is_readable = false;
|
||||
state.has_errored = false;
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
|
||||
// Repeatedly call `decode` or `decode_eof` while the buffer is "readable",
|
||||
// i.e. it _might_ contain data consumable as a frame or closing frame.
|
||||
// Both signal that there is no such data by returning `None`.
|
||||
@@ -165,7 +181,11 @@ where
|
||||
// pausing or framing
|
||||
if state.eof {
|
||||
// pausing
|
||||
let frame = pinned.codec.decode_eof(&mut state.buffer)?;
|
||||
let frame = pinned.codec.decode_eof(&mut state.buffer).map_err(|err| {
|
||||
trace!("Got an error, going to errored state");
|
||||
state.has_errored = true;
|
||||
err
|
||||
})?;
|
||||
if frame.is_none() {
|
||||
state.is_readable = false; // prepare pausing -> paused
|
||||
}
|
||||
@@ -176,7 +196,11 @@ where
|
||||
// framing
|
||||
trace!("attempting to decode a frame");
|
||||
|
||||
if let Some(frame) = pinned.codec.decode(&mut state.buffer)? {
|
||||
if let Some(frame) = pinned.codec.decode(&mut state.buffer).map_err(|op| {
|
||||
trace!("Got an error, going to errored state");
|
||||
state.has_errored = true;
|
||||
op
|
||||
})? {
|
||||
trace!("frame decoded from buffer");
|
||||
// implicit framing -> framing
|
||||
return Poll::Ready(Some(Ok(frame)));
|
||||
@@ -190,7 +214,13 @@ where
|
||||
// Make sure we've got room for at least one byte to read to ensure
|
||||
// that we don't get a spurious 0 that looks like EOF.
|
||||
state.buffer.reserve(1);
|
||||
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer)? {
|
||||
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer).map_err(
|
||||
|err| {
|
||||
trace!("Got an error, going to errored state");
|
||||
state.has_errored = true;
|
||||
err
|
||||
},
|
||||
)? {
|
||||
Poll::Ready(ct) => ct,
|
||||
// implicit reading -> reading or implicit paused -> paused
|
||||
Poll::Pending => return Poll::Pending,
|
||||
|
||||
@@ -51,6 +51,7 @@ where
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(capacity),
|
||||
has_errored: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -107,6 +108,32 @@ impl<T, D> FramedRead<T, D> {
|
||||
&mut self.inner.codec
|
||||
}
|
||||
|
||||
/// Maps the decoder `D` to `C`, preserving the read buffer
|
||||
/// wrapped by `Framed`.
|
||||
pub fn map_decoder<C, F>(self, map: F) -> FramedRead<T, C>
|
||||
where
|
||||
F: FnOnce(D) -> C,
|
||||
{
|
||||
// This could be potentially simplified once rust-lang/rust#86555 hits stable
|
||||
let FramedImpl {
|
||||
inner,
|
||||
state,
|
||||
codec,
|
||||
} = self.inner;
|
||||
FramedRead {
|
||||
inner: FramedImpl {
|
||||
inner,
|
||||
state,
|
||||
codec: map(codec),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying decoder.
|
||||
pub fn decoder_pin_mut(self: Pin<&mut Self>) -> &mut D {
|
||||
self.project().inner.project().codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the read buffer.
|
||||
pub fn read_buffer(&self) -> &BytesMut {
|
||||
&self.inner.state.buffer
|
||||
|
||||
@@ -88,6 +88,32 @@ impl<T, E> FramedWrite<T, E> {
|
||||
&mut self.inner.codec
|
||||
}
|
||||
|
||||
/// Maps the encoder `E` to `C`, preserving the write buffer
|
||||
/// wrapped by `Framed`.
|
||||
pub fn map_encoder<C, F>(self, map: F) -> FramedWrite<T, C>
|
||||
where
|
||||
F: FnOnce(E) -> C,
|
||||
{
|
||||
// This could be potentially simplified once rust-lang/rust#86555 hits stable
|
||||
let FramedImpl {
|
||||
inner,
|
||||
state,
|
||||
codec,
|
||||
} = self.inner;
|
||||
FramedWrite {
|
||||
inner: FramedImpl {
|
||||
inner,
|
||||
state,
|
||||
codec: map(codec),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying encoder.
|
||||
pub fn encoder_pin_mut(self: Pin<&mut Self>) -> &mut E {
|
||||
self.project().inner.project().codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the write buffer.
|
||||
pub fn write_buffer(&self) -> &BytesMut {
|
||||
&self.inner.state.buffer
|
||||
|
||||
@@ -421,7 +421,7 @@ pub struct LengthDelimitedCodecError {
|
||||
/// See [module level] documentation for more detail.
|
||||
///
|
||||
/// [module level]: index.html
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LengthDelimitedCodec {
|
||||
// Configuration values
|
||||
builder: Builder,
|
||||
@@ -746,7 +746,7 @@ impl Builder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the max frame length
|
||||
/// Sets the max frame length in bytes
|
||||
///
|
||||
/// This configuration option applies to both encoding and decoding. The
|
||||
/// default value is 8MB.
|
||||
@@ -767,7 +767,7 @@ impl Builder {
|
||||
///
|
||||
/// # fn bind_read<T: AsyncRead>(io: T) {
|
||||
/// LengthDelimitedCodec::builder()
|
||||
/// .max_frame_length(8 * 1024)
|
||||
/// .max_frame_length(8 * 1024 * 1024)
|
||||
/// .new_read(io);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
|
||||
@@ -13,6 +13,7 @@ pin_project! {
|
||||
pub struct Compat<T> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
seek_pos: Option<io::SeekFrom>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +81,10 @@ impl<T: tokio::io::AsyncWrite> TokioAsyncWriteCompatExt for T {}
|
||||
|
||||
impl<T> Compat<T> {
|
||||
fn new(inner: T) -> Self {
|
||||
Self { inner }
|
||||
Self {
|
||||
inner,
|
||||
seek_pos: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the `Future`, `Stream`, `AsyncRead`, or `AsyncWrite` object
|
||||
@@ -216,6 +220,45 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: tokio::io::AsyncSeek> futures_io::AsyncSeek for Compat<T> {
|
||||
fn poll_seek(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
pos: io::SeekFrom,
|
||||
) -> Poll<io::Result<u64>> {
|
||||
if self.seek_pos != Some(pos) {
|
||||
self.as_mut().project().inner.start_seek(pos)?;
|
||||
*self.as_mut().project().seek_pos = Some(pos);
|
||||
}
|
||||
let res = ready!(self.as_mut().project().inner.poll_complete(cx));
|
||||
*self.as_mut().project().seek_pos = None;
|
||||
Poll::Ready(res.map(|p| p as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: futures_io::AsyncSeek> tokio::io::AsyncSeek for Compat<T> {
|
||||
fn start_seek(mut self: Pin<&mut Self>, pos: io::SeekFrom) -> io::Result<()> {
|
||||
*self.as_mut().project().seek_pos = Some(pos);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
let pos = match self.seek_pos {
|
||||
None => {
|
||||
// tokio 1.x AsyncSeek recommends calling poll_complete before start_seek.
|
||||
// We don't have to guarantee that the value returned by
|
||||
// poll_complete called without start_seek is correct,
|
||||
// so we'll return 0.
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
Some(pos) => pos,
|
||||
};
|
||||
let res = ready!(self.as_mut().project().inner.poll_seek(cx, pos));
|
||||
*self.as_mut().project().seek_pos = None;
|
||||
Poll::Ready(res.map(|p| p as u64))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl<T: std::os::unix::io::AsRawFd> std::os::unix::io::AsRawFd for Compat<T> {
|
||||
fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
//! Helpers for IO related tasks.
|
||||
//!
|
||||
//! These types are often used in combination with hyper or reqwest, as they
|
||||
//! The stream types are often used in combination with hyper or reqwest, as they
|
||||
//! allow converting between a hyper [`Body`] and [`AsyncRead`].
|
||||
//!
|
||||
//! The [`SyncIoBridge`] type converts from the world of async I/O
|
||||
//! to synchronous I/O; this may often come up when using synchronous APIs
|
||||
//! inside [`tokio::task::spawn_blocking`].
|
||||
//!
|
||||
//! [`Body`]: https://docs.rs/hyper/0.13/hyper/struct.Body.html
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
|
||||
mod read_buf;
|
||||
mod reader_stream;
|
||||
mod stream_reader;
|
||||
cfg_io_util! {
|
||||
mod sync_bridge;
|
||||
pub use self::sync_bridge::SyncIoBridge;
|
||||
}
|
||||
|
||||
pub use self::read_buf::read_buf;
|
||||
pub use self::reader_stream::ReaderStream;
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
const CAPACITY: usize = 4096;
|
||||
const DEFAULT_CAPACITY: usize = 4096;
|
||||
|
||||
pin_project! {
|
||||
/// Convert an [`AsyncRead`] into a [`Stream`] of byte chunks.
|
||||
@@ -50,6 +50,7 @@ pin_project! {
|
||||
reader: Option<R>,
|
||||
// Working buffer, used to optimize allocations.
|
||||
buf: BytesMut,
|
||||
capacity: usize,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +64,21 @@ impl<R: AsyncRead> ReaderStream<R> {
|
||||
ReaderStream {
|
||||
reader: Some(reader),
|
||||
buf: BytesMut::new(),
|
||||
capacity: DEFAULT_CAPACITY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an [`AsyncRead`] into a [`Stream`] with item type
|
||||
/// `Result<Bytes, std::io::Error>`,
|
||||
/// with a specific read buffer initial capacity.
|
||||
///
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
pub fn with_capacity(reader: R, capacity: usize) -> Self {
|
||||
ReaderStream {
|
||||
reader: Some(reader),
|
||||
buf: BytesMut::with_capacity(capacity),
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +96,7 @@ impl<R: AsyncRead> Stream for ReaderStream<R> {
|
||||
};
|
||||
|
||||
if this.buf.capacity() == 0 {
|
||||
this.buf.reserve(CAPACITY);
|
||||
this.buf.reserve(*this.capacity);
|
||||
}
|
||||
|
||||
match poll_read_buf(reader, cx, &mut this.buf) {
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
use std::io::{Read, Write};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
|
||||
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
|
||||
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
|
||||
#[derive(Debug)]
|
||||
pub struct SyncIoBridge<T> {
|
||||
src: T,
|
||||
rt: tokio::runtime::Handle,
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + Unpin> Read for SyncIoBridge<T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(AsyncReadExt::read(src, buf))
|
||||
}
|
||||
|
||||
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(src.read_to_end(buf))
|
||||
}
|
||||
|
||||
fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(src.read_to_string(buf))
|
||||
}
|
||||
|
||||
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
let src = &mut self.src;
|
||||
// The AsyncRead trait returns the count, synchronous doesn't.
|
||||
let _n = self.rt.block_on(src.read_exact(buf))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite + Unpin> Write for SyncIoBridge<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(src.write(buf))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(src.flush())
|
||||
}
|
||||
|
||||
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(src.write_all(buf))
|
||||
}
|
||||
|
||||
fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(src.write_vectored(bufs))
|
||||
}
|
||||
}
|
||||
|
||||
// Because https://doc.rust-lang.org/std/io/trait.Write.html#method.is_write_vectored is at the time
|
||||
// of this writing still unstable, we expose this as part of a standalone method.
|
||||
impl<T: AsyncWrite> SyncIoBridge<T> {
|
||||
/// Determines if the underlying [`tokio::io::AsyncWrite`] target supports efficient vectored writes.
|
||||
///
|
||||
/// See [`tokio::io::AsyncWrite::is_write_vectored`].
|
||||
pub fn is_write_vectored(&self) -> bool {
|
||||
self.src.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> SyncIoBridge<T> {
|
||||
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
|
||||
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
|
||||
///
|
||||
/// When this struct is created, it captures a handle to the current thread's runtime with [`tokio::runtime::Handle::current`].
|
||||
/// It is hence OK to move this struct into a separate thread outside the runtime, as created
|
||||
/// by e.g. [`tokio::task::spawn_blocking`].
|
||||
///
|
||||
/// Stated even more strongly: to make use of this bridge, you *must* move
|
||||
/// it into a separate thread outside the runtime. The synchronous I/O will use the
|
||||
/// underlying handle to block on the backing asynchronous source, via
|
||||
/// [`tokio::runtime::Handle::block_on`]. As noted in the documentation for that
|
||||
/// function, an attempt to `block_on` from an asynchronous execution context
|
||||
/// will panic.
|
||||
///
|
||||
/// # Wrapping `!Unpin` types
|
||||
///
|
||||
/// Use e.g. `SyncIoBridge::new(Box::pin(src))`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// This will panic if called outside the context of a Tokio runtime.
|
||||
pub fn new(src: T) -> Self {
|
||||
Self::new_with_handle(src, tokio::runtime::Handle::current())
|
||||
}
|
||||
|
||||
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
|
||||
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
|
||||
///
|
||||
/// This is the same as [`SyncIoBridge::new`], but allows passing an arbitrary handle and hence may
|
||||
/// be initially invoked outside of an asynchronous context.
|
||||
pub fn new_with_handle(src: T, rt: tokio::runtime::Handle) -> Self {
|
||||
Self { src, rt }
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
@@ -31,6 +30,7 @@ cfg_codec! {
|
||||
|
||||
cfg_net! {
|
||||
pub mod udp;
|
||||
pub mod net;
|
||||
}
|
||||
|
||||
cfg_compat! {
|
||||
@@ -43,6 +43,7 @@ cfg_io! {
|
||||
|
||||
cfg_rt! {
|
||||
pub mod context;
|
||||
pub mod task;
|
||||
}
|
||||
|
||||
cfg_time! {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//! TCP/UDP/Unix helpers for tokio.
|
||||
|
||||
use crate::either::Either;
|
||||
use std::future::Future;
|
||||
use std::io::Result;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod unix;
|
||||
|
||||
/// A trait for a listener: `TcpListener` and `UnixListener`.
|
||||
pub trait Listener {
|
||||
/// The stream's type of this listener.
|
||||
type Io: tokio::io::AsyncRead + tokio::io::AsyncWrite;
|
||||
/// The socket address type of this listener.
|
||||
type Addr;
|
||||
|
||||
/// Polls to accept a new incoming connection to this listener.
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>>;
|
||||
|
||||
/// Accepts a new incoming connection from this listener.
|
||||
fn accept(&mut self) -> ListenerAcceptFut<'_, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ListenerAcceptFut { listener: self }
|
||||
}
|
||||
|
||||
/// Returns the local address that this listener is bound to.
|
||||
fn local_addr(&self) -> Result<Self::Addr>;
|
||||
}
|
||||
|
||||
impl Listener for tokio::net::TcpListener {
|
||||
type Io = tokio::net::TcpStream;
|
||||
type Addr = std::net::SocketAddr;
|
||||
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>> {
|
||||
Self::poll_accept(self, cx)
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> Result<Self::Addr> {
|
||||
self.local_addr().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// Future for accepting a new connection from a listener.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct ListenerAcceptFut<'a, L> {
|
||||
listener: &'a mut L,
|
||||
}
|
||||
|
||||
impl<'a, L> Future for ListenerAcceptFut<'a, L>
|
||||
where
|
||||
L: Listener,
|
||||
{
|
||||
type Output = Result<(L::Io, L::Addr)>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.listener.poll_accept(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> Either<L, R>
|
||||
where
|
||||
L: Listener,
|
||||
R: Listener,
|
||||
{
|
||||
/// Accepts a new incoming connection from this listener.
|
||||
pub async fn accept(&mut self) -> Result<Either<(L::Io, L::Addr), (R::Io, R::Addr)>> {
|
||||
match self {
|
||||
Either::Left(listener) => {
|
||||
let (stream, addr) = listener.accept().await?;
|
||||
Ok(Either::Left((stream, addr)))
|
||||
}
|
||||
Either::Right(listener) => {
|
||||
let (stream, addr) = listener.accept().await?;
|
||||
Ok(Either::Right((stream, addr)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the local address that this listener is bound to.
|
||||
pub fn local_addr(&self) -> Result<Either<L::Addr, R::Addr>> {
|
||||
match self {
|
||||
Either::Left(listener) => {
|
||||
let addr = listener.local_addr()?;
|
||||
Ok(Either::Left(addr))
|
||||
}
|
||||
Either::Right(listener) => {
|
||||
let addr = listener.local_addr()?;
|
||||
Ok(Either::Right(addr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//! Unix domain socket helpers.
|
||||
|
||||
use super::Listener;
|
||||
use std::io::Result;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
impl Listener for tokio::net::UnixListener {
|
||||
type Io = tokio::net::UnixStream;
|
||||
type Addr = tokio::net::unix::SocketAddr;
|
||||
|
||||
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>> {
|
||||
Self::poll_accept(self, cx)
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> Result<Self::Addr> {
|
||||
self.local_addr().map(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
//! Extra utilities for spawning tasks
|
||||
|
||||
mod spawn_pinned;
|
||||
pub use spawn_pinned::LocalPoolHandle;
|
||||
@@ -0,0 +1,307 @@
|
||||
use futures_util::future::{AbortHandle, Abortable};
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::future::Future;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::task::{spawn_local, JoinHandle, LocalSet};
|
||||
|
||||
/// A handle to a local pool, used for spawning `!Send` tasks.
|
||||
#[derive(Clone)]
|
||||
pub struct LocalPoolHandle {
|
||||
pool: Arc<LocalPool>,
|
||||
}
|
||||
|
||||
impl LocalPoolHandle {
|
||||
/// Create a new pool of threads to handle `!Send` tasks. Spawn tasks onto this
|
||||
/// pool via [`LocalPoolHandle::spawn_pinned`].
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the pool size is less than one.
|
||||
pub fn new(pool_size: usize) -> LocalPoolHandle {
|
||||
assert!(pool_size > 0);
|
||||
|
||||
let workers = (0..pool_size)
|
||||
.map(|_| LocalWorkerHandle::new_worker())
|
||||
.collect();
|
||||
|
||||
let pool = Arc::new(LocalPool { workers });
|
||||
|
||||
LocalPoolHandle { pool }
|
||||
}
|
||||
|
||||
/// Spawn a task onto a worker thread and pin it there so it can't be moved
|
||||
/// off of the thread. Note that the future is not [`Send`], but the
|
||||
/// [`FnOnce`] which creates it is.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::rc::Rc;
|
||||
/// use tokio_util::task::LocalPoolHandle;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// // Create the local pool
|
||||
/// let pool = LocalPoolHandle::new(1);
|
||||
///
|
||||
/// // Spawn a !Send future onto the pool and await it
|
||||
/// let output = pool
|
||||
/// .spawn_pinned(|| {
|
||||
/// // Rc is !Send + !Sync
|
||||
/// let local_data = Rc::new("test");
|
||||
///
|
||||
/// // This future holds an Rc, so it is !Send
|
||||
/// async move { local_data.to_string() }
|
||||
/// })
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
///
|
||||
/// assert_eq!(output, "test");
|
||||
/// }
|
||||
/// ```
|
||||
pub fn spawn_pinned<F, Fut>(&self, create_task: F) -> JoinHandle<Fut::Output>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
F: Send + 'static,
|
||||
Fut: Future + 'static,
|
||||
Fut::Output: Send + 'static,
|
||||
{
|
||||
self.pool.spawn_pinned(create_task)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LocalPoolHandle {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("LocalPoolHandle")
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalPool {
|
||||
workers: Vec<LocalWorkerHandle>,
|
||||
}
|
||||
|
||||
impl LocalPool {
|
||||
/// Spawn a `?Send` future onto a worker
|
||||
fn spawn_pinned<F, Fut>(&self, create_task: F) -> JoinHandle<Fut::Output>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
F: Send + 'static,
|
||||
Fut: Future + 'static,
|
||||
Fut::Output: Send + 'static,
|
||||
{
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
|
||||
let (worker, job_guard) = self.find_and_incr_least_burdened_worker();
|
||||
let worker_spawner = worker.spawner.clone();
|
||||
|
||||
// Spawn a future onto the worker's runtime so we can immediately return
|
||||
// a join handle.
|
||||
worker.runtime_handle.spawn(async move {
|
||||
// Move the job guard into the task
|
||||
let _job_guard = job_guard;
|
||||
|
||||
// Propagate aborts via Abortable/AbortHandle
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let _abort_guard = AbortGuard(abort_handle);
|
||||
|
||||
// Inside the future we can't run spawn_local yet because we're not
|
||||
// in the context of a LocalSet. We need to send create_task to the
|
||||
// LocalSet task for spawning.
|
||||
let spawn_task = Box::new(move || {
|
||||
// Once we're in the LocalSet context we can call spawn_local
|
||||
let join_handle =
|
||||
spawn_local(
|
||||
async move { Abortable::new(create_task(), abort_registration).await },
|
||||
);
|
||||
|
||||
// Send the join handle back to the spawner. If sending fails,
|
||||
// we assume the parent task was canceled, so cancel this task
|
||||
// as well.
|
||||
if let Err(join_handle) = sender.send(join_handle) {
|
||||
join_handle.abort()
|
||||
}
|
||||
});
|
||||
|
||||
// Send the callback to the LocalSet task
|
||||
if let Err(e) = worker_spawner.send(spawn_task) {
|
||||
// Propagate the error as a panic in the join handle.
|
||||
panic!("Failed to send job to worker: {}", e);
|
||||
}
|
||||
|
||||
// Wait for the task's join handle
|
||||
let join_handle = match receiver.await {
|
||||
Ok(handle) => handle,
|
||||
Err(e) => {
|
||||
// We sent the task successfully, but failed to get its
|
||||
// join handle... We assume something happened to the worker
|
||||
// and the task was not spawned. Propagate the error as a
|
||||
// panic in the join handle.
|
||||
panic!("Worker failed to send join handle: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for the task to complete
|
||||
let join_result = join_handle.await;
|
||||
|
||||
match join_result {
|
||||
Ok(Ok(output)) => output,
|
||||
Ok(Err(_)) => {
|
||||
// Pinned task was aborted. But that only happens if this
|
||||
// task is aborted. So this is an impossible branch.
|
||||
unreachable!(
|
||||
"Reaching this branch means this task was previously \
|
||||
aborted but it continued running anyways"
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
if e.is_panic() {
|
||||
std::panic::resume_unwind(e.into_panic());
|
||||
} else if e.is_cancelled() {
|
||||
// No one else should have the join handle, so this is
|
||||
// unexpected. Forward this error as a panic in the join
|
||||
// handle.
|
||||
panic!("spawn_pinned task was canceled: {}", e);
|
||||
} else {
|
||||
// Something unknown happened (not a panic or
|
||||
// cancellation). Forward this error as a panic in the
|
||||
// join handle.
|
||||
panic!("spawn_pinned task failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Find the worker with the least number of tasks, increment its task
|
||||
/// count, and return its handle. Make sure to actually spawn a task on
|
||||
/// the worker so the task count is kept consistent with load.
|
||||
///
|
||||
/// A job count guard is also returned to ensure the task count gets
|
||||
/// decremented when the job is done.
|
||||
fn find_and_incr_least_burdened_worker(&self) -> (&LocalWorkerHandle, JobCountGuard) {
|
||||
loop {
|
||||
let (worker, task_count) = self
|
||||
.workers
|
||||
.iter()
|
||||
.map(|worker| (worker, worker.task_count.load(Ordering::SeqCst)))
|
||||
.min_by_key(|&(_, count)| count)
|
||||
.expect("There must be more than one worker");
|
||||
|
||||
// Make sure the task count hasn't changed since when we choose this
|
||||
// worker. Otherwise, restart the search.
|
||||
if worker
|
||||
.task_count
|
||||
.compare_exchange(
|
||||
task_count,
|
||||
task_count + 1,
|
||||
Ordering::SeqCst,
|
||||
Ordering::Relaxed,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
return (worker, JobCountGuard(Arc::clone(&worker.task_count)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Automatically decrements a worker's job count when a job finishes (when
|
||||
/// this gets dropped).
|
||||
struct JobCountGuard(Arc<AtomicUsize>);
|
||||
|
||||
impl Drop for JobCountGuard {
|
||||
fn drop(&mut self) {
|
||||
// Decrement the job count
|
||||
let previous_value = self.0.fetch_sub(1, Ordering::SeqCst);
|
||||
debug_assert!(previous_value >= 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls abort on the handle when dropped.
|
||||
struct AbortGuard(AbortHandle);
|
||||
|
||||
impl Drop for AbortGuard {
|
||||
fn drop(&mut self) {
|
||||
self.0.abort();
|
||||
}
|
||||
}
|
||||
|
||||
type PinnedFutureSpawner = Box<dyn FnOnce() + Send + 'static>;
|
||||
|
||||
struct LocalWorkerHandle {
|
||||
runtime_handle: tokio::runtime::Handle,
|
||||
spawner: UnboundedSender<PinnedFutureSpawner>,
|
||||
task_count: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl LocalWorkerHandle {
|
||||
/// Create a new worker for executing pinned tasks
|
||||
fn new_worker() -> LocalWorkerHandle {
|
||||
let (sender, receiver) = unbounded_channel();
|
||||
let runtime = Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to start a pinned worker thread runtime");
|
||||
let runtime_handle = runtime.handle().clone();
|
||||
let task_count = Arc::new(AtomicUsize::new(0));
|
||||
let task_count_clone = Arc::clone(&task_count);
|
||||
|
||||
std::thread::spawn(|| Self::run(runtime, receiver, task_count_clone));
|
||||
|
||||
LocalWorkerHandle {
|
||||
runtime_handle,
|
||||
spawner: sender,
|
||||
task_count,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
runtime: tokio::runtime::Runtime,
|
||||
mut task_receiver: UnboundedReceiver<PinnedFutureSpawner>,
|
||||
task_count: Arc<AtomicUsize>,
|
||||
) {
|
||||
let local_set = LocalSet::new();
|
||||
local_set.block_on(&runtime, async {
|
||||
while let Some(spawn_task) = task_receiver.recv().await {
|
||||
// Calls spawn_local(future)
|
||||
(spawn_task)();
|
||||
}
|
||||
});
|
||||
|
||||
// If there are any tasks on the runtime associated with a LocalSet task
|
||||
// that has already completed, but whose output has not yet been
|
||||
// reported, let that task complete.
|
||||
//
|
||||
// Since the task_count is decremented when the runtime task exits,
|
||||
// reading that counter lets us know if any such tasks completed during
|
||||
// the call to `block_on`.
|
||||
//
|
||||
// Tasks on the LocalSet can't complete during this loop since they're
|
||||
// stored on the LocalSet and we aren't accessing it.
|
||||
let mut previous_task_count = task_count.load(Ordering::SeqCst);
|
||||
loop {
|
||||
// This call will also run tasks spawned on the runtime.
|
||||
runtime.block_on(tokio::task::yield_now());
|
||||
let new_task_count = task_count.load(Ordering::SeqCst);
|
||||
if new_task_count == previous_task_count {
|
||||
break;
|
||||
} else {
|
||||
previous_task_count = new_task_count;
|
||||
}
|
||||
}
|
||||
|
||||
// It's now no longer possible for a task on the runtime to be
|
||||
// associated with a LocalSet task that has completed. Drop both the
|
||||
// LocalSet and runtime to let tasks on the runtime be cancelled if and
|
||||
// only if they are still on the LocalSet.
|
||||
//
|
||||
// Drop the LocalSet task first so that anyone awaiting the runtime
|
||||
// JoinHandle will see the cancelled error after the LocalSet task
|
||||
// destructor has completed.
|
||||
drop(local_set);
|
||||
drop(runtime);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,13 @@ use crate::time::wheel::{self, Wheel};
|
||||
use futures_core::ready;
|
||||
use tokio::time::{error::Error, sleep_until, Duration, Instant, Sleep};
|
||||
|
||||
use core::ops::{Index, IndexMut};
|
||||
use slab::Slab;
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
@@ -128,7 +133,7 @@ use std::task::{self, Poll, Waker};
|
||||
#[derive(Debug)]
|
||||
pub struct DelayQueue<T> {
|
||||
/// Stores data associated with entries
|
||||
slab: Slab<Data<T>>,
|
||||
slab: SlabStorage<T>,
|
||||
|
||||
/// Lookup structure tracking all delays in the queue
|
||||
wheel: Wheel<Stack<T>>,
|
||||
@@ -152,6 +157,216 @@ pub struct DelayQueue<T> {
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SlabStorage<T> {
|
||||
inner: Slab<Data<T>>,
|
||||
|
||||
// A `compact` call requires a re-mapping of the `Key`s that were changed
|
||||
// during the `compact` call of the `slab`. Since the keys that were given out
|
||||
// cannot be changed retroactively we need to keep track of these re-mappings.
|
||||
// The keys of `key_map` correspond to the old keys that were given out and
|
||||
// the values to the `Key`s that were re-mapped by the `compact` call.
|
||||
key_map: HashMap<Key, KeyInternal>,
|
||||
|
||||
// Index used to create new keys to hand out.
|
||||
next_key_index: usize,
|
||||
|
||||
// Whether `compact` has been called, necessary in order to decide whether
|
||||
// to include keys in `key_map`.
|
||||
compact_called: bool,
|
||||
}
|
||||
|
||||
impl<T> SlabStorage<T> {
|
||||
pub(crate) fn with_capacity(capacity: usize) -> SlabStorage<T> {
|
||||
SlabStorage {
|
||||
inner: Slab::with_capacity(capacity),
|
||||
key_map: HashMap::new(),
|
||||
next_key_index: 0,
|
||||
compact_called: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts data into the inner slab and re-maps keys if necessary
|
||||
pub(crate) fn insert(&mut self, val: Data<T>) -> Key {
|
||||
let mut key = KeyInternal::new(self.inner.insert(val));
|
||||
let key_contained = self.key_map.contains_key(&key.into());
|
||||
|
||||
if key_contained {
|
||||
// It's possible that a `compact` call creates capacitiy in `self.inner` in
|
||||
// such a way that a `self.inner.insert` call creates a `key` which was
|
||||
// previously given out during an `insert` call prior to the `compact` call.
|
||||
// If `key` is contained in `self.key_map`, we have encountered this exact situation,
|
||||
// We need to create a new key `key_to_give_out` and include the relation
|
||||
// `key_to_give_out` -> `key` in `self.key_map`.
|
||||
let key_to_give_out = self.create_new_key();
|
||||
assert!(!self.key_map.contains_key(&key_to_give_out.into()));
|
||||
self.key_map.insert(key_to_give_out.into(), key);
|
||||
key = key_to_give_out;
|
||||
} else if self.compact_called {
|
||||
// Include an identity mapping in `self.key_map` in order to allow us to
|
||||
// panic if a key that was handed out is removed more than once.
|
||||
self.key_map.insert(key.into(), key);
|
||||
}
|
||||
|
||||
key.into()
|
||||
}
|
||||
|
||||
// Re-map the key in case compact was previously called.
|
||||
// Note: Since we include identity mappings in key_map after compact was called,
|
||||
// we have information about all keys that were handed out. In the case in which
|
||||
// compact was called and we try to remove a Key that was previously removed
|
||||
// we can detect invalid keys if no key is found in `key_map`. This is necessary
|
||||
// in order to prevent situations in which a previously removed key
|
||||
// corresponds to a re-mapped key internally and which would then be incorrectly
|
||||
// removed from the slab.
|
||||
//
|
||||
// Example to illuminate this problem:
|
||||
//
|
||||
// Let's assume our `key_map` is {1 -> 2, 2 -> 1} and we call remove(1). If we
|
||||
// were to remove 1 again, we would not find it inside `key_map` anymore.
|
||||
// If we were to imply from this that no re-mapping was necessary, we would
|
||||
// incorrectly remove 1 from `self.slab.inner`, which corresponds to the
|
||||
// handed-out key 2.
|
||||
pub(crate) fn remove(&mut self, key: &Key) -> Data<T> {
|
||||
let remapped_key = if self.compact_called {
|
||||
match self.key_map.remove(key) {
|
||||
Some(key_internal) => key_internal,
|
||||
None => panic!("invalid key"),
|
||||
}
|
||||
} else {
|
||||
(*key).into()
|
||||
};
|
||||
|
||||
self.inner.remove(remapped_key.index)
|
||||
}
|
||||
|
||||
pub(crate) fn shrink_to_fit(&mut self) {
|
||||
self.inner.shrink_to_fit();
|
||||
self.key_map.shrink_to_fit();
|
||||
}
|
||||
|
||||
pub(crate) fn compact(&mut self) {
|
||||
if !self.compact_called {
|
||||
for (key, _) in self.inner.iter() {
|
||||
self.key_map.insert(Key::new(key), KeyInternal::new(key));
|
||||
}
|
||||
}
|
||||
|
||||
let mut remapping = HashMap::new();
|
||||
self.inner.compact(|_, from, to| {
|
||||
remapping.insert(from, to);
|
||||
true
|
||||
});
|
||||
|
||||
// At this point `key_map` contains a mapping for every element.
|
||||
for internal_key in self.key_map.values_mut() {
|
||||
if let Some(new_internal_key) = remapping.get(&internal_key.index) {
|
||||
*internal_key = KeyInternal::new(*new_internal_key);
|
||||
}
|
||||
}
|
||||
|
||||
if self.key_map.capacity() > 2 * self.key_map.len() {
|
||||
self.key_map.shrink_to_fit();
|
||||
}
|
||||
|
||||
self.compact_called = true;
|
||||
}
|
||||
|
||||
// Tries to re-map a `Key` that was given out to the user to its
|
||||
// corresponding internal key.
|
||||
fn remap_key(&self, key: &Key) -> Option<KeyInternal> {
|
||||
let key_map = &self.key_map;
|
||||
if self.compact_called {
|
||||
key_map.get(&*key).copied()
|
||||
} else {
|
||||
Some((*key).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_new_key(&mut self) -> KeyInternal {
|
||||
while self.key_map.contains_key(&Key::new(self.next_key_index)) {
|
||||
self.next_key_index = self.next_key_index.wrapping_add(1);
|
||||
}
|
||||
|
||||
KeyInternal::new(self.next_key_index)
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub(crate) fn capacity(&self) -> usize {
|
||||
self.inner.capacity()
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.inner.clear();
|
||||
self.key_map.clear();
|
||||
self.compact_called = false;
|
||||
}
|
||||
|
||||
pub(crate) fn reserve(&mut self, additional: usize) {
|
||||
self.inner.reserve(additional);
|
||||
|
||||
if self.compact_called {
|
||||
self.key_map.reserve(additional);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn contains(&self, key: &Key) -> bool {
|
||||
let remapped_key = self.remap_key(key);
|
||||
|
||||
match remapped_key {
|
||||
Some(internal_key) => self.inner.contains(internal_key.index),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for SlabStorage<T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if fmt.alternate() {
|
||||
fmt.debug_map().entries(self.inner.iter()).finish()
|
||||
} else {
|
||||
fmt.debug_struct("Slab")
|
||||
.field("len", &self.len())
|
||||
.field("cap", &self.capacity())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<Key> for SlabStorage<T> {
|
||||
type Output = Data<T>;
|
||||
|
||||
fn index(&self, key: Key) -> &Self::Output {
|
||||
let remapped_key = self.remap_key(&key);
|
||||
|
||||
match remapped_key {
|
||||
Some(internal_key) => &self.inner[internal_key.index],
|
||||
None => panic!("Invalid index {}", key.index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<Key> for SlabStorage<T> {
|
||||
fn index_mut(&mut self, key: Key) -> &mut Data<T> {
|
||||
let remapped_key = self.remap_key(&key);
|
||||
|
||||
match remapped_key {
|
||||
Some(internal_key) => &mut self.inner[internal_key.index],
|
||||
None => panic!("Invalid index {}", key.index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry in `DelayQueue` that has expired and been removed.
|
||||
///
|
||||
/// Values are returned by [`DelayQueue::poll_expired`].
|
||||
@@ -176,15 +391,23 @@ pub struct Expired<T> {
|
||||
///
|
||||
/// [`DelayQueue`]: struct@DelayQueue
|
||||
/// [`DelayQueue::insert`]: method@DelayQueue::insert
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Key {
|
||||
index: usize,
|
||||
}
|
||||
|
||||
// Whereas `Key` is given out to users that use `DelayQueue`, internally we use
|
||||
// `KeyInternal` as the key type in order to make the logic of mapping between keys
|
||||
// as a result of `compact` calls clearer.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct KeyInternal {
|
||||
index: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Stack<T> {
|
||||
/// Head of the stack
|
||||
head: Option<usize>,
|
||||
head: Option<Key>,
|
||||
_p: PhantomData<fn() -> T>,
|
||||
}
|
||||
|
||||
@@ -201,10 +424,10 @@ struct Data<T> {
|
||||
expired: bool,
|
||||
|
||||
/// Next entry in the stack
|
||||
next: Option<usize>,
|
||||
next: Option<Key>,
|
||||
|
||||
/// Previous entry in the stack
|
||||
prev: Option<usize>,
|
||||
prev: Option<Key>,
|
||||
}
|
||||
|
||||
/// Maximum number of entries the queue can handle
|
||||
@@ -253,7 +476,7 @@ impl<T> DelayQueue<T> {
|
||||
pub fn with_capacity(capacity: usize) -> DelayQueue<T> {
|
||||
DelayQueue {
|
||||
wheel: Wheel::new(),
|
||||
slab: Slab::with_capacity(capacity),
|
||||
slab: SlabStorage::with_capacity(capacity),
|
||||
expired: Stack::default(),
|
||||
delay: None,
|
||||
wheel_now: 0,
|
||||
@@ -348,7 +571,7 @@ impl<T> DelayQueue<T> {
|
||||
}
|
||||
}
|
||||
|
||||
Key::new(key)
|
||||
key
|
||||
}
|
||||
|
||||
/// Attempts to pull out the next value of the delay queue, registering the
|
||||
@@ -369,13 +592,13 @@ impl<T> DelayQueue<T> {
|
||||
|
||||
let item = ready!(self.poll_idx(cx));
|
||||
Poll::Ready(item.map(|result| {
|
||||
result.map(|idx| {
|
||||
let data = self.slab.remove(idx);
|
||||
result.map(|key| {
|
||||
let data = self.slab.remove(&key);
|
||||
debug_assert!(data.next.is_none());
|
||||
debug_assert!(data.prev.is_none());
|
||||
|
||||
Expired {
|
||||
key: Key::new(idx),
|
||||
key,
|
||||
data: data.inner,
|
||||
deadline: self.start + Duration::from_millis(data.when),
|
||||
}
|
||||
@@ -437,7 +660,7 @@ impl<T> DelayQueue<T> {
|
||||
self.insert_at(value, Instant::now() + timeout)
|
||||
}
|
||||
|
||||
fn insert_idx(&mut self, when: u64, key: usize) {
|
||||
fn insert_idx(&mut self, when: u64, key: Key) {
|
||||
use self::wheel::{InsertError, Stack};
|
||||
|
||||
// Register the deadline with the timer wheel
|
||||
@@ -462,10 +685,10 @@ impl<T> DelayQueue<T> {
|
||||
use crate::time::wheel::Stack;
|
||||
|
||||
// Special case the `expired` queue
|
||||
if self.slab[key.index].expired {
|
||||
self.expired.remove(&key.index, &mut self.slab);
|
||||
if self.slab[*key].expired {
|
||||
self.expired.remove(key, &mut self.slab);
|
||||
} else {
|
||||
self.wheel.remove(&key.index, &mut self.slab);
|
||||
self.wheel.remove(key, &mut self.slab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,8 +721,19 @@ impl<T> DelayQueue<T> {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn remove(&mut self, key: &Key) -> Expired<T> {
|
||||
let prev_deadline = self.next_deadline();
|
||||
|
||||
self.remove_key(key);
|
||||
let data = self.slab.remove(key.index);
|
||||
let data = self.slab.remove(key);
|
||||
|
||||
let next_deadline = self.next_deadline();
|
||||
if prev_deadline != next_deadline {
|
||||
match (next_deadline, &mut self.delay) {
|
||||
(None, _) => self.delay = None,
|
||||
(Some(deadline), Some(delay)) => delay.as_mut().reset(deadline),
|
||||
(Some(deadline), None) => self.delay = Some(Box::pin(sleep_until(deadline))),
|
||||
}
|
||||
}
|
||||
|
||||
Expired {
|
||||
key: Key::new(key.index),
|
||||
@@ -548,10 +782,10 @@ impl<T> DelayQueue<T> {
|
||||
// Normalize the deadline. Values cannot be set to expire in the past.
|
||||
let when = self.normalize_deadline(when);
|
||||
|
||||
self.slab[key.index].when = when;
|
||||
self.slab[key.index].expired = false;
|
||||
self.slab[*key].when = when;
|
||||
self.slab[*key].expired = false;
|
||||
|
||||
self.insert_idx(when, key.index);
|
||||
self.insert_idx(when, *key);
|
||||
|
||||
let next_deadline = self.next_deadline();
|
||||
if let (Some(ref mut delay), Some(deadline)) = (&mut self.delay, next_deadline) {
|
||||
@@ -560,6 +794,50 @@ impl<T> DelayQueue<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shrink the capacity of the slab, which `DelayQueue` uses internally for storage allocation.
|
||||
/// This function is not guaranteed to, and in most cases, won't decrease the capacity of the slab
|
||||
/// to the number of elements still contained in it, because elements cannot be moved to a different
|
||||
/// index. To decrease the capacity to the size of the slab use [`compact`].
|
||||
///
|
||||
/// This function can take O(n) time even when the capacity cannot be reduced or the allocation is
|
||||
/// shrunk in place. Repeated calls run in O(1) though.
|
||||
///
|
||||
/// [`compact`]: method@Self::compact
|
||||
pub fn shrink_to_fit(&mut self) {
|
||||
self.slab.shrink_to_fit();
|
||||
}
|
||||
|
||||
/// Shrink the capacity of the slab, which `DelayQueue` uses internally for storage allocation,
|
||||
/// to the number of elements that are contained in it.
|
||||
///
|
||||
/// This methods runs in O(n).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::with_capacity(10);
|
||||
///
|
||||
/// let key1 = delay_queue.insert(5, Duration::from_secs(5));
|
||||
/// let key2 = delay_queue.insert(10, Duration::from_secs(10));
|
||||
/// let key3 = delay_queue.insert(15, Duration::from_secs(15));
|
||||
///
|
||||
/// delay_queue.remove(&key2);
|
||||
///
|
||||
/// delay_queue.compact();
|
||||
/// assert_eq!(delay_queue.capacity(), 2);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn compact(&mut self) {
|
||||
self.slab.compact();
|
||||
}
|
||||
|
||||
/// Returns the next time to poll as determined by the wheel
|
||||
fn next_deadline(&mut self) -> Option<Instant> {
|
||||
self.wheel
|
||||
@@ -739,7 +1017,7 @@ impl<T> DelayQueue<T> {
|
||||
/// should be returned.
|
||||
///
|
||||
/// A slot should be returned when the associated deadline has been reached.
|
||||
fn poll_idx(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Result<usize, Error>>> {
|
||||
fn poll_idx(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Result<Key, Error>>> {
|
||||
use self::wheel::Stack;
|
||||
|
||||
let expired = self.expired.pop(&mut self.slab);
|
||||
@@ -805,9 +1083,9 @@ impl<T> futures_core::Stream for DelayQueue<T> {
|
||||
}
|
||||
|
||||
impl<T> wheel::Stack for Stack<T> {
|
||||
type Owned = usize;
|
||||
type Borrowed = usize;
|
||||
type Store = Slab<Data<T>>;
|
||||
type Owned = Key;
|
||||
type Borrowed = Key;
|
||||
type Store = SlabStorage<T>;
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.head.is_none()
|
||||
@@ -826,28 +1104,29 @@ impl<T> wheel::Stack for Stack<T> {
|
||||
}
|
||||
|
||||
store[item].next = old;
|
||||
self.head = Some(item)
|
||||
self.head = Some(item);
|
||||
}
|
||||
|
||||
fn pop(&mut self, store: &mut Self::Store) -> Option<Self::Owned> {
|
||||
if let Some(idx) = self.head {
|
||||
self.head = store[idx].next;
|
||||
if let Some(key) = self.head {
|
||||
self.head = store[key].next;
|
||||
|
||||
if let Some(idx) = self.head {
|
||||
store[idx].prev = None;
|
||||
}
|
||||
|
||||
store[idx].next = None;
|
||||
debug_assert!(store[idx].prev.is_none());
|
||||
store[key].next = None;
|
||||
debug_assert!(store[key].prev.is_none());
|
||||
|
||||
Some(idx)
|
||||
Some(key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store) {
|
||||
assert!(store.contains(*item));
|
||||
let key = *item;
|
||||
assert!(store.contains(item));
|
||||
|
||||
// Ensure that the entry is in fact contained by the stack
|
||||
debug_assert!({
|
||||
@@ -856,29 +1135,31 @@ impl<T> wheel::Stack for Stack<T> {
|
||||
let mut contains = false;
|
||||
|
||||
while let Some(idx) = next {
|
||||
let data = &store[idx];
|
||||
|
||||
if idx == *item {
|
||||
debug_assert!(!contains);
|
||||
contains = true;
|
||||
}
|
||||
|
||||
next = store[idx].next;
|
||||
next = data.next;
|
||||
}
|
||||
|
||||
contains
|
||||
});
|
||||
|
||||
if let Some(next) = store[*item].next {
|
||||
store[next].prev = store[*item].prev;
|
||||
if let Some(next) = store[key].next {
|
||||
store[next].prev = store[key].prev;
|
||||
}
|
||||
|
||||
if let Some(prev) = store[*item].prev {
|
||||
store[prev].next = store[*item].next;
|
||||
if let Some(prev) = store[key].prev {
|
||||
store[prev].next = store[key].next;
|
||||
} else {
|
||||
self.head = store[*item].next;
|
||||
self.head = store[key].next;
|
||||
}
|
||||
|
||||
store[*item].next = None;
|
||||
store[*item].prev = None;
|
||||
store[key].next = None;
|
||||
store[key].prev = None;
|
||||
}
|
||||
|
||||
fn when(item: &Self::Borrowed, store: &Self::Store) -> u64 {
|
||||
@@ -901,6 +1182,24 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyInternal {
|
||||
pub(crate) fn new(index: usize) -> KeyInternal {
|
||||
KeyInternal { index }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Key> for KeyInternal {
|
||||
fn from(item: Key) -> Self {
|
||||
KeyInternal::new(item.index)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyInternal> for Key {
|
||||
fn from(item: KeyInternal) -> Self {
|
||||
Key::new(item.index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Expired<T> {
|
||||
/// Returns a reference to the inner value.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
@@ -921,4 +1220,9 @@ impl<T> Expired<T> {
|
||||
pub fn deadline(&self) -> Instant {
|
||||
self.deadline
|
||||
}
|
||||
|
||||
/// Returns the key that the expiration is indexed by.
|
||||
pub fn key(&self) -> Key {
|
||||
self.key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ mod stack;
|
||||
pub(crate) use self::stack::Stack;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::fmt::Debug;
|
||||
use std::usize;
|
||||
|
||||
/// Timing wheel implementation.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::cmp::Eq;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Abstracts the stack operations needed to track timeouts.
|
||||
pub(crate) trait Stack: Default {
|
||||
@@ -6,7 +8,7 @@ pub(crate) trait Stack: Default {
|
||||
type Owned: Borrow<Self::Borrowed>;
|
||||
|
||||
/// Borrowed item
|
||||
type Borrowed;
|
||||
type Borrowed: Eq + Hash;
|
||||
|
||||
/// Item storage, this allows a slab to be used instead of just the heap
|
||||
type Store;
|
||||
|
||||
@@ -35,7 +35,6 @@ use std::{io, mem::MaybeUninit};
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
|
||||
#[must_use = "sinks do nothing unless polled"]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "codec", feature = "udp"))))]
|
||||
#[derive(Debug)]
|
||||
pub struct UdpFramed<C, T = UdpSocket> {
|
||||
socket: T,
|
||||
@@ -144,7 +143,7 @@ where
|
||||
..
|
||||
} = *self;
|
||||
|
||||
let n = ready!(socket.borrow().poll_send_to(cx, &wr, *out_addr))?;
|
||||
let n = ready!(socket.borrow().poll_send_to(cx, wr, *out_addr))?;
|
||||
|
||||
let wrote_all = n == self.wr.len();
|
||||
self.wr.clear();
|
||||
|
||||
@@ -12,7 +12,10 @@ use std::task::{Context, Poll};
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
/// Encode and decode u32 values.
|
||||
struct U32Codec;
|
||||
#[derive(Default)]
|
||||
struct U32Codec {
|
||||
read_bytes: usize,
|
||||
}
|
||||
|
||||
impl Decoder for U32Codec {
|
||||
type Item = u32;
|
||||
@@ -24,6 +27,7 @@ impl Decoder for U32Codec {
|
||||
}
|
||||
|
||||
let n = buf.split_to(4).get_u32();
|
||||
self.read_bytes += 4;
|
||||
Ok(Some(n))
|
||||
}
|
||||
}
|
||||
@@ -39,6 +43,38 @@ impl Encoder<u32> for U32Codec {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode and decode u64 values.
|
||||
#[derive(Default)]
|
||||
struct U64Codec {
|
||||
read_bytes: usize,
|
||||
}
|
||||
|
||||
impl Decoder for U64Codec {
|
||||
type Item = u64;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u64>> {
|
||||
if buf.len() < 8 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let n = buf.split_to(8).get_u64();
|
||||
self.read_bytes += 8;
|
||||
Ok(Some(n))
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder<u64> for U64Codec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: u64, dst: &mut BytesMut) -> io::Result<()> {
|
||||
// Reserve space
|
||||
dst.reserve(8);
|
||||
dst.put_u64(item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This value should never be used
|
||||
struct DontReadIntoThis;
|
||||
|
||||
@@ -63,18 +99,39 @@ impl tokio::io::AsyncRead for DontReadIntoThis {
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_read_from_existing_buf() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec::default());
|
||||
parts.read_buf = BytesMut::from(&[0, 0, 0, 42][..]);
|
||||
|
||||
let mut framed = Framed::from_parts(parts);
|
||||
let num = assert_ok!(framed.next().await.unwrap());
|
||||
|
||||
assert_eq!(num, 42);
|
||||
assert_eq!(framed.codec().read_bytes, 4);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_read_from_existing_buf_after_codec_changed() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec::default());
|
||||
parts.read_buf = BytesMut::from(&[0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 84][..]);
|
||||
|
||||
let mut framed = Framed::from_parts(parts);
|
||||
let num = assert_ok!(framed.next().await.unwrap());
|
||||
|
||||
assert_eq!(num, 42);
|
||||
assert_eq!(framed.codec().read_bytes, 4);
|
||||
|
||||
let mut framed = framed.map_codec(|codec| U64Codec {
|
||||
read_bytes: codec.read_bytes,
|
||||
});
|
||||
let num = assert_ok!(framed.next().await.unwrap());
|
||||
|
||||
assert_eq!(num, 84);
|
||||
assert_eq!(framed.codec().read_bytes, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_buf_grows_to_init() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec::default());
|
||||
parts.read_buf = BytesMut::from(&[0, 0, 0, 42][..]);
|
||||
|
||||
let framed = Framed::from_parts(parts);
|
||||
@@ -85,7 +142,7 @@ fn external_buf_grows_to_init() {
|
||||
|
||||
#[test]
|
||||
fn external_buf_does_not_shrink() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec::default());
|
||||
parts.read_buf = BytesMut::from(&vec![0; INITIAL_CAPACITY * 2][..]);
|
||||
|
||||
let framed = Framed::from_parts(parts);
|
||||
|
||||
@@ -50,6 +50,22 @@ impl Decoder for U32Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
struct U64Decoder;
|
||||
|
||||
impl Decoder for U64Decoder {
|
||||
type Item = u64;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u64>> {
|
||||
if buf.len() < 8 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let n = buf.split_to(8).get_u64();
|
||||
Ok(Some(n))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_multi_frame_in_packet() {
|
||||
let mut task = task::spawn(());
|
||||
@@ -84,6 +100,24 @@ fn read_multi_frame_across_packets() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_multi_frame_in_packet_after_codec_changed() {
|
||||
let mut task = task::spawn(());
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x08".to_vec()),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx, _| {
|
||||
assert_read!(pin!(framed).poll_next(cx), 0x04);
|
||||
|
||||
let mut framed = framed.map_decoder(|_| U64Decoder);
|
||||
assert_read!(pin!(framed).poll_next(cx), 0x08);
|
||||
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_not_ready() {
|
||||
let mut task = task::spawn(());
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use futures_core::stream::Stream;
|
||||
use std::{io, pin::Pin};
|
||||
use tokio_test::{assert_ready, io::Builder, task};
|
||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||
|
||||
macro_rules! pin {
|
||||
($id:ident) => {
|
||||
Pin::new(&mut $id)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_read {
|
||||
($e:expr, $n:expr) => {{
|
||||
let val = assert_ready!($e);
|
||||
assert_eq!(val.unwrap().unwrap(), $n);
|
||||
}};
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn return_none_after_error() {
|
||||
let mut io = FramedRead::new(
|
||||
Builder::new()
|
||||
.read(b"abcdef")
|
||||
.read_error(io::Error::new(io::ErrorKind::Other, "Resource errored out"))
|
||||
.read(b"more data")
|
||||
.build(),
|
||||
BytesCodec::new(),
|
||||
);
|
||||
|
||||
let mut task = task::spawn(());
|
||||
|
||||
task.enter(|cx, _| {
|
||||
assert_read!(pin!(io).poll_next(cx), b"abcdef".to_vec());
|
||||
assert!(assert_ready!(pin!(io).poll_next(cx)).unwrap().is_err());
|
||||
assert!(assert_ready!(pin!(io).poll_next(cx)).is_none());
|
||||
assert_read!(pin!(io).poll_next(cx), b"more data".to_vec());
|
||||
})
|
||||
}
|
||||
@@ -39,6 +39,19 @@ impl Encoder<u32> for U32Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
struct U64Encoder;
|
||||
|
||||
impl Encoder<u64> for U64Encoder {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: u64, dst: &mut BytesMut) -> io::Result<()> {
|
||||
// Reserve space
|
||||
dst.reserve(8);
|
||||
dst.put_u64(item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_multi_frame_in_packet() {
|
||||
let mut task = task::spawn(());
|
||||
@@ -65,6 +78,32 @@ fn write_multi_frame_in_packet() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_multi_frame_after_codec_changed() {
|
||||
let mut task = task::spawn(());
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x08".to_vec()),
|
||||
};
|
||||
let mut framed = FramedWrite::new(mock, U32Encoder);
|
||||
|
||||
task.enter(|cx, _| {
|
||||
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
|
||||
assert!(pin!(framed).start_send(0x04).is_ok());
|
||||
|
||||
let mut framed = framed.map_encoder(|_| U64Encoder);
|
||||
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
|
||||
assert!(pin!(framed).start_send(0x08).is_ok());
|
||||
|
||||
// Nothing written yet
|
||||
assert_eq!(1, framed.get_ref().calls.len());
|
||||
|
||||
// Flush the writes
|
||||
assert!(assert_ready!(pin!(framed).poll_flush(cx)).is_ok());
|
||||
|
||||
assert_eq!(0, framed.get_ref().calls.len());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_hits_backpressure() {
|
||||
const ITER: usize = 2 * 1024;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#![cfg(feature = "io-util")]
|
||||
|
||||
use std::error::Error;
|
||||
use std::io::{Cursor, Read, Result as IoResult};
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio_util::io::SyncIoBridge;
|
||||
|
||||
async fn test_reader_len(
|
||||
r: impl AsyncRead + Unpin + Send + 'static,
|
||||
expected_len: usize,
|
||||
) -> IoResult<()> {
|
||||
let mut r = SyncIoBridge::new(r);
|
||||
let res = tokio::task::spawn_blocking(move || {
|
||||
let mut buf = Vec::new();
|
||||
r.read_to_end(&mut buf)?;
|
||||
Ok::<_, std::io::Error>(buf)
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(res?.len(), expected_len);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_read_to_sync() -> Result<(), Box<dyn Error>> {
|
||||
test_reader_len(tokio::io::empty(), 0).await?;
|
||||
let buf = b"hello world";
|
||||
test_reader_len(Cursor::new(buf), buf.len()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_write_to_sync() -> Result<(), Box<dyn Error>> {
|
||||
let mut dest = Vec::new();
|
||||
let src = b"hello world";
|
||||
let dest = tokio::task::spawn_blocking(move || -> Result<_, String> {
|
||||
let mut w = SyncIoBridge::new(Cursor::new(&mut dest));
|
||||
std::io::copy(&mut Cursor::new(src), &mut w).map_err(|e| e.to_string())?;
|
||||
Ok(dest)
|
||||
})
|
||||
.await??;
|
||||
assert_eq!(dest.as_slice(), src);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use tokio_util::task;
|
||||
|
||||
/// Simple test of running a !Send future via spawn_pinned
|
||||
#[tokio::test]
|
||||
async fn can_spawn_not_send_future() {
|
||||
let pool = task::LocalPoolHandle::new(1);
|
||||
|
||||
let output = pool
|
||||
.spawn_pinned(|| {
|
||||
// Rc is !Send + !Sync
|
||||
let local_data = Rc::new("test");
|
||||
|
||||
// This future holds an Rc, so it is !Send
|
||||
async move { local_data.to_string() }
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output, "test");
|
||||
}
|
||||
|
||||
/// Dropping the join handle still lets the task execute
|
||||
#[test]
|
||||
fn can_drop_future_and_still_get_output() {
|
||||
let pool = task::LocalPoolHandle::new(1);
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
|
||||
let _ = pool.spawn_pinned(move || {
|
||||
// Rc is !Send + !Sync
|
||||
let local_data = Rc::new("test");
|
||||
|
||||
// This future holds an Rc, so it is !Send
|
||||
async move {
|
||||
let _ = sender.send(local_data.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(receiver.recv(), Ok("test".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "assertion failed: pool_size > 0")]
|
||||
fn cannot_create_zero_sized_pool() {
|
||||
let _pool = task::LocalPoolHandle::new(0);
|
||||
}
|
||||
|
||||
/// We should be able to spawn multiple futures onto the pool at the same time.
|
||||
#[tokio::test]
|
||||
async fn can_spawn_multiple_futures() {
|
||||
let pool = task::LocalPoolHandle::new(2);
|
||||
|
||||
let join_handle1 = pool.spawn_pinned(|| {
|
||||
let local_data = Rc::new("test1");
|
||||
async move { local_data.to_string() }
|
||||
});
|
||||
let join_handle2 = pool.spawn_pinned(|| {
|
||||
let local_data = Rc::new("test2");
|
||||
async move { local_data.to_string() }
|
||||
});
|
||||
|
||||
assert_eq!(join_handle1.await.unwrap(), "test1");
|
||||
assert_eq!(join_handle2.await.unwrap(), "test2");
|
||||
}
|
||||
|
||||
/// A panic in the spawned task causes the join handle to return an error.
|
||||
/// But, you can continue to spawn tasks.
|
||||
#[tokio::test]
|
||||
async fn task_panic_propagates() {
|
||||
let pool = task::LocalPoolHandle::new(1);
|
||||
|
||||
let join_handle = pool.spawn_pinned(|| async {
|
||||
panic!("Test panic");
|
||||
});
|
||||
|
||||
let result = join_handle.await;
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
assert!(error.is_panic());
|
||||
let panic_str: &str = *error.into_panic().downcast().unwrap();
|
||||
assert_eq!(panic_str, "Test panic");
|
||||
|
||||
// Trying again with a "safe" task still works
|
||||
let join_handle = pool.spawn_pinned(|| async { "test" });
|
||||
let result = join_handle.await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "test");
|
||||
}
|
||||
|
||||
/// A panic during task creation causes the join handle to return an error.
|
||||
/// But, you can continue to spawn tasks.
|
||||
#[tokio::test]
|
||||
async fn callback_panic_does_not_kill_worker() {
|
||||
let pool = task::LocalPoolHandle::new(1);
|
||||
|
||||
let join_handle = pool.spawn_pinned(|| {
|
||||
panic!("Test panic");
|
||||
#[allow(unreachable_code)]
|
||||
async {}
|
||||
});
|
||||
|
||||
let result = join_handle.await;
|
||||
assert!(result.is_err());
|
||||
let error = result.unwrap_err();
|
||||
assert!(error.is_panic());
|
||||
let panic_str: &str = *error.into_panic().downcast().unwrap();
|
||||
assert_eq!(panic_str, "Test panic");
|
||||
|
||||
// Trying again with a "safe" callback works
|
||||
let join_handle = pool.spawn_pinned(|| async { "test" });
|
||||
let result = join_handle.await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "test");
|
||||
}
|
||||
|
||||
/// Canceling the task via the returned join handle cancels the spawned task
|
||||
/// (which has a different, internal join handle).
|
||||
#[tokio::test]
|
||||
async fn task_cancellation_propagates() {
|
||||
let pool = task::LocalPoolHandle::new(1);
|
||||
let notify_dropped = Arc::new(());
|
||||
let weak_notify_dropped = Arc::downgrade(¬ify_dropped);
|
||||
|
||||
let (start_sender, start_receiver) = tokio::sync::oneshot::channel();
|
||||
let (drop_sender, drop_receiver) = tokio::sync::oneshot::channel::<()>();
|
||||
let join_handle = pool.spawn_pinned(|| async move {
|
||||
let _drop_sender = drop_sender;
|
||||
// Move the Arc into the task
|
||||
let _notify_dropped = notify_dropped;
|
||||
let _ = start_sender.send(());
|
||||
|
||||
// Keep the task running until it gets aborted
|
||||
futures::future::pending::<()>().await;
|
||||
});
|
||||
|
||||
// Wait for the task to start
|
||||
let _ = start_receiver.await;
|
||||
|
||||
join_handle.abort();
|
||||
|
||||
// Wait for the inner task to abort, dropping the sender.
|
||||
// The top level join handle aborts quicker than the inner task (the abort
|
||||
// needs to propagate and get processed on the worker thread), so we can't
|
||||
// just await the top level join handle.
|
||||
let _ = drop_receiver.await;
|
||||
|
||||
// Check that the Arc has been dropped. This verifies that the inner task
|
||||
// was canceled as well.
|
||||
assert!(weak_notify_dropped.upgrade().is_none());
|
||||
}
|
||||
|
||||
/// Tasks should be given to the least burdened worker. When spawning two tasks
|
||||
/// on a pool with two empty workers the tasks should be spawned on separate
|
||||
/// workers.
|
||||
#[tokio::test]
|
||||
async fn tasks_are_balanced() {
|
||||
let pool = task::LocalPoolHandle::new(2);
|
||||
|
||||
// Spawn a task so one thread has a task count of 1
|
||||
let (start_sender1, start_receiver1) = tokio::sync::oneshot::channel();
|
||||
let (end_sender1, end_receiver1) = tokio::sync::oneshot::channel();
|
||||
let join_handle1 = pool.spawn_pinned(|| async move {
|
||||
let _ = start_sender1.send(());
|
||||
let _ = end_receiver1.await;
|
||||
std::thread::current().id()
|
||||
});
|
||||
|
||||
// Wait for the first task to start up
|
||||
let _ = start_receiver1.await;
|
||||
|
||||
// This task should be spawned on the other thread
|
||||
let (start_sender2, start_receiver2) = tokio::sync::oneshot::channel();
|
||||
let join_handle2 = pool.spawn_pinned(|| async move {
|
||||
let _ = start_sender2.send(());
|
||||
std::thread::current().id()
|
||||
});
|
||||
|
||||
// Wait for the second task to start up
|
||||
let _ = start_receiver2.await;
|
||||
|
||||
// Allow the first task to end
|
||||
let _ = end_sender1.send(());
|
||||
|
||||
let thread_id1 = join_handle1.await.unwrap();
|
||||
let thread_id2 = join_handle2.await.unwrap();
|
||||
|
||||
// Since the first task was active when the second task spawned, they should
|
||||
// be on separate workers/threads.
|
||||
assert_ne!(thread_id1, thread_id2);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use futures_test::task::new_count_waker;
|
||||
fn cancel_token() {
|
||||
let (waker, wake_counter) = new_count_waker();
|
||||
let token = CancellationToken::new();
|
||||
assert_eq!(false, token.is_cancelled());
|
||||
assert!(!token.is_cancelled());
|
||||
|
||||
let wait_fut = token.cancelled();
|
||||
pin!(wait_fut);
|
||||
@@ -27,7 +27,7 @@ fn cancel_token() {
|
||||
|
||||
token.cancel();
|
||||
assert_eq!(wake_counter, 1);
|
||||
assert_eq!(true, token.is_cancelled());
|
||||
assert!(token.is_cancelled());
|
||||
|
||||
assert_eq!(
|
||||
Poll::Ready(()),
|
||||
@@ -64,8 +64,8 @@ fn cancel_child_token_through_parent() {
|
||||
|
||||
token.cancel();
|
||||
assert_eq!(wake_counter, 2);
|
||||
assert_eq!(true, token.is_cancelled());
|
||||
assert_eq!(true, child_token.is_cancelled());
|
||||
assert!(token.is_cancelled());
|
||||
assert!(child_token.is_cancelled());
|
||||
|
||||
assert_eq!(
|
||||
Poll::Ready(()),
|
||||
@@ -101,8 +101,8 @@ fn cancel_child_token_without_parent() {
|
||||
|
||||
child_token_1.cancel();
|
||||
assert_eq!(wake_counter, 1);
|
||||
assert_eq!(false, token.is_cancelled());
|
||||
assert_eq!(true, child_token_1.is_cancelled());
|
||||
assert!(!token.is_cancelled());
|
||||
assert!(child_token_1.is_cancelled());
|
||||
|
||||
assert_eq!(
|
||||
Poll::Ready(()),
|
||||
@@ -128,8 +128,8 @@ fn cancel_child_token_without_parent() {
|
||||
|
||||
token.cancel();
|
||||
assert_eq!(wake_counter, 3);
|
||||
assert_eq!(true, token.is_cancelled());
|
||||
assert_eq!(true, child_token_2.is_cancelled());
|
||||
assert!(token.is_cancelled());
|
||||
assert!(child_token_2.is_cancelled());
|
||||
|
||||
assert_eq!(
|
||||
Poll::Ready(()),
|
||||
|
||||
@@ -109,6 +109,7 @@ async fn multi_delay_at_start() {
|
||||
|
||||
let start = Instant::now();
|
||||
for elapsed in 0..1200 {
|
||||
println!("elapsed: {:?}", elapsed);
|
||||
let elapsed = elapsed + 1;
|
||||
tokio::time::sleep_until(start + ms(elapsed)).await;
|
||||
|
||||
@@ -128,10 +129,12 @@ async fn multi_delay_at_start() {
|
||||
assert_pending!(poll!(queue));
|
||||
}
|
||||
}
|
||||
println!("finished multi_delay_start");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn insert_in_past_fires_immediately() {
|
||||
println!("running insert_in_past_fires_immediately");
|
||||
time::pause();
|
||||
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
@@ -142,6 +145,7 @@ async fn insert_in_past_fires_immediately() {
|
||||
queue.insert_at("foo", now);
|
||||
|
||||
assert_ready!(poll!(queue));
|
||||
println!("finished insert_in_past_fires_immediately");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -630,6 +634,185 @@ async fn insert_in_past_after_poll_fires_immediately() {
|
||||
assert_eq!(entry, "bar");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delay_queue_poll_expired_when_empty() {
|
||||
let mut delay_queue = task::spawn(DelayQueue::new());
|
||||
let key = delay_queue.insert(0, std::time::Duration::from_secs(10));
|
||||
assert_pending!(poll!(delay_queue));
|
||||
|
||||
delay_queue.remove(&key);
|
||||
assert!(assert_ready!(poll!(delay_queue)).is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn compact_expire_empty() {
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
queue.insert_at("foo1", now + ms(10));
|
||||
queue.insert_at("foo2", now + ms(10));
|
||||
|
||||
sleep(ms(10)).await;
|
||||
|
||||
let mut res = vec![];
|
||||
while res.len() < 2 {
|
||||
let entry = assert_ready_ok!(poll!(queue));
|
||||
res.push(entry.into_inner());
|
||||
}
|
||||
|
||||
queue.compact();
|
||||
|
||||
assert_eq!(queue.len(), 0);
|
||||
assert_eq!(queue.capacity(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn compact_remove_empty() {
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
let key1 = queue.insert_at("foo1", now + ms(10));
|
||||
let key2 = queue.insert_at("foo2", now + ms(10));
|
||||
|
||||
queue.remove(&key1);
|
||||
queue.remove(&key2);
|
||||
|
||||
queue.compact();
|
||||
|
||||
assert_eq!(queue.len(), 0);
|
||||
assert_eq!(queue.capacity(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
// Trigger a re-mapping of keys in the slab due to a `compact` call and
|
||||
// test removal of re-mapped keys
|
||||
async fn compact_remove_remapped_keys() {
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
queue.insert_at("foo1", now + ms(10));
|
||||
queue.insert_at("foo2", now + ms(10));
|
||||
|
||||
// should be assigned indices 3 and 4
|
||||
let key3 = queue.insert_at("foo3", now + ms(20));
|
||||
let key4 = queue.insert_at("foo4", now + ms(20));
|
||||
|
||||
sleep(ms(10)).await;
|
||||
|
||||
let mut res = vec![];
|
||||
while res.len() < 2 {
|
||||
let entry = assert_ready_ok!(poll!(queue));
|
||||
res.push(entry.into_inner());
|
||||
}
|
||||
|
||||
// items corresponding to `foo3` and `foo4` will be assigned
|
||||
// new indices here
|
||||
queue.compact();
|
||||
|
||||
queue.insert_at("foo5", now + ms(10));
|
||||
|
||||
// test removal of re-mapped keys
|
||||
let expired3 = queue.remove(&key3);
|
||||
let expired4 = queue.remove(&key4);
|
||||
|
||||
assert_eq!(expired3.into_inner(), "foo3");
|
||||
assert_eq!(expired4.into_inner(), "foo4");
|
||||
|
||||
queue.compact();
|
||||
assert_eq!(queue.len(), 1);
|
||||
assert_eq!(queue.capacity(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn compact_change_deadline() {
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
|
||||
let mut now = Instant::now();
|
||||
|
||||
queue.insert_at("foo1", now + ms(10));
|
||||
queue.insert_at("foo2", now + ms(10));
|
||||
|
||||
// should be assigned indices 3 and 4
|
||||
queue.insert_at("foo3", now + ms(20));
|
||||
let key4 = queue.insert_at("foo4", now + ms(20));
|
||||
|
||||
sleep(ms(10)).await;
|
||||
|
||||
let mut res = vec![];
|
||||
while res.len() < 2 {
|
||||
let entry = assert_ready_ok!(poll!(queue));
|
||||
res.push(entry.into_inner());
|
||||
}
|
||||
|
||||
// items corresponding to `foo3` and `foo4` should be assigned
|
||||
// new indices
|
||||
queue.compact();
|
||||
|
||||
now = Instant::now();
|
||||
|
||||
queue.insert_at("foo5", now + ms(10));
|
||||
let key6 = queue.insert_at("foo6", now + ms(10));
|
||||
|
||||
queue.reset_at(&key4, now + ms(20));
|
||||
queue.reset_at(&key6, now + ms(20));
|
||||
|
||||
// foo3 and foo5 will expire
|
||||
sleep(ms(10)).await;
|
||||
|
||||
while res.len() < 4 {
|
||||
let entry = assert_ready_ok!(poll!(queue));
|
||||
res.push(entry.into_inner());
|
||||
}
|
||||
|
||||
sleep(ms(10)).await;
|
||||
|
||||
while res.len() < 6 {
|
||||
let entry = assert_ready_ok!(poll!(queue));
|
||||
res.push(entry.into_inner());
|
||||
}
|
||||
|
||||
let entry = assert_ready!(poll!(queue));
|
||||
assert!(entry.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn remove_after_compact() {
|
||||
let now = Instant::now();
|
||||
let mut queue = DelayQueue::new();
|
||||
|
||||
let foo_key = queue.insert_at("foo", now + ms(10));
|
||||
queue.insert_at("bar", now + ms(20));
|
||||
queue.remove(&foo_key);
|
||||
queue.compact();
|
||||
|
||||
let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
queue.remove(&foo_key);
|
||||
}));
|
||||
assert!(panic.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn remove_after_compact_poll() {
|
||||
let now = Instant::now();
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
|
||||
let foo_key = queue.insert_at("foo", now + ms(10));
|
||||
queue.insert_at("bar", now + ms(20));
|
||||
|
||||
sleep(ms(10)).await;
|
||||
assert_eq!(assert_ready_ok!(poll!(queue)).key(), foo_key);
|
||||
|
||||
queue.compact();
|
||||
|
||||
let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
queue.remove(&foo_key);
|
||||
}));
|
||||
assert!(panic.is_err());
|
||||
}
|
||||
|
||||
fn ms(n: u64) -> Duration {
|
||||
Duration::from_millis(n)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,354 @@
|
||||
# 1.16.1 (January 28, 2022)
|
||||
|
||||
This release fixes a bug in [#4428] with the change [#4437].
|
||||
|
||||
[#4428]: https://github.com/tokio-rs/tokio/pull/4428
|
||||
[#4437]: https://github.com/tokio-rs/tokio/pull/4437
|
||||
|
||||
# 1.16.0 (January 27, 2022)
|
||||
|
||||
Fixes a soundness bug in `io::Take` ([#4428]). The unsoundness is exposed when
|
||||
leaking memory in the given `AsyncRead` implementation and then overwriting the
|
||||
supplied buffer:
|
||||
|
||||
```rust
|
||||
impl AsyncRead for Buggy {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>
|
||||
) -> Poll<Result<()>> {
|
||||
let new_buf = vec![0; 5].leak();
|
||||
*buf = ReadBuf::new(new_buf);
|
||||
buf.put_slice(b"hello");
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Also, this release includes improvements to the multi-threaded scheduler that
|
||||
can increase throughput by up to 20% in some cases ([#4383]).
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: **soundness** don't expose uninitialized memory when using `io::Take` in edge case ([#4428])
|
||||
- fs: ensure `File::write` results in a `write` syscall when the runtime shuts down ([#4316])
|
||||
- process: drop pipe after child exits in `wait_with_output` ([#4315])
|
||||
- rt: improve error message when spawning a thread fails ([#4398])
|
||||
- rt: reduce false-positive thread wakups in the multi-threaded scheduler ([#4383])
|
||||
- sync: don't inherit `Send` from `parking_lot::*Guard` ([#4359])
|
||||
|
||||
### Added
|
||||
|
||||
- net: `TcpSocket::linger()` and `set_linger()` ([#4324])
|
||||
- net: impl `UnwindSafe` for socket types ([#4384])
|
||||
- rt: impl `UnwindSafe` for `JoinHandle` ([#4418])
|
||||
- sync: `watch::Receiver::has_changed()` ([#4342])
|
||||
- sync: `oneshot::Receiver::blocking_recv()` ([#4334])
|
||||
- sync: `RwLock` blocking operations ([#4425])
|
||||
|
||||
### Unstable
|
||||
|
||||
The following changes only apply when building with `--cfg tokio_unstable`
|
||||
|
||||
- rt: **breaking change** overhaul runtime metrics API ([#4373])
|
||||
|
||||
[#4428]: https://github.com/tokio-rs/tokio/pull/4428
|
||||
[#4316]: https://github.com/tokio-rs/tokio/pull/4316
|
||||
[#4315]: https://github.com/tokio-rs/tokio/pull/4315
|
||||
[#4398]: https://github.com/tokio-rs/tokio/pull/4398
|
||||
[#4383]: https://github.com/tokio-rs/tokio/pull/4383
|
||||
[#4359]: https://github.com/tokio-rs/tokio/pull/4359
|
||||
[#4324]: https://github.com/tokio-rs/tokio/pull/4324
|
||||
[#4384]: https://github.com/tokio-rs/tokio/pull/4384
|
||||
[#4418]: https://github.com/tokio-rs/tokio/pull/4418
|
||||
[#4342]: https://github.com/tokio-rs/tokio/pull/4342
|
||||
[#4334]: https://github.com/tokio-rs/tokio/pull/4334
|
||||
[#4425]: https://github.com/tokio-rs/tokio/pull/4425
|
||||
[#4373]: https://github.com/tokio-rs/tokio/pull/4373
|
||||
|
||||
# 1.15.0 (December 15, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: add cooperative yielding support to `io::empty()` ([#4300])
|
||||
- time: make timeout robust against budget-depleting tasks ([#4314])
|
||||
|
||||
### Changed
|
||||
|
||||
- update minimum supported Rust version to 1.46.
|
||||
|
||||
### Added
|
||||
|
||||
- time: add `Interval::reset()` ([#4248])
|
||||
- io: add explicit lifetimes to `AsyncFdReadyGuard` ([#4267])
|
||||
- process: add `Command::as_std()` ([#4295])
|
||||
|
||||
### Added (unstable)
|
||||
|
||||
- tracing: instrument `tokio::sync` types ([#4302])
|
||||
|
||||
[#4302]: https://github.com/tokio-rs/tokio/pull/4302
|
||||
[#4300]: https://github.com/tokio-rs/tokio/pull/4300
|
||||
[#4295]: https://github.com/tokio-rs/tokio/pull/4295
|
||||
[#4267]: https://github.com/tokio-rs/tokio/pull/4267
|
||||
[#4248]: https://github.com/tokio-rs/tokio/pull/4248
|
||||
[#4314]: https://github.com/tokio-rs/tokio/pull/4314
|
||||
|
||||
# 1.14.0 (November 15, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- macros: fix compiler errors when using `mut` patterns in `select!` ([#4211])
|
||||
- sync: fix a data race between `oneshot::Sender::send` and awaiting a
|
||||
`oneshot::Receiver` when the oneshot has been closed ([#4226])
|
||||
- sync: make `AtomicWaker` panic safe ([#3689])
|
||||
- runtime: fix basic scheduler dropping tasks outside a runtime context
|
||||
([#4213])
|
||||
|
||||
### Added
|
||||
|
||||
- stats: add `RuntimeStats::busy_duration_total` ([#4179], [#4223])
|
||||
|
||||
### Changed
|
||||
|
||||
- io: updated `copy` buffer size to match `std::io::copy` ([#4209])
|
||||
|
||||
### Documented
|
||||
|
||||
- io: rename buffer to file in doc-test ([#4230])
|
||||
- sync: fix Notify example ([#4212])
|
||||
|
||||
[#4211]: https://github.com/tokio-rs/tokio/pull/4211
|
||||
[#4226]: https://github.com/tokio-rs/tokio/pull/4226
|
||||
[#3689]: https://github.com/tokio-rs/tokio/pull/3689
|
||||
[#4213]: https://github.com/tokio-rs/tokio/pull/4213
|
||||
[#4179]: https://github.com/tokio-rs/tokio/pull/4179
|
||||
[#4223]: https://github.com/tokio-rs/tokio/pull/4223
|
||||
[#4209]: https://github.com/tokio-rs/tokio/pull/4209
|
||||
[#4230]: https://github.com/tokio-rs/tokio/pull/4230
|
||||
[#4212]: https://github.com/tokio-rs/tokio/pull/4212
|
||||
|
||||
# 1.13.1 (November 15, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: fix a data race between `oneshot::Sender::send` and awaiting a
|
||||
`oneshot::Receiver` when the oneshot has been closed ([#4226])
|
||||
|
||||
[#4226]: https://github.com/tokio-rs/tokio/pull/4226
|
||||
|
||||
# 1.13.0 (October 29, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: fix `Notify` to clone the waker before locking its waiter list ([#4129])
|
||||
- tokio: add riscv32 to non atomic64 architectures ([#4185])
|
||||
|
||||
### Added
|
||||
|
||||
- net: add `poll_{recv,send}_ready` methods to `udp` and `uds_datagram` ([#4131])
|
||||
- net: add `try_*`, `readable`, `writable`, `ready`, and `peer_addr` methods to split halves ([#4120])
|
||||
- sync: add `blocking_lock` to `Mutex` ([#4130])
|
||||
- sync: add `watch::Sender::send_replace` ([#3962], [#4195])
|
||||
- sync: expand `Debug` for `Mutex<T>` impl to unsized `T` ([#4134])
|
||||
- tracing: instrument time::Sleep ([#4072])
|
||||
- tracing: use structured location fields for spawned tasks ([#4128])
|
||||
|
||||
### Changed
|
||||
|
||||
- io: add assert in `copy_bidirectional` that `poll_write` is sensible ([#4125])
|
||||
- macros: use qualified syntax when polling in `select!` ([#4192])
|
||||
- runtime: handle `block_on` wakeups better ([#4157])
|
||||
- task: allocate callback on heap immediately in debug mode ([#4203])
|
||||
- tokio: assert platform-minimum requirements at build time ([#3797])
|
||||
|
||||
### Documented
|
||||
|
||||
- docs: conversion of doc comments to indicative mood ([#4174])
|
||||
- docs: add returning on the first error example for `try_join!` ([#4133])
|
||||
- docs: fixing broken links in `tokio/src/lib.rs` ([#4132])
|
||||
- signal: add example with background listener ([#4171])
|
||||
- sync: add more oneshot examples ([#4153])
|
||||
- time: document `Interval::tick` cancel safety ([#4152])
|
||||
|
||||
[#3797]: https://github.com/tokio-rs/tokio/pull/3797
|
||||
[#3962]: https://github.com/tokio-rs/tokio/pull/3962
|
||||
[#4072]: https://github.com/tokio-rs/tokio/pull/4072
|
||||
[#4120]: https://github.com/tokio-rs/tokio/pull/4120
|
||||
[#4125]: https://github.com/tokio-rs/tokio/pull/4125
|
||||
[#4128]: https://github.com/tokio-rs/tokio/pull/4128
|
||||
[#4129]: https://github.com/tokio-rs/tokio/pull/4129
|
||||
[#4130]: https://github.com/tokio-rs/tokio/pull/4130
|
||||
[#4131]: https://github.com/tokio-rs/tokio/pull/4131
|
||||
[#4132]: https://github.com/tokio-rs/tokio/pull/4132
|
||||
[#4133]: https://github.com/tokio-rs/tokio/pull/4133
|
||||
[#4134]: https://github.com/tokio-rs/tokio/pull/4134
|
||||
[#4152]: https://github.com/tokio-rs/tokio/pull/4152
|
||||
[#4153]: https://github.com/tokio-rs/tokio/pull/4153
|
||||
[#4157]: https://github.com/tokio-rs/tokio/pull/4157
|
||||
[#4171]: https://github.com/tokio-rs/tokio/pull/4171
|
||||
[#4174]: https://github.com/tokio-rs/tokio/pull/4174
|
||||
[#4185]: https://github.com/tokio-rs/tokio/pull/4185
|
||||
[#4192]: https://github.com/tokio-rs/tokio/pull/4192
|
||||
[#4195]: https://github.com/tokio-rs/tokio/pull/4195
|
||||
[#4203]: https://github.com/tokio-rs/tokio/pull/4203
|
||||
|
||||
# 1.12.0 (September 21, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- mpsc: ensure `try_reserve` error is consistent with `try_send` ([#4119])
|
||||
- mpsc: use `spin_loop_hint` instead of `yield_now` ([#4115])
|
||||
- sync: make `SendError` field public ([#4097])
|
||||
|
||||
### Added
|
||||
|
||||
- io: add POSIX AIO on FreeBSD ([#4054])
|
||||
- io: add convenience method `AsyncSeekExt::rewind` ([#4107])
|
||||
- runtime: add tracing span for `block_on` futures ([#4094])
|
||||
- runtime: callback when a worker parks and unparks ([#4070])
|
||||
- sync: implement `try_recv` for mpsc channels ([#4113])
|
||||
|
||||
### Documented
|
||||
|
||||
- docs: clarify CPU-bound tasks on Tokio ([#4105])
|
||||
- mpsc: document spurious failures on `poll_recv` ([#4117])
|
||||
- mpsc: document that `PollSender` impls `Sink` ([#4110])
|
||||
- task: document non-guarantees of `yield_now` ([#4091])
|
||||
- time: document paused time details better ([#4061], [#4103])
|
||||
|
||||
[#4027]: https://github.com/tokio-rs/tokio/pull/4027
|
||||
[#4054]: https://github.com/tokio-rs/tokio/pull/4054
|
||||
[#4061]: https://github.com/tokio-rs/tokio/pull/4061
|
||||
[#4070]: https://github.com/tokio-rs/tokio/pull/4070
|
||||
[#4091]: https://github.com/tokio-rs/tokio/pull/4091
|
||||
[#4094]: https://github.com/tokio-rs/tokio/pull/4094
|
||||
[#4097]: https://github.com/tokio-rs/tokio/pull/4097
|
||||
[#4103]: https://github.com/tokio-rs/tokio/pull/4103
|
||||
[#4105]: https://github.com/tokio-rs/tokio/pull/4105
|
||||
[#4107]: https://github.com/tokio-rs/tokio/pull/4107
|
||||
[#4110]: https://github.com/tokio-rs/tokio/pull/4110
|
||||
[#4113]: https://github.com/tokio-rs/tokio/pull/4113
|
||||
[#4115]: https://github.com/tokio-rs/tokio/pull/4115
|
||||
[#4117]: https://github.com/tokio-rs/tokio/pull/4117
|
||||
[#4119]: https://github.com/tokio-rs/tokio/pull/4119
|
||||
|
||||
# 1.11.0 (August 31, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- time: don't panic when Instant is not monotonic ([#4044])
|
||||
- io: fix panic in `fill_buf` by not calling `poll_fill_buf` twice ([#4084])
|
||||
|
||||
### Added
|
||||
|
||||
- watch: add `watch::Sender::subscribe` ([#3800])
|
||||
- process: add `from_std` to `ChildStd*` ([#4045])
|
||||
- stats: initial work on runtime stats ([#4043])
|
||||
|
||||
### Changed
|
||||
|
||||
- tracing: change span naming to new console convention ([#4042])
|
||||
- io: speed-up waking by using uninitialized array ([#4055], [#4071], [#4075])
|
||||
|
||||
### Documented
|
||||
|
||||
- time: make Sleep examples easier to find ([#4040])
|
||||
|
||||
[#3800]: https://github.com/tokio-rs/tokio/pull/3800
|
||||
[#4040]: https://github.com/tokio-rs/tokio/pull/4040
|
||||
[#4042]: https://github.com/tokio-rs/tokio/pull/4042
|
||||
[#4043]: https://github.com/tokio-rs/tokio/pull/4043
|
||||
[#4044]: https://github.com/tokio-rs/tokio/pull/4044
|
||||
[#4045]: https://github.com/tokio-rs/tokio/pull/4045
|
||||
[#4055]: https://github.com/tokio-rs/tokio/pull/4055
|
||||
[#4071]: https://github.com/tokio-rs/tokio/pull/4071
|
||||
[#4075]: https://github.com/tokio-rs/tokio/pull/4075
|
||||
[#4084]: https://github.com/tokio-rs/tokio/pull/4084
|
||||
|
||||
# 1.10.1 (August 24, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: fix leak in UnownedTask ([#4063])
|
||||
|
||||
[#4063]: https://github.com/tokio-rs/tokio/pull/4063
|
||||
|
||||
# 1.10.0 (August 12, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `(read|write)_f(32|64)[_le]` methods ([#4022])
|
||||
- io: add `fill_buf` and `consume` to `AsyncBufReadExt` ([#3991])
|
||||
- process: add `Child::raw_handle()` on windows ([#3998])
|
||||
|
||||
### Fixed
|
||||
|
||||
- doc: fix non-doc builds with `--cfg docsrs` ([#4020])
|
||||
- io: flush eagerly in `io::copy` ([#4001])
|
||||
- runtime: a debug assert was sometimes triggered during shutdown ([#4005])
|
||||
- sync: use `spin_loop_hint` instead of `yield_now` in mpsc ([#4037])
|
||||
- tokio: the test-util feature depends on rt, sync, and time ([#4036])
|
||||
|
||||
### Changes
|
||||
|
||||
- runtime: reorganize parts of the runtime ([#3979], [#4005])
|
||||
- signal: make windows docs for signal module show up on unix builds ([#3770])
|
||||
- task: quickly send task to heap on debug mode ([#4009])
|
||||
|
||||
### Documented
|
||||
|
||||
- io: document cancellation safety of `AsyncBufReadExt` ([#3997])
|
||||
- sync: document when `watch::send` fails ([#4021])
|
||||
|
||||
[#3770]: https://github.com/tokio-rs/tokio/pull/3770
|
||||
[#3979]: https://github.com/tokio-rs/tokio/pull/3979
|
||||
[#3991]: https://github.com/tokio-rs/tokio/pull/3991
|
||||
[#3997]: https://github.com/tokio-rs/tokio/pull/3997
|
||||
[#3998]: https://github.com/tokio-rs/tokio/pull/3998
|
||||
[#4001]: https://github.com/tokio-rs/tokio/pull/4001
|
||||
[#4005]: https://github.com/tokio-rs/tokio/pull/4005
|
||||
[#4009]: https://github.com/tokio-rs/tokio/pull/4009
|
||||
[#4020]: https://github.com/tokio-rs/tokio/pull/4020
|
||||
[#4021]: https://github.com/tokio-rs/tokio/pull/4021
|
||||
[#4022]: https://github.com/tokio-rs/tokio/pull/4022
|
||||
[#4036]: https://github.com/tokio-rs/tokio/pull/4036
|
||||
[#4037]: https://github.com/tokio-rs/tokio/pull/4037
|
||||
|
||||
# 1.9.0 (July 22, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- net: allow customized I/O operations for `TcpStream` ([#3888])
|
||||
- sync: add getter for the mutex from a guard ([#3928])
|
||||
- task: expose nameable future for `TaskLocal::scope` ([#3273])
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix leak if output of future panics on drop ([#3967])
|
||||
- Fix leak in `LocalSet` ([#3978])
|
||||
|
||||
### Changes
|
||||
|
||||
- runtime: reorganize parts of the runtime ([#3909], [#3939], [#3950], [#3955], [#3980])
|
||||
- sync: clean up `OnceCell` ([#3945])
|
||||
- task: remove mutex in `JoinError` ([#3959])
|
||||
|
||||
[#3273]: https://github.com/tokio-rs/tokio/pull/3273
|
||||
[#3888]: https://github.com/tokio-rs/tokio/pull/3888
|
||||
[#3909]: https://github.com/tokio-rs/tokio/pull/3909
|
||||
[#3928]: https://github.com/tokio-rs/tokio/pull/3928
|
||||
[#3934]: https://github.com/tokio-rs/tokio/pull/3934
|
||||
[#3939]: https://github.com/tokio-rs/tokio/pull/3939
|
||||
[#3945]: https://github.com/tokio-rs/tokio/pull/3945
|
||||
[#3950]: https://github.com/tokio-rs/tokio/pull/3950
|
||||
[#3955]: https://github.com/tokio-rs/tokio/pull/3955
|
||||
[#3959]: https://github.com/tokio-rs/tokio/pull/3959
|
||||
[#3967]: https://github.com/tokio-rs/tokio/pull/3967
|
||||
[#3978]: https://github.com/tokio-rs/tokio/pull/3978
|
||||
[#3980]: https://github.com/tokio-rs/tokio/pull/3980
|
||||
|
||||
# 1.8.3 (July 26, 2021)
|
||||
|
||||
This release backports two fixes from 1.9.0
|
||||
|
||||
+27
-13
@@ -3,16 +3,15 @@ name = "tokio"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.0.x" git tag.
|
||||
version = "1.8.3"
|
||||
version = "1.16.1"
|
||||
edition = "2018"
|
||||
rust-version = "1.46"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/tokio/1.8.3/tokio/"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
description = """
|
||||
@@ -82,11 +81,16 @@ signal = [
|
||||
"winapi/consoleapi",
|
||||
]
|
||||
sync = []
|
||||
test-util = []
|
||||
test-util = ["rt", "sync", "time"]
|
||||
time = []
|
||||
|
||||
# Technically, removing this is a breaking change even though it only ever did
|
||||
# anything with the unstable flag on. It is probably safe to get rid of it after
|
||||
# a few releases.
|
||||
stats = []
|
||||
|
||||
[dependencies]
|
||||
tokio-macros = { version = "1.1.0", path = "../tokio-macros", optional = true }
|
||||
tokio-macros = { version = "1.7.0", path = "../tokio-macros", optional = true }
|
||||
|
||||
pin-project-lite = "0.2.0"
|
||||
|
||||
@@ -101,7 +105,7 @@ parking_lot = { version = "0.11.0", optional = true }
|
||||
# Currently unstable. The API exposed by these features may be broken at any time.
|
||||
# Requires `--cfg tokio_unstable` to enable.
|
||||
[target.'cfg(tokio_unstable)'.dependencies]
|
||||
tracing = { version = "0.1.21", default-features = false, features = ["std"], optional = true } # Not in full
|
||||
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true } # Not in full
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = { version = "0.2.42", optional = true }
|
||||
@@ -109,7 +113,7 @@ signal-hook-registry = { version = "1.1.1", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
libc = { version = "0.2.42" }
|
||||
nix = { version = "0.19.0" }
|
||||
nix = { version = "0.23" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.winapi]
|
||||
version = "0.3.8"
|
||||
@@ -123,21 +127,31 @@ version = "0.3.6"
|
||||
tokio-test = { version = "0.4.0", path = "../tokio-test" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
proptest = "1"
|
||||
rand = "0.8.0"
|
||||
mockall = "0.10.2"
|
||||
tempfile = "3.1.0"
|
||||
async-stream = "0.3"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
proptest = "1"
|
||||
rand = "0.8.0"
|
||||
socket2 = "0.4"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
|
||||
mio-aio = { version = "0.6.0", features = ["tokio"] }
|
||||
|
||||
[target.'cfg(loom)'.dev-dependencies]
|
||||
loom = { version = "0.5", features = ["futures", "checkpoint"] }
|
||||
|
||||
[build-dependencies]
|
||||
autocfg = "1" # Needed for conditionally enabling `track-caller`
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
# enable unstable features in the documentation
|
||||
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"]
|
||||
# it's necessary to _also_ pass `--cfg tokio_unstable` to rustc, or else
|
||||
# dependencies will not be enabled, and the docs build will fail.
|
||||
rustc-args = ["--cfg", "tokio_unstable"]
|
||||
|
||||
[package.metadata.playground]
|
||||
features = ["full", "test-util"]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
Copyright (c) 2022 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
+41
-6
@@ -50,7 +50,15 @@ an asynchronous application.
|
||||
|
||||
## Example
|
||||
|
||||
A basic TCP echo server with Tokio:
|
||||
A basic TCP echo server with Tokio.
|
||||
|
||||
Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
```rust,no_run
|
||||
use tokio::net::TcpListener;
|
||||
@@ -58,7 +66,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
let listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
@@ -132,7 +140,7 @@ several other libraries, including:
|
||||
|
||||
* [`tower`]: A library of modular and reusable components for building robust networking clients and servers.
|
||||
|
||||
* [`tracing`]: A framework for application-level tracing and async-aware diagnostics.
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
@@ -155,9 +163,36 @@ several other libraries, including:
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.45.
|
||||
The current Tokio version is not guaranteed to build on Rust versions earlier than the
|
||||
minimum supported version.
|
||||
Tokio is built against the latest stable release. The minimum supported version
|
||||
is 1.46. The current Tokio version is not guaranteed to build on Rust versions
|
||||
earlier than the minimum supported version.
|
||||
|
||||
## Release schedule
|
||||
|
||||
Tokio doesn't follow a fixed release schedule, but we typically make one to two
|
||||
new minor releases each month. We make patch releases for bugfixes as necessary.
|
||||
|
||||
## Bug patching policy
|
||||
|
||||
For the purposes of making patch releases with bugfixes, we have designated
|
||||
certain minor releases as LTS (long term support) releases. Whenever a bug
|
||||
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.8.x` - LTS release until February 2022.
|
||||
* `1.14.x` - LTS release until June 2022.
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least half a
|
||||
year. If you wish to use a fixed minor release in your project, we recommend
|
||||
that you use an LTS release.
|
||||
|
||||
To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.8.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.8", features = [...] }
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
use autocfg::AutoCfg;
|
||||
|
||||
fn main() {
|
||||
match AutoCfg::new() {
|
||||
Ok(ac) => {
|
||||
// The #[track_caller] attribute was stabilized in rustc 1.46.0.
|
||||
if ac.probe_rustc_version(1, 46) {
|
||||
autocfg::emit("tokio_track_caller")
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
// If we couldn't detect the compiler version and features, just
|
||||
// print a warning. This isn't a fatal error: we can still build
|
||||
// Tokio, we just can't enable cfgs automatically.
|
||||
println!(
|
||||
"cargo:warning=tokio: failed to detect compiler features: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
cfg_rt! {
|
||||
pub(crate) use crate::runtime::spawn_blocking;
|
||||
|
||||
cfg_fs! {
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use crate::runtime::spawn_mandatory_blocking;
|
||||
}
|
||||
|
||||
pub(crate) use crate::task::JoinHandle;
|
||||
}
|
||||
|
||||
@@ -16,7 +22,16 @@ cfg_not_rt! {
|
||||
{
|
||||
assert_send_sync::<JoinHandle<std::cell::Cell<()>>>();
|
||||
panic!("requires the `rt` Tokio feature flag")
|
||||
}
|
||||
|
||||
cfg_fs! {
|
||||
pub(crate) fn spawn_mandatory_blocking<F, R>(_f: F) -> Option<JoinHandle<R>>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
panic!("requires the `rt` Tokio feature flag")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct JoinHandle<R> {
|
||||
|
||||
+15
-16
@@ -59,24 +59,20 @@ impl Budget {
|
||||
const fn unconstrained() -> Budget {
|
||||
Budget(None)
|
||||
}
|
||||
}
|
||||
|
||||
cfg_rt_multi_thread! {
|
||||
impl Budget {
|
||||
fn has_remaining(self) -> bool {
|
||||
self.0.map(|budget| budget > 0).unwrap_or(true)
|
||||
}
|
||||
fn has_remaining(self) -> bool {
|
||||
self.0.map(|budget| budget > 0).unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the given closure with a cooperative task budget. When the function
|
||||
/// Runs the given closure with a cooperative task budget. When the function
|
||||
/// returns, the budget is reset to the value prior to calling the function.
|
||||
#[inline(always)]
|
||||
pub(crate) fn budget<R>(f: impl FnOnce() -> R) -> R {
|
||||
with_budget(Budget::initial(), f)
|
||||
}
|
||||
|
||||
/// Run the given closure with an unconstrained task budget. When the function returns, the budget
|
||||
/// Runs the given closure with an unconstrained task budget. When the function returns, the budget
|
||||
/// is reset to the value prior to calling the function.
|
||||
#[inline(always)]
|
||||
pub(crate) fn with_unconstrained<R>(f: impl FnOnce() -> R) -> R {
|
||||
@@ -107,20 +103,20 @@ fn with_budget<R>(budget: Budget, f: impl FnOnce() -> R) -> R {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn has_budget_remaining() -> bool {
|
||||
CURRENT.with(|cell| cell.get().has_remaining())
|
||||
}
|
||||
|
||||
cfg_rt_multi_thread! {
|
||||
/// Set the current task's budget
|
||||
/// Sets the current task's budget.
|
||||
pub(crate) fn set(budget: Budget) {
|
||||
CURRENT.with(|cell| cell.set(budget))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn has_budget_remaining() -> bool {
|
||||
CURRENT.with(|cell| cell.get().has_remaining())
|
||||
}
|
||||
}
|
||||
|
||||
cfg_rt! {
|
||||
/// Forcibly remove the budgeting constraints early.
|
||||
/// Forcibly removes the budgeting constraints early.
|
||||
///
|
||||
/// Returns the remaining budget
|
||||
pub(crate) fn stop() -> Budget {
|
||||
@@ -186,7 +182,7 @@ cfg_coop! {
|
||||
}
|
||||
|
||||
impl Budget {
|
||||
/// Decrement the budget. Returns `true` if successful. Decrementing fails
|
||||
/// Decrements the budget. Returns `true` if successful. Decrementing fails
|
||||
/// when there is not enough remaining budget.
|
||||
fn decrement(&mut self) -> bool {
|
||||
if let Some(num) = &mut self.0 {
|
||||
@@ -211,6 +207,9 @@ cfg_coop! {
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen_test::wasm_bindgen_test as test;
|
||||
|
||||
fn get() -> Budget {
|
||||
CURRENT.with(|cell| cell.get())
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
/// will ever accidentally use it.
|
||||
///
|
||||
/// [`never` type]: https://doc.rust-lang.org/std/primitive.never.html
|
||||
#[derive(Debug)]
|
||||
pub enum NotDefinedHere {}
|
||||
|
||||
pub mod os;
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::fs::asyncify;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
/// Creates a new, empty directory at the provided path
|
||||
/// Creates a new, empty directory at the provided path.
|
||||
///
|
||||
/// This is an async version of [`std::fs::create_dir`][std]
|
||||
///
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct DirBuilder {
|
||||
/// Indicates whether to create parent directories if they are missing.
|
||||
recursive: bool,
|
||||
|
||||
/// Set the Unix mode for newly created directories.
|
||||
/// Sets the Unix mode for newly created directories.
|
||||
#[cfg(unix)]
|
||||
pub(super) mode: Option<u32>,
|
||||
}
|
||||
|
||||
+40
-19
@@ -3,7 +3,7 @@
|
||||
//! [`File`]: File
|
||||
|
||||
use self::State::*;
|
||||
use crate::fs::{asyncify, sys};
|
||||
use crate::fs::asyncify;
|
||||
use crate::io::blocking::Buf;
|
||||
use crate::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
||||
use crate::sync::Mutex;
|
||||
@@ -19,6 +19,19 @@ use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use std::task::Poll::*;
|
||||
|
||||
#[cfg(test)]
|
||||
use super::mocks::JoinHandle;
|
||||
#[cfg(test)]
|
||||
use super::mocks::MockFile as StdFile;
|
||||
#[cfg(test)]
|
||||
use super::mocks::{spawn_blocking, spawn_mandatory_blocking};
|
||||
#[cfg(not(test))]
|
||||
use crate::blocking::JoinHandle;
|
||||
#[cfg(not(test))]
|
||||
use crate::blocking::{spawn_blocking, spawn_mandatory_blocking};
|
||||
#[cfg(not(test))]
|
||||
use std::fs::File as StdFile;
|
||||
|
||||
/// A reference to an open file on the filesystem.
|
||||
///
|
||||
/// This is a specialized version of [`std::fs::File`][std] for usage from the
|
||||
@@ -61,7 +74,7 @@ use std::task::Poll::*;
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Read the contents of a file into a buffer
|
||||
/// Read the contents of a file into a buffer:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::File;
|
||||
@@ -78,7 +91,7 @@ use std::task::Poll::*;
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct File {
|
||||
std: Arc<sys::File>,
|
||||
std: Arc<StdFile>,
|
||||
inner: Mutex<Inner>,
|
||||
}
|
||||
|
||||
@@ -96,7 +109,7 @@ struct Inner {
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Idle(Option<Buf>),
|
||||
Busy(sys::Blocking<(Operation, Buf)>),
|
||||
Busy(JoinHandle<(Operation, Buf)>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -142,7 +155,7 @@ impl File {
|
||||
/// [`AsyncReadExt`]: trait@crate::io::AsyncReadExt
|
||||
pub async fn open(path: impl AsRef<Path>) -> io::Result<File> {
|
||||
let path = path.as_ref().to_owned();
|
||||
let std = asyncify(|| sys::File::open(path)).await?;
|
||||
let std = asyncify(|| StdFile::open(path)).await?;
|
||||
|
||||
Ok(File::from_std(std))
|
||||
}
|
||||
@@ -182,7 +195,7 @@ impl File {
|
||||
/// [`AsyncWriteExt`]: trait@crate::io::AsyncWriteExt
|
||||
pub async fn create(path: impl AsRef<Path>) -> io::Result<File> {
|
||||
let path = path.as_ref().to_owned();
|
||||
let std_file = asyncify(move || sys::File::create(path)).await?;
|
||||
let std_file = asyncify(move || StdFile::create(path)).await?;
|
||||
Ok(File::from_std(std_file))
|
||||
}
|
||||
|
||||
@@ -199,7 +212,7 @@ impl File {
|
||||
/// let std_file = std::fs::File::open("foo.txt").unwrap();
|
||||
/// let file = tokio::fs::File::from_std(std_file);
|
||||
/// ```
|
||||
pub fn from_std(std: sys::File) -> File {
|
||||
pub fn from_std(std: StdFile) -> File {
|
||||
File {
|
||||
std: Arc::new(std),
|
||||
inner: Mutex::new(Inner {
|
||||
@@ -323,7 +336,7 @@ impl File {
|
||||
|
||||
let std = self.std.clone();
|
||||
|
||||
inner.state = Busy(sys::run(move || {
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
let res = if let Some(seek) = seek {
|
||||
(&*std).seek(seek).and_then(|_| std.set_len(size))
|
||||
} else {
|
||||
@@ -370,7 +383,7 @@ impl File {
|
||||
asyncify(move || std.metadata()).await
|
||||
}
|
||||
|
||||
/// Create a new `File` instance that shares the same underlying file handle
|
||||
/// Creates a new `File` instance that shares the same underlying file handle
|
||||
/// as the existing `File` instance. Reads, writes, and seeks will affect both
|
||||
/// File instances simultaneously.
|
||||
///
|
||||
@@ -409,7 +422,7 @@ impl File {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn into_std(mut self) -> sys::File {
|
||||
pub async fn into_std(mut self) -> StdFile {
|
||||
self.inner.get_mut().complete_inflight().await;
|
||||
Arc::try_unwrap(self.std).expect("Arc::try_unwrap failed")
|
||||
}
|
||||
@@ -434,7 +447,7 @@ impl File {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn try_into_std(mut self) -> Result<sys::File, Self> {
|
||||
pub fn try_into_std(mut self) -> Result<StdFile, Self> {
|
||||
match Arc::try_unwrap(self.std) {
|
||||
Ok(file) => Ok(file),
|
||||
Err(std_file_arc) => {
|
||||
@@ -502,7 +515,7 @@ impl AsyncRead for File {
|
||||
buf.ensure_capacity_for(dst);
|
||||
let std = me.std.clone();
|
||||
|
||||
inner.state = Busy(sys::run(move || {
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
let res = buf.read_from(&mut &*std);
|
||||
(Operation::Read(res), buf)
|
||||
}));
|
||||
@@ -569,7 +582,7 @@ impl AsyncSeek for File {
|
||||
|
||||
let std = me.std.clone();
|
||||
|
||||
inner.state = Busy(sys::run(move || {
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
let res = (&*std).seek(pos);
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
@@ -636,7 +649,7 @@ impl AsyncWrite for File {
|
||||
let n = buf.copy_from(src);
|
||||
let std = me.std.clone();
|
||||
|
||||
inner.state = Busy(sys::run(move || {
|
||||
let blocking_task_join_handle = spawn_mandatory_blocking(move || {
|
||||
let res = if let Some(seek) = seek {
|
||||
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
|
||||
} else {
|
||||
@@ -644,7 +657,12 @@ impl AsyncWrite for File {
|
||||
};
|
||||
|
||||
(Operation::Write(res), buf)
|
||||
}));
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "background task failed")
|
||||
})?;
|
||||
|
||||
inner.state = Busy(blocking_task_join_handle);
|
||||
|
||||
return Ready(Ok(n));
|
||||
}
|
||||
@@ -685,8 +703,8 @@ impl AsyncWrite for File {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sys::File> for File {
|
||||
fn from(std: sys::File) -> Self {
|
||||
impl From<StdFile> for File {
|
||||
fn from(std: StdFile) -> Self {
|
||||
Self::from_std(std)
|
||||
}
|
||||
}
|
||||
@@ -709,7 +727,7 @@ impl std::os::unix::io::AsRawFd for File {
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::FromRawFd for File {
|
||||
unsafe fn from_raw_fd(fd: std::os::unix::io::RawFd) -> Self {
|
||||
sys::File::from_raw_fd(fd).into()
|
||||
StdFile::from_raw_fd(fd).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,7 +741,7 @@ impl std::os::windows::io::AsRawHandle for File {
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::FromRawHandle for File {
|
||||
unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
|
||||
sys::File::from_raw_handle(handle).into()
|
||||
StdFile::from_raw_handle(handle).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,3 +774,6 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -1,80 +1,21 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
macro_rules! ready {
|
||||
($e:expr $(,)?) => {
|
||||
match $e {
|
||||
std::task::Poll::Ready(t) => t,
|
||||
std::task::Poll::Pending => return std::task::Poll::Pending,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cfg_fs {
|
||||
($($item:item)*) => { $($item)* }
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cfg_io_std {
|
||||
($($item:item)*) => { $($item)* }
|
||||
}
|
||||
|
||||
use futures::future;
|
||||
|
||||
// Load source
|
||||
#[allow(warnings)]
|
||||
#[path = "../src/fs/file.rs"]
|
||||
mod file;
|
||||
use file::File;
|
||||
|
||||
#[allow(warnings)]
|
||||
#[path = "../src/io/blocking.rs"]
|
||||
mod blocking;
|
||||
|
||||
// Load mocked types
|
||||
mod support {
|
||||
pub(crate) mod mock_file;
|
||||
pub(crate) mod mock_pool;
|
||||
}
|
||||
pub(crate) use support::mock_pool as pool;
|
||||
|
||||
// Place them where the source expects them
|
||||
pub(crate) mod io {
|
||||
pub(crate) use tokio::io::*;
|
||||
|
||||
pub(crate) use crate::blocking;
|
||||
|
||||
pub(crate) mod sys {
|
||||
pub(crate) use crate::support::mock_pool::{run, Blocking};
|
||||
}
|
||||
}
|
||||
pub(crate) mod fs {
|
||||
pub(crate) mod sys {
|
||||
pub(crate) use crate::support::mock_file::File;
|
||||
pub(crate) use crate::support::mock_pool::{run, Blocking};
|
||||
}
|
||||
|
||||
pub(crate) use crate::support::mock_pool::asyncify;
|
||||
}
|
||||
pub(crate) mod sync {
|
||||
pub(crate) use tokio::sync::Mutex;
|
||||
}
|
||||
use fs::sys;
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok, task};
|
||||
|
||||
use std::io::SeekFrom;
|
||||
use super::*;
|
||||
use crate::{
|
||||
fs::mocks::*,
|
||||
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt},
|
||||
};
|
||||
use mockall::{predicate::eq, Sequence};
|
||||
use tokio_test::{assert_pending, assert_ready_err, assert_ready_ok, task};
|
||||
|
||||
const HELLO: &[u8] = b"hello world...";
|
||||
const FOO: &[u8] = b"foo bar baz...";
|
||||
|
||||
#[test]
|
||||
fn open_read() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(HELLO);
|
||||
|
||||
let mut file = MockFile::default();
|
||||
file.expect_inner_read().once().returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
let mut buf = [0; 1024];
|
||||
@@ -83,12 +24,10 @@ fn open_read() {
|
||||
assert_eq!(0, pool::len());
|
||||
assert_pending!(t.poll());
|
||||
|
||||
assert_eq!(1, mock.remaining());
|
||||
assert_eq!(1, pool::len());
|
||||
|
||||
pool::run_one();
|
||||
|
||||
assert_eq!(0, mock.remaining());
|
||||
assert!(t.is_woken());
|
||||
|
||||
let n = assert_ready_ok!(t.poll());
|
||||
@@ -98,9 +37,11 @@ fn open_read() {
|
||||
|
||||
#[test]
|
||||
fn read_twice_before_dispatch() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(HELLO);
|
||||
|
||||
let mut file = MockFile::default();
|
||||
file.expect_inner_read().once().returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
let mut buf = [0; 1024];
|
||||
@@ -120,8 +61,11 @@ fn read_twice_before_dispatch() {
|
||||
|
||||
#[test]
|
||||
fn read_with_smaller_buf() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(HELLO);
|
||||
let mut file = MockFile::default();
|
||||
file.expect_inner_read().once().returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -153,8 +97,22 @@ fn read_with_smaller_buf() {
|
||||
|
||||
#[test]
|
||||
fn read_with_bigger_buf() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(&HELLO[..4]).read(&HELLO[4..]);
|
||||
let mut seq = Sequence::new();
|
||||
let mut file = MockFile::default();
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..4].copy_from_slice(&HELLO[..4]);
|
||||
Ok(4)
|
||||
});
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..HELLO.len() - 4].copy_from_slice(&HELLO[4..]);
|
||||
Ok(HELLO.len() - 4)
|
||||
});
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -194,8 +152,19 @@ fn read_with_bigger_buf() {
|
||||
|
||||
#[test]
|
||||
fn read_err_then_read_success() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read_err().read(&HELLO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Err(io::ErrorKind::Other.into()));
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -225,8 +194,11 @@ fn read_err_then_read_success() {
|
||||
|
||||
#[test]
|
||||
fn open_write() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write(HELLO);
|
||||
let mut file = MockFile::default();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.with(eq(HELLO))
|
||||
.returning(|buf| Ok(buf.len()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -235,12 +207,10 @@ fn open_write() {
|
||||
assert_eq!(0, pool::len());
|
||||
assert_ready_ok!(t.poll());
|
||||
|
||||
assert_eq!(1, mock.remaining());
|
||||
assert_eq!(1, pool::len());
|
||||
|
||||
pool::run_one();
|
||||
|
||||
assert_eq!(0, mock.remaining());
|
||||
assert!(!t.is_woken());
|
||||
|
||||
let mut t = task::spawn(file.flush());
|
||||
@@ -249,7 +219,7 @@ fn open_write() {
|
||||
|
||||
#[test]
|
||||
fn flush_while_idle() {
|
||||
let (_mock, file) = sys::File::mock();
|
||||
let file = MockFile::default();
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -271,13 +241,42 @@ fn read_with_buffer_larger_than_max() {
|
||||
for i in 0..(chunk_d - 1) {
|
||||
data.push((i % 151) as u8);
|
||||
}
|
||||
let data = Arc::new(data);
|
||||
let d0 = data.clone();
|
||||
let d1 = data.clone();
|
||||
let d2 = data.clone();
|
||||
let d3 = data.clone();
|
||||
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(&data[0..chunk_a])
|
||||
.read(&data[chunk_a..chunk_b])
|
||||
.read(&data[chunk_b..chunk_c])
|
||||
.read(&data[chunk_c..]);
|
||||
|
||||
let mut seq = Sequence::new();
|
||||
let mut file = MockFile::default();
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(move |buf| {
|
||||
buf[0..chunk_a].copy_from_slice(&d0[0..chunk_a]);
|
||||
Ok(chunk_a)
|
||||
});
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(move |buf| {
|
||||
buf[..chunk_a].copy_from_slice(&d1[chunk_a..chunk_b]);
|
||||
Ok(chunk_b - chunk_a)
|
||||
});
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(move |buf| {
|
||||
buf[..chunk_a].copy_from_slice(&d2[chunk_b..chunk_c]);
|
||||
Ok(chunk_c - chunk_b)
|
||||
});
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(move |buf| {
|
||||
buf[..chunk_a - 1].copy_from_slice(&d3[chunk_c..]);
|
||||
Ok(chunk_a - 1)
|
||||
});
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
let mut actual = vec![0; chunk_d];
|
||||
@@ -296,8 +295,7 @@ fn read_with_buffer_larger_than_max() {
|
||||
pos += n;
|
||||
}
|
||||
|
||||
assert_eq!(mock.remaining(), 0);
|
||||
assert_eq!(data, &actual[..data.len()]);
|
||||
assert_eq!(&data[..], &actual[..data.len()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -314,12 +312,34 @@ fn write_with_buffer_larger_than_max() {
|
||||
for i in 0..(chunk_d - 1) {
|
||||
data.push((i % 151) as u8);
|
||||
}
|
||||
let data = Arc::new(data);
|
||||
let d0 = data.clone();
|
||||
let d1 = data.clone();
|
||||
let d2 = data.clone();
|
||||
let d3 = data.clone();
|
||||
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write(&data[0..chunk_a])
|
||||
.write(&data[chunk_a..chunk_b])
|
||||
.write(&data[chunk_b..chunk_c])
|
||||
.write(&data[chunk_c..]);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.withf(move |buf| buf == &d0[0..chunk_a])
|
||||
.returning(|buf| Ok(buf.len()));
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.withf(move |buf| buf == &d1[chunk_a..chunk_b])
|
||||
.returning(|buf| Ok(buf.len()));
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.withf(move |buf| buf == &d2[chunk_b..chunk_c])
|
||||
.returning(|buf| Ok(buf.len()));
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.withf(move |buf| buf == &d3[chunk_c..chunk_d - 1])
|
||||
.returning(|buf| Ok(buf.len()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -344,14 +364,22 @@ fn write_with_buffer_larger_than_max() {
|
||||
}
|
||||
|
||||
pool::run_one();
|
||||
|
||||
assert_eq!(mock.remaining(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_twice_before_dispatch() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write(HELLO).write(FOO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(HELLO))
|
||||
.returning(|buf| Ok(buf.len()));
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(FOO))
|
||||
.returning(|buf| Ok(buf.len()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -380,10 +408,24 @@ fn write_twice_before_dispatch() {
|
||||
|
||||
#[test]
|
||||
fn incomplete_read_followed_by_write() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(HELLO)
|
||||
.seek_current_ok(-(HELLO.len() as i64), 0)
|
||||
.write(FOO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
file.expect_inner_seek()
|
||||
.once()
|
||||
.with(eq(SeekFrom::Current(-(HELLO.len() as i64))))
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Ok(0));
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.with(eq(FOO))
|
||||
.returning(|_| Ok(FOO.len()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -406,8 +448,25 @@ fn incomplete_read_followed_by_write() {
|
||||
|
||||
#[test]
|
||||
fn incomplete_partial_read_followed_by_write() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(HELLO).seek_current_ok(-10, 0).write(FOO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
file.expect_inner_seek()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(SeekFrom::Current(-10)))
|
||||
.returning(|_| Ok(0));
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(FOO))
|
||||
.returning(|_| Ok(FOO.len()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -433,10 +492,25 @@ fn incomplete_partial_read_followed_by_write() {
|
||||
|
||||
#[test]
|
||||
fn incomplete_read_followed_by_flush() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(HELLO)
|
||||
.seek_current_ok(-(HELLO.len() as i64), 0)
|
||||
.write(FOO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
file.expect_inner_seek()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(SeekFrom::Current(-(HELLO.len() as i64))))
|
||||
.returning(|_| Ok(0));
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(FOO))
|
||||
.returning(|_| Ok(FOO.len()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -458,8 +532,18 @@ fn incomplete_read_followed_by_flush() {
|
||||
|
||||
#[test]
|
||||
fn incomplete_flush_followed_by_write() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write(HELLO).write(FOO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(HELLO))
|
||||
.returning(|_| Ok(HELLO.len()));
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(FOO))
|
||||
.returning(|_| Ok(FOO.len()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -484,8 +568,10 @@ fn incomplete_flush_followed_by_write() {
|
||||
|
||||
#[test]
|
||||
fn read_err() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read_err();
|
||||
let mut file = MockFile::default();
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.returning(|_| Err(io::ErrorKind::Other.into()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -502,8 +588,10 @@ fn read_err() {
|
||||
|
||||
#[test]
|
||||
fn write_write_err() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write_err();
|
||||
let mut file = MockFile::default();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.returning(|_| Err(io::ErrorKind::Other.into()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -518,8 +606,19 @@ fn write_write_err() {
|
||||
|
||||
#[test]
|
||||
fn write_read_write_err() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write_err().read(HELLO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Err(io::ErrorKind::Other.into()));
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -541,8 +640,19 @@ fn write_read_write_err() {
|
||||
|
||||
#[test]
|
||||
fn write_read_flush_err() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write_err().read(HELLO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Err(io::ErrorKind::Other.into()));
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -564,8 +674,17 @@ fn write_read_flush_err() {
|
||||
|
||||
#[test]
|
||||
fn write_seek_write_err() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write_err().seek_start_ok(0);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Err(io::ErrorKind::Other.into()));
|
||||
file.expect_inner_seek()
|
||||
.once()
|
||||
.with(eq(SeekFrom::Start(0)))
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Ok(0));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -587,8 +706,17 @@ fn write_seek_write_err() {
|
||||
|
||||
#[test]
|
||||
fn write_seek_flush_err() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write_err().seek_start_ok(0);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Err(io::ErrorKind::Other.into()));
|
||||
file.expect_inner_seek()
|
||||
.once()
|
||||
.with(eq(SeekFrom::Start(0)))
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Ok(0));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
|
||||
@@ -610,8 +738,14 @@ fn write_seek_flush_err() {
|
||||
|
||||
#[test]
|
||||
fn sync_all_ordered_after_write() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write(HELLO).sync_all();
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(HELLO))
|
||||
.returning(|_| Ok(HELLO.len()));
|
||||
file.expect_sync_all().once().returning(|| Ok(()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
let mut t = task::spawn(file.write(HELLO));
|
||||
@@ -635,8 +769,16 @@ fn sync_all_ordered_after_write() {
|
||||
|
||||
#[test]
|
||||
fn sync_all_err_ordered_after_write() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write(HELLO).sync_all_err();
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(HELLO))
|
||||
.returning(|_| Ok(HELLO.len()));
|
||||
file.expect_sync_all()
|
||||
.once()
|
||||
.returning(|| Err(io::ErrorKind::Other.into()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
let mut t = task::spawn(file.write(HELLO));
|
||||
@@ -660,8 +802,14 @@ fn sync_all_err_ordered_after_write() {
|
||||
|
||||
#[test]
|
||||
fn sync_data_ordered_after_write() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write(HELLO).sync_data();
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(HELLO))
|
||||
.returning(|_| Ok(HELLO.len()));
|
||||
file.expect_sync_data().once().returning(|| Ok(()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
let mut t = task::spawn(file.write(HELLO));
|
||||
@@ -685,8 +833,16 @@ fn sync_data_ordered_after_write() {
|
||||
|
||||
#[test]
|
||||
fn sync_data_err_ordered_after_write() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.write(HELLO).sync_data_err();
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_write()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(HELLO))
|
||||
.returning(|_| Ok(HELLO.len()));
|
||||
file.expect_sync_data()
|
||||
.once()
|
||||
.returning(|| Err(io::ErrorKind::Other.into()));
|
||||
|
||||
let mut file = File::from_std(file);
|
||||
let mut t = task::spawn(file.write(HELLO));
|
||||
@@ -710,17 +866,15 @@ fn sync_data_err_ordered_after_write() {
|
||||
|
||||
#[test]
|
||||
fn open_set_len_ok() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.set_len(123);
|
||||
let mut file = MockFile::default();
|
||||
file.expect_set_len().with(eq(123)).returning(|_| Ok(()));
|
||||
|
||||
let file = File::from_std(file);
|
||||
let mut t = task::spawn(file.set_len(123));
|
||||
|
||||
assert_pending!(t.poll());
|
||||
assert_eq!(1, mock.remaining());
|
||||
|
||||
pool::run_one();
|
||||
assert_eq!(0, mock.remaining());
|
||||
|
||||
assert!(t.is_woken());
|
||||
assert_ready_ok!(t.poll());
|
||||
@@ -728,17 +882,17 @@ fn open_set_len_ok() {
|
||||
|
||||
#[test]
|
||||
fn open_set_len_err() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.set_len_err(123);
|
||||
let mut file = MockFile::default();
|
||||
file.expect_set_len()
|
||||
.with(eq(123))
|
||||
.returning(|_| Err(io::ErrorKind::Other.into()));
|
||||
|
||||
let file = File::from_std(file);
|
||||
let mut t = task::spawn(file.set_len(123));
|
||||
|
||||
assert_pending!(t.poll());
|
||||
assert_eq!(1, mock.remaining());
|
||||
|
||||
pool::run_one();
|
||||
assert_eq!(0, mock.remaining());
|
||||
|
||||
assert!(t.is_woken());
|
||||
assert_ready_err!(t.poll());
|
||||
@@ -746,11 +900,32 @@ fn open_set_len_err() {
|
||||
|
||||
#[test]
|
||||
fn partial_read_set_len_ok() {
|
||||
let (mock, file) = sys::File::mock();
|
||||
mock.read(HELLO)
|
||||
.seek_current_ok(-14, 0)
|
||||
.set_len(123)
|
||||
.read(FOO);
|
||||
let mut file = MockFile::default();
|
||||
let mut seq = Sequence::new();
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..HELLO.len()].copy_from_slice(HELLO);
|
||||
Ok(HELLO.len())
|
||||
});
|
||||
file.expect_inner_seek()
|
||||
.once()
|
||||
.with(eq(SeekFrom::Current(-(HELLO.len() as i64))))
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|_| Ok(0));
|
||||
file.expect_set_len()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.with(eq(123))
|
||||
.returning(|_| Ok(()));
|
||||
file.expect_inner_read()
|
||||
.once()
|
||||
.in_sequence(&mut seq)
|
||||
.returning(|buf| {
|
||||
buf[0..FOO.len()].copy_from_slice(FOO);
|
||||
Ok(FOO.len())
|
||||
});
|
||||
|
||||
let mut buf = [0; 32];
|
||||
let mut file = File::from_std(file);
|
||||
@@ -0,0 +1,151 @@
|
||||
//! Mock version of std::fs::File;
|
||||
use mockall::mock;
|
||||
|
||||
use crate::sync::oneshot;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::VecDeque,
|
||||
fs::{Metadata, Permissions},
|
||||
future::Future,
|
||||
io::{self, Read, Seek, SeekFrom, Write},
|
||||
path::PathBuf,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
mock! {
|
||||
#[derive(Debug)]
|
||||
pub File {
|
||||
pub fn create(pb: PathBuf) -> io::Result<Self>;
|
||||
// These inner_ methods exist because std::fs::File has two
|
||||
// implementations for each of these methods: one on "&mut self" and
|
||||
// one on "&&self". Defining both of those in terms of an inner_ method
|
||||
// allows us to specify the expectation the same way, regardless of
|
||||
// which method is used.
|
||||
pub fn inner_flush(&self) -> io::Result<()>;
|
||||
pub fn inner_read(&self, dst: &mut [u8]) -> io::Result<usize>;
|
||||
pub fn inner_seek(&self, pos: SeekFrom) -> io::Result<u64>;
|
||||
pub fn inner_write(&self, src: &[u8]) -> io::Result<usize>;
|
||||
pub fn metadata(&self) -> io::Result<Metadata>;
|
||||
pub fn open(pb: PathBuf) -> io::Result<Self>;
|
||||
pub fn set_len(&self, size: u64) -> io::Result<()>;
|
||||
pub fn set_permissions(&self, _perm: Permissions) -> io::Result<()>;
|
||||
pub fn sync_all(&self) -> io::Result<()>;
|
||||
pub fn sync_data(&self) -> io::Result<()>;
|
||||
pub fn try_clone(&self) -> io::Result<Self>;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::AsRawHandle for File {
|
||||
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::FromRawHandle for File {
|
||||
unsafe fn from_raw_handle(h: std::os::windows::io::RawHandle) -> Self;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::AsRawFd for File {
|
||||
fn as_raw_fd(&self) -> std::os::unix::io::RawFd;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::FromRawFd for File {
|
||||
unsafe fn from_raw_fd(h: std::os::unix::io::RawFd) -> Self;
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for MockFile {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
self.inner_read(dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for &'_ MockFile {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
self.inner_read(dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for &'_ MockFile {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
self.inner_seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for &'_ MockFile {
|
||||
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
|
||||
self.inner_write(src)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.inner_flush()
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static QUEUE: RefCell<VecDeque<Box<dyn FnOnce() + Send>>> = RefCell::new(VecDeque::new())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct JoinHandle<T> {
|
||||
rx: oneshot::Receiver<T>,
|
||||
}
|
||||
|
||||
pub(super) fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let task = Box::new(move || {
|
||||
let _ = tx.send(f());
|
||||
});
|
||||
|
||||
QUEUE.with(|cell| cell.borrow_mut().push_back(task));
|
||||
|
||||
JoinHandle { rx }
|
||||
}
|
||||
|
||||
pub(super) fn spawn_mandatory_blocking<F, R>(f: F) -> Option<JoinHandle<R>>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let task = Box::new(move || {
|
||||
let _ = tx.send(f());
|
||||
});
|
||||
|
||||
QUEUE.with(|cell| cell.borrow_mut().push_back(task));
|
||||
|
||||
Some(JoinHandle { rx })
|
||||
}
|
||||
|
||||
impl<T> Future for JoinHandle<T> {
|
||||
type Output = Result<T, io::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
use std::task::Poll::*;
|
||||
|
||||
match Pin::new(&mut self.rx).poll(cx) {
|
||||
Ready(Ok(v)) => Ready(Ok(v)),
|
||||
Ready(Err(e)) => panic!("error = {:?}", e),
|
||||
Pending => Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) mod pool {
|
||||
use super::*;
|
||||
|
||||
pub(in super::super) fn len() -> usize {
|
||||
QUEUE.with(|cell| cell.borrow().len())
|
||||
}
|
||||
|
||||
pub(in super::super) fn run_one() {
|
||||
let task = QUEUE
|
||||
.with(|cell| cell.borrow_mut().pop_front())
|
||||
.expect("expected task to run, but none ready");
|
||||
|
||||
task();
|
||||
}
|
||||
}
|
||||
+9
-10
@@ -84,6 +84,9 @@ pub use self::write::write;
|
||||
mod copy;
|
||||
pub use self::copy::copy;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mocks;
|
||||
|
||||
feature! {
|
||||
#![unix]
|
||||
|
||||
@@ -103,12 +106,17 @@ feature! {
|
||||
|
||||
use std::io;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use crate::blocking::spawn_blocking;
|
||||
#[cfg(test)]
|
||||
use mocks::spawn_blocking;
|
||||
|
||||
pub(crate) async fn asyncify<F, T>(f: F) -> io::Result<T>
|
||||
where
|
||||
F: FnOnce() -> io::Result<T> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
match sys::run(f).await {
|
||||
match spawn_blocking(f).await {
|
||||
Ok(res) => res,
|
||||
Err(_) => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@@ -116,12 +124,3 @@ where
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Types in this module can be mocked out in tests.
|
||||
mod sys {
|
||||
pub(crate) use std::fs::File;
|
||||
|
||||
// TODO: don't rename
|
||||
pub(crate) use crate::blocking::spawn_blocking as run;
|
||||
pub(crate) use crate::blocking::JoinHandle as Blocking;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@ use crate::fs::{asyncify, File};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock_open_options;
|
||||
#[cfg(test)]
|
||||
use mock_open_options::MockOpenOptions as StdOpenOptions;
|
||||
#[cfg(not(test))]
|
||||
use std::fs::OpenOptions as StdOpenOptions;
|
||||
|
||||
/// Options and flags which can be used to configure how a file is opened.
|
||||
///
|
||||
/// This builder exposes the ability to configure how a [`File`] is opened and
|
||||
@@ -69,7 +76,7 @@ use std::path::Path;
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OpenOptions(std::fs::OpenOptions);
|
||||
pub struct OpenOptions(StdOpenOptions);
|
||||
|
||||
impl OpenOptions {
|
||||
/// Creates a blank new set of options ready for configuration.
|
||||
@@ -89,7 +96,7 @@ impl OpenOptions {
|
||||
/// let future = options.read(true).open("foo.txt");
|
||||
/// ```
|
||||
pub fn new() -> OpenOptions {
|
||||
OpenOptions(std::fs::OpenOptions::new())
|
||||
OpenOptions(StdOpenOptions::new())
|
||||
}
|
||||
|
||||
/// Sets the option for read access.
|
||||
@@ -384,7 +391,7 @@ impl OpenOptions {
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying `std::fs::OpenOptions`
|
||||
pub(super) fn as_inner_mut(&mut self) -> &mut std::fs::OpenOptions {
|
||||
pub(super) fn as_inner_mut(&mut self) -> &mut StdOpenOptions {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
@@ -423,7 +430,7 @@ feature! {
|
||||
self
|
||||
}
|
||||
|
||||
/// Pass custom flags to the `flags` argument of `open`.
|
||||
/// Passes custom flags to the `flags` argument of `open`.
|
||||
///
|
||||
/// The bits that define the access mode are masked out with `O_ACCMODE`, to
|
||||
/// ensure they do not interfere with the access mode set by Rusts options.
|
||||
@@ -645,8 +652,8 @@ feature! {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::fs::OpenOptions> for OpenOptions {
|
||||
fn from(options: std::fs::OpenOptions) -> OpenOptions {
|
||||
impl From<StdOpenOptions> for OpenOptions {
|
||||
fn from(options: StdOpenOptions) -> OpenOptions {
|
||||
OpenOptions(options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//! Mock version of std::fs::OpenOptions;
|
||||
use mockall::mock;
|
||||
|
||||
use crate::fs::mocks::MockFile;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
use std::{io, path::Path};
|
||||
|
||||
mock! {
|
||||
#[derive(Debug)]
|
||||
pub OpenOptions {
|
||||
pub fn append(&mut self, append: bool) -> &mut Self;
|
||||
pub fn create(&mut self, create: bool) -> &mut Self;
|
||||
pub fn create_new(&mut self, create_new: bool) -> &mut Self;
|
||||
pub fn open<P: AsRef<Path> + 'static>(&self, path: P) -> io::Result<MockFile>;
|
||||
pub fn read(&mut self, read: bool) -> &mut Self;
|
||||
pub fn truncate(&mut self, truncate: bool) -> &mut Self;
|
||||
pub fn write(&mut self, write: bool) -> &mut Self;
|
||||
}
|
||||
impl Clone for OpenOptions {
|
||||
fn clone(&self) -> Self;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
impl OpenOptionsExt for OpenOptions {
|
||||
fn custom_flags(&mut self, flags: i32) -> &mut Self;
|
||||
fn mode(&mut self, mode: u32) -> &mut Self;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
impl OpenOptionsExt for OpenOptions {
|
||||
fn access_mode(&mut self, access: u32) -> &mut Self;
|
||||
fn share_mode(&mut self, val: u32) -> &mut Self;
|
||||
fn custom_flags(&mut self, flags: u32) -> &mut Self;
|
||||
fn attributes(&mut self, val: u32) -> &mut Self;
|
||||
fn security_qos_flags(&mut self, flags: u32) -> &mut Self;
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,12 @@ use std::{io, path::Path};
|
||||
/// buffer based on the file size when available, so it is generally faster than
|
||||
/// reading into a vector created with `Vec::new()`.
|
||||
///
|
||||
/// This operation is implemented by running the equivalent blocking operation
|
||||
/// on a separate thread pool using [`spawn_blocking`].
|
||||
///
|
||||
/// [`File::open`]: super::File::open
|
||||
/// [`read_to_end`]: crate::io::AsyncReadExt::read_to_end
|
||||
/// [`spawn_blocking`]: crate::task::spawn_blocking
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::fs::{asyncify, sys};
|
||||
use crate::fs::asyncify;
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{FileType, Metadata};
|
||||
@@ -10,9 +10,23 @@ use std::sync::Arc;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
|
||||
#[cfg(test)]
|
||||
use super::mocks::spawn_blocking;
|
||||
#[cfg(test)]
|
||||
use super::mocks::JoinHandle;
|
||||
#[cfg(not(test))]
|
||||
use crate::blocking::spawn_blocking;
|
||||
#[cfg(not(test))]
|
||||
use crate::blocking::JoinHandle;
|
||||
|
||||
/// Returns a stream over the entries within a directory.
|
||||
///
|
||||
/// This is an async version of [`std::fs::read_dir`](std::fs::read_dir)
|
||||
///
|
||||
/// This operation is implemented by running the equivalent blocking
|
||||
/// operation on a separate thread pool using [`spawn_blocking`].
|
||||
///
|
||||
/// [`spawn_blocking`]: crate::task::spawn_blocking
|
||||
pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref().to_owned();
|
||||
let std = asyncify(|| std::fs::read_dir(path)).await?;
|
||||
@@ -20,7 +34,7 @@ pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
|
||||
Ok(ReadDir(State::Idle(Some(std))))
|
||||
}
|
||||
|
||||
/// Read the the entries in a directory.
|
||||
/// Reads the the entries in a directory.
|
||||
///
|
||||
/// This struct is returned from the [`read_dir`] function of this module and
|
||||
/// will yield instances of [`DirEntry`]. Through a [`DirEntry`] information
|
||||
@@ -45,11 +59,15 @@ pub struct ReadDir(State);
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Idle(Option<std::fs::ReadDir>),
|
||||
Pending(sys::Blocking<(Option<io::Result<std::fs::DirEntry>>, std::fs::ReadDir)>),
|
||||
Pending(JoinHandle<(Option<io::Result<std::fs::DirEntry>>, std::fs::ReadDir)>),
|
||||
}
|
||||
|
||||
impl ReadDir {
|
||||
/// Returns the next entry in the directory stream.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancellation safe.
|
||||
pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
|
||||
use crate::future::poll_fn;
|
||||
poll_fn(|cx| self.poll_next_entry(cx)).await
|
||||
@@ -79,7 +97,7 @@ impl ReadDir {
|
||||
State::Idle(ref mut std) => {
|
||||
let mut std = std.take().unwrap();
|
||||
|
||||
self.0 = State::Pending(sys::run(move || {
|
||||
self.0 = State::Pending(spawn_blocking(move || {
|
||||
let ret = std.next();
|
||||
(ret, std)
|
||||
}));
|
||||
@@ -269,7 +287,7 @@ impl DirEntry {
|
||||
asyncify(move || std.file_type()).await
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying `std::fs::DirEntry`
|
||||
/// Returns a reference to the underlying `std::fs::DirEntry`.
|
||||
#[cfg(unix)]
|
||||
pub(super) fn as_inner(&self) -> &std::fs::DirEntry {
|
||||
&self.0
|
||||
|
||||
@@ -7,6 +7,10 @@ use std::{io, path::Path};
|
||||
///
|
||||
/// This is the async equivalent of [`std::fs::read_to_string`][std].
|
||||
///
|
||||
/// This operation is implemented by running the equivalent blocking operation
|
||||
/// on a separate thread pool using [`spawn_blocking`].
|
||||
///
|
||||
/// [`spawn_blocking`]: crate::task::spawn_blocking
|
||||
/// [std]: fn@std::fs::read_to_string
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user