mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
312
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 | ||
|
|
afb734d189 | ||
|
|
2b731c0e01 | ||
|
|
8f27c04a9e | ||
|
|
c85a0e524e | ||
|
|
1eb468be08 | ||
|
|
378409d15d | ||
|
|
51ff95c144 | ||
|
|
df10b68d47 | ||
|
|
b280c6dcd7 | ||
|
|
998dc5a2eb | ||
|
|
ced7992f65 | ||
|
|
0cefa85bfa | ||
|
|
2087f3e0eb | ||
|
|
5ae2855eaf | ||
|
|
252004811f | ||
|
|
549e89e9cd | ||
|
|
c8fc492748 | ||
|
|
7147042534 | ||
|
|
634fcac2d9 | ||
|
|
7c4183a45d | ||
|
|
aef2d64b0a | ||
|
|
8f10d81613 | ||
|
|
7a11cfd77a | ||
|
|
6610ba9bd6 | ||
|
|
3fd88dac64 | ||
|
|
ccd495647d | ||
|
|
80d8d40a34 | ||
|
|
3b38ebd7f5 | ||
|
|
127983e5b4 | ||
|
|
c306bf853a | ||
|
|
c6fbb9aeb1 | ||
|
|
5d61c997e9 | ||
|
|
c505a2f81a | ||
|
|
e2589a0e40 | ||
|
|
51fad066e2 | ||
|
|
ae233c1e9f | ||
|
|
be26ca7625 | ||
|
|
2d41f0303e | ||
|
|
e0c527f383 | ||
|
|
1160e8864c | ||
|
|
998a125717 | ||
|
|
cbfdc9d69e | ||
|
|
08337c5f79 | ||
|
|
87f4969fbc | ||
|
|
e385108920 | ||
|
|
4818c2ed05 | ||
|
|
80f0801e19 | ||
|
|
8148b2107c | ||
|
|
2e7de1ae1d | ||
|
|
37e60fc7f9 | ||
|
|
8170e2787c | ||
|
|
677107d8d9 | ||
|
|
c8ecfc894d | ||
|
|
08ed41f339 | ||
|
|
90e1935c48 | ||
|
|
b877629cb1 | ||
|
|
0531549b6e | ||
|
|
38204f5fba | ||
|
|
8fa29cb00a | ||
|
|
b521cc2689 | ||
|
|
57c90c9750 | ||
|
|
d35ff7064f | ||
|
|
ab0791b817 | ||
|
|
959c5c997f | ||
|
|
845626410a | ||
|
|
3207479fa7 | ||
|
|
7601dc6d2a | ||
|
|
d1aa2df80e | ||
|
|
e979ad7f2d | ||
|
|
5ad3dd3378 | ||
|
|
60bd40d529 | ||
|
|
4a93af4d25 | ||
|
|
34c6a26c01 | ||
|
|
97e7830364 | ||
|
|
606206ecad | ||
|
|
dfe4013ff2 | ||
|
|
18779aa2e2 | ||
|
|
2c24a028f6 | ||
|
|
f55b77aadd | ||
|
|
21de476ae7 | ||
|
|
cb147a2b3f | ||
|
|
f759240254 | ||
|
|
2e44cd29df | ||
|
|
2ab1fb00a9 | ||
|
|
d16e50639a | ||
|
|
e7d74b3119 | ||
|
|
d101feac50 | ||
|
|
9d8b37d51a | ||
|
|
d4c89758fc | ||
|
|
932be12481 | ||
|
|
bdd6765016 | ||
|
|
8f6d8b25bf | ||
|
|
eb7aee980c | ||
|
|
1baea398c4 | ||
|
|
3fbcf1ba50 | ||
|
|
21264f1d33 | ||
|
|
81ee3d202a | ||
|
|
a39e6c2439 | ||
|
|
4abeca7bc5 | ||
|
|
5ed84e1cd8 | ||
|
|
44a070d1b4 | ||
|
|
ce9ca45c92 | ||
|
|
f3ed064a26 | ||
|
|
652f0ae728 | ||
|
|
ff9b0ef7ca | ||
|
|
aaa150d211 | ||
|
|
deb1f98125 |
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
msrv = "1.46"
|
||||
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [tokio-rs]
|
||||
@@ -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
|
||||
+124
-30
@@ -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,10 +150,11 @@ 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
|
||||
run: cargo test --features full
|
||||
run: cargo test --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
@@ -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"
|
||||
@@ -238,7 +275,17 @@ jobs:
|
||||
cargo hack --remove-dev-deps --workspace
|
||||
# Update Cargo.lock to minimal version dependencies.
|
||||
cargo update -Z minimal-versions
|
||||
cargo check --all-features
|
||||
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 ${{ env.minrust }} && rustup default ${{ env.minrust }}
|
||||
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.5.0", features = ["full"] }
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -66,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?;
|
||||
@@ -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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Benchmark implementation details of the theaded scheduler. These benches are
|
||||
//! Benchmark implementation details of the threaded scheduler. These benches are
|
||||
//! intended to be used as a form of regression testing and not as a general
|
||||
//! purpose benchmark demonstrating real-world performance.
|
||||
|
||||
|
||||
+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);
|
||||
|
||||
+20
-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,9 +20,12 @@ 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"
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.winapi]
|
||||
version = "0.3.8"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
@@ -76,3 +79,15 @@ path = "custom-executor.rs"
|
||||
[[example]]
|
||||
name = "custom-executor-tokio-context"
|
||||
path = "custom-executor-tokio-context.rs"
|
||||
|
||||
[[example]]
|
||||
name = "named-pipe"
|
||||
path = "named-pipe.rs"
|
||||
|
||||
[[example]]
|
||||
name = "named-pipe-ready"
|
||||
path = "named-pipe-ready.rs"
|
||||
|
||||
[[example]]
|
||||
name = "named-pipe-multi-client"
|
||||
path = "named-pipe-multi-client.rs"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
use std::io;
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn windows_main() -> io::Result<()> {
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
|
||||
use tokio::time;
|
||||
use winapi::shared::winerror;
|
||||
|
||||
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client";
|
||||
const N: usize = 10;
|
||||
|
||||
// The first server needs to be constructed early so that clients can
|
||||
// be correctly connected. Otherwise a waiting client will error.
|
||||
//
|
||||
// Here we also make use of `first_pipe_instance`, which will ensure
|
||||
// that there are no other servers up and running already.
|
||||
let mut server = ServerOptions::new()
|
||||
.first_pipe_instance(true)
|
||||
.create(PIPE_NAME)?;
|
||||
|
||||
let server = tokio::spawn(async move {
|
||||
// Artificial workload.
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
for _ in 0..N {
|
||||
// Wait for client to connect.
|
||||
server.connect().await?;
|
||||
let mut inner = server;
|
||||
|
||||
// Construct the next server to be connected before sending the one
|
||||
// we already have of onto a task. This ensures that the server
|
||||
// isn't closed (after it's done in the task) before a new one is
|
||||
// available. Otherwise the client might error with
|
||||
// `io::ErrorKind::NotFound`.
|
||||
server = ServerOptions::new().create(PIPE_NAME)?;
|
||||
|
||||
let _ = tokio::spawn(async move {
|
||||
let mut buf = vec![0u8; 4];
|
||||
inner.read_exact(&mut buf).await?;
|
||||
inner.write_all(b"pong").await?;
|
||||
Ok::<_, io::Error>(())
|
||||
});
|
||||
}
|
||||
|
||||
Ok::<_, io::Error>(())
|
||||
});
|
||||
|
||||
let mut clients = Vec::new();
|
||||
|
||||
for _ in 0..N {
|
||||
clients.push(tokio::spawn(async move {
|
||||
// This showcases a generic connect loop.
|
||||
//
|
||||
// We immediately try to create a client, if it's not found or
|
||||
// the pipe is busy we use the specialized wait function on the
|
||||
// client builder.
|
||||
let mut client = loop {
|
||||
match ClientOptions::new().open(PIPE_NAME) {
|
||||
Ok(client) => break client,
|
||||
Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
time::sleep(Duration::from_millis(5)).await;
|
||||
};
|
||||
|
||||
let mut buf = [0u8; 4];
|
||||
client.write_all(b"ping").await?;
|
||||
client.read_exact(&mut buf).await?;
|
||||
Ok::<_, io::Error>(buf)
|
||||
}));
|
||||
}
|
||||
|
||||
for client in clients {
|
||||
let result = client.await?;
|
||||
assert_eq!(&result?[..], b"pong");
|
||||
}
|
||||
|
||||
server.await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_main().await?;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
println!("Named pipes are only supported on Windows!");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
use std::io;
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn windows_main() -> io::Result<()> {
|
||||
use tokio::io::Interest;
|
||||
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
|
||||
|
||||
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client";
|
||||
|
||||
let server = ServerOptions::new().create(PIPE_NAME)?;
|
||||
|
||||
let server = tokio::spawn(async move {
|
||||
// Note: we wait for a client to connect.
|
||||
server.connect().await?;
|
||||
|
||||
let buf = {
|
||||
let mut read_buf = [0u8; 5];
|
||||
let mut read_buf_cursor = 0;
|
||||
|
||||
loop {
|
||||
server.readable().await?;
|
||||
|
||||
let buf = &mut read_buf[read_buf_cursor..];
|
||||
|
||||
match server.try_read(buf) {
|
||||
Ok(n) => {
|
||||
read_buf_cursor += n;
|
||||
|
||||
if read_buf_cursor == read_buf.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
read_buf
|
||||
};
|
||||
|
||||
{
|
||||
let write_buf = b"pong\n";
|
||||
let mut write_buf_cursor = 0;
|
||||
|
||||
loop {
|
||||
let buf = &write_buf[write_buf_cursor..];
|
||||
|
||||
if buf.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
server.writable().await?;
|
||||
|
||||
match server.try_write(buf) {
|
||||
Ok(n) => {
|
||||
write_buf_cursor += n;
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<_, io::Error>(buf)
|
||||
});
|
||||
|
||||
let client = tokio::spawn(async move {
|
||||
// There's no need to use a connect loop here, since we know that the
|
||||
// server is already up - `open` was called before spawning any of the
|
||||
// tasks.
|
||||
let client = ClientOptions::new().open(PIPE_NAME)?;
|
||||
|
||||
let mut read_buf = [0u8; 5];
|
||||
let mut read_buf_cursor = 0;
|
||||
let write_buf = b"ping\n";
|
||||
let mut write_buf_cursor = 0;
|
||||
|
||||
loop {
|
||||
let mut interest = Interest::READABLE;
|
||||
if write_buf_cursor < write_buf.len() {
|
||||
interest |= Interest::WRITABLE;
|
||||
}
|
||||
|
||||
let ready = client.ready(interest).await?;
|
||||
|
||||
if ready.is_readable() {
|
||||
let buf = &mut read_buf[read_buf_cursor..];
|
||||
|
||||
match client.try_read(buf) {
|
||||
Ok(n) => {
|
||||
read_buf_cursor += n;
|
||||
|
||||
if read_buf_cursor == read_buf.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ready.is_writable() {
|
||||
let buf = &write_buf[write_buf_cursor..];
|
||||
|
||||
if buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match client.try_write(buf) {
|
||||
Ok(n) => {
|
||||
write_buf_cursor += n;
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let buf = String::from_utf8_lossy(&read_buf).into_owned();
|
||||
|
||||
Ok::<_, io::Error>(buf)
|
||||
});
|
||||
|
||||
let (server, client) = tokio::try_join!(server, client)?;
|
||||
|
||||
assert_eq!(server?, *b"ping\n");
|
||||
assert_eq!(client?, "pong\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_main().await?;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
println!("Named pipes are only supported on Windows!");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use std::io;
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn windows_main() -> io::Result<()> {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
|
||||
|
||||
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client";
|
||||
|
||||
let server = ServerOptions::new().create(PIPE_NAME)?;
|
||||
|
||||
let server = tokio::spawn(async move {
|
||||
// Note: we wait for a client to connect.
|
||||
server.connect().await?;
|
||||
|
||||
let mut server = BufReader::new(server);
|
||||
|
||||
let mut buf = String::new();
|
||||
server.read_line(&mut buf).await?;
|
||||
server.write_all(b"pong\n").await?;
|
||||
Ok::<_, io::Error>(buf)
|
||||
});
|
||||
|
||||
let client = tokio::spawn(async move {
|
||||
// There's no need to use a connect loop here, since we know that the
|
||||
// server is already up - `open` was called before spawning any of the
|
||||
// tasks.
|
||||
let client = ClientOptions::new().open(PIPE_NAME)?;
|
||||
|
||||
let mut client = BufReader::new(client);
|
||||
|
||||
let mut buf = String::new();
|
||||
client.write_all(b"ping\n").await?;
|
||||
client.read_line(&mut buf).await?;
|
||||
Ok::<_, io::Error>(buf)
|
||||
});
|
||||
|
||||
let (server, client) = tokio::try_join!(server, client)?;
|
||||
|
||||
assert_eq!(server?, "ping\n");
|
||||
assert_eq!(client?, "pong\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_main().await?;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
println!("Named pipes are only supported on Windows!");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+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,36 @@
|
||||
# 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
|
||||
|
||||
- stream: implement `Error` and `Display` for `BroadcastStreamRecvError` ([#3745])
|
||||
|
||||
### Fixed
|
||||
|
||||
- stream: avoid yielding in `AllFuture` and `AnyFuture` ([#3625])
|
||||
|
||||
[#3745]: https://github.com/tokio-rs/tokio/pull/3745
|
||||
[#3625]: https://github.com/tokio-rs/tokio/pull/3625
|
||||
|
||||
# 0.1.5 (March 20, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -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.5"
|
||||
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.5/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.
|
||||
///
|
||||
@@ -515,7 +609,7 @@ pub trait StreamExt: Stream {
|
||||
/// Skip elements from the underlying stream while the provided predicate
|
||||
/// resolves to `true`.
|
||||
///
|
||||
/// This function, like [`Iterator::skip_while`], will ignore elemets from the
|
||||
/// This function, like [`Iterator::skip_while`], will ignore elements from the
|
||||
/// stream until the predicate `f` resolves to `false`. Once one element
|
||||
/// returns false, the rest of the elements will be yielded.
|
||||
///
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,18 @@ impl<S: Stream> Stream for Timeout<S> {
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.stream.size_hint()
|
||||
let (lower, upper) = self.stream.size_hint();
|
||||
|
||||
// The timeout stream may insert an error before and after each message
|
||||
// from the underlying stream, but no more than one error between each
|
||||
// message. Hence the upper bound is computed as 2x+1.
|
||||
|
||||
// Using a helper function to enable use of question mark operator.
|
||||
fn twice_plus_one(value: Option<usize>) -> Option<usize> {
|
||||
value?.checked_mul(2)?.checked_add(1)
|
||||
}
|
||||
|
||||
(lower, twice_plus_one(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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use tokio::sync::watch::error::RecvError;
|
||||
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// This stream will always start by yielding the current value when the WatchStream is polled,
|
||||
/// regardles of whether it was the initial value or sent afterwards.
|
||||
/// regardless of whether it was the initial value or sent afterwards.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::diverging_sub_expression)]
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -89,12 +89,12 @@ fn size_overflow() {
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(usize::max_value(), Some(usize::max_value()))
|
||||
(usize::MAX, Some(usize::MAX))
|
||||
}
|
||||
}
|
||||
|
||||
let m1 = Monster;
|
||||
let m2 = Monster;
|
||||
let m = m1.chain(m2);
|
||||
assert_eq!(m.size_hint(), (usize::max_value(), None));
|
||||
assert_eq!(m.size_hint(), (usize::MAX, None));
|
||||
}
|
||||
|
||||
@@ -72,12 +72,12 @@ fn size_overflow() {
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(usize::max_value(), Some(usize::max_value()))
|
||||
(usize::MAX, Some(usize::MAX))
|
||||
}
|
||||
}
|
||||
|
||||
let m1 = Monster;
|
||||
let m2 = Monster;
|
||||
let m = m1.merge(m2);
|
||||
assert_eq!(m.size_hint(), (usize::max_value(), None));
|
||||
assert_eq!(m.size_hint(), (usize::MAX, None));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
# 0.4.2 (May 14, 2021)
|
||||
|
||||
- test: add `assert_elapsed!` macro ([#3728])
|
||||
|
||||
[#3728]: https://github.com/tokio-rs/tokio/pull/3728
|
||||
|
||||
# 0.4.1 (March 10, 2021)
|
||||
|
||||
- Fix `io::Mock` to be `Send` and `Sync` ([#3594])
|
||||
|
||||
@@ -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.1"
|
||||
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.1/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,13 +4,12 @@
|
||||
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))
|
||||
))]
|
||||
|
||||
//! Tokio and Futures based testing utilites
|
||||
//! Tokio and Futures based testing utilities
|
||||
|
||||
pub mod io;
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ impl ThreadWaker {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears any previously received wakes, avoiding potential spurrious
|
||||
/// Clears any previously received wakes, avoiding potential spurious
|
||||
/// wake notifications. This should only be called immediately before running the
|
||||
/// task.
|
||||
fn clear(&self) {
|
||||
|
||||
@@ -1,3 +1,49 @@
|
||||
# 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
|
||||
|
||||
- udp: make `UdpFramed` take `Borrow<UdpSocket>` ([#3451])
|
||||
- compat: implement `AsRawFd`/`AsRawHandle` for `Compat<T>` ([#3765])
|
||||
|
||||
[#3451]: https://github.com/tokio-rs/tokio/pull/3451
|
||||
[#3765]: https://github.com/tokio-rs/tokio/pull/3765
|
||||
|
||||
# 0.6.6 (April 12, 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.6"
|
||||
# - 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.6/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)*) => {
|
||||
$(
|
||||
|
||||
@@ -234,7 +234,7 @@ impl Default for AnyDelimiterCodec {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occured while encoding or decoding a chunk.
|
||||
/// An error occurred while encoding or decoding a chunk.
|
||||
#[derive(Debug)]
|
||||
pub enum AnyDelimiterCodecError {
|
||||
/// The maximum chunk length was exceeded.
|
||||
|
||||
@@ -28,7 +28,7 @@ use std::io;
|
||||
/// It is up to the Decoder to keep track of a restart after an EOF,
|
||||
/// and to decide how to handle such an event by, for example,
|
||||
/// allowing frames to cross EOF boundaries, re-emitting opening frames, or
|
||||
/// reseting the entire internal state.
|
||||
/// resetting the entire internal state.
|
||||
///
|
||||
/// [`Framed`]: crate::codec::Framed
|
||||
/// [`FramedRead`]: crate::codec::FramedRead
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,48 +128,64 @@ where
|
||||
// to a combination of the `is_readable` and `eof` flags. States persist across
|
||||
// loop entries and most state transitions occur with a return.
|
||||
//
|
||||
// The intitial state is `reading`.
|
||||
// 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`.
|
||||
//
|
||||
// If `decode` couldn't read a frame and the upstream source has returned eof,
|
||||
// `decode_eof` will attemp to decode the remaining bytes as closing frames.
|
||||
// `decode_eof` will attempt to decode the remaining bytes as closing frames.
|
||||
//
|
||||
// If the underlying AsyncRead is resumable, we may continue after an EOF,
|
||||
// but must finish emmiting all of it's associated `decode_eof` frames.
|
||||
// but must finish emitting all of it's associated `decode_eof` frames.
|
||||
// Furthermore, we don't want to emit any `decode_eof` frames on retried
|
||||
// reads after an EOF unless we've actually read more data.
|
||||
if state.is_readable {
|
||||
// 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,
|
||||
@@ -486,7 +486,7 @@ impl LengthDelimitedCodec {
|
||||
// Skip the required bytes
|
||||
src.advance(self.builder.length_field_offset);
|
||||
|
||||
// match endianess
|
||||
// match endianness
|
||||
let n = if self.builder.length_field_is_big_endian {
|
||||
src.get_uint(field_len)
|
||||
} else {
|
||||
@@ -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() {}
|
||||
|
||||
@@ -203,12 +203,12 @@ impl Default for LinesCodec {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occured while encoding or decoding a line.
|
||||
/// An error occurred while encoding or decoding a line.
|
||||
#[derive(Debug)]
|
||||
pub enum LinesCodecError {
|
||||
/// The maximum line length was exceeded.
|
||||
MaxLineLengthExceeded,
|
||||
/// An IO error occured.
|
||||
/// An IO error occurred.
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -67,7 +67,7 @@ pub enum Either<L, R> {
|
||||
}
|
||||
|
||||
/// A small helper macro which reduces amount of boilerplate in the actual trait method implementation.
|
||||
/// It takes an invokation of method as an argument (e.g. `self.poll(cx)`), and redirects it to either
|
||||
/// It takes an invocation of method as an argument (e.g. `self.poll(cx)`), and redirects it to either
|
||||
/// enum variant held in `self`.
|
||||
macro_rules! delegate_call {
|
||||
($self:ident.$method:ident($($args:ident),+)) => {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! An asynchronously awaitable `CancellationToken`.
|
||||
//! The token allows to signal a cancellation request to one or more tasks.
|
||||
pub(crate) mod guard;
|
||||
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::loom::sync::Mutex;
|
||||
@@ -11,6 +12,8 @@ use core::ptr::NonNull;
|
||||
use core::sync::atomic::Ordering;
|
||||
use core::task::{Context, Poll, Waker};
|
||||
|
||||
use guard::DropGuard;
|
||||
|
||||
/// A token which can be used to signal a cancellation request to one or more
|
||||
/// tasks.
|
||||
///
|
||||
@@ -275,6 +278,14 @@ impl CancellationToken {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `DropGuard` for this token.
|
||||
///
|
||||
/// Returned guard will cancel this token (and all its children) on drop
|
||||
/// unless disarmed.
|
||||
pub fn drop_guard(self) -> DropGuard {
|
||||
DropGuard { inner: Some(self) }
|
||||
}
|
||||
|
||||
unsafe fn register(
|
||||
&self,
|
||||
wait_node: &mut ListNode<WaitQueueEntry>,
|
||||
@@ -764,8 +775,8 @@ impl CancellationTokenState {
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
||||
// So far the token is not cancelled. However it could be cancelld before
|
||||
// we get the chance to store the `Waker`. Therfore we need to check
|
||||
// So far the token is not cancelled. However it could be cancelled before
|
||||
// we get the chance to store the `Waker`. Therefore we need to check
|
||||
// for cancellation again inside the mutex.
|
||||
let mut guard = self.synchronized.lock().unwrap();
|
||||
if guard.is_cancelled {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
use crate::sync::CancellationToken;
|
||||
|
||||
/// A wrapper for cancellation token which automatically cancels
|
||||
/// it on drop. It is created using `drop_guard` method on the `CancellationToken`.
|
||||
#[derive(Debug)]
|
||||
pub struct DropGuard {
|
||||
pub(super) inner: Option<CancellationToken>,
|
||||
}
|
||||
|
||||
impl DropGuard {
|
||||
/// Returns stored cancellation token and removes this drop guard instance
|
||||
/// (i.e. it will no longer cancel token). Other guards for this token
|
||||
/// are not affected.
|
||||
pub fn disarm(mut self) -> CancellationToken {
|
||||
self.inner
|
||||
.take()
|
||||
.expect("`inner` can be only None in a destructor")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DropGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = &self.inner {
|
||||
inner.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +222,7 @@ impl<T> LinkedList<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the linked list doesn not contain any node
|
||||
/// Returns whether the linked list does not contain any node
|
||||
pub fn is_empty(&self) -> bool {
|
||||
if self.head.is_some() {
|
||||
return false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Synchronization primitives
|
||||
|
||||
mod cancellation_token;
|
||||
pub use cancellation_token::{CancellationToken, WaitForCancellationFuture};
|
||||
pub use cancellation_token::{guard::DropGuard, CancellationToken, WaitForCancellationFuture};
|
||||
|
||||
mod intrusive_double_linked_list;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ use std::time::Duration;
|
||||
|
||||
mod wheel;
|
||||
|
||||
#[doc(inline)]
|
||||
pub mod delay_queue;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use delay_queue::DelayQueue;
|
||||
|
||||
// ===== Internal utils =====
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -6,9 +6,9 @@ use tokio_util::sync::PollSemaphore;
|
||||
|
||||
type SemRet = Option<OwnedSemaphorePermit>;
|
||||
|
||||
fn semaphore_poll<'a>(
|
||||
sem: &'a mut PollSemaphore,
|
||||
) -> tokio_test::task::Spawn<impl Future<Output = SemRet> + 'a> {
|
||||
fn semaphore_poll(
|
||||
sem: &mut PollSemaphore,
|
||||
) -> tokio_test::task::Spawn<impl Future<Output = SemRet> + '_> {
|
||||
let fut = futures::future::poll_fn(move |cx| sem.poll_acquire(cx));
|
||||
tokio_test::task::spawn(fut)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user