mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
161
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3768696d92 | ||
|
|
421a7b001c | ||
|
|
b1bdb3c57b | ||
|
|
00754c8f9c | ||
|
|
1ae9434e8e | ||
|
|
8895bba448 | ||
|
|
48ca254d92 | ||
|
|
a0af02a396 | ||
|
|
0ce3a1188a | ||
|
|
1ea9ce11d4 | ||
|
|
4d4d12613b | ||
|
|
5490267a79 | ||
|
|
1434b32b5a | ||
|
|
159a3b2c85 | ||
|
|
ce87dcfbf0 | ||
|
|
d41d49d202 | ||
|
|
7a6c424f6e | ||
|
|
c3037adac9 | ||
|
|
964fd06e0f | ||
|
|
817fa605ee | ||
|
|
77de684ed9 | ||
|
|
83d550e511 | ||
|
|
1b3d3e7cd6 | ||
|
|
9e044e144b | ||
|
|
cb08fbc6c3 | ||
|
|
e59584a661 | ||
|
|
f7fb0bdc7a | ||
|
|
9faea740df | ||
|
|
2a8c551631 | ||
|
|
676630785b | ||
|
|
ec4b1d7215 | ||
|
|
e3c3a56718 | ||
|
|
a7b658c35b | ||
|
|
c1c8d1033d | ||
|
|
aa303bc205 | ||
|
|
7b6ccb515f | ||
|
|
4b174ce2c9 | ||
|
|
0ec4d0db4d | ||
|
|
d83ba30d8d | ||
|
|
f339587b27 | ||
|
|
b663abe091 | ||
|
|
9a11efc262 | ||
|
|
d760b26666 | ||
|
|
d413c9c02a | ||
|
|
addbfb9204 | ||
|
|
5687043328 | ||
|
|
72c87a7724 | ||
|
|
8507e28f89 | ||
|
|
7efcab43c9 | ||
|
|
e4a39d2ef6 | ||
|
|
afd3678f89 | ||
|
|
8182ecf262 | ||
|
|
a258bff701 | ||
|
|
e076d21f67 | ||
|
|
042433cdcc | ||
|
|
0284d1b5c8 | ||
|
|
710bc8071e | ||
|
|
a2b12bd579 | ||
|
|
e7b593cbee | ||
|
|
3aaf4a5377 | ||
|
|
8e741c1c0e | ||
|
|
47d46455bd | ||
|
|
20c1fdc678 | ||
|
|
638ce93591 | ||
|
|
c853991b1e | ||
|
|
6d410f6c90 | ||
|
|
a27575f284 | ||
|
|
13fbdace66 | ||
|
|
4380c3d821 | ||
|
|
383da87313 | ||
|
|
17117b591e | ||
|
|
aa70f6c5f0 | ||
|
|
67c343d9e9 | ||
|
|
34cdcc7d87 | ||
|
|
8e134172dd | ||
|
|
605ef578df | ||
|
|
9b578f0c9d | ||
|
|
0a15768380 | ||
|
|
eb1a2ee990 | ||
|
|
8713d39228 | ||
|
|
7e27911911 | ||
|
|
4b3da20c98 | ||
|
|
b8ac94ed70 | ||
|
|
5086e56dcb | ||
|
|
2671ffb55b | ||
|
|
7f09959b0a | ||
|
|
fb7dec0e95 | ||
|
|
ee19b0ed73 | ||
|
|
c081dfe3ce | ||
|
|
21a13f9eea | ||
|
|
a82bdeebe9 | ||
|
|
435e39001b | ||
|
|
dabae570b1 | ||
|
|
6bd3be2e45 | ||
|
|
6fc1a8c8da | ||
|
|
5c8cd33820 | ||
|
|
5f3296df77 | ||
|
|
cc974a646b | ||
|
|
15495fd883 | ||
|
|
ad4183412a | ||
|
|
bd3e857737 | ||
|
|
15f73666f1 | ||
|
|
fd2048dad1 | ||
|
|
e8f39157b6 | ||
|
|
67f127769b | ||
|
|
463502cbaf | ||
|
|
a1520f5525 | ||
|
|
acd6627d6d | ||
|
|
2353806daf | ||
|
|
7be2bfa744 | ||
|
|
e066431c94 | ||
|
|
2052938a9f | ||
|
|
b3ff911c38 | ||
|
|
9d42b977df | ||
|
|
970d880ceb | ||
|
|
4ca13e6015 | ||
|
|
b54b9d4338 | ||
|
|
10e23d1c62 | ||
|
|
aa7e0cef72 | ||
|
|
bfa8cadaa0 | ||
|
|
6d15c6cacb | ||
|
|
79a2afae9f | ||
|
|
48e07a6d10 | ||
|
|
eb72ddde3b | ||
|
|
dc16b12edb | ||
|
|
67355c6d23 | ||
|
|
405d746d38 | ||
|
|
e0d1293fac | ||
|
|
480c010b01 | ||
|
|
c032ea0203 | ||
|
|
0b31c2f73d | ||
|
|
129f9fc0c8 | ||
|
|
b5c227d51f | ||
|
|
dcae2b9eb8 | ||
|
|
bb9d57017e | ||
|
|
af9c683d52 | ||
|
|
4bc5a1a058 | ||
|
|
f8948ea021 | ||
|
|
bce9780dd3 | ||
|
|
38151f30cb | ||
|
|
5dda72d338 | ||
|
|
c07257f99f | ||
|
|
d08578fc9a | ||
|
|
4047d7962a | ||
|
|
cbdceb91ac | ||
|
|
d4178cf349 | ||
|
|
2f899144ed | ||
|
|
6255598baa | ||
|
|
772e0ca8a6 | ||
|
|
3b677d1fde | ||
|
|
bb7ca7507b | ||
|
|
4a34b77af5 | ||
|
|
8897885425 | ||
|
|
0dbdd196b6 | ||
|
|
94e55c092b | ||
|
|
4468f27c31 | ||
|
|
070a825999 | ||
|
|
946401c345 | ||
|
|
0c01fd23b4 | ||
|
|
ebe241647e | ||
|
|
9681ce2b95 |
@@ -1,25 +0,0 @@
|
||||
version: 2.1
|
||||
jobs:
|
||||
test-arm:
|
||||
machine:
|
||||
image: default
|
||||
resource_class: arm.medium
|
||||
environment:
|
||||
# Change to pin rust version
|
||||
RUST_STABLE: stable
|
||||
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 --default-toolchain $RUST_STABLE
|
||||
source "$HOME"/.cargo/env
|
||||
# Only run Tokio tests
|
||||
- run: cargo test --all-features -p tokio
|
||||
|
||||
workflows:
|
||||
ci:
|
||||
jobs:
|
||||
- test-arm
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
|
||||
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-1
|
||||
image_family: freebsd-14-2
|
||||
env:
|
||||
RUST_STABLE: 1.81
|
||||
RUST_NIGHTLY: nightly-2024-05-05
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2025-01-25
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
|
||||
@@ -12,7 +12,7 @@ reproduce the failure on other operating systems, don't worry! The
|
||||
[tokio-rs/illumos] team is responsible for maintaining Tokio's illumos support,
|
||||
and can be called on to assist contributors with illumos-specific issues. Please
|
||||
feel free to tag @tokio-rs/illumos to ask for help resolving build failures on
|
||||
illumos
|
||||
illumos.
|
||||
|
||||
[illumos]: https://www.illumos.org/
|
||||
[Buildomat]: https://github.com/oxidecomputer/buildomat
|
||||
|
||||
@@ -13,18 +13,12 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
cargo-deny:
|
||||
permissions:
|
||||
checks: write # for rustsec/audit-check to create check
|
||||
contents: read # for actions/checkout to fetch code
|
||||
issues: write # for rustsec/audit-check to create issues
|
||||
checks: write
|
||||
contents: read
|
||||
issues: write
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Audit Check
|
||||
# https://github.com/rustsec/audit-check/issues/2
|
||||
uses: rustsec/audit-check@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
+145
-74
@@ -16,9 +16,9 @@ env:
|
||||
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2024-05-05
|
||||
rust_nightly: nightly-2025-01-25
|
||||
# Pin a specific miri version
|
||||
rust_miri_nightly: nightly-2024-09-19
|
||||
rust_miri_nightly: nightly-2025-01-25
|
||||
rust_clippy: '1.77'
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
@@ -47,10 +47,11 @@ jobs:
|
||||
- test-workspace-all-features
|
||||
- test-integration-tests-per-feature
|
||||
- test-parking_lot
|
||||
- test-tracing-instrumentation
|
||||
- valgrind
|
||||
- test-unstable
|
||||
- miri
|
||||
- miri-lib
|
||||
- miri-test
|
||||
- miri-doc
|
||||
- asan
|
||||
- cross-check
|
||||
- cross-check-tier3
|
||||
@@ -180,7 +181,7 @@ jobs:
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run --workspace --exclude tokio-macros --exclude tests-build --all-features --tests
|
||||
|
||||
|
||||
test-integration-tests-per-feature:
|
||||
needs: basics
|
||||
name: Run integration tests for each feature
|
||||
@@ -246,34 +247,6 @@ jobs:
|
||||
- name: Check tests with all features enabled
|
||||
run: cargo check --workspace --all-features --tests
|
||||
|
||||
test-tracing-instrumentation:
|
||||
# These tests use the as-yet unpublished `tracing-mock` crate to test the
|
||||
# tracing instrumentation present in Tokio. As such they are placed in
|
||||
# their own test crate outside of the workspace.
|
||||
needs: basics
|
||||
name: test tokio instrumentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: test tracing-instrumentation
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run
|
||||
working-directory: tokio/tests/tracing-instrumentation
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
needs: basics
|
||||
@@ -283,7 +256,7 @@ jobs:
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
toolchain: 1.82
|
||||
|
||||
- name: Install Valgrind
|
||||
uses: taiki-e/install-action@valgrind
|
||||
@@ -409,8 +382,8 @@ jobs:
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_internal_mt_counters
|
||||
|
||||
miri:
|
||||
name: miri
|
||||
miri-lib:
|
||||
name: miri-lib
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -427,7 +400,49 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: miri
|
||||
run: |
|
||||
cargo miri nextest run --features full --lib --tests --no-fail-fast
|
||||
cargo miri nextest run --features full --lib --no-fail-fast
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
|
||||
miri-test:
|
||||
name: miri-test
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_miri_nightly }}
|
||||
components: miri
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: miri
|
||||
run: |
|
||||
cargo miri nextest run --features full --test '*' --no-fail-fast
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
|
||||
miri-doc:
|
||||
name: miri-doc
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_miri_nightly }}
|
||||
components: miri
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: miri-doc-test
|
||||
run: |
|
||||
cargo miri test --doc --all-features --no-fail-fast
|
||||
working-directory: tokio
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
@@ -460,10 +475,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check semver
|
||||
- name: Check `tokio` semver
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: ${{ env.rust_stable }}
|
||||
package: tokio
|
||||
release-type: minor
|
||||
- name: Check semver for rest of the workspace
|
||||
if: ${{ !startsWith(github.event.pull_request.base.ref, 'tokio-1.') }}
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: ${{ env.rust_stable }}
|
||||
exclude: tokio
|
||||
release-type: minor
|
||||
|
||||
cross-check:
|
||||
@@ -496,6 +519,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- name: x86_64-unknown-haiku
|
||||
- name: armv7-sony-vita-newlibeabihf
|
||||
exclude_features: "process,signal,rt-process-signal,full"
|
||||
steps:
|
||||
@@ -516,16 +540,22 @@ jobs:
|
||||
|
||||
cross-test-with-parking_lot:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: armv5te-unknown-linux-gnueabi
|
||||
os: ubuntu-latest
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust stable
|
||||
@@ -556,16 +586,22 @@ jobs:
|
||||
|
||||
cross-test-without-parking_lot:
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: armv5te-unknown-linux-gnueabi
|
||||
os: ubuntu-latest
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust stable
|
||||
@@ -694,7 +730,14 @@ jobs:
|
||||
toolchain: ${{ env.rust_min }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "check --workspace --all-features"
|
||||
run: cargo check --workspace --all-features
|
||||
run: |
|
||||
if [[ "${{ github.event.pull_request.base.ref }}" =~ ^tokio-1\..* ]]; then
|
||||
# Only check `tokio` crate as the PR is backporting to an earlier tokio release.
|
||||
cargo check -p tokio --all-features
|
||||
else
|
||||
# Check all crates in the workspace
|
||||
cargo check --workspace --all-features
|
||||
fi
|
||||
env:
|
||||
RUSTFLAGS: "" # remove -Dwarnings
|
||||
|
||||
@@ -768,7 +811,15 @@ jobs:
|
||||
|
||||
docs:
|
||||
name: docs
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.run.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
run:
|
||||
- os: windows-latest
|
||||
- os: ubuntu-latest
|
||||
RUSTFLAGS: --cfg tokio_taskdump
|
||||
RUSTDOCFLAGS: --cfg tokio_taskdump
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
@@ -780,8 +831,8 @@ jobs:
|
||||
run: |
|
||||
cargo doc --lib --no-deps --all-features --document-private-items
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable ${{ matrix.run.RUSTFLAGS }}
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings ${{ matrix.run.RUSTDOCFLAGS }}
|
||||
|
||||
loom-compile:
|
||||
name: build loom tests
|
||||
@@ -982,10 +1033,10 @@ jobs:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
# Install dependencies
|
||||
- name: Install cargo-hack, wasmtime, and cargo-wasi
|
||||
- name: Install cargo-hack, wasmtime
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-hack,wasmtime,cargo-wasi
|
||||
tool: cargo-hack,wasmtime
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: WASI test tokio full
|
||||
@@ -1011,9 +1062,12 @@ jobs:
|
||||
|
||||
- name: test tests-integration --features wasi-rt
|
||||
# TODO: this should become: `cargo hack wasi test --each-feature`
|
||||
run: cargo wasi test --test rt_yield --features wasi-rt
|
||||
run: cargo test --target ${{ matrix.target }} --test rt_yield --features wasi-rt
|
||||
if: matrix.target == 'wasm32-wasip1'
|
||||
working-directory: tests-integration
|
||||
env:
|
||||
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
|
||||
RUSTFLAGS: -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864
|
||||
|
||||
- name: test tests-integration --features wasi-threads-rt
|
||||
run: cargo test --target ${{ matrix.target }} --features wasi-threads-rt
|
||||
@@ -1035,7 +1089,7 @@ jobs:
|
||||
rust:
|
||||
# `check-external-types` requires a specific Rust nightly version. See
|
||||
# the README for details: https://github.com/awslabs/cargo-check-external-types
|
||||
- nightly-2023-10-21
|
||||
- nightly-2024-06-30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
@@ -1046,28 +1100,11 @@ jobs:
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v1
|
||||
with:
|
||||
tool: [email protected]0
|
||||
tool: [email protected]3
|
||||
- name: check-external-types
|
||||
run: cargo check-external-types --all-features
|
||||
working-directory: tokio
|
||||
|
||||
check-unexpected-lints-cfgs:
|
||||
name: check unexpected lints and cfgs
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
- name: don't allow warnings
|
||||
run: sed -i '/#!\[allow(unknown_lints, unexpected_cfgs)\]/d' */src/lib.rs */tests/*.rs
|
||||
- name: check for unknown lints and cfgs
|
||||
run: cargo check --all-features --tests
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings --check-cfg=cfg(loom,tokio_unstable,tokio_taskdump,fuzzing,mio_unsupported_force_poll_poll,tokio_internal_mt_counters,fs,tokio_no_parking_lot,tokio_no_tuning_tests) -Funexpected_cfgs -Funknown_lints
|
||||
|
||||
check-fuzzing:
|
||||
name: check-fuzzing
|
||||
needs: basics
|
||||
@@ -1105,23 +1142,57 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Make sure dictionary words are sorted and unique
|
||||
run: |
|
||||
# `sed` removes the first line (number of words) and
|
||||
# the last line (new line).
|
||||
#
|
||||
FILE="spellcheck.dic"
|
||||
|
||||
# Verify the first line is an integer.
|
||||
first_line=$(head -n 1 "$FILE")
|
||||
if ! [[ "$first_line" =~ ^[0-9]+$ ]]; then
|
||||
echo "Error: The first line of $FILE must be an integer, but got: '$first_line'"
|
||||
exit 1
|
||||
fi
|
||||
expected_count="$first_line"
|
||||
|
||||
# Check that the number of lines matches the integer.
|
||||
# xargs (with no arguments) will strip leading/trailing whitespacefrom wc's output.
|
||||
actual_count=$(sed '1d' "$FILE" | wc -l | xargs)
|
||||
if [ "$expected_count" -ne "$actual_count" ]; then
|
||||
echo "Error: The number of lines ($actual_count) does not match $expected_count."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# `sed` removes the first line (number of words).
|
||||
#
|
||||
# `sort` makes sure everything in between is sorted
|
||||
# and contains no duplicates.
|
||||
#
|
||||
#
|
||||
# Since `sort` is sensitive to locale, we set it
|
||||
# using LC_ALL to en_US.UTF8 to be consistent in different
|
||||
# environments.
|
||||
|
||||
(
|
||||
sed '1d; $d' spellcheck.dic | LC_ALL=en_US.UTF8 sort -uc
|
||||
sed '1d' $FILE | LC_ALL=en_US.UTF8 sort -uc
|
||||
) || {
|
||||
echo "Dictionary is not in sorted order. Correct order is:"
|
||||
LC_ALL=en_US.UTF8 sort -u <(sed '1d; $d' spellcheck.dic)
|
||||
LC_ALL=en_US.UTF8 sort -u <(sed '1d' $FILE)
|
||||
false
|
||||
}
|
||||
- name: Run cargo-spellcheck
|
||||
run: cargo spellcheck --code 1
|
||||
|
||||
run: |
|
||||
if ! cargo spellcheck --code 1
|
||||
then
|
||||
echo ''
|
||||
echo ''
|
||||
echo 'If this is a Rust method/type/variable name, then you should'
|
||||
echo 'enclose it in backticks like this: `MyRustType`.'
|
||||
echo ''
|
||||
echo 'If this is a real word, then you can add it to spellcheck.dic'
|
||||
exit 1
|
||||
fi
|
||||
- name: Detect trailing whitespace
|
||||
run: |
|
||||
if grep --exclude-dir=.git --exclude-dir=target -rne '\s$' .
|
||||
then
|
||||
echo ''
|
||||
echo 'Please remove trailing whitespace from these lines.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
@@ -16,17 +16,8 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
|
||||
- name: Generate lockfile
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- name: Audit dependencies
|
||||
run: cargo audit
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
@@ -13,7 +13,7 @@ env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_stable: 1.82
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
+5
-5
@@ -196,12 +196,12 @@ LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_as
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
|
||||
run loom tests that test unstable features.
|
||||
run loom tests that test unstable features.
|
||||
|
||||
You can run miri tests with
|
||||
```
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields" \
|
||||
cargo +nightly miri test --features full --lib
|
||||
cargo +nightly miri test --features full --lib --tests
|
||||
```
|
||||
|
||||
### Performing spellcheck on tokio codebase
|
||||
@@ -216,8 +216,8 @@ cargo install --locked cargo-spellcheck
|
||||
cargo spellcheck check
|
||||
```
|
||||
|
||||
if the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
if the command rejects a word, you should backtick the rejected word if it's code related. If not, the
|
||||
rejected word should be put into `spellcheck.dic` file.
|
||||
|
||||
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
|
||||
the total number of words included in the file
|
||||
@@ -269,7 +269,7 @@ To list the available fuzzing harnesses you can run;
|
||||
$ cd tokio
|
||||
$ cargo fuzz list
|
||||
fuzz_linked_list
|
||||
````
|
||||
```
|
||||
|
||||
Running a fuzz test is as simple as;
|
||||
|
||||
|
||||
+13
@@ -17,3 +17,16 @@ members = [
|
||||
|
||||
[workspace.metadata.spellcheck]
|
||||
config = "spellcheck.toml"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(fuzzing)',
|
||||
'cfg(loom)',
|
||||
'cfg(mio_unsupported_force_poll_poll)',
|
||||
'cfg(tokio_allow_from_blocking_fd)',
|
||||
'cfg(tokio_internal_mt_counters)',
|
||||
'cfg(tokio_no_parking_lot)',
|
||||
'cfg(tokio_no_tuning_tests)',
|
||||
'cfg(tokio_taskdump)',
|
||||
'cfg(tokio_unstable)',
|
||||
] }
|
||||
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.41.0", features = ["full"] }
|
||||
tokio = { version = "1.45.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -78,7 +78,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(0) => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
@@ -205,8 +205,8 @@ works with the MSRV of that minor release.
|
||||
|
||||
## 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.
|
||||
Tokio doesn't follow a fixed release schedule, but we typically make one minor
|
||||
release each month. We make patch releases for bugfixes as necessary.
|
||||
|
||||
## Bug patching policy
|
||||
|
||||
@@ -216,9 +216,8 @@ warrants a patch release with a fix for the bug, it will be backported and
|
||||
released as a new patch release for each LTS minor version. Our current LTS
|
||||
releases are:
|
||||
|
||||
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
|
||||
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
|
||||
* `1.38.x` - LTS release until July 2025. (MSRV 1.63)
|
||||
* `1.43.x` - LTS release until March 2026. (MSRV 1.70)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
@@ -228,7 +227,7 @@ To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.32.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.32", features = [...] }
|
||||
tokio = { version = "~1.38", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -238,6 +237,8 @@ tokio = { version = "~1.32", features = [...] }
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
* `1.25.x` - LTS release until March 2024.
|
||||
* `1.32.x` - LTS release until September 2024.
|
||||
* `1.36.x` - LTS release until March 2025.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+5
-1
@@ -3,6 +3,7 @@ name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[features]
|
||||
test-util = ["tokio/test-util"]
|
||||
@@ -15,7 +16,7 @@ rand_chacha = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
|
||||
tokio-stream = { path = "../tokio-stream" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.42"
|
||||
@@ -94,3 +95,6 @@ harness = false
|
||||
name = "time_timeout"
|
||||
path = "time_timeout.rs"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+19
-14
@@ -10,10 +10,21 @@ async fn work() -> usize {
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
fn single_rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn multi_rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = single_rt();
|
||||
|
||||
c.bench_function("basic_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
@@ -26,9 +37,7 @@ fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = single_rt();
|
||||
|
||||
c.bench_function("basic_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
@@ -46,10 +55,8 @@ fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = multi_rt();
|
||||
|
||||
c.bench_function("threaded_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
@@ -61,10 +68,8 @@ fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
let runtime = multi_rt();
|
||||
|
||||
c.bench_function("threaded_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
|
||||
@@ -37,7 +37,7 @@ fn create_medium<const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
fn send_data<T: Default, const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>, prefix: &str) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function(format!("{}_{}", prefix, SIZE), |b| {
|
||||
g.bench_function(format!("{prefix}_{SIZE}"), |b| {
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<T>(SIZE);
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# https://embarkstudios.github.io/cargo-deny/cli/init.html
|
||||
|
||||
[graph]
|
||||
all-features = true
|
||||
|
||||
[licenses]
|
||||
allow = [
|
||||
"MIT",
|
||||
"Apache-2.0",
|
||||
]
|
||||
exceptions = [
|
||||
{ allow = ["Unicode-3.0", "Unicode-DFS-2016"], crate = "unicode-ident" },
|
||||
]
|
||||
|
||||
[bans]
|
||||
multiple-versions = "allow"
|
||||
wildcards = "deny"
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
+14
-6
@@ -3,6 +3,7 @@ name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead, and delete the **path**.
|
||||
@@ -32,17 +33,21 @@ name = "chat"
|
||||
path = "chat.rs"
|
||||
|
||||
[[example]]
|
||||
name = "connect"
|
||||
path = "connect.rs"
|
||||
name = "connect-tcp"
|
||||
path = "connect-tcp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "connect-udp"
|
||||
path = "connect-udp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-tcp"
|
||||
path = "echo-tcp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-udp"
|
||||
path = "echo-udp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo"
|
||||
path = "echo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "hello_world.rs"
|
||||
@@ -94,3 +99,6 @@ path = "named-pipe-multi-client.rs"
|
||||
[[example]]
|
||||
name = "dump"
|
||||
path = "dump.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ cargo run --example $name
|
||||
```
|
||||
|
||||
A good starting point for the examples would be [`hello_world`](hello_world.rs)
|
||||
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
|
||||
and [`echo-tcp`](echo-tcp.rs). Additionally [the tokio website][tokioweb] contains
|
||||
additional guides for some of the examples.
|
||||
|
||||
For a larger "real world" example, see the [`mini-redis`][redis] repository.
|
||||
|
||||
+3
-3
@@ -193,7 +193,7 @@ async fn process(
|
||||
// A client has connected, let's let everyone know.
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{} has joined the chat", username);
|
||||
let msg = format!("{username} has joined the chat");
|
||||
tracing::info!("{}", msg);
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
@@ -210,7 +210,7 @@ async fn process(
|
||||
// broadcast this message to the other users.
|
||||
Some(Ok(msg)) => {
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{}: {}", username, msg);
|
||||
let msg = format!("{username}: {msg}");
|
||||
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
@@ -234,7 +234,7 @@ async fn process(
|
||||
let mut state = state.lock().await;
|
||||
state.peers.remove(&addr);
|
||||
|
||||
let msg = format!("{} has left the chat", username);
|
||||
let msg = format!("{username} has left the chat");
|
||||
tracing::info!("{}", msg);
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
//! An example of hooking up stdin/stdout to a TCP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. Each line entered on stdin will be translated to a TCP
|
||||
//! packet which is then sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{stdin, stdout};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse what address we're going to connect to
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
|
||||
|
||||
connect(&addr, stdin, stdout).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={e}");
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
|
||||
(Err(e), _) | (_, Err(e)) => Err(e.into()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! An example of hooking up stdin/stdout to a UDP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. Each line entered on stdin will be translated to a UDP
|
||||
//! packet which is then sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{stdin, stdout};
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse what address we're going to connect to
|
||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
|
||||
|
||||
connect(&addr, stdin, stdout).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
let bind_addr = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
};
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
|
||||
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
|
||||
writer: &UdpSocket,
|
||||
) -> Result<(), std::io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
writer.send(&buf[..]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
|
||||
reader: &UdpSocket,
|
||||
) -> Result<(), std::io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(Bytes::from(buf)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
//! An example of hooking up stdin/stdout to either a TCP or UDP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. An optional `--udp` argument can be passed to specify
|
||||
//! that the connection should be made over UDP instead of TCP, translating each
|
||||
//! line entered on stdin to a UDP packet to be sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures::StreamExt;
|
||||
use tokio::io;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Determine if we're going to run in TCP or UDP mode
|
||||
let mut args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let tcp = match args.iter().position(|a| a == "--udp") {
|
||||
Some(i) => {
|
||||
args.remove(i);
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
// Parse what address we're going to connect to
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(io::stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(io::stdout(), BytesCodec::new());
|
||||
|
||||
if tcp {
|
||||
tcp::connect(&addr, stdin, stdout).await?;
|
||||
} else {
|
||||
udp::connect(&addr, stdin, stdout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod tcp {
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::{error::Error, io, net::SocketAddr};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={}", e);
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
|
||||
(Err(e), _) | (_, Err(e)) => Err(e.into()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use bytes::Bytes;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
let bind_addr = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
};
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
|
||||
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
writer: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
writer.send(&buf[..]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
reader: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(Bytes::from(buf)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
|
||||
//! This example demonstrates tokio's experimental task dumping functionality.
|
||||
//! This application deadlocks. Input CTRL+C to display traces of each task, or
|
||||
//! input CTRL+C twice within 1 second to quit.
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
//!
|
||||
//! To see this server in action, you can run this in one terminal:
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//! cargo run --example echo-tcp
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be echo'd back to
|
||||
//! Each line you type in to the `connect-tcp` terminal should be echo'd back to
|
||||
//! you! If you open up multiple terminals running the `connect` example you
|
||||
//! should be able to see them all make progress simultaneously.
|
||||
|
||||
@@ -40,7 +40,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop.
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
println!("Listening on: {addr}");
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
@@ -6,9 +6,9 @@
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect -- --udp 127.0.0.1:8080
|
||||
//! cargo run --example connect-udp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `nc` terminal should be echo'd back to you!
|
||||
//! Each line you type in to the `connect-udp` terminal should be echo'd back to you!
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
@@ -38,7 +38,7 @@ impl Server {
|
||||
if let Some((size, peer)) = to_send {
|
||||
let amt = socket.send_to(&buf[..size], &peer).await?;
|
||||
|
||||
println!("Echoed {}/{} bytes to {}", amt, size, peer);
|
||||
println!("Echoed {amt}/{size} bytes to {peer}");
|
||||
}
|
||||
|
||||
// If we're here then `to_send` is `None`, so we take a look for the
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be written to terminal!
|
||||
//! Each line you type in to the `connect-tcp` terminal should be written to terminal!
|
||||
//!
|
||||
//! Minimal js example:
|
||||
//!
|
||||
@@ -75,7 +75,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
println!("Listening on: {addr}");
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
@@ -96,8 +96,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// The stream will return None once the client disconnects.
|
||||
while let Some(message) = framed.next().await {
|
||||
match message {
|
||||
Ok(bytes) => println!("bytes: {:?}", bytes),
|
||||
Err(err) => println!("Socket closed with error: {:?}", err),
|
||||
Ok(bytes) => println!("bytes: {bytes:?}"),
|
||||
Err(err) => println!("Socket closed with error: {err:?}"),
|
||||
}
|
||||
}
|
||||
println!("Socket received FIN packet and closed connection");
|
||||
|
||||
+5
-5
@@ -11,11 +11,11 @@
|
||||
//!
|
||||
//! This in another terminal
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//! cargo run --example echo-tcp
|
||||
//!
|
||||
//! And finally this in another terminal
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8081
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8081
|
||||
//!
|
||||
//! This final terminal will connect to our proxy, which will in turn connect to
|
||||
//! the echo server, and you'll be able to see data flowing between them.
|
||||
@@ -38,8 +38,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.nth(2)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
println!("Listening on: {listen_addr}");
|
||||
println!("Proxying to: {server_addr}");
|
||||
|
||||
let listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
@@ -50,7 +50,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
copy_bidirectional(&mut inbound, &mut outbound)
|
||||
.map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
println!("Failed to transfer; error={e}");
|
||||
}
|
||||
})
|
||||
.await
|
||||
|
||||
+11
-11
@@ -12,9 +12,9 @@
|
||||
//!
|
||||
//! and next in another windows run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//! cargo run --example connect-tcp 127.0.0.1:8080
|
||||
//!
|
||||
//! In the `connect` window you can type in commands where when you hit enter
|
||||
//! In the `connect-tcp` window you can type in commands where when you hit enter
|
||||
//! you'll get a response from the server for that command. An example session
|
||||
//! is:
|
||||
//!
|
||||
@@ -90,7 +90,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
println!("Listening on: {addr}");
|
||||
|
||||
// Create the shared state of this server that will be shared amongst all
|
||||
// clients. We populate the initial database and then create the `Database`
|
||||
@@ -131,11 +131,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let response = response.serialize();
|
||||
|
||||
if let Err(e) = lines.send(response.as_str()).await {
|
||||
println!("error on sending response; error = {:?}", e);
|
||||
println!("error on sending response; error = {e:?}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error on decoding from socket; error = {:?}", e);
|
||||
println!("error on decoding from socket; error = {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// The connection will be closed at this point as `lines.next()` has returned `None`.
|
||||
});
|
||||
}
|
||||
Err(e) => println!("error accepting socket; error = {:?}", e),
|
||||
Err(e) => println!("error accepting socket; error = {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ fn handle_request(line: &str, db: &Arc<Database>) -> Response {
|
||||
value: value.clone(),
|
||||
},
|
||||
None => Response::Error {
|
||||
msg: format!("no key {}", key),
|
||||
msg: format!("no key {key}"),
|
||||
},
|
||||
},
|
||||
Request::Set { key, value } => {
|
||||
@@ -203,7 +203,7 @@ impl Request {
|
||||
value: value.to_string(),
|
||||
})
|
||||
}
|
||||
Some(cmd) => Err(format!("unknown command: {}", cmd)),
|
||||
Some(cmd) => Err(format!("unknown command: {cmd}")),
|
||||
None => Err("empty input".into()),
|
||||
}
|
||||
}
|
||||
@@ -212,13 +212,13 @@ impl Request {
|
||||
impl Response {
|
||||
fn serialize(&self) -> String {
|
||||
match *self {
|
||||
Response::Value { ref key, ref value } => format!("{} = {}", key, value),
|
||||
Response::Value { ref key, ref value } => format!("{key} = {value}"),
|
||||
Response::Set {
|
||||
ref key,
|
||||
ref value,
|
||||
ref previous,
|
||||
} => format!("set {} = `{}`, previous: {:?}", key, value, previous),
|
||||
Response::Error { ref msg } => format!("error: {}", msg),
|
||||
} => format!("set {key} = `{value}`, previous: {previous:?}"),
|
||||
Response::Error { ref msg } => format!("error: {msg}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,13 +31,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let server = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
println!("Listening on: {addr}");
|
||||
|
||||
loop {
|
||||
let (stream, _) = server.accept().await?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process(stream).await {
|
||||
println!("failed to process connection; error = {}", e);
|
||||
println!("failed to process connection; error = {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -159,7 +159,7 @@ impl Decoder for Http {
|
||||
let mut parsed_headers = [httparse::EMPTY_HEADER; 16];
|
||||
let mut r = httparse::Request::new(&mut parsed_headers);
|
||||
let status = r.parse(src).map_err(|e| {
|
||||
let msg = format!("failed to parse http request: {:?}", e);
|
||||
let msg = format!("failed to parse http request: {e:?}");
|
||||
io::Error::new(io::ErrorKind::Other, msg)
|
||||
})?;
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// Run both futures simultaneously of `a` and `b` sending messages back and forth.
|
||||
match tokio::try_join!(a, b) {
|
||||
Err(e) => println!("an error occurred; error = {:?}", e),
|
||||
Err(e) => println!("an error occurred; error = {e:?}"),
|
||||
_ => println!("done!"),
|
||||
}
|
||||
|
||||
|
||||
+12
-3
@@ -1,4 +1,4 @@
|
||||
292
|
||||
302
|
||||
&
|
||||
+
|
||||
<
|
||||
@@ -30,11 +30,13 @@
|
||||
450ms
|
||||
50ms
|
||||
8MB
|
||||
ABI
|
||||
accessors
|
||||
adaptor
|
||||
adaptors
|
||||
Adaptors
|
||||
AIO
|
||||
ambiant
|
||||
ambient
|
||||
amongst
|
||||
api
|
||||
APIs
|
||||
@@ -68,6 +70,7 @@ connectionless
|
||||
coroutines
|
||||
cpu
|
||||
cpus
|
||||
customizable
|
||||
Customizable
|
||||
datagram
|
||||
Datagram
|
||||
@@ -75,8 +78,12 @@ datagrams
|
||||
deallocate
|
||||
deallocated
|
||||
Deallocates
|
||||
debuginfo
|
||||
decrement
|
||||
decrementing
|
||||
demangled
|
||||
dequeued
|
||||
dereferenced
|
||||
deregister
|
||||
deregistered
|
||||
deregistering
|
||||
@@ -133,6 +140,7 @@ implementers
|
||||
implementor
|
||||
implementors
|
||||
incrementing
|
||||
inlining
|
||||
interoperate
|
||||
invariants
|
||||
Invariants
|
||||
@@ -162,6 +170,7 @@ mio's
|
||||
miri
|
||||
misconfigured
|
||||
mock's
|
||||
monomorphization
|
||||
mpmc
|
||||
mpsc
|
||||
multi
|
||||
@@ -274,6 +283,7 @@ unparks
|
||||
Unparks
|
||||
unreceived
|
||||
unsafety
|
||||
unsets
|
||||
Unsets
|
||||
unsynchronized
|
||||
untrusted
|
||||
@@ -291,4 +301,3 @@ Wakers
|
||||
wakeup
|
||||
wakeups
|
||||
workstealing
|
||||
|
||||
|
||||
@@ -3,12 +3,16 @@ name = "stress-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio/", features = ["full"] }
|
||||
tokio = { version = "1.0.0", path = "../tokio/", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "tests-build"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
@@ -10,7 +11,10 @@ full = ["tokio/full"]
|
||||
rt = ["tokio/rt", "tokio/macros"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", optional = true }
|
||||
tokio = { version = "1.0.0", path = "../tokio", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -6,5 +6,5 @@ To run all of the tests in this directory, run the following commands:
|
||||
cargo test --features full
|
||||
cargo test --features rt
|
||||
```
|
||||
If one of the tests fail, you can pass `TRYBUILD=overwrite` to the `cargo test`
|
||||
If any of the tests fail, you can pass `TRYBUILD=overwrite` to the `cargo test`
|
||||
command that failed to have it regenerate the test output.
|
||||
|
||||
@@ -23,6 +23,40 @@ async fn extra_semicolon() -> Result<(), ()> {
|
||||
Ok(());
|
||||
}
|
||||
|
||||
/// This test is a characterization test for the `?` operator.
|
||||
///
|
||||
/// See <https://github.com/tokio-rs/tokio/issues/6930#issuecomment-2572502517> for more details.
|
||||
///
|
||||
/// It should fail with a single error message about the return type of the function, but instead
|
||||
/// if fails with an extra error message due to the `?` operator being used within the async block
|
||||
/// rather than the original function.
|
||||
///
|
||||
/// ```text
|
||||
/// 28 | None?;
|
||||
/// | ^ cannot use the `?` operator in an async block that returns `()`
|
||||
/// ```
|
||||
#[tokio::main]
|
||||
async fn question_mark_operator_with_invalid_option() -> Option<()> {
|
||||
None?;
|
||||
}
|
||||
|
||||
/// This test is a characterization test for the `?` operator.
|
||||
///
|
||||
/// See <https://github.com/tokio-rs/tokio/issues/6930#issuecomment-2572502517> for more details.
|
||||
///
|
||||
/// It should fail with a single error message about the return type of the function, but instead
|
||||
/// if fails with an extra error message due to the `?` operator being used within the async block
|
||||
/// rather than the original function.
|
||||
///
|
||||
/// ```text
|
||||
/// 33 | Ok(())?;
|
||||
/// | ^ cannot use the `?` operator in an async block that returns `()`
|
||||
/// ```
|
||||
#[tokio::main]
|
||||
async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
|
||||
Ok(())?;
|
||||
}
|
||||
|
||||
// https://github.com/tokio-rs/tokio/issues/4635
|
||||
#[allow(redundant_semicolons)]
|
||||
#[rustfmt::skip]
|
||||
|
||||
@@ -49,11 +49,64 @@ help: try adding an expression at the end of the block
|
||||
24 + Ok(())
|
||||
|
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:32:5
|
||||
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
|
||||
--> tests/fail/macros_type_mismatch.rs:40:9
|
||||
|
|
||||
30 | async fn issue_4635() {
|
||||
38 | #[tokio::main]
|
||||
| -------------- this function should return `Result` or `Option` to accept `?`
|
||||
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
|
||||
40 | None?;
|
||||
| ^ cannot use the `?` operator in an async block that returns `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:40:5
|
||||
|
|
||||
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
|
||||
| ---------- expected `Option<()>` because of return type
|
||||
40 | None?;
|
||||
| ^^^^^^ expected `Option<()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Option<()>`
|
||||
found unit type `()`
|
||||
help: try adding an expression at the end of the block
|
||||
|
|
||||
40 ~ None?;;
|
||||
41 + None
|
||||
|
|
||||
40 ~ None?;;
|
||||
41 + Some(())
|
||||
|
|
||||
|
||||
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
|
||||
--> tests/fail/macros_type_mismatch.rs:57:11
|
||||
|
|
||||
55 | #[tokio::main]
|
||||
| -------------- this function should return `Result` or `Option` to accept `?`
|
||||
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
|
||||
57 | Ok(())?;
|
||||
| ^ cannot use the `?` operator in an async block that returns `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:57:5
|
||||
|
|
||||
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
|
||||
| -------------- expected `Result<(), ()>` because of return type
|
||||
57 | Ok(())?;
|
||||
| ^^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
help: try adding an expression at the end of the block
|
||||
|
|
||||
57 ~ Ok(())?;;
|
||||
58 + Ok(())
|
||||
|
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> tests/fail/macros_type_mismatch.rs:66:5
|
||||
|
|
||||
64 | async fn issue_4635() {
|
||||
| - help: try adding a return type: `-> i32`
|
||||
31 | return 1;
|
||||
32 | ;
|
||||
65 | return 1;
|
||||
66 | ;
|
||||
| ^ expected `()`, found integer
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
@@ -55,8 +56,11 @@ rt = ["tokio/rt"]
|
||||
rt-multi-thread = ["rt", "tokio/rt-multi-thread"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio" }
|
||||
tokio-test = { path = "../tokio-test", optional = true }
|
||||
tokio = { version = "1.0.0", path = "../tokio" }
|
||||
tokio-test = { version = "0.4", path = "../tokio-test", optional = true }
|
||||
doc-comment = "0.3.1"
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
bytes = "1.0.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# 2.5.0 (Jan 8th, 2025)
|
||||
|
||||
- macros: suppress `clippy::needless_return` in `#[tokio::main]` ([#6874])
|
||||
|
||||
[#6874]: https://github.com/tokio-rs/tokio/pull/6874
|
||||
|
||||
# 2.4.0 (July 22nd, 2024)
|
||||
|
||||
- msrv: increase MSRV to 1.70 ([#6645])
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-macros"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.x.y" git tag.
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -31,3 +31,6 @@ tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+12
-15
@@ -20,7 +20,7 @@ impl RuntimeFlavor {
|
||||
"single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()),
|
||||
"basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()),
|
||||
"threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()),
|
||||
_ => Err(format!("No such runtime flavor `{}`. The runtime flavors are `current_thread` and `multi_thread`.", s)),
|
||||
_ => Err(format!("No such runtime flavor `{s}`. The runtime flavors are `current_thread` and `multi_thread`.")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ impl UnhandledPanic {
|
||||
match s {
|
||||
"ignore" => Ok(UnhandledPanic::Ignore),
|
||||
"shutdown_runtime" => Ok(UnhandledPanic::ShutdownRuntime),
|
||||
_ => Err(format!("No such unhandled panic behavior `{}`. The unhandled panic behaviors are `ignore` and `shutdown_runtime`.", s)),
|
||||
_ => Err(format!("No such unhandled panic behavior `{s}`. The unhandled panic behaviors are `ignore` and `shutdown_runtime`.")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,12 +239,12 @@ fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as integer: {}", field, e),
|
||||
format!("Failed to parse value of `{field}` as integer: {e}"),
|
||||
)),
|
||||
},
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as integer.", field),
|
||||
format!("Failed to parse value of `{field}` as integer."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,7 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
|
||||
syn::Lit::Verbatim(s) => Ok(s.to_string()),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as string.", field),
|
||||
format!("Failed to parse value of `{field}` as string."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -275,7 +275,7 @@ fn parse_path(lit: syn::Lit, span: Span, field: &str) -> Result<Path, syn::Error
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as path.", field),
|
||||
format!("Failed to parse value of `{field}` as path."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -285,7 +285,7 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
|
||||
syn::Lit::Bool(b) => Ok(b.value),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as bool.", field),
|
||||
format!("Failed to parse value of `{field}` as bool."),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -342,8 +342,7 @@ fn build_config(
|
||||
}
|
||||
name => {
|
||||
let msg = format!(
|
||||
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
|
||||
name,
|
||||
"Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
|
||||
);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
@@ -358,21 +357,19 @@ fn build_config(
|
||||
let msg = match name.as_str() {
|
||||
"threaded_scheduler" | "multi_thread" => {
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"multi_thread\")].",
|
||||
macro_name
|
||||
"Set the runtime flavor with #[{macro_name}(flavor = \"multi_thread\")]."
|
||||
)
|
||||
}
|
||||
"basic_scheduler" | "current_thread" | "single_threaded" => {
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"current_thread\")].",
|
||||
macro_name
|
||||
"Set the runtime flavor with #[{macro_name}(flavor = \"current_thread\")]."
|
||||
)
|
||||
}
|
||||
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
|
||||
format!("The `{}` attribute requires an argument.", name)
|
||||
format!("The `{name}` attribute requires an argument.")
|
||||
}
|
||||
name => {
|
||||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.", name)
|
||||
format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.")
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -211,7 +210,6 @@ use proc_macro::TokenStream;
|
||||
/// This option is only compatible with the `current_thread` runtime.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![allow(unknown_lints, unexpected_cfgs)]
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[tokio::main(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
|
||||
/// async fn main() {
|
||||
@@ -226,7 +224,6 @@ use proc_macro::TokenStream;
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![allow(unknown_lints, unexpected_cfgs)]
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
@@ -480,7 +477,6 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// This option is only compatible with the `current_thread` runtime.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![allow(unknown_lints, unexpected_cfgs)]
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[tokio::test(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
|
||||
/// async fn my_test() {
|
||||
@@ -495,7 +491,6 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// Equivalent code not using `#[tokio::test]`
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![allow(unknown_lints, unexpected_cfgs)]
|
||||
/// #[cfg(tokio_unstable)]
|
||||
/// #[test]
|
||||
/// fn my_test() {
|
||||
|
||||
@@ -11,7 +11,7 @@ pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
};
|
||||
|
||||
let variants = (0..branches)
|
||||
.map(|num| Ident::new(&format!("_{}", num), Span::call_site()))
|
||||
.map(|num| Ident::new(&format!("_{num}"), Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Use a bitfield to track which futures completed
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
# 0.1.17 (December 6th, 2024)
|
||||
|
||||
- deps: fix dev-dependency on tokio-test ([#6931], [#7019])
|
||||
- stream: fix link on `Peekable` ([#6861])
|
||||
- sync: fix `Stream` link in broadcast docs ([#6873])
|
||||
|
||||
[#6861]: https://github.com/tokio-rs/tokio/pull/6861
|
||||
[#6873]: https://github.com/tokio-rs/tokio/pull/6873
|
||||
[#6931]: https://github.com/tokio-rs/tokio/pull/6931
|
||||
[#7019]: https://github.com/tokio-rs/tokio/pull/7019
|
||||
|
||||
# 0.1.16 (September 5th, 2024)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.70.
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-stream"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.16"
|
||||
version = "0.1.17"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -45,7 +45,7 @@ tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
|
||||
async-stream = "0.3"
|
||||
parking_lot = "0.12.0"
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
tokio-test = { version = "0.4", path = "../tokio-test" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
@@ -56,3 +56,6 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
# 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"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
# will never complete. Use
|
||||
/// [`stream::empty()`](super::empty()) to obtain a stream that is is
|
||||
/// [`stream::empty()`](super::empty()) to obtain a stream that is
|
||||
/// immediately empty but returns no values.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@@ -10,6 +10,29 @@ use std::task::{ready, Context, Poll};
|
||||
|
||||
/// A wrapper around [`tokio::sync::broadcast::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::broadcast;
|
||||
/// use tokio_stream::wrappers::BroadcastStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), tokio::sync::broadcast::error::SendError<u8>> {
|
||||
/// let (tx, rx) = broadcast::channel(16);
|
||||
/// tx.send(10)?;
|
||||
/// tx.send(20)?;
|
||||
/// # // prevent the doc test from hanging
|
||||
/// drop(tx);
|
||||
///
|
||||
/// let mut stream = BroadcastStream::new(rx);
|
||||
/// assert_eq!(stream.next().await, Some(Ok(10)));
|
||||
/// assert_eq!(stream.next().await, Some(Ok(20)));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::sync::broadcast::Receiver`]: struct@tokio::sync::broadcast::Receiver
|
||||
/// [`Stream`]: trait@futures_core::Stream
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
|
||||
|
||||
@@ -5,6 +5,26 @@ use tokio::time::{Instant, Interval};
|
||||
|
||||
/// A wrapper around [`Interval`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::time::{Duration, Instant, interval};
|
||||
/// use tokio_stream::wrappers::IntervalStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let start = Instant::now();
|
||||
/// let interval = interval(Duration::from_millis(10));
|
||||
/// let mut stream = IntervalStream::new(interval);
|
||||
/// for _ in 0..3 {
|
||||
/// if let Some(instant) = stream.next().await {
|
||||
/// println!("elapsed: {:.1?}", instant.duration_since(start));
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Interval`]: struct@tokio::time::Interval
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -8,6 +8,24 @@ use tokio::io::{AsyncBufRead, Lines};
|
||||
pin_project! {
|
||||
/// A wrapper around [`tokio::io::Lines`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::io::AsyncBufReadExt;
|
||||
/// use tokio_stream::wrappers::LinesStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let input = b"Hello\nWorld\n";
|
||||
/// let mut stream = LinesStream::new(input.lines());
|
||||
/// while let Some(line) = stream.next().await {
|
||||
/// println!("{}", line?);
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::io::Lines`]: struct@tokio::io::Lines
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -5,6 +5,29 @@ use tokio::sync::mpsc::Receiver;
|
||||
|
||||
/// A wrapper around [`tokio::sync::mpsc::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::mpsc;
|
||||
/// use tokio_stream::wrappers::ReceiverStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), tokio::sync::mpsc::error::SendError<u8>> {
|
||||
/// let (tx, rx) = mpsc::channel(2);
|
||||
/// tx.send(10).await?;
|
||||
/// tx.send(20).await?;
|
||||
/// # // prevent the doc test from hanging
|
||||
/// drop(tx);
|
||||
///
|
||||
/// let mut stream = ReceiverStream::new(rx);
|
||||
/// assert_eq!(stream.next().await, Some(10));
|
||||
/// assert_eq!(stream.next().await, Some(20));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::sync::mpsc::Receiver`]: struct@tokio::sync::mpsc::Receiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -5,6 +5,29 @@ use tokio::sync::mpsc::UnboundedReceiver;
|
||||
|
||||
/// A wrapper around [`tokio::sync::mpsc::UnboundedReceiver`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::mpsc;
|
||||
/// use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), tokio::sync::mpsc::error::SendError<u8>> {
|
||||
/// let (tx, rx) = mpsc::unbounded_channel();
|
||||
/// tx.send(10)?;
|
||||
/// tx.send(20)?;
|
||||
/// # // prevent the doc test from hanging
|
||||
/// drop(tx);
|
||||
///
|
||||
/// let mut stream = UnboundedReceiverStream::new(rx);
|
||||
/// assert_eq!(stream.next().await, Some(10));
|
||||
/// assert_eq!(stream.next().await, Some(20));
|
||||
/// assert_eq!(stream.next().await, None);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::sync::mpsc::UnboundedReceiver`]: struct@tokio::sync::mpsc::UnboundedReceiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -6,6 +6,24 @@ use tokio::fs::{DirEntry, ReadDir};
|
||||
|
||||
/// A wrapper around [`tokio::fs::ReadDir`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::fs::read_dir;
|
||||
/// use tokio_stream::{StreamExt, wrappers::ReadDirStream};
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let dirs = read_dir(".").await?;
|
||||
/// let mut dirs = ReadDirStream::new(dirs);
|
||||
/// while let Some(dir) = dirs.next().await {
|
||||
/// let dir = dir?;
|
||||
/// println!("{}", dir.path().display());
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`tokio::fs::ReadDir`]: struct@tokio::fs::ReadDir
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -5,6 +5,22 @@ use tokio::signal::unix::Signal;
|
||||
|
||||
/// A wrapper around [`Signal`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::signal::unix::{signal, SignalKind};
|
||||
/// use tokio_stream::{StreamExt, wrappers::SignalStream};
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let signals = signal(SignalKind::hangup())?;
|
||||
/// let mut stream = SignalStream::new(signals);
|
||||
/// while stream.next().await.is_some() {
|
||||
/// println!("hangup signal received");
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
/// [`Signal`]: struct@tokio::signal::unix::Signal
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -7,6 +7,23 @@ use tokio::signal::windows::{CtrlBreak, CtrlC};
|
||||
///
|
||||
/// [`CtrlC`]: struct@tokio::signal::windows::CtrlC
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::signal::windows::ctrl_c;
|
||||
/// use tokio_stream::{StreamExt, wrappers::CtrlCStream};
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let signals = ctrl_c()?;
|
||||
/// let mut stream = CtrlCStream::new(signals);
|
||||
/// while stream.next().await.is_some() {
|
||||
/// println!("ctrl-c received");
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(windows, feature = "signal"))))]
|
||||
pub struct CtrlCStream {
|
||||
@@ -47,6 +64,23 @@ impl AsMut<CtrlC> for CtrlCStream {
|
||||
|
||||
/// A wrapper around [`CtrlBreak`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::signal::windows::ctrl_break;
|
||||
/// use tokio_stream::{StreamExt, wrappers::CtrlBreakStream};
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let signals = ctrl_break()?;
|
||||
/// let mut stream = CtrlBreakStream::new(signals);
|
||||
/// while stream.next().await.is_some() {
|
||||
/// println!("ctrl-break received");
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`CtrlBreak`]: struct@tokio::signal::windows::CtrlBreak
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -8,6 +8,24 @@ use tokio::io::{AsyncBufRead, Split};
|
||||
pin_project! {
|
||||
/// A wrapper around [`tokio::io::Split`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::io::AsyncBufReadExt;
|
||||
/// use tokio_stream::{StreamExt, wrappers::SplitStream};
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let input = "Hello\nWorld\n".as_bytes();
|
||||
/// let lines = AsyncBufReadExt::split(input, b'\n');
|
||||
///
|
||||
/// let mut stream = SplitStream::new(lines);
|
||||
/// while let Some(line) = stream.next().await {
|
||||
/// println!("length = {}", line?.len())
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
/// [`tokio::io::Split`]: struct@tokio::io::Split
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -6,6 +6,33 @@ use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
/// A wrapper around [`TcpListener`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Accept connections from both IPv4 and IPv6 listeners in the same loop:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
///
|
||||
/// use tokio::net::TcpListener;
|
||||
/// use tokio_stream::{StreamExt, wrappers::TcpListenerStream};
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let ipv4_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 8080)).await?;
|
||||
/// let ipv6_listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 8080)).await?;
|
||||
/// let ipv4_connections = TcpListenerStream::new(ipv4_listener);
|
||||
/// let ipv6_connections = TcpListenerStream::new(ipv6_listener);
|
||||
///
|
||||
/// let mut connections = ipv4_connections.chain(ipv6_connections);
|
||||
/// while let Some(tcp_stream) = connections.next().await {
|
||||
/// let stream = tcp_stream?;
|
||||
/// let peer_addr = stream.peer_addr()?;
|
||||
/// println!("accepted connection; peer address = {peer_addr}");
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`TcpListener`]: struct@tokio::net::TcpListener
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -6,6 +6,25 @@ use tokio::net::{UnixListener, UnixStream};
|
||||
|
||||
/// A wrapper around [`UnixListener`] that implements [`Stream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::UnixListener;
|
||||
/// use tokio_stream::{StreamExt, wrappers::UnixListenerStream};
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let listener = UnixListener::bind("/tmp/sock")?;
|
||||
/// let mut incoming = UnixListenerStream::new(listener);
|
||||
///
|
||||
/// while let Some(stream) = incoming.next().await {
|
||||
/// let stream = stream?;
|
||||
/// let peer_addr = stream.peer_addr()?;
|
||||
/// println!("Accepted connection from: {peer_addr:?}");
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
/// [`UnixListener`]: struct@tokio::net::UnixListener
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -49,6 +49,7 @@ where
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)] // Block on https://github.com/tokio-rs/tokio/issues/6860
|
||||
async fn pending_first() {
|
||||
let (tx1, rx1) = mpsc::unbounded_channel_stream();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel_stream();
|
||||
|
||||
@@ -19,9 +19,6 @@ categories = ["asynchronous", "development-tools::testing"]
|
||||
[dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio-stream = { version = "0.1.1", path = "../tokio-stream" }
|
||||
async-stream = "0.3.3"
|
||||
|
||||
bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -30,3 +27,6 @@ futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+53
-9
@@ -52,6 +52,7 @@ pub struct Handle {
|
||||
pub struct Builder {
|
||||
// Sequence of actions for the Mock to take
|
||||
actions: VecDeque<Action>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -71,6 +72,7 @@ struct Inner {
|
||||
sleep: Option<Pin<Box<Sleep>>>,
|
||||
read_wait: Option<Waker>,
|
||||
rx: UnboundedReceiverStream<Action>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
@@ -127,6 +129,12 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set name of the mock IO object to include in panic messages and debug output
|
||||
pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
|
||||
self.name = name.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Build a `Mock` value according to the defined script.
|
||||
pub fn build(&mut self) -> Mock {
|
||||
let (mock, _) = self.build_with_handle();
|
||||
@@ -135,7 +143,7 @@ impl Builder {
|
||||
|
||||
/// Build a `Mock` value paired with a handle
|
||||
pub fn build_with_handle(&mut self) -> (Mock, Handle) {
|
||||
let (inner, handle) = Inner::new(self.actions.clone());
|
||||
let (inner, handle) = Inner::new(self.actions.clone(), self.name.clone());
|
||||
|
||||
let mock = Mock { inner };
|
||||
|
||||
@@ -184,7 +192,7 @@ impl Handle {
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new(actions: VecDeque<Action>) -> (Inner, Handle) {
|
||||
fn new(actions: VecDeque<Action>, name: String) -> (Inner, Handle) {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let rx = UnboundedReceiverStream::new(rx);
|
||||
@@ -195,6 +203,7 @@ impl Inner {
|
||||
read_wait: None,
|
||||
rx,
|
||||
waiting: None,
|
||||
name,
|
||||
};
|
||||
|
||||
let handle = Handle { tx };
|
||||
@@ -256,7 +265,7 @@ impl Inner {
|
||||
Action::Write(ref mut expect) => {
|
||||
let n = cmp::min(src.len(), expect.len());
|
||||
|
||||
assert_eq!(&src[..n], &expect[..n]);
|
||||
assert_eq!(&src[..n], &expect[..n], "name={} i={}", self.name, i);
|
||||
|
||||
// Drop data that was matched
|
||||
expect.drain(..n);
|
||||
@@ -418,7 +427,7 @@ impl AsyncWrite for Mock {
|
||||
self.inner.actions.push_back(action);
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
panic!("unexpected write");
|
||||
panic!("unexpected write {}", self.pmsg());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,7 +438,7 @@ impl AsyncWrite for Mock {
|
||||
let until = Instant::now() + rem;
|
||||
self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
|
||||
} else {
|
||||
panic!("unexpected WouldBlock");
|
||||
panic!("unexpected WouldBlock {}", self.pmsg());
|
||||
}
|
||||
}
|
||||
Ok(0) => {
|
||||
@@ -445,7 +454,7 @@ impl AsyncWrite for Mock {
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
panic!("unexpected write");
|
||||
panic!("unexpected write {}", self.pmsg());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,8 +484,16 @@ impl Drop for Mock {
|
||||
}
|
||||
|
||||
self.inner.actions.iter().for_each(|a| match a {
|
||||
Action::Read(data) => assert!(data.is_empty(), "There is still data left to read."),
|
||||
Action::Write(data) => assert!(data.is_empty(), "There is still data left to write."),
|
||||
Action::Read(data) => assert!(
|
||||
data.is_empty(),
|
||||
"There is still data left to read. {}",
|
||||
self.pmsg()
|
||||
),
|
||||
Action::Write(data) => assert!(
|
||||
data.is_empty(),
|
||||
"There is still data left to write. {}",
|
||||
self.pmsg()
|
||||
),
|
||||
_ => (),
|
||||
});
|
||||
}
|
||||
@@ -505,6 +522,33 @@ fn is_task_ctx() -> bool {
|
||||
|
||||
impl fmt::Debug for Inner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Inner {{...}}")
|
||||
if self.name.is_empty() {
|
||||
write!(f, "Inner {{...}}")
|
||||
} else {
|
||||
write!(f, "Inner {{name={}, ...}}", self.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PanicMsgSnippet<'a>(&'a Inner);
|
||||
|
||||
impl<'a> fmt::Display for PanicMsgSnippet<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.0.name.is_empty() {
|
||||
write!(f, "({} actions remain)", self.0.actions.len())
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"(name {}, {} actions remain)",
|
||||
self.0.name,
|
||||
self.0.actions.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mock {
|
||||
fn pmsg(&self) -> PanicMsgSnippet<'_> {
|
||||
PanicMsgSnippet(&self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
|
||||
@@ -161,8 +161,7 @@ impl<T: Unpin> Drop for StreamMock<T> {
|
||||
|
||||
assert!(
|
||||
undropped_count == 0,
|
||||
"StreamMock was dropped before all actions were consumed, {} actions were not consumed",
|
||||
undropped_count
|
||||
"StreamMock was dropped before all actions were consumed, {undropped_count} actions were not consumed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ async fn read_error() {
|
||||
match mock.read(&mut buf).await {
|
||||
Err(error) => {
|
||||
assert_eq!(error.kind(), io::ErrorKind::Other);
|
||||
assert_eq!("cruel", format!("{}", error));
|
||||
assert_eq!("cruel", format!("{error}"));
|
||||
}
|
||||
Ok(_) => panic!("error not received"),
|
||||
}
|
||||
@@ -87,7 +87,7 @@ async fn write_error() {
|
||||
match mock.write_all(b"whoa").await {
|
||||
Err(error) => {
|
||||
assert_eq!(error.kind(), io::ErrorKind::Other);
|
||||
assert_eq!("cruel", format!("{}", error));
|
||||
assert_eq!("cruel", format!("{error}"));
|
||||
}
|
||||
Ok(_) => panic!("error not received"),
|
||||
}
|
||||
|
||||
+65
-1
@@ -1,3 +1,67 @@
|
||||
# 0.7.15 (April 23rd, 2025)
|
||||
|
||||
### Fixed
|
||||
|
||||
- task: properly handle removed entries in `JoinMap` ([#7264])
|
||||
|
||||
### Updated
|
||||
|
||||
- deps: update hashbrown to 0.15 ([#7219])
|
||||
|
||||
### Documented
|
||||
|
||||
- task: explicitly state that `TaskTracker` does not abort tasks on Drop ([#7223])
|
||||
|
||||
[#7219]: https://github.com/tokio-rs/tokio/pull/7219
|
||||
[#7223]: https://github.com/tokio-rs/tokio/pull/7223
|
||||
[#7264]: https://github.com/tokio-rs/tokio/pull/7264
|
||||
|
||||
# 0.7.14 (March 12th, 2025)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `get_ref` and `get_mut` for `SyncIoBridge` ([#7128])
|
||||
- io: add `read_exact_arc` ([#7165])
|
||||
- sync: add `CancellationToken::run_until_cancelled_owned` ([#7081])
|
||||
|
||||
### Changed
|
||||
|
||||
- codec: optimize buffer reserve for `AnyDelimiterCodec::encode` ([#7188])
|
||||
- either: enable `Either` to use underlying `AsyncWrite` implementation ([#7025])
|
||||
|
||||
### Fixed
|
||||
|
||||
- codec: fix typo in API docs ([#7044])
|
||||
- util: fix example in `StreamReader` docs ([#7167])
|
||||
|
||||
### Documented
|
||||
|
||||
- io: add docs for `SyncIoBridge` with examples and alternatives ([#6815])
|
||||
|
||||
### Internal
|
||||
|
||||
- io: clean up buffer casts ([#7142])
|
||||
- task: run `spawn_pinned` tests with miri ([#7023])
|
||||
|
||||
[#6815]: https://github.com/tokio-rs/tokio/pull/6815
|
||||
[#7023]: https://github.com/tokio-rs/tokio/pull/7023
|
||||
[#7025]: https://github.com/tokio-rs/tokio/pull/7025
|
||||
[#7044]: https://github.com/tokio-rs/tokio/pull/7044
|
||||
[#7081]: https://github.com/tokio-rs/tokio/pull/7081
|
||||
[#7128]: https://github.com/tokio-rs/tokio/pull/7128
|
||||
[#7142]: https://github.com/tokio-rs/tokio/pull/7142
|
||||
[#7165]: https://github.com/tokio-rs/tokio/pull/7165
|
||||
[#7167]: https://github.com/tokio-rs/tokio/pull/7167
|
||||
[#7188]: https://github.com/tokio-rs/tokio/pull/7188
|
||||
|
||||
# 0.7.13 (December 4th, 2024)
|
||||
|
||||
### Fixed
|
||||
|
||||
- codec: fix incorrect handling of invalid utf-8 in `LinesCodec::decode_eof` ([#7011])
|
||||
|
||||
[#7011]: https://github.com/tokio-rs/tokio/pull/7011
|
||||
|
||||
# 0.7.12 (September 5th, 2024)
|
||||
|
||||
This release bumps the MSRV to 1.70. ([#6645])
|
||||
@@ -127,7 +191,7 @@ This release contains one performance improvement:
|
||||
[#5630]: https://github.com/tokio-rs/tokio/pull/5630
|
||||
[#5632]: https://github.com/tokio-rs/tokio/pull/5632
|
||||
|
||||
# 0.7.7 (February 12, 2023)
|
||||
# 0.7.7 (February 12th, 2023)
|
||||
|
||||
This release reverts the removal of the `Encoder` bound on the `FramedParts`
|
||||
constructor from [#5280] since it turned out to be a breaking change. ([#5450])
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-util"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.7.x" git tag.
|
||||
version = "0.7.12"
|
||||
version = "0.7.15"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -35,7 +35,7 @@ __docs_rs = ["futures-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.28.0", path = "../tokio", features = ["sync"] }
|
||||
bytes = "1.0.0"
|
||||
bytes = "1.5.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
futures-io = { version = "0.3.0", optional = true }
|
||||
@@ -45,7 +45,7 @@ slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
|
||||
tracing = { version = "0.1.29", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
[target.'cfg(tokio_unstable)'.dependencies]
|
||||
hashbrown = { version = "0.14.0", default-features = false, optional = true }
|
||||
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
@@ -68,3 +68,6 @@ rustc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"]
|
||||
|
||||
[package.metadata.playground]
|
||||
features = ["full"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -217,7 +217,7 @@ where
|
||||
|
||||
fn encode(&mut self, chunk: T, buf: &mut BytesMut) -> Result<(), AnyDelimiterCodecError> {
|
||||
let chunk = chunk.as_ref();
|
||||
buf.reserve(chunk.len() + 1);
|
||||
buf.reserve(chunk.len() + self.sequence_writer.len());
|
||||
buf.put(chunk.as_bytes());
|
||||
buf.put(self.sequence_writer.as_ref());
|
||||
|
||||
@@ -249,7 +249,7 @@ impl fmt::Display for AnyDelimiterCodecError {
|
||||
AnyDelimiterCodecError::MaxChunkLengthExceeded => {
|
||||
write!(f, "max chunk length exceeded")
|
||||
}
|
||||
AnyDelimiterCodecError::Io(e) => write!(f, "{}", e),
|
||||
AnyDelimiterCodecError::Io(e) => write!(f, "{e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ impl Decoder for LinesCodec {
|
||||
Ok(match self.decode(buf)? {
|
||||
Some(frame) => Some(frame),
|
||||
None => {
|
||||
self.next_index = 0;
|
||||
// No terminating newline - return remaining data, if any
|
||||
if buf.is_empty() || buf == &b"\r"[..] {
|
||||
None
|
||||
@@ -176,7 +177,6 @@ impl Decoder for LinesCodec {
|
||||
let line = buf.split_to(buf.len());
|
||||
let line = without_carriage_return(&line);
|
||||
let line = utf8(line)?;
|
||||
self.next_index = 0;
|
||||
Some(line.to_string())
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,7 @@ impl fmt::Display for LinesCodecError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LinesCodecError::MaxLineLengthExceeded => write!(f, "max line length exceeded"),
|
||||
LinesCodecError::Io(e) => write!(f, "{}", e),
|
||||
LinesCodecError::Io(e) => write!(f, "{e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
//! The main method on the `Encoder` trait is the [`encode`] method. This method
|
||||
//! takes an item that is being written, and a buffer to write the item to. The
|
||||
//! buffer may already contain data, and in this case, the encoder should append
|
||||
//! the new frame the to buffer rather than overwrite the existing data.
|
||||
//! the new frame to the buffer rather than overwrite the existing data.
|
||||
//!
|
||||
//! It is guaranteed that, from one call to `encode` to another, the provided
|
||||
//! buffer will contain the exact same data as before, except that some of the
|
||||
|
||||
@@ -1,5 +1,110 @@
|
||||
//! Compatibility between the `tokio::io` and `futures-io` versions of the
|
||||
//! `AsyncRead` and `AsyncWrite` traits.
|
||||
//!
|
||||
//! ## Bridging Tokio and Futures I/O with `compat()`
|
||||
//!
|
||||
//! The [`compat()`] function provides a compatibility layer that allows types implementing
|
||||
//! [`tokio::io::AsyncRead`] or [`tokio::io::AsyncWrite`] to be used as their
|
||||
//! [`futures::io::AsyncRead`] or [`futures::io::AsyncWrite`] counterparts — and vice versa.
|
||||
//!
|
||||
//! This is especially useful when working with libraries that expect I/O types from one ecosystem
|
||||
//! (usually `futures`) but you are using types from the other (usually `tokio`).
|
||||
//!
|
||||
//! ## Compatibility Overview
|
||||
//!
|
||||
//! | Inner Type Implements... | `Compat<T>` Implements... |
|
||||
//! |-----------------------------|-----------------------------|
|
||||
//! | [`tokio::io::AsyncRead`] | [`futures::io::AsyncRead`] |
|
||||
//! | [`futures::io::AsyncRead`] | [`tokio::io::AsyncRead`] |
|
||||
//! | [`tokio::io::AsyncWrite`] | [`futures::io::AsyncWrite`] |
|
||||
//! | [`futures::io::AsyncWrite`] | [`tokio::io::AsyncWrite`] |
|
||||
//!
|
||||
//! ## Feature Flag
|
||||
//!
|
||||
//! This functionality is available through the `compat` feature flag:
|
||||
//!
|
||||
//! ```toml
|
||||
//! tokio-util = { version = "...", features = ["compat"] }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Example 1: Tokio -> Futures (`AsyncRead`)
|
||||
//!
|
||||
//! This example demonstrates sending data over a [`tokio::net::TcpStream`] and using
|
||||
//! [`futures::io::AsyncReadExt::read`] from the `futures` crate to read it after adapting the
|
||||
//! stream via [`compat()`].
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use tokio::net::{TcpListener, TcpStream};
|
||||
//! use tokio::io::AsyncWriteExt;
|
||||
//! use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
//! use futures::io::AsyncReadExt;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> std::io::Result<()> {
|
||||
//! let listener = TcpListener::bind("127.0.0.1:8081").await?;
|
||||
//!
|
||||
//! tokio::spawn(async {
|
||||
//! let mut client = TcpStream::connect("127.0.0.1:8081").await.unwrap();
|
||||
//! client.write_all(b"Hello World").await.unwrap();
|
||||
//! });
|
||||
//!
|
||||
//! let (stream, _) = listener.accept().await?;
|
||||
//!
|
||||
//! // Adapt `tokio::TcpStream` to be used with `futures::io::AsyncReadExt`
|
||||
//! let mut compat_stream = stream.compat();
|
||||
//! let mut buffer = [0; 20];
|
||||
//! let n = compat_stream.read(&mut buffer).await?;
|
||||
//! println!("Received: {}", String::from_utf8_lossy(&buffer[..n]));
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Example 2: Futures -> Tokio (`AsyncRead`)
|
||||
//!
|
||||
//! The reverse is also possible: you can take a [`futures::io::AsyncRead`] (e.g. a cursor) and
|
||||
//! adapt it to be used with [`tokio::io::AsyncReadExt::read_to_end`]
|
||||
//!
|
||||
//! ```
|
||||
//! use futures::io::Cursor;
|
||||
//! use tokio_util::compat::FuturesAsyncReadCompatExt;
|
||||
//! use tokio::io::AsyncReadExt;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let future = async {
|
||||
//! let reader = Cursor::new(b"Hello from futures");
|
||||
//! let mut compat_reader = reader.compat();
|
||||
//! let mut buf = Vec::new();
|
||||
//! compat_reader.read_to_end(&mut buf).await.unwrap();
|
||||
//! assert_eq!(&buf, b"Hello from futures");
|
||||
//! };
|
||||
//!
|
||||
//! // Run the future inside a Tokio runtime
|
||||
//! tokio::runtime::Runtime::new().unwrap().block_on(future);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Common Use Cases
|
||||
//!
|
||||
//! - Using `tokio` sockets with `async-tungstenite`, `async-compression`, or `futures-rs`-based
|
||||
//! libraries.
|
||||
//! - Bridging I/O interfaces between mixed-ecosystem libraries.
|
||||
//! - Avoiding rewrites or duplication of I/O code in async environments.
|
||||
//!
|
||||
//! ## See Also
|
||||
//!
|
||||
//! - [`Compat`] type
|
||||
//! - [`TokioAsyncReadCompatExt`]
|
||||
//! - [`FuturesAsyncReadCompatExt`]
|
||||
//! - [`tokio::io`]
|
||||
//! - [`futures::io`]
|
||||
//!
|
||||
//! [`futures::io`]: https://docs.rs/futures/latest/futures/io/
|
||||
//! [`futures::io::AsyncRead`]: https://docs.rs/futures/latest/futures/io/trait.AsyncRead.html
|
||||
//! [`futures::io::AsyncWrite`]: https://docs.rs/futures/latest/futures/io/trait.AsyncWrite.html
|
||||
//! [`futures::io::AsyncReadExt::read`]: https://docs.rs/futures/latest/futures/io/trait.AsyncReadExt.html#method.read
|
||||
//! [`compat()`]: TokioAsyncReadCompatExt::compat
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
|
||||
@@ -150,6 +150,21 @@ where
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<tokio::io::Result<()>> {
|
||||
delegate_call!(self.poll_shutdown(cx))
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<std::result::Result<usize, std::io::Error>> {
|
||||
delegate_call!(self.poll_write_vectored(cx, bufs))
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
match self {
|
||||
Self::Left(l) => l.is_write_vectored(),
|
||||
Self::Right(r) => r.is_write_vectored(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> futures_core::stream::Stream for Either<L, R>
|
||||
|
||||
@@ -18,6 +18,9 @@ mod sink_writer;
|
||||
mod stream_reader;
|
||||
|
||||
cfg_io_util! {
|
||||
mod read_arc;
|
||||
pub use self::read_arc::read_exact_arc;
|
||||
|
||||
mod sync_bridge;
|
||||
pub use self::sync_bridge::SyncIoBridge;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use std::io;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
|
||||
/// Read data from an `AsyncRead` into an `Arc`.
|
||||
///
|
||||
/// This uses `Arc::new_uninit_slice` and reads into the resulting uninitialized `Arc`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// use tokio_util::io::read_exact_arc;
|
||||
///
|
||||
/// let read = tokio::io::repeat(42);
|
||||
///
|
||||
/// let arc = read_exact_arc(read, 4).await?;
|
||||
///
|
||||
/// assert_eq!(&arc[..], &[42; 4]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn read_exact_arc<R: AsyncRead>(read: R, len: usize) -> io::Result<Arc<[u8]>> {
|
||||
tokio::pin!(read);
|
||||
// TODO(MSRV 1.82): When bumping MSRV, switch to `Arc::new_uninit_slice(len)`. The following is
|
||||
// equivalent, and generates the same assembly, but works without requiring MSRV 1.82.
|
||||
let arc: Arc<[MaybeUninit<u8>]> = (0..len).map(|_| MaybeUninit::uninit()).collect();
|
||||
// TODO(MSRV future): Use `Arc::get_mut_unchecked` once it's stabilized.
|
||||
// SAFETY: We're the only owner of the `Arc`, and we keep the `Arc` valid throughout this loop
|
||||
// as we write through this reference.
|
||||
let mut buf = unsafe { &mut *(Arc::as_ptr(&arc) as *mut [MaybeUninit<u8>]) };
|
||||
while !buf.is_empty() {
|
||||
if read.read_buf(&mut buf).await? == 0 {
|
||||
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "early eof"));
|
||||
}
|
||||
}
|
||||
// TODO(MSRV 1.82): When bumping MSRV, switch to `arc.assume_init()`. The following is
|
||||
// equivalent, and generates the same assembly, but works without requiring MSRV 1.82.
|
||||
// SAFETY: This changes `[MaybeUninit<u8>]` to `[u8]`, and we've initialized all the bytes in
|
||||
// the loop above.
|
||||
Ok(unsafe { Arc::from_raw(Arc::into_raw(arc) as *const [u8]) })
|
||||
}
|
||||
@@ -169,7 +169,7 @@ where
|
||||
/// Convert a stream of byte chunks into an [`AsyncRead`].
|
||||
///
|
||||
/// The item should be a [`Result`] with the ok variant being something that
|
||||
/// implements the [`Buf`] trait (e.g. `Vec<u8>` or `Bytes`). The error
|
||||
/// implements the [`Buf`] trait (e.g. `Cursor<Vec<u8>>` or `Bytes`). The error
|
||||
/// should be convertible into an [io error].
|
||||
///
|
||||
/// [`Result`]: std::result::Result
|
||||
|
||||
@@ -5,7 +5,258 @@ use tokio::io::{
|
||||
};
|
||||
|
||||
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
|
||||
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
|
||||
/// a [`tokio::io::AsyncWrite`] synchronously as a [`std::io::Write`].
|
||||
///
|
||||
/// # Alternatives
|
||||
///
|
||||
/// In many cases, there are better alternatives to using `SyncIoBridge`, especially
|
||||
/// if you want to avoid blocking the async runtime. Consider the following scenarios:
|
||||
///
|
||||
/// When hashing data, using `SyncIoBridge` can lead to suboptimal performance and
|
||||
/// might not fully leverage the async capabilities of the system.
|
||||
///
|
||||
/// ### Why It Matters:
|
||||
///
|
||||
/// `SyncIoBridge` allows you to use asynchronous I/O operations in an synchronous
|
||||
/// context by blocking the current thread. However, this can be inefficient because:
|
||||
/// - **Inefficient Resource Usage**: `SyncIoBridge` takes up an entire OS thread,
|
||||
/// which is inefficient compared to asynchronous code that can multiplex many
|
||||
/// tasks on a single thread.
|
||||
/// - **Thread Pool Saturation**: Excessive use of `SyncIoBridge` can exhaust the
|
||||
/// async runtime's thread pool, reducing the number of threads available for
|
||||
/// other tasks and impacting overall performance.
|
||||
/// - **Missed Concurrency Benefits**: By using synchronous operations with
|
||||
/// `SyncIoBridge`, you lose the ability to interleave tasks efficiently,
|
||||
/// which is a key advantage of asynchronous programming.
|
||||
///
|
||||
/// ## Example 1: Hashing Data
|
||||
///
|
||||
/// The use of `SyncIoBridge` is unnecessary when hashing data. Instead, you can
|
||||
/// process the data asynchronously by reading it into memory, which avoids blocking
|
||||
/// the async runtime.
|
||||
///
|
||||
/// There are two strategies for avoiding `SyncIoBridge` when hashing data. When
|
||||
/// the data fits into memory, the easiest is to read the data into a `Vec<u8>`
|
||||
/// and hash it:
|
||||
///
|
||||
/// Explanation: This example demonstrates how to asynchronously read data from a
|
||||
/// reader into memory and hash it using a synchronous hashing function. The
|
||||
/// `SyncIoBridge` is avoided, ensuring that the async runtime is not blocked.
|
||||
/// ```rust
|
||||
/// use tokio::io::AsyncReadExt;
|
||||
/// use tokio::io::AsyncRead;
|
||||
/// use std::io::Cursor;
|
||||
/// # mod blake3 { pub fn hash(_: &[u8]) {} }
|
||||
///
|
||||
/// async fn hash_contents(mut reader: impl AsyncRead + Unpin) -> Result<(), std::io::Error> {
|
||||
/// // Read all data from the reader into a Vec<u8>.
|
||||
/// let mut data = Vec::new();
|
||||
/// reader.read_to_end(&mut data).await?;
|
||||
///
|
||||
/// // Hash the data using the blake3 hashing function.
|
||||
/// let hash = blake3::hash(&data);
|
||||
///
|
||||
/// Ok(hash)
|
||||
///}
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// hash_contents(reader).await
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// When the data doesn't fit into memory, the hashing library will usually
|
||||
/// provide a `hasher` that you can repeatedly call `update` on to hash the data
|
||||
/// one chunk at the time.
|
||||
///
|
||||
/// Explanation: This example demonstrates how to asynchronously stream data in
|
||||
/// chunks for hashing. Each chunk is read asynchronously, and the hash is updated
|
||||
/// incrementally. This avoids blocking and improves performance over using
|
||||
/// `SyncIoBridge`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::io::AsyncReadExt;
|
||||
/// use tokio::io::AsyncRead;
|
||||
/// use std::io::Cursor;
|
||||
/// # struct Hasher;
|
||||
/// # impl Hasher { pub fn update(&mut self, _: &[u8]) {} pub fn finalize(&self) {} }
|
||||
///
|
||||
/// /// Asynchronously streams data from an async reader, processes it in chunks,
|
||||
/// /// and hashes the data incrementally.
|
||||
/// async fn hash_stream(mut reader: impl AsyncRead + Unpin, mut hasher: Hasher) -> Result<(), std::io::Error> {
|
||||
/// // Create a buffer to read data into, sized for performance.
|
||||
/// let mut data = vec![0; 64 * 1024];
|
||||
/// loop {
|
||||
/// // Read data from the reader into the buffer.
|
||||
/// let len = reader.read(&mut data).await?;
|
||||
/// if len == 0 { break; } // Exit loop if no more data.
|
||||
///
|
||||
/// // Update the hash with the data read.
|
||||
/// hasher.update(&data[..len]);
|
||||
/// }
|
||||
///
|
||||
/// // Finalize the hash after all data has been processed.
|
||||
/// let hash = hasher.finalize();
|
||||
///
|
||||
/// Ok(hash)
|
||||
///}
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// let hasher = Hasher;
|
||||
/// hash_stream(reader, hasher).await
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// ## Example 2: Compressing Data
|
||||
///
|
||||
/// When compressing data, the use of `SyncIoBridge` is unnecessary as it introduces
|
||||
/// blocking and inefficient code. Instead, you can utilize an async compression library
|
||||
/// such as the [`async-compression`](https://docs.rs/async-compression/latest/async_compression/)
|
||||
/// crate, which is built to handle asynchronous data streams efficiently.
|
||||
///
|
||||
/// Explanation: This example shows how to asynchronously compress data using an
|
||||
/// async compression library. By reading and writing asynchronously, it avoids
|
||||
/// blocking and is more efficient than using `SyncIoBridge` with a non-async
|
||||
/// compression library.
|
||||
///
|
||||
/// ```ignore
|
||||
/// use async_compression::tokio::write::GzipEncoder;
|
||||
/// use std::io::Cursor;
|
||||
/// use tokio::io::AsyncRead;
|
||||
///
|
||||
/// /// Asynchronously compresses data from an async reader using Gzip and an async encoder.
|
||||
/// async fn compress_data(mut reader: impl AsyncRead + Unpin) -> Result<(), std::io::Error> {
|
||||
/// let writer = tokio::io::sink();
|
||||
///
|
||||
/// // Create a Gzip encoder that wraps the writer.
|
||||
/// let mut encoder = GzipEncoder::new(writer);
|
||||
///
|
||||
/// // Copy data from the reader to the encoder, compressing it.
|
||||
/// tokio::io::copy(&mut reader, &mut encoder).await?;
|
||||
///
|
||||
/// Ok(())
|
||||
///}
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// compress_data(reader).await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// ## Example 3: Parsing Data Formats
|
||||
///
|
||||
///
|
||||
/// `SyncIoBridge` is not ideal when parsing data formats such as `JSON`, as it
|
||||
/// blocks async operations. A more efficient approach is to read data asynchronously
|
||||
/// into memory and then `deserialize` it, avoiding unnecessary synchronization overhead.
|
||||
///
|
||||
/// Explanation: This example shows how to asynchronously read data into memory
|
||||
/// and then parse it as `JSON`. By avoiding `SyncIoBridge`, the asynchronous runtime
|
||||
/// remains unblocked, leading to better performance when working with asynchronous
|
||||
/// I/O streams.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use tokio::io::AsyncRead;
|
||||
/// use tokio::io::AsyncReadExt;
|
||||
/// use std::io::Cursor;
|
||||
/// # mod serde {
|
||||
/// # pub trait DeserializeOwned: 'static {}
|
||||
/// # impl<T: 'static> DeserializeOwned for T {}
|
||||
/// # }
|
||||
/// # mod serde_json {
|
||||
/// # use super::serde::DeserializeOwned;
|
||||
/// # pub fn from_slice<T: DeserializeOwned>(_: &[u8]) -> Result<T, std::io::Error> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// # #[derive(Debug)] struct MyStruct;
|
||||
///
|
||||
///
|
||||
/// async fn parse_json(mut reader: impl AsyncRead + Unpin) -> Result<MyStruct, std::io::Error> {
|
||||
/// // Read all data from the reader into a Vec<u8>.
|
||||
/// let mut data = Vec::new();
|
||||
/// reader.read_to_end(&mut data).await?;
|
||||
///
|
||||
/// // Deserialize the data from the Vec<u8> into a MyStruct instance.
|
||||
/// let value: MyStruct = serde_json::from_slice(&data)?;
|
||||
///
|
||||
/// Ok(value)
|
||||
///}
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// parse_json(reader).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Correct Usage of `SyncIoBridge` inside `spawn_blocking`
|
||||
///
|
||||
/// `SyncIoBridge` is mainly useful when you need to interface with synchronous
|
||||
/// libraries from an asynchronous context.
|
||||
///
|
||||
/// Explanation: This example shows how to use `SyncIoBridge` inside a `spawn_blocking`
|
||||
/// task to safely perform synchronous I/O without blocking the async runtime. The
|
||||
/// `spawn_blocking` ensures that the synchronous code is offloaded to a dedicated
|
||||
/// thread pool, preventing it from interfering with the async tasks.
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::task::spawn_blocking;
|
||||
/// use tokio_util::io::SyncIoBridge;
|
||||
/// use tokio::io::AsyncRead;
|
||||
/// use std::marker::Unpin;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// /// Wraps an async reader with `SyncIoBridge` and performs synchronous I/O operations in a blocking task.
|
||||
/// async fn process_sync_io(reader: impl AsyncRead + Unpin + Send + 'static) -> Result<Vec<u8>, std::io::Error> {
|
||||
/// // Wrap the async reader with `SyncIoBridge` to allow synchronous reading.
|
||||
/// let mut sync_reader = SyncIoBridge::new(reader);
|
||||
///
|
||||
/// // Spawn a blocking task to perform synchronous I/O operations.
|
||||
/// let result = spawn_blocking(move || {
|
||||
/// // Create an in-memory buffer to hold the copied data.
|
||||
/// let mut buffer = Vec::new();
|
||||
/// // Copy data from the sync_reader to the buffer.
|
||||
/// std::io::copy(&mut sync_reader, &mut buffer)?;
|
||||
/// // Return the buffer containing the copied data.
|
||||
/// Ok::<_, std::io::Error>(buffer)
|
||||
/// })
|
||||
/// .await??;
|
||||
///
|
||||
/// // Return the result from the blocking task.
|
||||
/// Ok(result)
|
||||
///}
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), std::io::Error> {
|
||||
/// // Example: In-memory data.
|
||||
/// let data = b"Hello, world!"; // A byte slice.
|
||||
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
|
||||
/// let result = process_sync_io(reader).await?;
|
||||
///
|
||||
/// // You can use `result` here as needed.
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct SyncIoBridge<T> {
|
||||
src: T,
|
||||
@@ -154,3 +405,15 @@ impl<T: Unpin> SyncIoBridge<T> {
|
||||
self.src
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for SyncIoBridge<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.src
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for SyncIoBridge<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.src
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -17,8 +16,6 @@
|
||||
//! This crate is not versioned in lockstep with the core
|
||||
//! [`tokio`] crate. However, `tokio-util` _will_ respect Rust's
|
||||
//! semantic versioning policy, especially with regard to breaking changes.
|
||||
//!
|
||||
//! [`tokio`]: https://docs.rs/tokio
|
||||
|
||||
#[macro_use]
|
||||
mod cfg;
|
||||
|
||||
@@ -287,6 +287,22 @@ impl CancellationToken {
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
/// Runs a future to completion and returns its result wrapped inside of an `Option`
|
||||
/// unless the `CancellationToken` is cancelled. In that case the function returns
|
||||
/// `None` and the future gets dropped.
|
||||
///
|
||||
/// The function takes self by value and returns a future that owns the token.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is only cancel safe if `fut` is cancel safe.
|
||||
pub async fn run_until_cancelled_owned<F>(self, fut: F) -> Option<F::Output>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
self.run_until_cancelled(fut).await
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl WaitForCancellationFuture =====
|
||||
|
||||
@@ -469,16 +469,19 @@ where
|
||||
///
|
||||
/// [`tokio::select!`]: tokio::select
|
||||
pub async fn join_next(&mut self) -> Option<(K, Result<V, JoinError>)> {
|
||||
let (res, id) = match self.tasks.join_next_with_id().await {
|
||||
Some(Ok((id, output))) => (Ok(output), id),
|
||||
Some(Err(e)) => {
|
||||
let id = e.id();
|
||||
(Err(e), id)
|
||||
loop {
|
||||
let (res, id) = match self.tasks.join_next_with_id().await {
|
||||
Some(Ok((id, output))) => (Ok(output), id),
|
||||
Some(Err(e)) => {
|
||||
let id = e.id();
|
||||
(Err(e), id)
|
||||
}
|
||||
None => return None,
|
||||
};
|
||||
if let Some(key) = self.remove_by_id(id) {
|
||||
break Some((key, res));
|
||||
}
|
||||
None => return None,
|
||||
};
|
||||
let key = self.remove_by_id(id)?;
|
||||
Some((key, res))
|
||||
}
|
||||
}
|
||||
|
||||
/// Aborts all tasks and waits for them to finish shutting down.
|
||||
|
||||
@@ -39,9 +39,9 @@ use tokio::task::{spawn_local, JoinHandle, LocalSet};
|
||||
/// task::spawn_local(async move {
|
||||
/// println!("{}", data_clone);
|
||||
/// });
|
||||
///
|
||||
///
|
||||
/// data.to_string()
|
||||
/// }
|
||||
/// }
|
||||
/// }).await.unwrap();
|
||||
/// println!("output: {}", output);
|
||||
/// }
|
||||
@@ -249,7 +249,7 @@ impl LocalPool {
|
||||
// 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);
|
||||
panic!("Failed to send job to worker: {e}");
|
||||
}
|
||||
|
||||
// Wait for the task's join handle
|
||||
@@ -260,7 +260,7 @@ impl LocalPool {
|
||||
// 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);
|
||||
panic!("Worker failed to send join handle: {e}");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -284,12 +284,12 @@ impl LocalPool {
|
||||
// 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);
|
||||
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);
|
||||
panic!("spawn_pinned task failed: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ use tokio::{
|
||||
/// `TaskTracker`, this does not happen. Once tasks exit, they are immediately removed from the
|
||||
/// `TaskTracker`.
|
||||
///
|
||||
/// Note that unlike [`JoinSet`], dropping a `TaskTracker` does not abort the tasks.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// For more examples, please see the topic page on [graceful shutdown].
|
||||
|
||||
@@ -665,7 +665,7 @@ impl<T> DelayQueue<T> {
|
||||
// The delay is already expired, store it in the expired queue
|
||||
self.expired.push(key, &mut self.slab);
|
||||
}
|
||||
Err((_, err)) => panic!("invalid deadline; err={:?}", err),
|
||||
Err((_, err)) => panic!("invalid deadline; err={err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,86 +39,10 @@ const LEVEL_MULT: usize = 64;
|
||||
|
||||
impl<T: Stack> Level<T> {
|
||||
pub(crate) fn new(level: usize) -> Level<T> {
|
||||
// Rust's derived implementations for arrays require that the value
|
||||
// contained by the array be `Copy`. So, here we have to manually
|
||||
// initialize every single slot.
|
||||
macro_rules! s {
|
||||
() => {
|
||||
T::default()
|
||||
};
|
||||
}
|
||||
|
||||
Level {
|
||||
level,
|
||||
occupied: 0,
|
||||
slot: [
|
||||
// It does not look like the necessary traits are
|
||||
// derived for [T; 64].
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
s!(),
|
||||
],
|
||||
slot: std::array::from_fn(|_| T::default()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -280,13 +280,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_level_for() {
|
||||
for pos in 0..64 {
|
||||
assert_eq!(
|
||||
0,
|
||||
level_for(0, pos),
|
||||
"level_for({}) -- binary = {:b}",
|
||||
pos,
|
||||
pos
|
||||
);
|
||||
assert_eq!(0, level_for(0, pos), "level_for({pos}) -- binary = {pos:b}");
|
||||
}
|
||||
|
||||
for level in 1..5 {
|
||||
@@ -295,9 +289,7 @@ mod test {
|
||||
assert_eq!(
|
||||
level,
|
||||
level_for(0, a as u64),
|
||||
"level_for({}) -- binary = {:b}",
|
||||
a,
|
||||
a
|
||||
"level_for({a}) -- binary = {a:b}"
|
||||
);
|
||||
|
||||
if pos > level {
|
||||
@@ -305,9 +297,7 @@ mod test {
|
||||
assert_eq!(
|
||||
level,
|
||||
level_for(0, a as u64),
|
||||
"level_for({}) -- binary = {:b}",
|
||||
a,
|
||||
a
|
||||
"level_for({a}) -- binary = {a:b}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -316,9 +306,7 @@ mod test {
|
||||
assert_eq!(
|
||||
level,
|
||||
level_for(0, a as u64),
|
||||
"level_for({}) -- binary = {:b}",
|
||||
a,
|
||||
a
|
||||
"level_for({a}) -- binary = {a:b}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ use tokio::{io::ReadBuf, net::UdpSocket};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures_sink::Sink;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{ready, Context, Poll};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
};
|
||||
use std::{io, mem::MaybeUninit};
|
||||
|
||||
/// A unified [`Stream`] and [`Sink`] interface to an underlying `UdpSocket`, using
|
||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
||||
@@ -83,7 +83,7 @@ where
|
||||
let addr = {
|
||||
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
|
||||
// transparent wrapper around `[MaybeUninit<u8>]`.
|
||||
let buf = unsafe { &mut *(pin.rd.chunk_mut() as *mut _ as *mut [MaybeUninit<u8>]) };
|
||||
let buf = unsafe { pin.rd.chunk_mut().as_uninit_slice_mut() };
|
||||
let mut read = ReadBuf::uninit(buf);
|
||||
let ptr = read.filled().as_ptr();
|
||||
let res = ready!(pin.socket.borrow().poll_recv_from(cx, &mut read));
|
||||
@@ -91,9 +91,10 @@ where
|
||||
assert_eq!(ptr, read.filled().as_ptr());
|
||||
let addr = res?;
|
||||
|
||||
let filled = read.filled().len();
|
||||
// Safety: This is guaranteed to be the number of initialized (and read) bytes due
|
||||
// to the invariants provided by `ReadBuf::filled`.
|
||||
unsafe { pin.rd.advance_mut(read.filled().len()) };
|
||||
unsafe { pin.rd.advance_mut(filled) };
|
||||
|
||||
addr
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
use bytes::{Buf, BufMut};
|
||||
use std::io::{self, IoSlice};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::pin::Pin;
|
||||
use std::task::{ready, Context, Poll};
|
||||
|
||||
@@ -59,7 +58,7 @@ pub fn poll_read_buf<T: AsyncRead + ?Sized, B: BufMut>(
|
||||
|
||||
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
|
||||
// transparent wrapper around `[MaybeUninit<u8>]`.
|
||||
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
|
||||
let dst = unsafe { dst.as_uninit_slice_mut() };
|
||||
let mut buf = ReadBuf::uninit(dst);
|
||||
let ptr = buf.filled().as_ptr();
|
||||
ready!(io.poll_read(cx, &mut buf)?);
|
||||
|
||||
+24
-45
@@ -62,6 +62,19 @@ fn lines_decoder() {
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_invalid_utf8() {
|
||||
let mut codec = LinesCodec::new();
|
||||
let buf = &mut BytesMut::new();
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"line 1\xc3\x28");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert!(codec.decode_eof(buf).is_err());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
buf.put_slice(b"line 22222222222222\n");
|
||||
assert_eq!("line 22222222222222", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length() {
|
||||
const MAX_LENGTH: usize = 6;
|
||||
@@ -75,32 +88,17 @@ fn lines_decoder_max_length() {
|
||||
assert!(codec.decode(buf).is_err());
|
||||
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert!(line.len() <= MAX_LENGTH, "{line:?}.len() <= {MAX_LENGTH:?}");
|
||||
assert_eq!("line 2", line);
|
||||
|
||||
assert!(codec.decode(buf).is_err());
|
||||
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert!(line.len() <= MAX_LENGTH, "{line:?}.len() <= {MAX_LENGTH:?}");
|
||||
assert_eq!("line 4", line);
|
||||
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert!(line.len() <= MAX_LENGTH, "{line:?}.len() <= {MAX_LENGTH:?}");
|
||||
assert_eq!("", line);
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
@@ -109,12 +107,7 @@ fn lines_decoder_max_length() {
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
let line = codec.decode_eof(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert!(line.len() <= MAX_LENGTH, "{line:?}.len() <= {MAX_LENGTH:?}");
|
||||
assert_eq!("\rk", line);
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
@@ -273,18 +266,14 @@ fn any_delimiters_decoder_max_length() {
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
"{chunk:?}.len() <= {MAX_LENGTH:?}"
|
||||
);
|
||||
assert_eq!("chunk 2", chunk);
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
"{chunk:?}.len() <= {MAX_LENGTH:?}"
|
||||
);
|
||||
assert_eq!("chunk 3", chunk);
|
||||
|
||||
@@ -292,36 +281,28 @@ fn any_delimiters_decoder_max_length() {
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
"{chunk:?}.len() <= {MAX_LENGTH:?}"
|
||||
);
|
||||
assert_eq!("", chunk);
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
"{chunk:?}.len() <= {MAX_LENGTH:?}"
|
||||
);
|
||||
assert_eq!("chunk 4", chunk);
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
"{chunk:?}.len() <= {MAX_LENGTH:?}"
|
||||
);
|
||||
assert_eq!("", chunk);
|
||||
|
||||
let chunk = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
"{chunk:?}.len() <= {MAX_LENGTH:?}"
|
||||
);
|
||||
assert_eq!("", chunk);
|
||||
|
||||
@@ -333,9 +314,7 @@ fn any_delimiters_decoder_max_length() {
|
||||
let chunk = codec.decode_eof(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
chunk.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
chunk,
|
||||
MAX_LENGTH
|
||||
"{chunk:?}.len() <= {MAX_LENGTH:?}"
|
||||
);
|
||||
assert_eq!("k", chunk);
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ impl Write for Mock {
|
||||
Ok(data.len())
|
||||
}
|
||||
Some(Err(e)) => Err(e),
|
||||
None => panic!("unexpected write; {:?}", src),
|
||||
None => panic!("unexpected write; {src:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ async fn correct_behavior_on_errors() {
|
||||
let mut had_error = false;
|
||||
loop {
|
||||
let item = stream.next().await.unwrap();
|
||||
println!("{:?}", item);
|
||||
println!("{item:?}");
|
||||
match item {
|
||||
Ok(bytes) => {
|
||||
let bytes = &*bytes;
|
||||
|
||||
@@ -789,7 +789,7 @@ impl AsyncWrite for Mock {
|
||||
match self.calls.pop_front() {
|
||||
Some(Poll::Ready(Ok(Op::Data(data)))) => {
|
||||
let len = data.len();
|
||||
assert!(src.len() >= len, "expect={:?}; actual={:?}", data, src);
|
||||
assert!(src.len() >= len, "expect={data:?}; actual={src:?}");
|
||||
assert_eq!(&data[..], &src[..len]);
|
||||
Poll::Ready(Ok(len))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(not(target_os = "wasi"))] // Wasi doesn't support threads
|
||||
// Blocked on https://github.com/rust-lang/miri/issues/3911
|
||||
#![cfg(not(miri))]
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -493,3 +493,58 @@ fn run_until_cancelled_test() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_until_cancelled_owned_test() {
|
||||
let (waker, _) = new_count_waker();
|
||||
|
||||
{
|
||||
let token = CancellationToken::new();
|
||||
let to_cancel = token.clone();
|
||||
|
||||
let takes_ownership = move |token: CancellationToken| {
|
||||
token.run_until_cancelled_owned(std::future::pending::<()>())
|
||||
};
|
||||
|
||||
let fut = takes_ownership(token);
|
||||
pin!(fut);
|
||||
|
||||
assert_eq!(
|
||||
Poll::Pending,
|
||||
fut.as_mut().poll(&mut Context::from_waker(&waker))
|
||||
);
|
||||
|
||||
to_cancel.cancel();
|
||||
|
||||
assert_eq!(
|
||||
Poll::Ready(None),
|
||||
fut.as_mut().poll(&mut Context::from_waker(&waker))
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
|
||||
let token = CancellationToken::new();
|
||||
let takes_ownership = move |token: CancellationToken, rx: oneshot::Receiver<()>| {
|
||||
token.run_until_cancelled_owned(async move {
|
||||
rx.await.unwrap();
|
||||
42
|
||||
})
|
||||
};
|
||||
let fut = takes_ownership(token, rx);
|
||||
pin!(fut);
|
||||
|
||||
assert_eq!(
|
||||
Poll::Pending,
|
||||
fut.as_mut().poll(&mut Context::from_waker(&waker))
|
||||
);
|
||||
|
||||
tx.send(()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Poll::Ready(Some(42)),
|
||||
fut.as_mut().poll(&mut Context::from_waker(&waker))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![allow(unknown_lints, unexpected_cfgs)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(all(feature = "rt", tokio_unstable))]
|
||||
|
||||
@@ -298,3 +297,49 @@ async fn abort_all() {
|
||||
assert!(was_seen);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicate_keys() {
|
||||
let mut map = JoinMap::new();
|
||||
map.spawn(1, async { 1 });
|
||||
map.spawn(1, async { 2 });
|
||||
|
||||
assert_eq!(map.len(), 1);
|
||||
|
||||
let (key, res) = map.join_next().await.unwrap();
|
||||
assert_eq!(key, 1);
|
||||
assert_eq!(res.unwrap(), 2);
|
||||
|
||||
assert!(map.join_next().await.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicate_keys2() {
|
||||
let (send, recv) = oneshot::channel::<()>();
|
||||
|
||||
let mut map = JoinMap::new();
|
||||
map.spawn(1, async { 1 });
|
||||
map.spawn(1, async {
|
||||
recv.await.unwrap();
|
||||
2
|
||||
});
|
||||
|
||||
assert_eq!(map.len(), 1);
|
||||
|
||||
tokio::select! {
|
||||
biased;
|
||||
res = map.join_next() => match res {
|
||||
Some((_key, res)) => panic!("Task {res:?} exited."),
|
||||
None => panic!("Phantom task completeion."),
|
||||
},
|
||||
() = tokio::task::yield_now() => {},
|
||||
}
|
||||
|
||||
send.send(()).unwrap();
|
||||
|
||||
let (key, res) = map.join_next().await.unwrap();
|
||||
assert_eq!(key, 1);
|
||||
assert_eq!(res.unwrap(), 2);
|
||||
|
||||
assert!(map.join_next().await.is_none());
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ async fn multi_delay_at_start() {
|
||||
|
||||
let start = Instant::now();
|
||||
for elapsed in 0..1200 {
|
||||
println!("elapsed: {:?}", elapsed);
|
||||
println!("elapsed: {elapsed:?}");
|
||||
let elapsed = elapsed + 1;
|
||||
tokio::time::sleep_until(start + ms(elapsed)).await;
|
||||
|
||||
@@ -328,7 +328,7 @@ async fn remove_at_timer_wheel_threshold() {
|
||||
let entry = queue.remove(&key1).into_inner();
|
||||
assert_eq!(entry, "foo");
|
||||
}
|
||||
other => panic!("other: {:?}", other),
|
||||
other => panic!("other: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+276
-1
@@ -1,4 +1,265 @@
|
||||
# 1.41.0 (Oct 22th, 2024)
|
||||
# 1.45.1 (May 24th, 2025)
|
||||
|
||||
This fixes a regression on the wasm32-unknown-unknown target, where code that
|
||||
previously did not panic due to calls to `Instant::now()` started failing. This
|
||||
is due to the stabilization of the first time-based metric.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Disable time-based metrics on wasm32-unknown-unknown ([#7322])
|
||||
|
||||
[#7322]: https://github.com/tokio-rs/tokio/pull/7322
|
||||
|
||||
# 1.45.0 (May 5th, 2025)
|
||||
|
||||
### Added
|
||||
|
||||
- metrics: stabilize `worker_total_busy_duration`, `worker_park_count`, and
|
||||
`worker_unpark_count` ([#6899], [#7276])
|
||||
- process: add `Command::spawn_with` ([#7249])
|
||||
|
||||
### Changed
|
||||
|
||||
- io: do not require `Unpin` for some trait impls ([#7204])
|
||||
- rt: mark `runtime::Handle` as unwind safe ([#7230])
|
||||
- time: revert internal sharding implementation ([#7226])
|
||||
|
||||
### Unstable
|
||||
|
||||
- rt: remove alt multi-threaded runtime ([#7275])
|
||||
|
||||
[#6899]: https://github.com/tokio-rs/tokio/pull/6899
|
||||
[#7276]: https://github.com/tokio-rs/tokio/pull/7276
|
||||
[#7249]: https://github.com/tokio-rs/tokio/pull/7249
|
||||
[#7204]: https://github.com/tokio-rs/tokio/pull/7204
|
||||
[#7230]: https://github.com/tokio-rs/tokio/pull/7230
|
||||
[#7226]: https://github.com/tokio-rs/tokio/pull/7226
|
||||
[#7275]: https://github.com/tokio-rs/tokio/pull/7275
|
||||
|
||||
|
||||
# 1.44.2 (April 5th, 2025)
|
||||
|
||||
This release fixes a soundness issue in the broadcast channel. The channel
|
||||
accepts values that are `Send` but `!Sync`. Previously, the channel called
|
||||
`clone()` on these values without synchronizing. This release fixes the channel
|
||||
by synchronizing calls to `.clone()` (Thanks Austin Bonander for finding and
|
||||
reporting the issue).
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: synchronize `clone()` call in broadcast channel ([#7232])
|
||||
|
||||
[#7232]: https://github.com/tokio-rs/tokio/pull/7232
|
||||
|
||||
# 1.44.1 (March 13th, 2025)
|
||||
|
||||
### Fixed
|
||||
|
||||
- rt: skip defer queue in `block_in_place` context ([#7216])
|
||||
|
||||
[#7216]: https://github.com/tokio-rs/tokio/pull/7216
|
||||
|
||||
# 1.44.0 (March 7th, 2025)
|
||||
|
||||
This release changes the `from_std` method on sockets to panic if a blocking
|
||||
socket is provided. We determined this change is not a breaking change as Tokio is not
|
||||
intended to operate using blocking sockets. Doing so results in runtime hangs and
|
||||
should be considered a bug. Accidentally passing a blocking socket to Tokio is one
|
||||
of the most common user mistakes. If this change causes an issue for you, please
|
||||
comment on [#7172].
|
||||
|
||||
### Added
|
||||
|
||||
- coop: add `task::coop` module ([#7116])
|
||||
- process: add `Command::get_kill_on_drop()` ([#7086])
|
||||
- sync: add `broadcast::Sender::closed` ([#6685], [#7090])
|
||||
- sync: add `broadcast::WeakSender` ([#7100])
|
||||
- sync: add `oneshot::Receiver::is_empty()` ([#7153])
|
||||
- sync: add `oneshot::Receiver::is_terminated()` ([#7152])
|
||||
|
||||
### Fixed
|
||||
|
||||
- fs: empty reads on `File` should not start a background read ([#7139])
|
||||
- process: calling `start_kill` on exited child should not fail ([#7160])
|
||||
- signal: fix `CTRL_CLOSE`, `CTRL_LOGOFF`, `CTRL_SHUTDOWN` on windows ([#7122])
|
||||
- sync: properly handle panic during mpsc drop ([#7094])
|
||||
|
||||
### Changes
|
||||
|
||||
- runtime: clean up magic number in registration set ([#7112])
|
||||
- coop: make coop yield using waker defer strategy ([#7185])
|
||||
- macros: make `select!` budget-aware ([#7164])
|
||||
- net: panic when passing a blocking socket to `from_std` ([#7166])
|
||||
- io: clean up buffer casts ([#7142])
|
||||
|
||||
### Changes to unstable APIs
|
||||
|
||||
- rt: add before and after task poll callbacks ([#7120])
|
||||
- tracing: make the task tracing API unstable public ([#6972])
|
||||
|
||||
### Documented
|
||||
|
||||
- docs: fix nesting of sections in top-level docs ([#7159])
|
||||
- fs: rename symlink and hardlink parameter names ([#7143])
|
||||
- io: swap reader/writer in simplex doc test ([#7176])
|
||||
- macros: docs about `select!` alternatives ([#7110])
|
||||
- net: rename the argument for `send_to` ([#7146])
|
||||
- process: add example for reading `Child` stdout ([#7141])
|
||||
- process: clarify `Child::kill` behavior ([#7162])
|
||||
- process: fix grammar of the `ChildStdin` struct doc comment ([#7192])
|
||||
- runtime: consistently use `worker_threads` instead of `core_threads` ([#7186])
|
||||
|
||||
[#6685]: https://github.com/tokio-rs/tokio/pull/6685
|
||||
[#6972]: https://github.com/tokio-rs/tokio/pull/6972
|
||||
[#7086]: https://github.com/tokio-rs/tokio/pull/7086
|
||||
[#7090]: https://github.com/tokio-rs/tokio/pull/7090
|
||||
[#7094]: https://github.com/tokio-rs/tokio/pull/7094
|
||||
[#7100]: https://github.com/tokio-rs/tokio/pull/7100
|
||||
[#7110]: https://github.com/tokio-rs/tokio/pull/7110
|
||||
[#7112]: https://github.com/tokio-rs/tokio/pull/7112
|
||||
[#7116]: https://github.com/tokio-rs/tokio/pull/7116
|
||||
[#7120]: https://github.com/tokio-rs/tokio/pull/7120
|
||||
[#7122]: https://github.com/tokio-rs/tokio/pull/7122
|
||||
[#7139]: https://github.com/tokio-rs/tokio/pull/7139
|
||||
[#7141]: https://github.com/tokio-rs/tokio/pull/7141
|
||||
[#7142]: https://github.com/tokio-rs/tokio/pull/7142
|
||||
[#7143]: https://github.com/tokio-rs/tokio/pull/7143
|
||||
[#7146]: https://github.com/tokio-rs/tokio/pull/7146
|
||||
[#7152]: https://github.com/tokio-rs/tokio/pull/7152
|
||||
[#7153]: https://github.com/tokio-rs/tokio/pull/7153
|
||||
[#7159]: https://github.com/tokio-rs/tokio/pull/7159
|
||||
[#7160]: https://github.com/tokio-rs/tokio/pull/7160
|
||||
[#7162]: https://github.com/tokio-rs/tokio/pull/7162
|
||||
[#7164]: https://github.com/tokio-rs/tokio/pull/7164
|
||||
[#7166]: https://github.com/tokio-rs/tokio/pull/7166
|
||||
[#7172]: https://github.com/tokio-rs/tokio/pull/7172
|
||||
[#7176]: https://github.com/tokio-rs/tokio/pull/7176
|
||||
[#7185]: https://github.com/tokio-rs/tokio/pull/7185
|
||||
[#7186]: https://github.com/tokio-rs/tokio/pull/7186
|
||||
[#7192]: https://github.com/tokio-rs/tokio/pull/7192
|
||||
|
||||
# 1.43.1 (April 5th, 2025)
|
||||
|
||||
This release fixes a soundness issue in the broadcast channel. The channel
|
||||
accepts values that are `Send` but `!Sync`. Previously, the channel called
|
||||
`clone()` on these values without synchronizing. This release fixes the channel
|
||||
by synchronizing calls to `.clone()` (Thanks Austin Bonander for finding and
|
||||
reporting the issue).
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: synchronize `clone()` call in broadcast channel ([#7232])
|
||||
|
||||
[#7232]: https://github.com/tokio-rs/tokio/pull/7232
|
||||
|
||||
# 1.43.0 (Jan 8th, 2025)
|
||||
|
||||
### Added
|
||||
|
||||
- net: add `UdpSocket::peek` methods ([#7068])
|
||||
- net: add support for Haiku OS ([#7042])
|
||||
- process: add `Command::into_std()` ([#7014])
|
||||
- signal: add `SignalKind::info` on illumos ([#6995])
|
||||
- signal: add support for realtime signals on illumos ([#7029])
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: don't call `set_len` before initializing vector in `Blocking` ([#7054])
|
||||
- macros: suppress `clippy::needless_return` in `#[tokio::main]` ([#6874])
|
||||
- runtime: fix thread parking on WebAssembly ([#7041])
|
||||
|
||||
### Changes
|
||||
|
||||
- chore: use unsync loads for `unsync_load` ([#7073])
|
||||
- io: use `Buf::put_bytes` in `Repeat` read impl ([#7055])
|
||||
- task: drop the join waker of a task eagerly ([#6986])
|
||||
|
||||
### Changes to unstable APIs
|
||||
|
||||
- metrics: improve flexibility of H2Histogram Configuration ([#6963])
|
||||
- taskdump: add accessor methods for backtrace ([#6975])
|
||||
|
||||
### Documented
|
||||
|
||||
- io: clarify `ReadBuf::uninit` allows initialized buffers as well ([#7053])
|
||||
- net: fix ambiguity in `TcpStream::try_write_vectored` docs ([#7067])
|
||||
- runtime: fix `LocalRuntime` doc links ([#7074])
|
||||
- sync: extend documentation for `watch::Receiver::wait_for` ([#7038])
|
||||
- sync: fix typos in `OnceCell` docs ([#7047])
|
||||
|
||||
[#6874]: https://github.com/tokio-rs/tokio/pull/6874
|
||||
[#6963]: https://github.com/tokio-rs/tokio/pull/6963
|
||||
[#6975]: https://github.com/tokio-rs/tokio/pull/6975
|
||||
[#6986]: https://github.com/tokio-rs/tokio/pull/6986
|
||||
[#6995]: https://github.com/tokio-rs/tokio/pull/6995
|
||||
[#7014]: https://github.com/tokio-rs/tokio/pull/7014
|
||||
[#7029]: https://github.com/tokio-rs/tokio/pull/7029
|
||||
[#7038]: https://github.com/tokio-rs/tokio/pull/7038
|
||||
[#7041]: https://github.com/tokio-rs/tokio/pull/7041
|
||||
[#7042]: https://github.com/tokio-rs/tokio/pull/7042
|
||||
[#7047]: https://github.com/tokio-rs/tokio/pull/7047
|
||||
[#7053]: https://github.com/tokio-rs/tokio/pull/7053
|
||||
[#7054]: https://github.com/tokio-rs/tokio/pull/7054
|
||||
[#7055]: https://github.com/tokio-rs/tokio/pull/7055
|
||||
[#7067]: https://github.com/tokio-rs/tokio/pull/7067
|
||||
[#7068]: https://github.com/tokio-rs/tokio/pull/7068
|
||||
[#7073]: https://github.com/tokio-rs/tokio/pull/7073
|
||||
[#7074]: https://github.com/tokio-rs/tokio/pull/7074
|
||||
|
||||
# 1.42.1 (April 8th, 2025)
|
||||
|
||||
This release fixes a soundness issue in the broadcast channel. The channel
|
||||
accepts values that are `Send` but `!Sync`. Previously, the channel called
|
||||
`clone()` on these values without synchronizing. This release fixes the channel
|
||||
by synchronizing calls to `.clone()` (Thanks Austin Bonander for finding and
|
||||
reporting the issue).
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: synchronize `clone()` call in broadcast channel ([#7232])
|
||||
|
||||
[#7232]: https://github.com/tokio-rs/tokio/pull/7232
|
||||
|
||||
# 1.42.0 (Dec 3rd, 2024)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `AsyncFd::{try_io, try_io_mut}` ([#6967])
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: avoid `ptr->ref->ptr` roundtrip in RegistrationSet ([#6929])
|
||||
- runtime: do not defer `yield_now` inside `block_in_place` ([#6999])
|
||||
|
||||
### Changes
|
||||
|
||||
- io: simplify io readiness logic ([#6966])
|
||||
|
||||
### Documented
|
||||
|
||||
- net: fix docs for `tokio::net::unix::{pid_t, gid_t, uid_t}` ([#6791])
|
||||
- time: fix a typo in `Instant` docs ([#6982])
|
||||
|
||||
[#6791]: https://github.com/tokio-rs/tokio/pull/6791
|
||||
[#6929]: https://github.com/tokio-rs/tokio/pull/6929
|
||||
[#6966]: https://github.com/tokio-rs/tokio/pull/6966
|
||||
[#6967]: https://github.com/tokio-rs/tokio/pull/6967
|
||||
[#6982]: https://github.com/tokio-rs/tokio/pull/6982
|
||||
[#6999]: https://github.com/tokio-rs/tokio/pull/6999
|
||||
|
||||
# 1.41.1 (Nov 7th, 2024)
|
||||
|
||||
### Fixed
|
||||
|
||||
- metrics: fix bug with wrong number of buckets for the histogram ([#6957])
|
||||
- net: display `net` requirement for `net::UdpSocket` in docs ([#6938])
|
||||
- net: fix typo in `TcpStream` internal comment ([#6944])
|
||||
|
||||
[#6957]: https://github.com/tokio-rs/tokio/pull/6957
|
||||
[#6938]: https://github.com/tokio-rs/tokio/pull/6938
|
||||
[#6944]: https://github.com/tokio-rs/tokio/pull/6944
|
||||
|
||||
# 1.41.0 (Oct 22nd, 2024)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -210,6 +471,20 @@ Yanked. Please use 1.39.1 instead.
|
||||
[#6709]: https://github.com/tokio-rs/tokio/pull/6709
|
||||
[#6710]: https://github.com/tokio-rs/tokio/pull/6710
|
||||
|
||||
# 1.38.2 (April 2nd, 2025)
|
||||
|
||||
This release fixes a soundness issue in the broadcast channel. The channel
|
||||
accepts values that are `Send` but `!Sync`. Previously, the channel called
|
||||
`clone()` on these values without synchronizing. This release fixes the channel
|
||||
by synchronizing calls to `.clone()` (Thanks Austin Bonander for finding and
|
||||
reporting the issue).
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: synchronize `clone()` call in broadcast channel ([#7232])
|
||||
|
||||
[#7232]: https://github.com/tokio-rs/tokio/pull/7232
|
||||
|
||||
# 1.38.1 (July 16th, 2024)
|
||||
|
||||
This release fixes the bug identified as ([#6682]), which caused timers not
|
||||
|
||||
+14
-7
@@ -6,7 +6,7 @@ name = "tokio"
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.x.y" git tag.
|
||||
version = "1.41.0"
|
||||
version = "1.45.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -86,12 +86,12 @@ test-util = ["rt", "sync", "time"]
|
||||
time = []
|
||||
|
||||
[dependencies]
|
||||
tokio-macros = { version = "~2.4.0", path = "../tokio-macros", optional = true }
|
||||
tokio-macros = { version = "~2.5.0", path = "../tokio-macros", optional = true }
|
||||
|
||||
pin-project-lite = "0.2.11"
|
||||
|
||||
# Everything else is optional...
|
||||
bytes = { version = "1.0.0", optional = true }
|
||||
bytes = { version = "1.2.1", optional = true }
|
||||
mio = { version = "1.0.1", optional = true, default-features = false }
|
||||
parking_lot = { version = "0.12.0", optional = true }
|
||||
|
||||
@@ -109,11 +109,11 @@ tracing = { version = "0.1.29", default-features = false, features = ["std"], op
|
||||
backtrace = { version = "0.3.58" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = { version = "0.2.149", optional = true }
|
||||
libc = { version = "0.2.168", optional = true }
|
||||
signal-hook-registry = { version = "1.1.1", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
libc = { version = "0.2.149" }
|
||||
libc = { version = "0.2.168" }
|
||||
nix = { version = "0.29.0", default-features = false, features = ["aio", "fs", "socket"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
@@ -131,8 +131,9 @@ features = [
|
||||
tokio-test = { version = "0.4.0", path = "../tokio-test" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
mockall = "0.11.1"
|
||||
mockall = "0.13.0"
|
||||
async-stream = "0.3"
|
||||
futures-concurrency = "7.6.3"
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
|
||||
socket2 = "0.5.5"
|
||||
@@ -146,11 +147,14 @@ rand = "0.8.0"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
|
||||
mio-aio = { version = "0.9.0", features = ["tokio"] }
|
||||
mio-aio = { version = "1", features = ["tokio"] }
|
||||
|
||||
[target.'cfg(loom)'.dev-dependencies]
|
||||
loom = { version = "0.7", features = ["futures", "checkpoint"] }
|
||||
|
||||
[target.'cfg(all(tokio_unstable, target_has_atomic = "64"))'.dev-dependencies]
|
||||
tracing-mock = "= 0.1.0-beta.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# enable unstable features in the documentation
|
||||
@@ -170,3 +174,6 @@ allowed_external_types = [
|
||||
"bytes::buf::buf_mut::BufMut",
|
||||
"tokio_macros::*",
|
||||
]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+8
-7
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.41.0", features = ["full"] }
|
||||
tokio = { version = "1.45.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -78,7 +78,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(0) => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
@@ -205,8 +205,8 @@ works with the MSRV of that minor release.
|
||||
|
||||
## 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.
|
||||
Tokio doesn't follow a fixed release schedule, but we typically make one minor
|
||||
release each month. We make patch releases for bugfixes as necessary.
|
||||
|
||||
## Bug patching policy
|
||||
|
||||
@@ -216,9 +216,8 @@ warrants a patch release with a fix for the bug, it will be backported and
|
||||
released as a new patch release for each LTS minor version. Our current LTS
|
||||
releases are:
|
||||
|
||||
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
|
||||
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
|
||||
* `1.38.x` - LTS release until July 2025. (MSRV 1.63)
|
||||
* `1.43.x` - LTS release until March 2026. (MSRV 1.70)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
@@ -228,7 +227,7 @@ To use a fixed minor version, you can specify the version with a tilde. For
|
||||
example, to specify that you wish to use the newest `1.32.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.32", features = [...] }
|
||||
tokio = { version = "~1.38", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -238,6 +237,8 @@ tokio = { version = "~1.32", features = [...] }
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
* `1.25.x` - LTS release until March 2024.
|
||||
* `1.32.x` - LTS release until September 2024.
|
||||
* `1.36.x` - LTS release until March 2025.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -24,21 +24,21 @@ pub enum NotDefinedHere {}
|
||||
impl mio::event::Source for NotDefinedHere {
|
||||
fn register(
|
||||
&mut self,
|
||||
registry: &mio::Registry,
|
||||
token: mio::Token,
|
||||
interests: mio::Interest,
|
||||
_registry: &mio::Registry,
|
||||
_token: mio::Token,
|
||||
_interests: mio::Interest,
|
||||
) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn reregister(
|
||||
&mut self,
|
||||
registry: &mio::Registry,
|
||||
token: mio::Token,
|
||||
interests: mio::Interest,
|
||||
_registry: &mio::Registry,
|
||||
_token: mio::Token,
|
||||
_interests: mio::Interest,
|
||||
) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn deregister(&mut self, registry: &mio::Registry) -> std::io::Result<()> {
|
||||
fn deregister(&mut self, _registry: &mio::Registry) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::io::blocking::{Buf, DEFAULT_MAX_BUF_SIZE};
|
||||
use crate::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
||||
use crate::sync::Mutex;
|
||||
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::fs::{Metadata, Permissions};
|
||||
use std::future::Future;
|
||||
@@ -586,6 +587,7 @@ impl AsyncRead for File {
|
||||
dst: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
|
||||
let me = self.get_mut();
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
@@ -594,17 +596,20 @@ impl AsyncRead for File {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
if !buf.is_empty() {
|
||||
if !buf.is_empty() || dst.remaining() == 0 {
|
||||
buf.copy_to(dst);
|
||||
*buf_cell = Some(buf);
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
buf.ensure_capacity_for(dst, me.max_buf_size);
|
||||
let std = me.std.clone();
|
||||
|
||||
let max_buf_size = cmp::min(dst.remaining(), me.max_buf_size);
|
||||
inner.state = State::Busy(spawn_blocking(move || {
|
||||
let res = buf.read_from(&mut &*std);
|
||||
// SAFETY: the `Read` implementation of `std` does not
|
||||
// read from the buffer it is borrowing and correctly
|
||||
// reports the length of the data written into the buffer.
|
||||
let res = unsafe { buf.read_from(&mut &*std, max_buf_size) };
|
||||
(Operation::Read(res), buf)
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::path::Path;
|
||||
///
|
||||
/// This is an async version of [`std::fs::hard_link`].
|
||||
///
|
||||
/// The `dst` path will be a link pointing to the `src` path. Note that systems
|
||||
/// The `link` path will be a link pointing to the `original` path. Note that systems
|
||||
/// often require these two paths to both be located on the same filesystem.
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
@@ -23,7 +23,7 @@ use std::path::Path;
|
||||
/// This function will return an error in the following situations, but is not
|
||||
/// limited to just these cases:
|
||||
///
|
||||
/// * The `src` path is not a file or doesn't exist.
|
||||
/// * The `original` path is not a file or doesn't exist.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -36,9 +36,9 @@ use std::path::Path;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
pub async fn hard_link(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
|
||||
let original = original.as_ref().to_owned();
|
||||
let link = link.as_ref().to_owned();
|
||||
|
||||
asyncify(move || std::fs::hard_link(src, dst)).await
|
||||
asyncify(move || std::fs::hard_link(original, link)).await
|
||||
}
|
||||
|
||||
@@ -62,6 +62,13 @@ impl Read for MockFile {
|
||||
|
||||
impl Read for &'_ MockFile {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
// Placate Miri. Tokio will call this method with an uninitialized
|
||||
// buffer, which is ok because std::io::Read::read implementations don't usually read
|
||||
// from their input buffers. But Mockall 0.12-0.13 will try to Debug::fmt the
|
||||
// buffer, even if there is no failure, triggering an uninitialized data access alert from
|
||||
// Miri. Initialize the data here just to prevent those Miri alerts.
|
||||
// This can be removed after upgrading to Mockall 0.14.
|
||||
dst.fill(0);
|
||||
self.inner_read(dst)
|
||||
}
|
||||
}
|
||||
@@ -129,7 +136,7 @@ impl<T> Future for JoinHandle<T> {
|
||||
|
||||
match Pin::new(&mut self.rx).poll(cx) {
|
||||
Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)),
|
||||
Poll::Ready(Err(e)) => panic!("error = {:?}", e),
|
||||
Poll::Ready(Err(e)) => panic!("error = {e:?}"),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ use std::path::Path;
|
||||
|
||||
/// Creates a new symbolic link on the filesystem.
|
||||
///
|
||||
/// The `dst` path will be a symbolic link pointing to the `src` path.
|
||||
/// The `link` path will be a symbolic link pointing to the `original` path.
|
||||
///
|
||||
/// This is an async version of [`std::os::unix::fs::symlink`].
|
||||
pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
pub async fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
|
||||
let original = original.as_ref().to_owned();
|
||||
let link = link.as_ref().to_owned();
|
||||
|
||||
asyncify(move || std::os::unix::fs::symlink(src, dst)).await
|
||||
asyncify(move || std::os::unix::fs::symlink(original, link)).await
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ use std::path::Path;
|
||||
|
||||
/// Creates a new directory symlink on the filesystem.
|
||||
///
|
||||
/// The `dst` path will be a directory symbolic link pointing to the `src`
|
||||
/// The `link` path will be a directory symbolic link pointing to the `original`
|
||||
/// path.
|
||||
///
|
||||
/// This is an async version of [`std::os::windows::fs::symlink_dir`][std]
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html
|
||||
pub async fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
pub async fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
|
||||
let original = original.as_ref().to_owned();
|
||||
let link = link.as_ref().to_owned();
|
||||
|
||||
asyncify(move || std::os::windows::fs::symlink_dir(src, dst)).await
|
||||
asyncify(move || std::os::windows::fs::symlink_dir(original, link)).await
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user