mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
264
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3911cb8523 | ||
|
|
d545aa2601 | ||
|
|
911ab21d70 | ||
|
|
9e94fa7e15 | ||
|
|
0d234c3cf9 | ||
|
|
3754e059b6 | ||
|
|
6d868d96ce | ||
|
|
0a3fe46086 | ||
|
|
154d7d5fe6 | ||
|
|
611b793356 | ||
|
|
888ee60e41 | ||
|
|
7dd4d8a30e | ||
|
|
085e616c87 | ||
|
|
a7896d07f1 | ||
|
|
aff24dfbeb | ||
|
|
71cc9ab4c2 | ||
|
|
02cbe4591b | ||
|
|
0783797520 | ||
|
|
ab3ff69cf2 | ||
|
|
a0d5b8ab30 | ||
|
|
a1ee3ef218 | ||
|
|
171cd148a3 | ||
|
|
3f1f268583 | ||
|
|
3e890cc017 | ||
|
|
69290a6432 | ||
|
|
e2b175848b | ||
|
|
b7a75b5be3 | ||
|
|
6b705b3053 | ||
|
|
3636fd018a | ||
|
|
2506c9fa99 | ||
|
|
b3a14483bf | ||
|
|
013f323def | ||
|
|
b926700065 | ||
|
|
99a03a502e | ||
|
|
933fa498d0 | ||
|
|
9f848c9f54 | ||
|
|
912b862a05 | ||
|
|
714e5b571f | ||
|
|
8e999e3806 | ||
|
|
1d980145cb | ||
|
|
8259133ca0 | ||
|
|
38d88c6799 | ||
|
|
c38de96b94 | ||
|
|
2440d113ff | ||
|
|
ab8d7b82a1 | ||
|
|
9563707aaa | ||
|
|
193c1574a1 | ||
|
|
328bd049f6 | ||
|
|
4380de9fe9 | ||
|
|
98f527f42d | ||
|
|
3768696d92 | ||
|
|
d7d4f7d08b | ||
|
|
421a7b001c | ||
|
|
b1bdb3c57b | ||
|
|
7ec77a0677 | ||
|
|
55e3ed2a39 | ||
|
|
17d8c2b29d | ||
|
|
327bec2caf | ||
|
|
ea30a5ea5e | ||
|
|
0cf95f0673 | ||
|
|
a48e418dcb | ||
|
|
4cbcb687f4 | ||
|
|
0715e6defc | ||
|
|
bdd64cc9d3 | ||
|
|
f0fdef80c4 | ||
|
|
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 | ||
|
|
01e04daaa1 | ||
|
|
92ccadeb3c | ||
|
|
fbfeb9a68a | ||
|
|
da745ff335 | ||
|
|
ce1c74f1cc | ||
|
|
28c9a14a2e | ||
|
|
32e0b4325f | ||
|
|
1656d8e231 | ||
|
|
c9e998e4b3 | ||
|
|
512e9decfb | ||
|
|
5ada5114df | ||
|
|
161b8c80d5 | ||
|
|
9cc4a81678 | ||
|
|
679d7657dc | ||
|
|
c3a935541d | ||
|
|
29cd6ec1ec | ||
|
|
b68f5c7f38 | ||
|
|
6c5dbfa08c | ||
|
|
2c14f88c90 | ||
|
|
e2e1e8e71d | ||
|
|
21df16d759 | ||
|
|
c8af499990 | ||
|
|
623928e371 | ||
|
|
09bc9a05e4 | ||
|
|
82628b8a78 | ||
|
|
21cf5a5469 | ||
|
|
8ef5163df8 | ||
|
|
542197cdb9 | ||
|
|
a302367b8f | ||
|
|
b5de84d19b | ||
|
|
02aaea28b9 | ||
|
|
83e922f051 | ||
|
|
a2496548d1 | ||
|
|
0cea36fa3d | ||
|
|
d6213594ca | ||
|
|
91169992b2 | ||
|
|
8046a87a99 | ||
|
|
5dcc848fc8 | ||
|
|
bd4ccae184 | ||
|
|
4ed0fa21e4 | ||
|
|
12b2567b95 | ||
|
|
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
|
||||
+2
-2
@@ -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: stable
|
||||
RUST_NIGHTLY: nightly-2024-05-05
|
||||
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
|
||||
|
||||
@@ -19,10 +19,3 @@ R-loom-multi-thread:
|
||||
- tokio/src/runtime/scheduler/multi_thread/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
R-loom-multi-thread-alt:
|
||||
- tokio/src/runtime/scheduler/*
|
||||
- tokio/src/runtime/scheduler/multi_thread_alt/*
|
||||
- tokio/src/runtime/scheduler/multi_thread_alt/**
|
||||
- tokio/src/runtime/task/*
|
||||
- tokio/src/runtime/task/**
|
||||
|
||||
@@ -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
|
||||
|
||||
+194
-78
@@ -16,8 +16,10 @@ env:
|
||||
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2024-05-05
|
||||
rust_clippy: '1.77'
|
||||
rust_nightly: nightly-2025-01-25
|
||||
# Pin a specific miri version
|
||||
rust_miri_nightly: nightly-2025-06-02
|
||||
rust_clippy: '1.88'
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
# - tokio/README.md
|
||||
@@ -45,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
|
||||
@@ -178,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
|
||||
@@ -244,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
|
||||
@@ -281,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
|
||||
@@ -375,6 +350,39 @@ jobs:
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_taskdump
|
||||
|
||||
test-uring:
|
||||
name: test tokio full --cfg tokio_uring
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: 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 tokio full --cfg tokio_uring
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cargo nextest run --all-features
|
||||
cargo test --doc --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_uring -Dwarnings
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_uring
|
||||
|
||||
check-unstable-mt-counters:
|
||||
name: check tokio full --internal-mt-counters
|
||||
needs: basics
|
||||
@@ -407,23 +415,67 @@ 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:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
- name: Install Rust ${{ env.rust_miri_nightly }}
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
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
|
||||
# Many of tests in tokio/tests and doctests use #[tokio::test] or
|
||||
# #[tokio::main] that calls epoll_create1 that Miri does not support.
|
||||
run: |
|
||||
cargo miri test --features full --lib --no-fail-fast
|
||||
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
|
||||
@@ -456,10 +508,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:
|
||||
@@ -492,6 +552,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- name: x86_64-unknown-haiku
|
||||
- name: armv7-sony-vita-newlibeabihf
|
||||
exclude_features: "process,signal,rt-process-signal,full"
|
||||
steps:
|
||||
@@ -512,16 +573,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-24.04-arm
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-24.04-arm
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust stable
|
||||
@@ -552,16 +619,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-24.04-arm
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-24.04-arm
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: aarch64-pc-windows-msvc
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust stable
|
||||
@@ -663,6 +736,8 @@ jobs:
|
||||
- { name: "--unstable", rustflags: "--cfg tokio_unstable -Dwarnings" }
|
||||
# Try with unstable and taskdump feature flags
|
||||
- { name: "--unstable --taskdump", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump" }
|
||||
- { name: "--tokio_uring", rustflags: "-Dwarnings --cfg tokio_uring" }
|
||||
- { name: "--unstable --taskdump --tokio_uring", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump --cfg tokio_uring" }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
@@ -690,7 +765,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
|
||||
|
||||
@@ -718,7 +800,7 @@ jobs:
|
||||
cargo hack check --all-features --ignore-private
|
||||
- name: "check --all-features --unstable -Z minimal-versions"
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump --cfg tokio_uring -Dwarnings
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
@@ -764,7 +846,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 --cfg tokio_uring
|
||||
RUSTDOCFLAGS: --cfg tokio_taskdump --cfg tokio_uring
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
@@ -776,8 +866,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
|
||||
@@ -978,10 +1068,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
|
||||
@@ -1007,9 +1097,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
|
||||
@@ -1031,7 +1124,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 }}
|
||||
@@ -1042,28 +1135,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
|
||||
@@ -1101,17 +1177,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' $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,32 +91,7 @@ jobs:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
loom-multi-thread-alt:
|
||||
name: loom ALT multi-thread scheduler
|
||||
# base_ref is null when it's not a pull request
|
||||
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread-alt') || (github.base_ref == null))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- scope: loom_multi_thread_alt::group_a
|
||||
- scope: loom_multi_thread_alt::group_b
|
||||
- scope: loom_multi_thread_alt::group_c
|
||||
- scope: loom_multi_thread_alt::group_d
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- $SCOPE
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
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
|
||||
|
||||
+7
-7
@@ -149,7 +149,7 @@ When updating this, also update:
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.77 clippy --all --tests --all-features
|
||||
cargo +1.88 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation, a simple `cargo doc` is not sufficient. To produce
|
||||
@@ -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-tag-raw-pointers" \
|
||||
cargo +nightly miri test --features full --lib
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields" \
|
||||
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;
|
||||
|
||||
|
||||
+15
@@ -17,3 +17,18 @@ 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)',
|
||||
'cfg(tokio_uring)',
|
||||
'cfg(target_os, values("cygwin"))',
|
||||
] }
|
||||
|
||||
@@ -39,7 +39,7 @@ level, it provides a few major components:
|
||||
|
||||
* A multithreaded, work-stealing based task [scheduler].
|
||||
* A reactor backed by the operating system's event queue (epoll, kqueue,
|
||||
IOCP, etc...).
|
||||
IOCP, etc.).
|
||||
* Asynchronous [TCP and UDP][net] sockets.
|
||||
|
||||
These components provide the runtime components necessary for building
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.40.0", features = ["full"] }
|
||||
tokio = { version = "1.47.0", 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
|
||||
|
||||
|
||||
+7
-3
@@ -3,6 +3,7 @@ name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[features]
|
||||
test-util = ["tokio/test-util"]
|
||||
@@ -10,12 +11,12 @@ test-util = ["tokio/test-util"]
|
||||
[dependencies]
|
||||
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
criterion = "0.5.1"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
|
||||
[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
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ impl SlowHddWriter {
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
let service_res = self.as_mut().service_write(cx);
|
||||
|
||||
if service_res.is_pending() && self.blocking_rng.gen_bool(PROBABILITY_FLUSH_WAIT) {
|
||||
if service_res.is_pending() && self.blocking_rng.random_bool(PROBABILITY_FLUSH_WAIT) {
|
||||
return Poll::Pending;
|
||||
}
|
||||
let available = self.buffer_size - self.buffer_used;
|
||||
@@ -145,7 +145,7 @@ impl ChunkReader {
|
||||
fn new(chunk_size: usize, service_interval: Duration) -> Self {
|
||||
let mut service_intervals = interval(service_interval);
|
||||
service_intervals.set_missed_tick_behavior(MissedTickBehavior::Burst);
|
||||
let data: Vec<u8> = std::iter::repeat(0).take(chunk_size).collect();
|
||||
let data: Vec<u8> = std::iter::repeat_n(0, chunk_size).collect();
|
||||
Self {
|
||||
data,
|
||||
service_intervals,
|
||||
|
||||
+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 {
|
||||
|
||||
@@ -17,7 +17,7 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
let _ = write!(&mut message, " {i}={}", rng.random::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
use std::fmt::Write;
|
||||
let mut message = String::new();
|
||||
for i in 1..=10 {
|
||||
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
|
||||
let _ = write!(&mut message, " {i}={}", rng.random::<f64>());
|
||||
}
|
||||
message
|
||||
.as_bytes()
|
||||
|
||||
@@ -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"
|
||||
+15
-8
@@ -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**.
|
||||
@@ -22,27 +23,30 @@ serde_json = "1.0"
|
||||
httparse = "1.0"
|
||||
httpdate = "1.0"
|
||||
once_cell = "1.5.2"
|
||||
rand = "0.8.3"
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.windows-sys]
|
||||
version = "0.52"
|
||||
version = "0.59"
|
||||
|
||||
[[example]]
|
||||
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 +98,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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-17
@@ -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}");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -82,9 +82,7 @@ async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
let response = response
|
||||
.body(body)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||
let response = response.body(body).map_err(io::Error::other)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
@@ -159,8 +157,8 @@ 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);
|
||||
io::Error::new(io::ErrorKind::Other, msg)
|
||||
let msg = format!("failed to parse http request: {e:?}");
|
||||
io::Error::other(msg)
|
||||
})?;
|
||||
|
||||
let amt = match status {
|
||||
@@ -180,8 +178,7 @@ impl Decoder for Http {
|
||||
headers[i] = Some((k, v));
|
||||
}
|
||||
|
||||
let method = http::Method::try_from(r.method.unwrap())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let method = http::Method::try_from(r.method.unwrap()).map_err(io::Error::other)?;
|
||||
|
||||
(
|
||||
method,
|
||||
@@ -191,10 +188,7 @@ impl Decoder for Http {
|
||||
)
|
||||
};
|
||||
if version != 1 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"only HTTP/1.1 accepted",
|
||||
));
|
||||
return Err(io::Error::other("only HTTP/1.1 accepted"));
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
@@ -209,13 +203,11 @@ impl Decoder for Http {
|
||||
None => break,
|
||||
};
|
||||
let value = HeaderValue::from_bytes(data.slice(v.0..v.1).as_ref())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "header decode error"))?;
|
||||
.map_err(|_| io::Error::other("header decode error"))?;
|
||||
ret = ret.header(&data[k.0..k.1], value);
|
||||
}
|
||||
|
||||
let req = ret
|
||||
.body(())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
let req = ret.body(()).map_err(io::Error::other)?;
|
||||
Ok(Some(req))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!"),
|
||||
}
|
||||
|
||||
|
||||
+25
-3
@@ -1,4 +1,4 @@
|
||||
285
|
||||
306
|
||||
&
|
||||
+
|
||||
<
|
||||
@@ -12,12 +12,17 @@
|
||||
0xA
|
||||
0xD
|
||||
100ms
|
||||
100ns
|
||||
10ms
|
||||
10μs
|
||||
~12
|
||||
120s
|
||||
12.5%
|
||||
±1m
|
||||
±1ms
|
||||
1ms
|
||||
1s
|
||||
25%
|
||||
250ms
|
||||
2x
|
||||
~4
|
||||
@@ -25,11 +30,13 @@
|
||||
450ms
|
||||
50ms
|
||||
8MB
|
||||
ABI
|
||||
accessors
|
||||
adaptor
|
||||
adaptors
|
||||
Adaptors
|
||||
AIO
|
||||
ambiant
|
||||
ambient
|
||||
amongst
|
||||
api
|
||||
APIs
|
||||
@@ -63,6 +70,10 @@ connectionless
|
||||
coroutines
|
||||
cpu
|
||||
cpus
|
||||
cqe
|
||||
CQE
|
||||
cqe's
|
||||
customizable
|
||||
Customizable
|
||||
datagram
|
||||
Datagram
|
||||
@@ -70,8 +81,12 @@ datagrams
|
||||
deallocate
|
||||
deallocated
|
||||
Deallocates
|
||||
debuginfo
|
||||
decrement
|
||||
decrementing
|
||||
demangled
|
||||
dequeued
|
||||
dereferenced
|
||||
deregister
|
||||
deregistered
|
||||
deregistering
|
||||
@@ -99,6 +114,7 @@ errored
|
||||
EWMA
|
||||
expirations
|
||||
fcntl
|
||||
fd
|
||||
fd's
|
||||
FIFOs
|
||||
filename
|
||||
@@ -115,9 +131,11 @@ GID
|
||||
goroutines
|
||||
Growable
|
||||
gzip
|
||||
H2
|
||||
hashmaps
|
||||
HashMaps
|
||||
hashsets
|
||||
HdrHistogram
|
||||
ie
|
||||
Illumos
|
||||
impl
|
||||
@@ -125,6 +143,7 @@ implementers
|
||||
implementor
|
||||
implementors
|
||||
incrementing
|
||||
inlining
|
||||
interoperate
|
||||
invariants
|
||||
Invariants
|
||||
@@ -151,8 +170,10 @@ metadata
|
||||
mio
|
||||
Mio
|
||||
mio's
|
||||
miri
|
||||
misconfigured
|
||||
mock's
|
||||
monomorphization
|
||||
mpmc
|
||||
mpsc
|
||||
multi
|
||||
@@ -265,9 +286,11 @@ unparks
|
||||
Unparks
|
||||
unreceived
|
||||
unsafety
|
||||
unsets
|
||||
Unsets
|
||||
unsynchronized
|
||||
untrusted
|
||||
uring
|
||||
usecases
|
||||
Valgrind
|
||||
Varghese
|
||||
@@ -282,4 +305,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"
|
||||
rand = "0.9"
|
||||
|
||||
[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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn compile_fail_full() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
use futures::future::poll_fn;
|
||||
use std::future::poll_fn;
|
||||
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(all(feature = "full", not(target_os = "wasi")))]
|
||||
#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::join;
|
||||
@@ -25,7 +25,7 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
// Produce n lines on the child's stdout.
|
||||
let write = async {
|
||||
for i in 0..n {
|
||||
let bytes = format!("line {}\n", i).into_bytes();
|
||||
let bytes = format!("line {i}\n").into_bytes();
|
||||
stdin.write_all(&bytes).await.unwrap();
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
(false, 0) => panic!("broken pipe"),
|
||||
(true, n) if n != 0 => panic!("extraneous data"),
|
||||
_ => {
|
||||
let expected = format!("line {}", num_lines);
|
||||
let expected = format!("line {num_lines}");
|
||||
assert_eq!(expected, data);
|
||||
}
|
||||
};
|
||||
@@ -206,7 +206,7 @@ async fn vectored_writes() {
|
||||
let mut input = Bytes::from_static(b"hello\n").chain(Bytes::from_static(b"world!\n"));
|
||||
let mut writes_completed = 0;
|
||||
|
||||
futures::future::poll_fn(|cx| loop {
|
||||
std::future::poll_fn(|cx| loop {
|
||||
let mut slices = [IoSlice::new(&[]); 2];
|
||||
let vectored = input.chunks_vectored(&mut slices);
|
||||
if vectored == 0 {
|
||||
|
||||
@@ -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
|
||||
|
||||
+14
-16
@@ -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));
|
||||
@@ -438,8 +435,9 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
};
|
||||
|
||||
let body_ident = quote! { body };
|
||||
// This explicit `return` is intentional. See tokio-rs/tokio#4636
|
||||
let last_block = quote_spanned! {last_stmt_end_span=>
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return)]
|
||||
{
|
||||
return #rt
|
||||
.enable_all()
|
||||
|
||||
@@ -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,26 @@
|
||||
# 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.
|
||||
|
||||
- stream: add `next_many` and `poll_next_many` to `StreamMap` ([#6409])
|
||||
- stream: make stream adapters public ([#6658])
|
||||
- readme: add readme for tokio-stream ([#6456])
|
||||
|
||||
[#6409]: https://github.com/tokio-rs/tokio/pull/6409
|
||||
[#6658]: https://github.com/tokio-rs/tokio/pull/6658
|
||||
[#6456]: https://github.com/tokio-rs/tokio/pull/6456
|
||||
|
||||
# 0.1.15 (March 14th, 2024)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.63.
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-stream"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.15"
|
||||
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)]
|
||||
#![allow(
|
||||
clippy::cognitive_complexity,
|
||||
clippy::large_enum_variant,
|
||||
@@ -74,9 +73,6 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod poll_fn;
|
||||
pub(crate) use poll_fn::poll_fn;
|
||||
|
||||
pub mod wrappers;
|
||||
|
||||
mod stream_ext;
|
||||
|
||||
@@ -17,7 +17,7 @@ unsafe impl<T> Sync for Pending<T> {}
|
||||
///
|
||||
/// The returned stream is never ready. Attempting to call
|
||||
/// [`next()`](crate::StreamExt::next) 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
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub(crate) struct PollFn<F> {
|
||||
f: F,
|
||||
}
|
||||
|
||||
pub(crate) fn poll_fn<T, F>(f: F) -> PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
PollFn { f }
|
||||
}
|
||||
|
||||
impl<T, F> Future for PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
|
||||
// Safety: We never construct a `Pin<&mut F>` anywhere, so accessing `f`
|
||||
// mutably in an unpinned way is sound.
|
||||
//
|
||||
// This use of unsafe cannot be replaced with the pin-project macro
|
||||
// because:
|
||||
// * If we put `#[pin]` on the field, then it gives us a `Pin<&mut F>`,
|
||||
// which we can't use to call the closure.
|
||||
// * If we don't put `#[pin]` on the field, then it makes `PollFn` be
|
||||
// unconditionally `Unpin`, which we also don't want.
|
||||
let me = unsafe { Pin::into_inner_unchecked(self) };
|
||||
(me.f)(cx)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use crate::stream_ext::Fuse;
|
||||
use crate::StreamExt;
|
||||
|
||||
pin_project! {
|
||||
/// Stream returned by the [`chain`](super::StreamExt::peekable) method.
|
||||
/// Stream returned by the [`peekable`](super::StreamExt::peekable) method.
|
||||
pub struct Peekable<T: Stream> {
|
||||
peek: Option<T::Item>,
|
||||
#[pin]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{poll_fn, Stream};
|
||||
use crate::Stream;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::future::poll_fn;
|
||||
use std::hash::Hash;
|
||||
use std::pin::Pin;
|
||||
use std::task::{ready, Context, Poll};
|
||||
|
||||
@@ -10,8 +10,31 @@ 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@crate::Stream
|
||||
/// [`Stream`]: trait@futures_core::Stream
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
|
||||
pub struct BroadcastStream<T> {
|
||||
inner: ReusableBoxFuture<'static, (Result<T, RecvError>, Receiver<T>)>,
|
||||
@@ -30,7 +53,7 @@ pub enum BroadcastStreamRecvError {
|
||||
impl fmt::Display for BroadcastStreamRecvError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BroadcastStreamRecvError::Lagged(amt) => write!(f, "channel lagged by {}", amt),
|
||||
BroadcastStreamRecvError::Lagged(amt) => write!(f, "channel lagged by {amt}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -12,7 +12,7 @@ async fn watch_stream_message_not_twice() {
|
||||
|
||||
let mut counter = 0;
|
||||
let mut stream = WatchStream::new(rx).map(move |payload| {
|
||||
println!("{}", payload);
|
||||
println!("{payload}");
|
||||
if payload == "goodbye" {
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-45
@@ -26,11 +26,10 @@
|
||||
//! ```
|
||||
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
use std::task::{Context, Poll, Wake, Waker};
|
||||
|
||||
use tokio_stream::Stream;
|
||||
|
||||
@@ -171,7 +170,7 @@ impl MockTask {
|
||||
F: FnOnce(&mut Context<'_>) -> R,
|
||||
{
|
||||
self.waker.clear();
|
||||
let waker = self.waker();
|
||||
let waker = self.clone().into_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
f(&mut cx)
|
||||
@@ -190,11 +189,8 @@ impl MockTask {
|
||||
Arc::strong_count(&self.waker)
|
||||
}
|
||||
|
||||
fn waker(&self) -> Waker {
|
||||
unsafe {
|
||||
let raw = to_raw(self.waker.clone());
|
||||
Waker::from_raw(raw)
|
||||
}
|
||||
fn into_waker(self) -> Waker {
|
||||
self.waker.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,8 +222,14 @@ impl ThreadWaker {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wake(&self) {
|
||||
impl Wake for ThreadWaker {
|
||||
fn wake(self: Arc<Self>) {
|
||||
self.wake_by_ref();
|
||||
}
|
||||
|
||||
fn wake_by_ref(self: &Arc<Self>) {
|
||||
// First, try transitioning from IDLE -> NOTIFY, this does not require a lock.
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let prev = *state;
|
||||
@@ -247,39 +249,3 @@ impl ThreadWaker {
|
||||
self.condvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
|
||||
|
||||
unsafe fn to_raw(waker: Arc<ThreadWaker>) -> RawWaker {
|
||||
RawWaker::new(Arc::into_raw(waker) as *const (), &VTABLE)
|
||||
}
|
||||
|
||||
unsafe fn from_raw(raw: *const ()) -> Arc<ThreadWaker> {
|
||||
Arc::from_raw(raw as *const ThreadWaker)
|
||||
}
|
||||
|
||||
unsafe fn clone(raw: *const ()) -> RawWaker {
|
||||
let waker = from_raw(raw);
|
||||
|
||||
// Increment the ref count
|
||||
mem::forget(waker.clone());
|
||||
|
||||
to_raw(waker)
|
||||
}
|
||||
|
||||
unsafe fn wake(raw: *const ()) {
|
||||
let waker = from_raw(raw);
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
unsafe fn wake_by_ref(raw: *const ()) {
|
||||
let waker = from_raw(raw);
|
||||
waker.wake();
|
||||
|
||||
// We don't actually own a reference to the unparker
|
||||
mem::forget(waker);
|
||||
}
|
||||
|
||||
unsafe fn drop_waker(raw: *const ()) {
|
||||
let _ = from_raw(raw);
|
||||
}
|
||||
|
||||
@@ -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,17 +35,17 @@ __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 }
|
||||
futures-util = { version = "0.3.0", optional = true }
|
||||
pin-project-lite = "0.2.11"
|
||||
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
|
||||
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true }
|
||||
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, 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
|
||||
|
||||
@@ -141,11 +141,9 @@ impl Decoder for AnyDelimiterCodec {
|
||||
// there's no max_length set, we'll read to the end of the buffer.
|
||||
let read_to = cmp::min(self.max_length.saturating_add(1), buf.len());
|
||||
|
||||
let new_chunk_offset = buf[self.next_index..read_to].iter().position(|b| {
|
||||
self.seek_delimiters
|
||||
.iter()
|
||||
.any(|delimiter| *b == *delimiter)
|
||||
});
|
||||
let new_chunk_offset = buf[self.next_index..read_to]
|
||||
.iter()
|
||||
.position(|b| self.seek_delimiters.contains(b));
|
||||
|
||||
match (self.is_discarding, new_chunk_offset) {
|
||||
(true, Some(offset)) => {
|
||||
@@ -217,7 +215,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 +247,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;
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Listener for tokio::net::TcpListener {
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> Result<Self::Addr> {
|
||||
self.local_addr().map(Into::into)
|
||||
self.local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ impl Listener for tokio::net::UnixListener {
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> Result<Self::Addr> {
|
||||
self.local_addr().map(Into::into)
|
||||
self.local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! An asynchronously awaitable `CancellationToken`.
|
||||
//! An asynchronously awaitable [`CancellationToken`].
|
||||
//! The token allows to signal a cancellation request to one or more tasks.
|
||||
pub(crate) mod guard;
|
||||
pub(crate) mod guard_ref;
|
||||
mod tree_node;
|
||||
|
||||
use crate::loom::sync::Arc;
|
||||
@@ -10,6 +11,7 @@ use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use guard::DropGuard;
|
||||
use guard_ref::DropGuardRef;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
/// A token which can be used to signal a cancellation request to one or more
|
||||
@@ -110,7 +112,7 @@ impl core::fmt::Debug for CancellationToken {
|
||||
}
|
||||
|
||||
impl Clone for CancellationToken {
|
||||
/// Creates a clone of the `CancellationToken` which will get cancelled
|
||||
/// Creates a clone of the [`CancellationToken`] which will get cancelled
|
||||
/// whenever the current token gets cancelled, and vice versa.
|
||||
fn clone(&self) -> Self {
|
||||
tree_node::increase_handle_refcount(&self.inner);
|
||||
@@ -133,15 +135,15 @@ impl Default for CancellationToken {
|
||||
}
|
||||
|
||||
impl CancellationToken {
|
||||
/// Creates a new `CancellationToken` in the non-cancelled state.
|
||||
/// Creates a new [`CancellationToken`] in the non-cancelled state.
|
||||
pub fn new() -> CancellationToken {
|
||||
CancellationToken {
|
||||
inner: Arc::new(tree_node::TreeNode::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `CancellationToken` which will get cancelled whenever the
|
||||
/// current token gets cancelled. Unlike a cloned `CancellationToken`,
|
||||
/// Creates a [`CancellationToken`] which will get cancelled whenever the
|
||||
/// current token gets cancelled. Unlike a cloned [`CancellationToken`],
|
||||
/// cancelling a child token does not cancel the parent token.
|
||||
///
|
||||
/// If the current token is already cancelled, the child token will get
|
||||
@@ -204,12 +206,18 @@ impl CancellationToken {
|
||||
tree_node::is_cancelled(&self.inner)
|
||||
}
|
||||
|
||||
/// Returns a `Future` that gets fulfilled when cancellation is requested.
|
||||
/// Returns a [`Future`] that gets fulfilled when cancellation is requested.
|
||||
///
|
||||
/// Equivalent to:
|
||||
///
|
||||
/// ```ignore
|
||||
/// async fn cancelled(&self);
|
||||
/// ```
|
||||
///
|
||||
/// The future will complete immediately if the token is already cancelled
|
||||
/// when this method is called.
|
||||
///
|
||||
/// # Cancel safety
|
||||
/// # Cancellation safety
|
||||
///
|
||||
/// This method is cancel safe.
|
||||
pub fn cancelled(&self) -> WaitForCancellationFuture<'_> {
|
||||
@@ -219,7 +227,13 @@ impl CancellationToken {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Future` that gets fulfilled when cancellation is requested.
|
||||
/// Returns a [`Future`] that gets fulfilled when cancellation is requested.
|
||||
///
|
||||
/// Equivalent to:
|
||||
///
|
||||
/// ```ignore
|
||||
/// async fn cancelled_owned(self);
|
||||
/// ```
|
||||
///
|
||||
/// The future will complete immediately if the token is already cancelled
|
||||
/// when this method is called.
|
||||
@@ -227,14 +241,14 @@ impl CancellationToken {
|
||||
/// The function takes self by value and returns a future that owns the
|
||||
/// token.
|
||||
///
|
||||
/// # Cancel safety
|
||||
/// # Cancellation safety
|
||||
///
|
||||
/// This method is cancel safe.
|
||||
pub fn cancelled_owned(self) -> WaitForCancellationFutureOwned {
|
||||
WaitForCancellationFutureOwned::new(self)
|
||||
}
|
||||
|
||||
/// Creates a `DropGuard` for this token.
|
||||
/// Creates a [`DropGuard`] for this token.
|
||||
///
|
||||
/// Returned guard will cancel this token (and all its children) on drop
|
||||
/// unless disarmed.
|
||||
@@ -242,11 +256,25 @@ impl CancellationToken {
|
||||
DropGuard { inner: Some(self) }
|
||||
}
|
||||
|
||||
/// Creates a [`DropGuardRef`] for this token.
|
||||
///
|
||||
/// Returned guard will cancel this token (and all its children) on drop
|
||||
/// unless disarmed.
|
||||
pub fn drop_guard_ref(&self) -> DropGuardRef<'_> {
|
||||
DropGuardRef { inner: Some(self) }
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// unless the [`CancellationToken`] is cancelled. In that case the function returns
|
||||
/// `None` and the future gets dropped.
|
||||
///
|
||||
/// # Cancel safety
|
||||
/// # Fairness
|
||||
///
|
||||
/// Calling this on an already-cancelled token directly returns `None`.
|
||||
/// For all subsequent polls, in case of concurrent completion and
|
||||
/// cancellation, this is biased towards the future completion.
|
||||
///
|
||||
/// # Cancellation safety
|
||||
///
|
||||
/// This method is only cancel safe if `fut` is cancel safe.
|
||||
pub async fn run_until_cancelled<F>(&self, fut: F) -> Option<F::Output>
|
||||
@@ -281,11 +309,37 @@ impl CancellationToken {
|
||||
}
|
||||
}
|
||||
|
||||
RunUntilCancelledFuture {
|
||||
cancellation: self.cancelled(),
|
||||
future: fut,
|
||||
if self.is_cancelled() {
|
||||
None
|
||||
} else {
|
||||
RunUntilCancelledFuture {
|
||||
cancellation: self.cancelled(),
|
||||
future: fut,
|
||||
}
|
||||
.await
|
||||
}
|
||||
.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.
|
||||
///
|
||||
/// # Fairness
|
||||
///
|
||||
/// Calling this on an already-cancelled token directly returns `None`.
|
||||
/// For all subsequent polls, in case of concurrent completion and
|
||||
/// cancellation, this is biased towards the future completion.
|
||||
///
|
||||
/// # Cancellation 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::sync::CancellationToken;
|
||||
|
||||
/// A wrapper for cancellation token which automatically cancels
|
||||
/// it on drop. It is created using `drop_guard` method on the `CancellationToken`.
|
||||
/// it on drop. It is created using [`drop_guard`] method on the [`CancellationToken`].
|
||||
///
|
||||
/// [`drop_guard`]: CancellationToken::drop_guard
|
||||
#[derive(Debug)]
|
||||
pub struct DropGuard {
|
||||
pub(super) inner: Option<CancellationToken>,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
use crate::sync::CancellationToken;
|
||||
|
||||
/// A wrapper for cancellation token which automatically cancels
|
||||
/// it on drop. It is created using [`drop_guard_ref`] method on the [`CancellationToken`].
|
||||
///
|
||||
/// This is a borrowed version of [`DropGuard`].
|
||||
///
|
||||
/// [`drop_guard_ref`]: CancellationToken::drop_guard_ref
|
||||
/// [`DropGuard`]: super::DropGuard
|
||||
#[derive(Debug)]
|
||||
pub struct DropGuardRef<'a> {
|
||||
pub(super) inner: Option<&'a CancellationToken>,
|
||||
}
|
||||
|
||||
impl<'a> DropGuardRef<'a> {
|
||||
/// Returns stored cancellation token and removes this drop guard instance
|
||||
/// (i.e. it will no longer cancel token). Other guards for this token
|
||||
/// are not affected.
|
||||
pub fn disarm(mut self) -> &'a CancellationToken {
|
||||
self.inner
|
||||
.take()
|
||||
.expect("`inner` can be only None in a destructor")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DropGuardRef<'_> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.inner {
|
||||
inner.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,16 @@
|
||||
//! Those invariants shall be true at any time.
|
||||
//!
|
||||
//! 1. A node that has no parents and no handles can no longer be cancelled.
|
||||
//! This is important during both cancellation and refcounting.
|
||||
//! This is important during both cancellation and refcounting.
|
||||
//!
|
||||
//! 2. If node B *is* or *was* a child of node A, then node B was created *after* node A.
|
||||
//! This is important for deadlock safety, as it is used for lock order.
|
||||
//! Node B can only become the child of node A in two ways:
|
||||
//! - being created with `child_node()`, in which case it is trivially true that
|
||||
//! node A already existed when node B was created
|
||||
//! - being moved A->C->B to A->B because node C was removed in `decrease_handle_refcount()`
|
||||
//! or `cancel()`. In this case the invariant still holds, as B was younger than C, and C
|
||||
//! was younger than A, therefore B is also younger than A.
|
||||
//! This is important for deadlock safety, as it is used for lock order.
|
||||
//! Node B can only become the child of node A in two ways:
|
||||
//! - being created with `child_node()`, in which case it is trivially true that
|
||||
//! node A already existed when node B was created
|
||||
//! - being moved A->C->B to A->B because node C was removed in `decrease_handle_refcount()`
|
||||
//! or `cancel()`. In this case the invariant still holds, as B was younger than C, and C
|
||||
//! was younger than A, therefore B is also younger than A.
|
||||
//!
|
||||
//! 3. If two nodes are both unlocked and node A is the parent of node B, then node B is a child of
|
||||
//! node A. It is important to always restore that invariant before dropping the lock of a node.
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
mod cancellation_token;
|
||||
pub use cancellation_token::{
|
||||
guard::DropGuard, CancellationToken, WaitForCancellationFuture, WaitForCancellationFutureOwned,
|
||||
guard::DropGuard, guard_ref::DropGuardRef, CancellationToken, WaitForCancellationFuture,
|
||||
WaitForCancellationFutureOwned,
|
||||
};
|
||||
|
||||
mod mpsc;
|
||||
|
||||
@@ -5,6 +5,7 @@ use tokio::task::{AbortHandle, JoinError, JoinHandle};
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
mem::ManuallyDrop,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
@@ -46,6 +47,15 @@ impl<T> AbortOnDropHandle<T> {
|
||||
pub fn abort_handle(&self) -> AbortHandle {
|
||||
self.0.abort_handle()
|
||||
}
|
||||
|
||||
/// Cancels aborting on drop and returns the original [`JoinHandle`].
|
||||
pub fn detach(self) -> JoinHandle<T> {
|
||||
// Avoid invoking `AbortOnDropHandle`'s `Drop` impl
|
||||
let this = ManuallyDrop::new(self);
|
||||
// SAFETY: `&this.0` is a reference, so it is certainly initialized, and
|
||||
// it won't be double-dropped because it's in a `ManuallyDrop`
|
||||
unsafe { std::ptr::read(&this.0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for AbortOnDropHandle<T> {
|
||||
|
||||
+78
-113
@@ -1,5 +1,5 @@
|
||||
use hashbrown::hash_map::RawEntryMut;
|
||||
use hashbrown::HashMap;
|
||||
use hashbrown::hash_table::Entry;
|
||||
use hashbrown::{HashMap, HashTable};
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::fmt;
|
||||
@@ -103,13 +103,8 @@ use tokio::task::{AbortHandle, Id, JoinError, JoinSet, LocalSet};
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "rt", tokio_unstable))))]
|
||||
pub struct JoinMap<K, V, S = RandomState> {
|
||||
/// A map of the [`AbortHandle`]s of the tasks spawned on this `JoinMap`,
|
||||
/// indexed by their keys and task IDs.
|
||||
///
|
||||
/// The [`Key`] type contains both the task's `K`-typed key provided when
|
||||
/// spawning tasks, and the task's IDs. The IDs are stored here to resolve
|
||||
/// hash collisions when looking up tasks based on their pre-computed hash
|
||||
/// (as stored in the `hashes_by_task` map).
|
||||
tasks_by_key: HashMap<Key<K>, AbortHandle, S>,
|
||||
/// indexed by their keys.
|
||||
tasks_by_key: HashTable<(K, AbortHandle)>,
|
||||
|
||||
/// A map from task IDs to the hash of the key associated with that task.
|
||||
///
|
||||
@@ -125,21 +120,6 @@ pub struct JoinMap<K, V, S = RandomState> {
|
||||
tasks: JoinSet<V>,
|
||||
}
|
||||
|
||||
/// A [`JoinMap`] key.
|
||||
///
|
||||
/// This holds both a `K`-typed key (the actual key as seen by the user), _and_
|
||||
/// a task ID, so that hash collisions between `K`-typed keys can be resolved
|
||||
/// using either `K`'s `Eq` impl *or* by checking the task IDs.
|
||||
///
|
||||
/// This allows looking up a task using either an actual key (such as when the
|
||||
/// user queries the map with a key), *or* using a task ID and a hash (such as
|
||||
/// when removing completed tasks from the map).
|
||||
#[derive(Debug)]
|
||||
struct Key<K> {
|
||||
key: K,
|
||||
id: Id,
|
||||
}
|
||||
|
||||
impl<K, V> JoinMap<K, V> {
|
||||
/// Creates a new empty `JoinMap`.
|
||||
///
|
||||
@@ -176,7 +156,7 @@ impl<K, V> JoinMap<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, S: Clone> JoinMap<K, V, S> {
|
||||
impl<K, V, S> JoinMap<K, V, S> {
|
||||
/// Creates an empty `JoinMap` which will use the given hash builder to hash
|
||||
/// keys.
|
||||
///
|
||||
@@ -226,7 +206,7 @@ impl<K, V, S: Clone> JoinMap<K, V, S> {
|
||||
#[must_use]
|
||||
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
|
||||
Self {
|
||||
tasks_by_key: HashMap::with_capacity_and_hasher(capacity, hash_builder.clone()),
|
||||
tasks_by_key: HashTable::with_capacity(capacity),
|
||||
hashes_by_task: HashMap::with_capacity_and_hasher(capacity, hash_builder),
|
||||
tasks: JoinSet::new(),
|
||||
}
|
||||
@@ -415,33 +395,42 @@ where
|
||||
self.insert(key, task)
|
||||
}
|
||||
|
||||
fn insert(&mut self, key: K, abort: AbortHandle) {
|
||||
let hash = self.hash(&key);
|
||||
fn insert(&mut self, mut key: K, mut abort: AbortHandle) {
|
||||
let hash_builder = self.hashes_by_task.hasher();
|
||||
let hash = hash_one(hash_builder, &key);
|
||||
let id = abort.id();
|
||||
let map_key = Key { id, key };
|
||||
|
||||
// Insert the new key into the map of tasks by keys.
|
||||
let entry = self
|
||||
.tasks_by_key
|
||||
.raw_entry_mut()
|
||||
.from_hash(hash, |k| k.key == map_key.key);
|
||||
let entry =
|
||||
self.tasks_by_key
|
||||
.entry(hash, |(k, _)| *k == key, |(k, _)| hash_one(hash_builder, k));
|
||||
match entry {
|
||||
RawEntryMut::Occupied(mut occ) => {
|
||||
Entry::Occupied(occ) => {
|
||||
// There was a previous task spawned with the same key! Cancel
|
||||
// that task, and remove its ID from the map of hashes by task IDs.
|
||||
let Key { id: prev_id, .. } = occ.insert_key(map_key);
|
||||
occ.insert(abort).abort();
|
||||
let _prev_hash = self.hashes_by_task.remove(&prev_id);
|
||||
(key, abort) = std::mem::replace(occ.into_mut(), (key, abort));
|
||||
|
||||
// Remove the old task ID.
|
||||
let _prev_hash = self.hashes_by_task.remove(&abort.id());
|
||||
debug_assert_eq!(Some(hash), _prev_hash);
|
||||
|
||||
// Associate the key's hash with the new task's ID, for looking up tasks by ID.
|
||||
let _prev = self.hashes_by_task.insert(id, hash);
|
||||
debug_assert!(_prev.is_none(), "no prior task should have had the same ID");
|
||||
|
||||
// Note: it's important to drop `key` and abort the task here.
|
||||
// This defends against any panics during drop handling for causing inconsistent state.
|
||||
abort.abort();
|
||||
drop(key);
|
||||
}
|
||||
RawEntryMut::Vacant(vac) => {
|
||||
vac.insert(map_key, abort);
|
||||
Entry::Vacant(vac) => {
|
||||
vac.insert((key, abort));
|
||||
|
||||
// Associate the key's hash with this task's ID, for looking up tasks by ID.
|
||||
let _prev = self.hashes_by_task.insert(id, hash);
|
||||
debug_assert!(_prev.is_none(), "no prior task should have had the same ID");
|
||||
}
|
||||
};
|
||||
|
||||
// Associate the key's hash with this task's ID, for looking up tasks by ID.
|
||||
let _prev = self.hashes_by_task.insert(id, hash);
|
||||
debug_assert!(_prev.is_none(), "no prior task should have had the same ID");
|
||||
}
|
||||
|
||||
/// Waits until one of the tasks in the map completes and returns its
|
||||
@@ -469,16 +458,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.
|
||||
@@ -620,7 +612,7 @@ where
|
||||
// Note: this method iterates over the tasks and keys *without* removing
|
||||
// any entries, so that the keys from aborted tasks can still be
|
||||
// returned when calling `join_next` in the future.
|
||||
for (Key { ref key, .. }, task) in &self.tasks_by_key {
|
||||
for (key, task) in &self.tasks_by_key {
|
||||
if predicate(key) {
|
||||
task.abort();
|
||||
}
|
||||
@@ -635,7 +627,7 @@ where
|
||||
/// [`join_next`]: fn@Self::join_next
|
||||
pub fn keys(&self) -> JoinMapKeys<'_, K, V> {
|
||||
JoinMapKeys {
|
||||
iter: self.tasks_by_key.keys(),
|
||||
iter: self.tasks_by_key.iter(),
|
||||
_value: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -663,7 +655,7 @@ where
|
||||
/// [`join_next`]: fn@Self::join_next
|
||||
/// [task ID]: tokio::task::Id
|
||||
pub fn contains_task(&self, task: &Id) -> bool {
|
||||
self.get_by_id(task).is_some()
|
||||
self.hashes_by_task.contains_key(task)
|
||||
}
|
||||
|
||||
/// Reserves capacity for at least `additional` more tasks to be spawned
|
||||
@@ -687,7 +679,9 @@ where
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.tasks_by_key.reserve(additional);
|
||||
let hash_builder = self.hashes_by_task.hasher();
|
||||
self.tasks_by_key
|
||||
.reserve(additional, |(k, _)| hash_one(hash_builder, k));
|
||||
self.hashes_by_task.reserve(additional);
|
||||
}
|
||||
|
||||
@@ -713,7 +707,9 @@ where
|
||||
#[inline]
|
||||
pub fn shrink_to_fit(&mut self) {
|
||||
self.hashes_by_task.shrink_to_fit();
|
||||
self.tasks_by_key.shrink_to_fit();
|
||||
let hash_builder = self.hashes_by_task.hasher();
|
||||
self.tasks_by_key
|
||||
.shrink_to_fit(|(k, _)| hash_one(hash_builder, k));
|
||||
}
|
||||
|
||||
/// Shrinks the capacity of the map with a lower limit. It will drop
|
||||
@@ -742,27 +738,20 @@ where
|
||||
#[inline]
|
||||
pub fn shrink_to(&mut self, min_capacity: usize) {
|
||||
self.hashes_by_task.shrink_to(min_capacity);
|
||||
self.tasks_by_key.shrink_to(min_capacity)
|
||||
let hash_builder = self.hashes_by_task.hasher();
|
||||
self.tasks_by_key
|
||||
.shrink_to(min_capacity, |(k, _)| hash_one(hash_builder, k))
|
||||
}
|
||||
|
||||
/// Look up a task in the map by its key, returning the key and abort handle.
|
||||
fn get_by_key<'map, Q: ?Sized>(&'map self, key: &Q) -> Option<(&'map Key<K>, &'map AbortHandle)>
|
||||
fn get_by_key<'map, Q: ?Sized>(&'map self, key: &Q) -> Option<&'map (K, AbortHandle)>
|
||||
where
|
||||
Q: Hash + Eq,
|
||||
K: Borrow<Q>,
|
||||
{
|
||||
let hash = self.hash(key);
|
||||
self.tasks_by_key
|
||||
.raw_entry()
|
||||
.from_hash(hash, |k| k.key.borrow() == key)
|
||||
}
|
||||
|
||||
/// Look up a task in the map by its task ID, returning the key and abort handle.
|
||||
fn get_by_id<'map>(&'map self, id: &Id) -> Option<(&'map Key<K>, &'map AbortHandle)> {
|
||||
let hash = self.hashes_by_task.get(id)?;
|
||||
self.tasks_by_key
|
||||
.raw_entry()
|
||||
.from_hash(*hash, |k| &k.id == id)
|
||||
let hash_builder = self.hashes_by_task.hasher();
|
||||
let hash = hash_one(hash_builder, key);
|
||||
self.tasks_by_key.find(hash, |(k, _)| k.borrow() == key)
|
||||
}
|
||||
|
||||
/// Remove a task from the map by ID, returning the key for that task.
|
||||
@@ -773,28 +762,25 @@ where
|
||||
// Remove the entry for that hash.
|
||||
let entry = self
|
||||
.tasks_by_key
|
||||
.raw_entry_mut()
|
||||
.from_hash(hash, |k| k.id == id);
|
||||
let (Key { id: _key_id, key }, handle) = match entry {
|
||||
RawEntryMut::Occupied(entry) => entry.remove_entry(),
|
||||
.find_entry(hash, |(_, abort)| abort.id() == id);
|
||||
let (key, _) = match entry {
|
||||
Ok(entry) => entry.remove().0,
|
||||
_ => return None,
|
||||
};
|
||||
debug_assert_eq!(_key_id, id);
|
||||
debug_assert_eq!(id, handle.id());
|
||||
self.hashes_by_task.remove(&id);
|
||||
Some(key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hash for a given key.
|
||||
#[inline]
|
||||
fn hash<Q: ?Sized>(&self, key: &Q) -> u64
|
||||
where
|
||||
Q: Hash,
|
||||
{
|
||||
let mut hasher = self.tasks_by_key.hasher().build_hasher();
|
||||
key.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
/// Returns the hash for a given key.
|
||||
#[inline]
|
||||
fn hash_one<S: BuildHasher, Q: ?Sized>(hash_builder: &S, key: &Q) -> u64
|
||||
where
|
||||
Q: Hash,
|
||||
{
|
||||
let mut hasher = hash_builder.build_hasher();
|
||||
key.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
impl<K, V, S> JoinMap<K, V, S>
|
||||
@@ -828,11 +814,11 @@ impl<K: fmt::Debug, V, S> fmt::Debug for JoinMap<K, V, S> {
|
||||
// printing the key and task ID pairs, without format the `Key` struct
|
||||
// itself or the `AbortHandle`, which would just format the task's ID
|
||||
// again.
|
||||
struct KeySet<'a, K: fmt::Debug, S>(&'a HashMap<Key<K>, AbortHandle, S>);
|
||||
impl<K: fmt::Debug, S> fmt::Debug for KeySet<'_, K, S> {
|
||||
struct KeySet<'a, K: fmt::Debug>(&'a HashTable<(K, AbortHandle)>);
|
||||
impl<K: fmt::Debug> fmt::Debug for KeySet<'_, K> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_map()
|
||||
.entries(self.0.keys().map(|Key { key, id }| (key, id)))
|
||||
.entries(self.0.iter().map(|(key, abort)| (key, abort.id())))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -853,31 +839,10 @@ impl<K, V> Default for JoinMap<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
// === impl Key ===
|
||||
|
||||
impl<K: Hash> Hash for Key<K> {
|
||||
// Don't include the task ID in the hash.
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
self.key.hash(hasher);
|
||||
}
|
||||
}
|
||||
|
||||
// Because we override `Hash` for this type, we must also override the
|
||||
// `PartialEq` impl, so that all instances with the same hash are equal.
|
||||
impl<K: PartialEq> PartialEq for Key<K> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key == other.key
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq> Eq for Key<K> {}
|
||||
|
||||
/// An iterator over the keys of a [`JoinMap`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JoinMapKeys<'a, K, V> {
|
||||
iter: hashbrown::hash_map::Keys<'a, Key<K>, AbortHandle>,
|
||||
iter: hashbrown::hash_table::Iter<'a, (K, AbortHandle)>,
|
||||
/// To make it easier to change `JoinMap` in the future, keep V as a generic
|
||||
/// parameter.
|
||||
_value: PhantomData<&'a V>,
|
||||
@@ -887,7 +852,7 @@ impl<'a, K, V> Iterator for JoinMapKeys<'a, K, V> {
|
||||
type Item = &'a K;
|
||||
|
||||
fn next(&mut self) -> Option<&'a K> {
|
||||
self.iter.next().map(|key| &key.key)
|
||||
self.iter.next().map(|(key, _)| key)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
|
||||
@@ -14,7 +14,7 @@ use tokio::task::{spawn_local, JoinHandle, LocalSet};
|
||||
/// Internally the local pool uses a [`tokio::task::LocalSet`] for each worker thread
|
||||
/// in the pool. Consequently you can also use [`tokio::task::spawn_local`] (which will
|
||||
/// execute on the same thread) inside the Future you supply to the various spawn methods
|
||||
/// of `LocalPoolHandle`,
|
||||
/// of `LocalPoolHandle`.
|
||||
///
|
||||
/// [`tokio::task::LocalSet`]: tokio::task::LocalSet
|
||||
/// [`tokio::task::spawn_local`]: tokio::task::spawn_local
|
||||
@@ -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);
|
||||
/// }
|
||||
@@ -194,7 +194,7 @@ enum WorkerChoice {
|
||||
}
|
||||
|
||||
struct LocalPool {
|
||||
workers: Vec<LocalWorkerHandle>,
|
||||
workers: Box<[LocalWorkerHandle]>,
|
||||
}
|
||||
|
||||
impl LocalPool {
|
||||
@@ -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:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//!
|
||||
//! This type must be used from within the context of the `Runtime`.
|
||||
|
||||
use futures_core::Future;
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Timeout;
|
||||
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,10 +51,7 @@ impl<T: Stack> Level<T> {
|
||||
pub(crate) fn next_expiration(&self, now: u64) -> Option<Expiration> {
|
||||
// Use the `occupied` bit field to get the index of the next slot that
|
||||
// needs to be processed.
|
||||
let slot = match self.next_occupied_slot(now) {
|
||||
Some(slot) => slot,
|
||||
None => return None,
|
||||
};
|
||||
let slot = self.next_occupied_slot(now)?;
|
||||
|
||||
// From the slot index, calculate the `Instant` at which it needs to be
|
||||
// processed. This value *must* be in the future with respect to `now`.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user