mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
107
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14c657dec5 | ||
|
|
9eb3f5b556 | ||
|
|
3a94eb0893 | ||
|
|
93bde0870f | ||
|
|
ddd7250e62 | ||
|
|
f64a1a3dbd | ||
|
|
c88f9bc930 | ||
|
|
29a6f468a6 | ||
|
|
8c076cb00d | ||
|
|
c84d0a14b1 | ||
|
|
a883fd4378 | ||
|
|
1014262d34 | ||
|
|
f6313f4382 | ||
|
|
70364b7079 | ||
|
|
dd9471d13a | ||
|
|
4e2ef63c4e | ||
|
|
dec390df1e | ||
|
|
89b73f39bf | ||
|
|
a26fc9c9f9 | ||
|
|
7fe88ce4ad | ||
|
|
f2d033e454 | ||
|
|
c999699f5e | ||
|
|
7430865d65 | ||
|
|
56239a9035 | ||
|
|
1b4106a1ce | ||
|
|
3abe877bf7 | ||
|
|
61b68a8abc | ||
|
|
52bc6b6f2d | ||
|
|
f478ff4a24 | ||
|
|
660eac71f0 | ||
|
|
1d785fd66f | ||
|
|
6a8f6f5a90 | ||
|
|
398dfda56d | ||
|
|
9bdc475539 | ||
|
|
b5a5ddb4cf | ||
|
|
74c6e6c683 | ||
|
|
f21d596099 | ||
|
|
66c62a4b74 | ||
|
|
a86c052218 | ||
|
|
c1778eda38 | ||
|
|
11b8807544 | ||
|
|
e789b61424 | ||
|
|
2cd4f4ab46 | ||
|
|
b6bbe5f487 | ||
|
|
57ba4a4b10 | ||
|
|
5e6c6bdafd | ||
|
|
77e3911806 | ||
|
|
b9868b23aa | ||
|
|
623483c81f | ||
|
|
5cef6eba7b | ||
|
|
db543639e1 | ||
|
|
9f9e596eec | ||
|
|
f6cb6e084b | ||
|
|
7aea597a8f | ||
|
|
9507f8b374 | ||
|
|
6037faeede | ||
|
|
8497f379b5 | ||
|
|
1b22cbfd33 | ||
|
|
effead29d1 | ||
|
|
3b16564ce0 | ||
|
|
fc1e03f91b | ||
|
|
ea5d448ee8 | ||
|
|
abc93f615e | ||
|
|
cf9a03c107 | ||
|
|
3b45e8614d | ||
|
|
b02c550c52 | ||
|
|
03912b9cf7 | ||
|
|
d4afbad6e5 | ||
|
|
b1ca0d8b12 | ||
|
|
16cdb109f4 | ||
|
|
88445e762c | ||
|
|
3c403d6ee8 | ||
|
|
d63d659078 | ||
|
|
b31f1a4662 | ||
|
|
663e56e983 | ||
|
|
1df874ead4 | ||
|
|
614fe357fc | ||
|
|
68b02db154 | ||
|
|
822af18cf5 | ||
|
|
92d33b7181 | ||
|
|
1cb7bf11b3 | ||
|
|
768ede65c1 | ||
|
|
54a394696f | ||
|
|
35dd635630 | ||
|
|
a7bb054414 | ||
|
|
0c8e8248f8 | ||
|
|
2dfe4e8885 | ||
|
|
b489acb46c | ||
|
|
d46c844bb9 | ||
|
|
4cd4b02389 | ||
|
|
17cc283f58 | ||
|
|
0a93ed7e7a | ||
|
|
cef98e25e7 | ||
|
|
e7bd754231 | ||
|
|
d459a93453 | ||
|
|
f177aad6e4 | ||
|
|
4ea632005d | ||
|
|
bfc43795f9 | ||
|
|
89329cd07f | ||
|
|
e34978233b | ||
|
|
8eb94a33c0 | ||
|
|
2b7b1a0494 | ||
|
|
002f4a28c8 | ||
|
|
ff2f286c12 | ||
|
|
bd4ce68864 | ||
|
|
abd92fb27f | ||
|
|
9931901d5c |
@@ -1,2 +0,0 @@
|
||||
# [build]
|
||||
# rustflags = ["--cfg", "tokio_unstable"]
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
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-12-4
|
||||
image_family: freebsd-13-1
|
||||
env:
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2022-10-25
|
||||
@@ -14,7 +14,7 @@ env:
|
||||
task:
|
||||
name: FreeBSD 64-bit
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
|
||||
- . $HOME/.cargo/env
|
||||
@@ -31,7 +31,7 @@ task:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_NIGHTLY
|
||||
- . $HOME/.cargo/env
|
||||
@@ -45,7 +45,7 @@ task:
|
||||
task:
|
||||
name: FreeBSD 32-bit
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- pkg install -y bash
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
|
||||
- . $HOME/.cargo/env
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
msrv = "1.49"
|
||||
msrv = "1.56"
|
||||
|
||||
+126
-23
@@ -11,7 +11,7 @@ env:
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2022-11-03
|
||||
rust_nightly: nightly-2023-05-18
|
||||
rust_clippy: 1.65.0
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
@@ -21,7 +21,7 @@ env:
|
||||
# - tokio-util/Cargo.toml
|
||||
# - tokio-test/Cargo.toml
|
||||
# - tokio-stream/Cargo.toml
|
||||
rust_min: 1.49.0
|
||||
rust_min: 1.56.0
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -31,7 +31,7 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Depends on all action sthat are required for a "successful" CI run.
|
||||
# Depends on all actions that are required for a "successful" CI run.
|
||||
tests-pass:
|
||||
name: all systems go
|
||||
runs-on: ubuntu-latest
|
||||
@@ -59,10 +59,25 @@ jobs:
|
||||
- wasm32-unknown-unknown
|
||||
- wasm32-wasi
|
||||
- check-external-types
|
||||
- check-fuzzing
|
||||
- check-unstable-mt-counters
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
# Basic actions that must pass before we kick off more expensive tests.
|
||||
basics:
|
||||
name: basic checks
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- clippy
|
||||
- fmt
|
||||
- docs
|
||||
- minrust
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
test:
|
||||
needs: basics
|
||||
name: test tokio full
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
@@ -119,6 +134,7 @@ jobs:
|
||||
# This relies on the potentially affected Tokio type being listed in
|
||||
# `tokio/tokio/tests/async_send_sync.rs`.
|
||||
name: compile tests with parking lot send guards
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -135,6 +151,7 @@ jobs:
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -167,13 +184,14 @@ jobs:
|
||||
|
||||
test-unstable:
|
||||
name: test tokio full --unstable
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
include:
|
||||
- os: windows-latest
|
||||
- os: ubuntu-latest
|
||||
- os: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
@@ -190,9 +208,60 @@ jobs:
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable
|
||||
|
||||
test-unstable-taskdump:
|
||||
name: test tokio full --unstable --taskdump
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" and "taskdump" cfg flags.
|
||||
- name: test tokio full --cfg unstable --cfg taskdump
|
||||
run: cargo test --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_taskdump
|
||||
|
||||
check-unstable-mt-counters:
|
||||
name: check tokio full --internal-mt-counters
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# Run `tokio` with "unstable" and "taskdump" cfg flags.
|
||||
- name: check tokio full --cfg unstable --cfg internal-mt-counters
|
||||
run: cargo test --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_internal_mt_counters -Dwarnings
|
||||
# in order to run doctests for unstable features, we must also pass
|
||||
# the unstable cfg to RustDoc
|
||||
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_internal_mt_counters
|
||||
|
||||
miri:
|
||||
name: miri
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -212,6 +281,7 @@ jobs:
|
||||
|
||||
asan:
|
||||
name: asan
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -232,23 +302,19 @@ jobs:
|
||||
|
||||
semver:
|
||||
name: semver
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
- name: Check semver
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
- name: Install cargo-semver-checks
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-semver-checks
|
||||
- name: Check semver compatibility
|
||||
run: |
|
||||
cargo semver-checks check-release --release-type minor
|
||||
rust-toolchain: ${{ env.rust_stable }}
|
||||
release-type: minor
|
||||
|
||||
cross-check:
|
||||
name: cross-check
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -273,14 +339,17 @@ jobs:
|
||||
|
||||
cross-test:
|
||||
name: cross-test
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
rustflags: --cfg tokio_taskdump
|
||||
|
||||
# Run a platform without AtomicU64 and no const Mutex::new
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
@@ -309,6 +378,7 @@ jobs:
|
||||
# See https://github.com/tokio-rs/tokio/issues/5187
|
||||
no-atomic-u64:
|
||||
name: Test i686-unknown-linux-gnu without AtomicU64
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -325,18 +395,19 @@ jobs:
|
||||
target: i686-unknown-linux-gnu
|
||||
- run: cargo test -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64
|
||||
# https://github.com/tokio-rs/tokio/pull/5356
|
||||
# https://github.com/tokio-rs/tokio/issues/5373
|
||||
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64 --cfg tokio_no_const_mutex_new
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64 --cfg tokio_no_const_mutex_new
|
||||
- run: cargo hack build -p tokio --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_atomic_u64
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_atomic_u64
|
||||
|
||||
features:
|
||||
name: features
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -355,6 +426,11 @@ jobs:
|
||||
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
# Try with unstable and taskdump feature flags
|
||||
- name: check --feature-powerset --unstable --taskdump
|
||||
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
|
||||
minrust:
|
||||
name: minrust
|
||||
@@ -386,6 +462,7 @@ jobs:
|
||||
|
||||
minimal-versions:
|
||||
name: minimal-versions
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -406,7 +483,7 @@ jobs:
|
||||
cargo hack check --all-features --ignore-private
|
||||
- name: "check --all-features --unstable -Z minimal-versions"
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
@@ -463,11 +540,12 @@ jobs:
|
||||
- name: "doc --lib --all-features"
|
||||
run: cargo doc --lib --no-deps --all-features --document-private-items
|
||||
env:
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
|
||||
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump
|
||||
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
|
||||
loom-compile:
|
||||
name: build loom tests
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -496,6 +574,7 @@ jobs:
|
||||
|
||||
test-hyper:
|
||||
name: Test hyper
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -529,6 +608,7 @@ jobs:
|
||||
|
||||
x86_64-fortanix-unknown-sgx:
|
||||
name: build tokio for x86_64-fortanix-unknown-sgx
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -545,6 +625,7 @@ jobs:
|
||||
|
||||
wasm32-unknown-unknown:
|
||||
name: test tokio for wasm32-unknown-unknown
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -561,6 +642,7 @@ jobs:
|
||||
|
||||
wasm32-wasi:
|
||||
name: wasm32-wasi
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -608,6 +690,7 @@ jobs:
|
||||
|
||||
check-external-types:
|
||||
name: check-external-types
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -629,3 +712,23 @@ jobs:
|
||||
cargo install cargo-check-external-types --locked --version 0.1.6
|
||||
cargo check-external-types --all-features --config external-types.toml
|
||||
working-directory: tokio
|
||||
|
||||
check-fuzzing:
|
||||
name: check-fuzzing
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-fuzz
|
||||
run: cargo install cargo-fuzz
|
||||
- name: Check /tokio/
|
||||
run: cargo fuzz check --all-features
|
||||
working-directory: tokio
|
||||
- name: Check /tokio-stream/
|
||||
run: cargo fuzz check --all-features
|
||||
working-directory: tokio-stream
|
||||
|
||||
@@ -24,13 +24,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
scope:
|
||||
- --skip loom_pool
|
||||
- loom_pool::group_a
|
||||
- loom_pool::group_b
|
||||
- loom_pool::group_c
|
||||
- loom_pool::group_d
|
||||
- time::driver
|
||||
include:
|
||||
- scope: --skip loom_pool
|
||||
max_preemptions: 2
|
||||
- scope: loom_pool::group_a
|
||||
max_preemptions: 1
|
||||
- scope: loom_pool::group_b
|
||||
max_preemptions: 2
|
||||
- scope: loom_pool::group_c
|
||||
max_preemptions: 1
|
||||
- scope: loom_pool::group_d
|
||||
max_preemptions: 1
|
||||
- scope: time::driver
|
||||
max_preemptions: 2
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
@@ -42,7 +48,7 @@ jobs:
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings -C debug-assertions
|
||||
LOOM_MAX_PREEMPTIONS: ${{ matrix.max_preemptions }}
|
||||
LOOM_MAX_BRANCHES: 10000
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
@@ -2,3 +2,4 @@ target
|
||||
Cargo.lock
|
||||
|
||||
.cargo/config.toml
|
||||
.cargo/config
|
||||
|
||||
+26
-5
@@ -131,8 +131,11 @@ cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
Clippy must be run using the MSRV, so Tokio can avoid having to `#[allow]` new
|
||||
lints whose fixes would be incompatible with the current MSRV:
|
||||
Ideally, you should use the same version of clippy as the one used in CI
|
||||
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
|
||||
might have new lints:
|
||||
|
||||
[ci.yml]: .github/workflows/ci.yml
|
||||
|
||||
<!--
|
||||
When updating this, also update:
|
||||
@@ -146,7 +149,7 @@ When updating this, also update:
|
||||
-->
|
||||
|
||||
```
|
||||
cargo +1.49.0 clippy --all --tests --all-features
|
||||
cargo +1.65.0 clippy --all --tests --all-features
|
||||
```
|
||||
|
||||
When building documentation normally, the markers that list the features
|
||||
@@ -197,8 +200,22 @@ If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: integration tests and documentation tests
|
||||
(Tokio avoids unit tests as much as possible).
|
||||
There are two ways to write tests: [integration tests][integration-tests]
|
||||
and [documentation tests][documentation-tests].
|
||||
(Tokio avoids [unit tests][unit-tests] as much as possible).
|
||||
|
||||
Tokio uses [conditional compilation attributes][conditional-compilation]
|
||||
throughout the codebase, to modify rustc's behavior. Code marked with such
|
||||
attributes can be enabled using RUSTFLAGS and RUSTDOCFLAGS environment
|
||||
variables. One of the most prevalent flags passed in these variables is
|
||||
the `--cfg` option. To run tests in a particular file, check first what
|
||||
options #![cfg] declaration defines for that file.
|
||||
|
||||
For instance, to run a test marked with the 'tokio_unstable' cfg option,
|
||||
you must pass this flag to the compiler when running the test.
|
||||
```
|
||||
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
|
||||
```
|
||||
|
||||
#### Integration tests
|
||||
|
||||
@@ -658,3 +675,7 @@ When releasing a new version of a crate, follow these steps:
|
||||
entry for that release version into your editor and close the window.
|
||||
|
||||
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog/blob/master/CHANGELOG.md
|
||||
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
|
||||
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
|
||||
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
|
||||
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.26.0", features = ["full"] }
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -187,7 +187,20 @@ When updating this, also update:
|
||||
|
||||
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
|
||||
least** 6 months. When increasing the MSRV, the new Rust version must have been
|
||||
released at least six months ago. The current MSRV is 1.49.0.
|
||||
released at least six months ago. The current MSRV is 1.56.0.
|
||||
|
||||
Note that the MSRV is not increased automatically, and only as part of a minor
|
||||
release. The MSRV history for past minor releases can be found below:
|
||||
|
||||
* 1.27 to now - Rust 1.56
|
||||
* 1.17 to 1.26 - Rust 1.49
|
||||
* 1.15 to 1.16 - Rust 1.46
|
||||
* 1.0 to 1.14 - Rust 1.45
|
||||
|
||||
Note that although we try to avoid the situation where a dependency transitively
|
||||
increases the MSRV of Tokio, we do not guarantee that this does not happen.
|
||||
However, every minor release will have some set of versions of dependencies that
|
||||
works with the MSRV of that minor release.
|
||||
|
||||
## Release schedule
|
||||
|
||||
@@ -202,8 +215,9 @@ 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.18.x` - LTS release until June 2023
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
* `1.18.x` - LTS release until June 2023. (MSRV 1.49)
|
||||
* `1.20.x` - LTS release until September 2023. (MSRV 1.49)
|
||||
* `1.25.x` - LTS release until March 2024. (MSRV 1.49)
|
||||
|
||||
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
|
||||
|
||||
@@ -40,11 +40,20 @@ name = "sync_watch"
|
||||
path = "sync_watch.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_current_thread"
|
||||
path = "rt_current_thread.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_multi_threaded"
|
||||
path = "rt_multi_threaded.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_notify"
|
||||
path = "sync_notify.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_rwlock"
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
//! Benchmark implementation details of the threaded scheduler. These benches are
|
||||
//! intended to be used as a form of regression testing and not as a general
|
||||
//! purpose benchmark demonstrating real-world performance.
|
||||
|
||||
use tokio::runtime::{self, Runtime};
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(tokio::spawn(async move {}));
|
||||
}
|
||||
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_busy(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
rt.spawn(async {
|
||||
fn iter() {
|
||||
tokio::spawn(async { iter() });
|
||||
}
|
||||
|
||||
iter()
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_current_thread().build().unwrap()
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
@@ -10,9 +10,10 @@ use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
fn spawn_many(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const NUM_WORKERS: usize = 4;
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
@@ -38,6 +39,52 @@ fn spawn_many(b: &mut Bencher) {
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_busy(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(2 * NUM_WORKERS) {
|
||||
rt.spawn(async {
|
||||
loop {
|
||||
tokio::task::yield_now().await;
|
||||
std::thread::sleep(std::time::Duration::from_micros(10));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn yield_many(b: &mut Bencher) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS: usize = 200;
|
||||
@@ -140,12 +187,20 @@ fn chained_spawn(b: &mut Bencher) {
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread()
|
||||
.worker_threads(4)
|
||||
.worker_threads(NUM_WORKERS)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
benchmark_group!(scheduler, spawn_many, ping_pong, yield_many, chained_spawn,);
|
||||
benchmark_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy,
|
||||
ping_pong,
|
||||
yield_many,
|
||||
chained_spawn,
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
use bencher::Bencher;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Notify;
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn notify_waiters<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
for _ in 0..N_WAITERS {
|
||||
rt.spawn({
|
||||
let notify = notify.clone();
|
||||
let counter = counter.clone();
|
||||
async move {
|
||||
loop {
|
||||
notify.notified().await;
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_waiters();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn notify_one<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
for _ in 0..N_WAITERS {
|
||||
rt.spawn({
|
||||
let notify = notify.clone();
|
||||
let counter = counter.clone();
|
||||
async move {
|
||||
loop {
|
||||
notify.notified().await;
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_one();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
notify_waiters_simple,
|
||||
notify_waiters::<10>,
|
||||
notify_waiters::<50>,
|
||||
notify_waiters::<100>,
|
||||
notify_waiters::<200>,
|
||||
notify_waiters::<500>
|
||||
);
|
||||
|
||||
bencher::benchmark_group!(
|
||||
notify_one_simple,
|
||||
notify_one::<10>,
|
||||
notify_one::<50>,
|
||||
notify_one::<100>,
|
||||
notify_one::<200>,
|
||||
notify_one::<500>
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(notify_waiters_simple, notify_one_simple);
|
||||
+5
-1
@@ -25,7 +25,7 @@ once_cell = "1.5.2"
|
||||
rand = "0.8.3"
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.windows-sys]
|
||||
version = "0.45"
|
||||
version = "0.48"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
@@ -90,3 +90,7 @@ path = "named-pipe-ready.rs"
|
||||
[[example]]
|
||||
name = "named-pipe-multi-client"
|
||||
path = "named-pipe-multi-client.rs"
|
||||
|
||||
[[example]]
|
||||
name = "dump"
|
||||
path = "dump.rs"
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//! This example demonstrates tokio's experimental taskdumping functionality.
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
use std::hint::black_box;
|
||||
|
||||
#[inline(never)]
|
||||
async fn a() {
|
||||
black_box(b()).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn b() {
|
||||
black_box(c()).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn c() {
|
||||
black_box(tokio::task::yield_now()).await
|
||||
}
|
||||
|
||||
tokio::spawn(a());
|
||||
tokio::spawn(b());
|
||||
tokio::spawn(c());
|
||||
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
let dump = handle.dump();
|
||||
|
||||
for (i, task) in dump.tasks().iter().enumerate() {
|
||||
let trace = task.trace();
|
||||
println!("task {i} trace:");
|
||||
println!("{trace}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
)))]
|
||||
fn main() {
|
||||
println!("task dumps are not available")
|
||||
}
|
||||
@@ -18,7 +18,7 @@ use futures::SinkExt;
|
||||
use http::{header::HeaderValue, Request, Response, StatusCode};
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
use std::{env, error::Error, fmt, io};
|
||||
use std::{convert::TryFrom, env, error::Error, fmt, io};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed};
|
||||
@@ -180,8 +180,11 @@ 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))?;
|
||||
|
||||
(
|
||||
toslice(r.method.unwrap().as_bytes()),
|
||||
method,
|
||||
toslice(r.path.unwrap().as_bytes()),
|
||||
r.version.unwrap(),
|
||||
amt,
|
||||
@@ -195,7 +198,7 @@ impl Decoder for Http {
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
ret = ret.method(&data[method.0..method.1]);
|
||||
ret = ret.method(method);
|
||||
let s = data.slice(path.0..path.1);
|
||||
let s = unsafe { String::from_utf8_unchecked(Vec::from(s.as_ref())) };
|
||||
ret = ret.uri(s);
|
||||
|
||||
@@ -36,13 +36,10 @@ async fn test_worker_threads_not_int() {}
|
||||
async fn test_worker_threads_and_current_thread() {}
|
||||
|
||||
#[tokio::test(crate = 456)]
|
||||
async fn test_crate_not_ident_int() {}
|
||||
async fn test_crate_not_path_int() {}
|
||||
|
||||
#[tokio::test(crate = "456")]
|
||||
async fn test_crate_not_ident_invalid() {}
|
||||
|
||||
#[tokio::test(crate = "abc::edf")]
|
||||
async fn test_crate_not_ident_path() {}
|
||||
async fn test_crate_not_path_invalid() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[test]
|
||||
|
||||
@@ -64,34 +64,28 @@ error: The `worker_threads` option requires the `multi_thread` runtime flavor. U
|
||||
35 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
|
||||
| ^
|
||||
|
||||
error: Failed to parse value of `crate` as ident.
|
||||
error: Failed to parse value of `crate` as path.
|
||||
--> $DIR/macros_invalid_input.rs:38:23
|
||||
|
|
||||
38 | #[tokio::test(crate = 456)]
|
||||
| ^^^
|
||||
|
||||
error: Failed to parse value of `crate` as ident: "456"
|
||||
error: Failed to parse value of `crate` as path: "456"
|
||||
--> $DIR/macros_invalid_input.rs:41:23
|
||||
|
|
||||
41 | #[tokio::test(crate = "456")]
|
||||
| ^^^^^
|
||||
|
||||
error: Failed to parse value of `crate` as ident: "abc::edf"
|
||||
--> $DIR/macros_invalid_input.rs:44:23
|
||||
|
|
||||
44 | #[tokio::test(crate = "abc::edf")]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: second test attribute is supplied
|
||||
--> $DIR/macros_invalid_input.rs:48:1
|
||||
--> $DIR/macros_invalid_input.rs:45:1
|
||||
|
|
||||
48 | #[test]
|
||||
45 | #[test]
|
||||
| ^^^^^^^
|
||||
|
||||
error: duplicated attribute
|
||||
--> $DIR/macros_invalid_input.rs:48:1
|
||||
--> $DIR/macros_invalid_input.rs:45:1
|
||||
|
|
||||
48 | #[test]
|
||||
45 | #[test]
|
||||
| ^^^^^^^
|
||||
|
|
||||
note: the lint level is defined here
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:5:5
|
||||
--> tests/fail/macros_type_mismatch.rs:5:5
|
||||
|
|
||||
4 | async fn missing_semicolon_or_return_type() {
|
||||
| - help: a return type might be missing here: `-> _`
|
||||
5 | Ok(())
|
||||
| ^^^^^^ expected `()`, found enum `Result`
|
||||
| ^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:10:5
|
||||
--> tests/fail/macros_type_mismatch.rs:10:5
|
||||
|
|
||||
9 | async fn missing_return_type() {
|
||||
| - help: a return type might be missing here: `-> _`
|
||||
10 | return Ok(());
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found enum `Result`
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:23:5
|
||||
--> tests/fail/macros_type_mismatch.rs:23:5
|
||||
|
|
||||
14 | async fn extra_semicolon() -> Result<(), ()> {
|
||||
| -------------- expected `Result<(), ()>` because of return type
|
||||
...
|
||||
23 | Ok(());
|
||||
| ^^^^^^^ expected enum `Result`, found `()`
|
||||
| ^^^^^^^ expected `Result<(), ()>`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
@@ -38,7 +38,7 @@ help: try adding an expression at the end of the block
|
||||
|
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:32:5
|
||||
--> tests/fail/macros_type_mismatch.rs:32:5
|
||||
|
|
||||
30 | async fn issue_4635() {
|
||||
| - help: try adding a return type: `-> i32`
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
# 2.1.0 (April 25th, 2023)
|
||||
|
||||
- macros: fix typo in `#[tokio::test]` docs ([#5636])
|
||||
- macros: make entrypoints more efficient ([#5621])
|
||||
|
||||
[#5621]: https://github.com/tokio-rs/tokio/pull/5621
|
||||
[#5636]: https://github.com/tokio-rs/tokio/pull/5636
|
||||
|
||||
# 2.0.0 (March 24th, 2023)
|
||||
|
||||
This major release updates the dependency on the syn crate to 2.0.0, and
|
||||
increases the MSRV to 1.56.
|
||||
|
||||
As part of this release, we are adopting a policy of depending on a specific minor
|
||||
release of tokio-macros. This prevents Tokio from being able to pull in many different
|
||||
versions of tokio-macros.
|
||||
|
||||
- macros: update `syn` ([#5572])
|
||||
- macros: accept path as crate rename ([#5557])
|
||||
|
||||
[#5572]: https://github.com/tokio-rs/tokio/pull/5572
|
||||
[#5557]: https://github.com/tokio-rs/tokio/pull/5557
|
||||
|
||||
# 1.8.2 (November 30th, 2022)
|
||||
|
||||
- fix a regression introduced in 1.8.1 ([#5244])
|
||||
|
||||
@@ -4,9 +4,9 @@ name = "tokio-macros"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.x.y" git tag.
|
||||
version = "1.8.2"
|
||||
version = "2.1.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -24,7 +24,7 @@ proc-macro = true
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.7"
|
||||
quote = "1"
|
||||
syn = { version = "1.0.56", features = ["full"] }
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
+169
-72
@@ -1,10 +1,10 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use proc_macro2::{Span, TokenStream, TokenTree};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::parse::Parser;
|
||||
use syn::parse::{Parse, ParseStream, Parser};
|
||||
use syn::{braced, Attribute, Ident, Path, Signature, Visibility};
|
||||
|
||||
// syn::AttributeArgs does not implement syn::Parse
|
||||
type AttributeArgs = syn::punctuated::Punctuated<syn::NestedMeta, syn::Token![,]>;
|
||||
type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum RuntimeFlavor {
|
||||
@@ -29,7 +29,7 @@ struct FinalConfig {
|
||||
flavor: RuntimeFlavor,
|
||||
worker_threads: Option<usize>,
|
||||
start_paused: Option<bool>,
|
||||
crate_name: Option<String>,
|
||||
crate_name: Option<Path>,
|
||||
}
|
||||
|
||||
/// Config used in case of the attribute not being able to build a valid config
|
||||
@@ -47,7 +47,7 @@ struct Configuration {
|
||||
worker_threads: Option<(usize, Span)>,
|
||||
start_paused: Option<(bool, Span)>,
|
||||
is_test: bool,
|
||||
crate_name: Option<String>,
|
||||
crate_name: Option<Path>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
@@ -112,8 +112,8 @@ impl Configuration {
|
||||
if self.crate_name.is_some() {
|
||||
return Err(syn::Error::new(span, "`crate` set multiple times."));
|
||||
}
|
||||
let name_ident = parse_ident(name, span, "crate")?;
|
||||
self.crate_name = Some(name_ident.to_string());
|
||||
let name_path = parse_path(name, span, "crate")?;
|
||||
self.crate_name = Some(name_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -199,23 +199,22 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ident(lit: syn::Lit, span: Span, field: &str) -> Result<Ident, syn::Error> {
|
||||
fn parse_path(lit: syn::Lit, span: Span, field: &str) -> Result<Path, syn::Error> {
|
||||
match lit {
|
||||
syn::Lit::Str(s) => {
|
||||
let err = syn::Error::new(
|
||||
span,
|
||||
format!(
|
||||
"Failed to parse value of `{}` as ident: \"{}\"",
|
||||
"Failed to parse value of `{}` as path: \"{}\"",
|
||||
field,
|
||||
s.value()
|
||||
),
|
||||
);
|
||||
let path = s.parse::<syn::Path>().map_err(|_| err.clone())?;
|
||||
path.get_ident().cloned().ok_or(err)
|
||||
s.parse::<syn::Path>().map_err(|_| err.clone())
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse value of `{}` as ident.", field),
|
||||
format!("Failed to parse value of `{}` as path.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -231,7 +230,7 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
|
||||
}
|
||||
|
||||
fn build_config(
|
||||
input: syn::ItemFn,
|
||||
input: &ItemFn,
|
||||
args: AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_multi_thread: bool,
|
||||
@@ -246,7 +245,7 @@ fn build_config(
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
|
||||
syn::Meta::NameValue(namevalue) => {
|
||||
let ident = namevalue
|
||||
.path
|
||||
.get_ident()
|
||||
@@ -255,34 +254,26 @@ fn build_config(
|
||||
})?
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
let lit = match &namevalue.value {
|
||||
syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit,
|
||||
expr => return Err(syn::Error::new_spanned(expr, "Must be a literal")),
|
||||
};
|
||||
match ident.as_str() {
|
||||
"worker_threads" => {
|
||||
config.set_worker_threads(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
config.set_worker_threads(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"flavor" => {
|
||||
config.set_flavor(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
config.set_flavor(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"start_paused" => {
|
||||
config.set_start_paused(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
config.set_start_paused(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
"core_threads" => {
|
||||
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
"crate" => {
|
||||
config.set_crate_name(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
config.set_crate_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
|
||||
}
|
||||
name => {
|
||||
let msg = format!(
|
||||
@@ -293,7 +284,7 @@ fn build_config(
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
||||
syn::Meta::Path(path) => {
|
||||
let name = path
|
||||
.get_ident()
|
||||
.ok_or_else(|| syn::Error::new_spanned(&path, "Must have specified ident"))?
|
||||
@@ -333,18 +324,13 @@ fn build_config(
|
||||
config.build()
|
||||
}
|
||||
|
||||
fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> TokenStream {
|
||||
fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenStream {
|
||||
input.sig.asyncness = None;
|
||||
|
||||
// If type mismatch occurs, the current rustc points to the last statement.
|
||||
let (last_stmt_start_span, last_stmt_end_span) = {
|
||||
let mut last_stmt = input
|
||||
.block
|
||||
.stmts
|
||||
.last()
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_default()
|
||||
.into_iter();
|
||||
let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter();
|
||||
|
||||
// `Span` on stable Rust has a limitation that only points to the first
|
||||
// token, not the whole tokens. We can work around this limitation by
|
||||
// using the first/last span of the tokens like
|
||||
@@ -354,16 +340,17 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
|
||||
(start, end)
|
||||
};
|
||||
|
||||
let crate_name = config.crate_name.as_deref().unwrap_or("tokio");
|
||||
|
||||
let crate_ident = Ident::new(crate_name, last_stmt_start_span);
|
||||
let crate_path = config
|
||||
.crate_name
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream());
|
||||
|
||||
let mut rt = match config.flavor {
|
||||
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_ident::runtime::Builder::new_current_thread()
|
||||
#crate_path::runtime::Builder::new_current_thread()
|
||||
},
|
||||
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
|
||||
#crate_ident::runtime::Builder::new_multi_thread()
|
||||
#crate_path::runtime::Builder::new_multi_thread()
|
||||
},
|
||||
};
|
||||
if let Some(v) = config.worker_threads {
|
||||
@@ -381,10 +368,8 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let body = &input.block;
|
||||
let brace_token = input.block.brace_token;
|
||||
let body_ident = quote! { body };
|
||||
let block_expr = quote_spanned! {last_stmt_end_span=>
|
||||
let last_block = quote_spanned! {last_stmt_end_span=>
|
||||
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
||||
{
|
||||
return #rt
|
||||
@@ -395,6 +380,8 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
|
||||
}
|
||||
};
|
||||
|
||||
let body = input.body();
|
||||
|
||||
// For test functions pin the body to the stack and use `Pin<&mut dyn
|
||||
// Future>` to reduce the amount of `Runtime::block_on` (and related
|
||||
// functions) copies we generate during compilation due to the generic
|
||||
@@ -414,7 +401,7 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
|
||||
};
|
||||
quote! {
|
||||
let body = async #body;
|
||||
#crate_ident::pin!(body);
|
||||
#crate_path::pin!(body);
|
||||
let body: ::std::pin::Pin<&mut dyn ::std::future::Future<Output = #output_type>> = body;
|
||||
}
|
||||
} else {
|
||||
@@ -423,25 +410,11 @@ fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> To
|
||||
}
|
||||
};
|
||||
|
||||
input.block = syn::parse2(quote! {
|
||||
{
|
||||
#body
|
||||
#block_expr
|
||||
}
|
||||
})
|
||||
.expect("Parsing failure");
|
||||
input.block.brace_token = brace_token;
|
||||
|
||||
let result = quote! {
|
||||
#header
|
||||
#input
|
||||
};
|
||||
|
||||
result.into()
|
||||
input.into_tokens(header, body, last_block)
|
||||
}
|
||||
|
||||
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
||||
tokens.extend(TokenStream::from(error.into_compile_error()));
|
||||
tokens.extend(error.into_compile_error());
|
||||
tokens
|
||||
}
|
||||
|
||||
@@ -450,7 +423,7 @@ pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
let input: ItemFn = match syn::parse2(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
@@ -460,8 +433,8 @@ pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
Err(syn::Error::new_spanned(&input.sig.ident, msg))
|
||||
} else {
|
||||
AttributeArgs::parse_terminated
|
||||
.parse(args)
|
||||
.and_then(|args| build_config(input.clone(), args, false, rt_multi_thread))
|
||||
.parse2(args)
|
||||
.and_then(|args| build_config(&input, args, false, rt_multi_thread))
|
||||
};
|
||||
|
||||
match config {
|
||||
@@ -474,17 +447,17 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
// If any of the steps for this macro fail, we still want to expand to an item that is as close
|
||||
// to the expected output as possible. This helps out IDEs such that completions and other
|
||||
// related features keep working.
|
||||
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
||||
let input: ItemFn = match syn::parse2(item.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(e) => return token_stream_with_error(item, e),
|
||||
};
|
||||
let config = if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) {
|
||||
let config = if let Some(attr) = input.attrs().find(|attr| attr.meta.path().is_ident("test")) {
|
||||
let msg = "second test attribute is supplied";
|
||||
Err(syn::Error::new_spanned(attr, msg))
|
||||
} else {
|
||||
AttributeArgs::parse_terminated
|
||||
.parse(args)
|
||||
.and_then(|args| build_config(input.clone(), args, true, rt_multi_thread))
|
||||
.parse2(args)
|
||||
.and_then(|args| build_config(&input, args, true, rt_multi_thread))
|
||||
};
|
||||
|
||||
match config {
|
||||
@@ -492,3 +465,127 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e),
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemFn {
|
||||
outer_attrs: Vec<Attribute>,
|
||||
vis: Visibility,
|
||||
sig: Signature,
|
||||
brace_token: syn::token::Brace,
|
||||
inner_attrs: Vec<Attribute>,
|
||||
stmts: Vec<proc_macro2::TokenStream>,
|
||||
}
|
||||
|
||||
impl ItemFn {
|
||||
/// Access all attributes of the function item.
|
||||
fn attrs(&self) -> impl Iterator<Item = &Attribute> {
|
||||
self.outer_attrs.iter().chain(self.inner_attrs.iter())
|
||||
}
|
||||
|
||||
/// Get the body of the function item in a manner so that it can be
|
||||
/// conveniently used with the `quote!` macro.
|
||||
fn body(&self) -> Body<'_> {
|
||||
Body {
|
||||
brace_token: self.brace_token,
|
||||
stmts: &self.stmts,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert our local function item into a token stream.
|
||||
fn into_tokens(
|
||||
self,
|
||||
header: proc_macro2::TokenStream,
|
||||
body: proc_macro2::TokenStream,
|
||||
last_block: proc_macro2::TokenStream,
|
||||
) -> TokenStream {
|
||||
let mut tokens = proc_macro2::TokenStream::new();
|
||||
header.to_tokens(&mut tokens);
|
||||
|
||||
// Outer attributes are simply streamed as-is.
|
||||
for attr in self.outer_attrs {
|
||||
attr.to_tokens(&mut tokens);
|
||||
}
|
||||
|
||||
// Inner attributes require extra care, since they're not supported on
|
||||
// blocks (which is what we're expanded into) we instead lift them
|
||||
// outside of the function. This matches the behaviour of `syn`.
|
||||
for mut attr in self.inner_attrs {
|
||||
attr.style = syn::AttrStyle::Outer;
|
||||
attr.to_tokens(&mut tokens);
|
||||
}
|
||||
|
||||
self.vis.to_tokens(&mut tokens);
|
||||
self.sig.to_tokens(&mut tokens);
|
||||
|
||||
self.brace_token.surround(&mut tokens, |tokens| {
|
||||
body.to_tokens(tokens);
|
||||
last_block.to_tokens(tokens);
|
||||
});
|
||||
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ItemFn {
|
||||
#[inline]
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
// This parse implementation has been largely lifted from `syn`, with
|
||||
// the exception of:
|
||||
// * We don't have access to the plumbing necessary to parse inner
|
||||
// attributes in-place.
|
||||
// * We do our own statements parsing to avoid recursively parsing
|
||||
// entire statements and only look for the parts we're interested in.
|
||||
|
||||
let outer_attrs = input.call(Attribute::parse_outer)?;
|
||||
let vis: Visibility = input.parse()?;
|
||||
let sig: Signature = input.parse()?;
|
||||
|
||||
let content;
|
||||
let brace_token = braced!(content in input);
|
||||
let inner_attrs = Attribute::parse_inner(&content)?;
|
||||
|
||||
let mut buf = proc_macro2::TokenStream::new();
|
||||
let mut stmts = Vec::new();
|
||||
|
||||
while !content.is_empty() {
|
||||
if let Some(semi) = content.parse::<Option<syn::Token![;]>>()? {
|
||||
semi.to_tokens(&mut buf);
|
||||
stmts.push(buf);
|
||||
buf = proc_macro2::TokenStream::new();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse a single token tree and extend our current buffer with it.
|
||||
// This avoids parsing the entire content of the sub-tree.
|
||||
buf.extend([content.parse::<TokenTree>()?]);
|
||||
}
|
||||
|
||||
if !buf.is_empty() {
|
||||
stmts.push(buf);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
outer_attrs,
|
||||
vis,
|
||||
sig,
|
||||
brace_token,
|
||||
inner_attrs,
|
||||
stmts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Body<'a> {
|
||||
brace_token: syn::token::Brace,
|
||||
// Statements, with terminating `;`.
|
||||
stmts: &'a [TokenStream],
|
||||
}
|
||||
|
||||
impl ToTokens for Body<'_> {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
self.brace_token.surround(tokens, |tokens| {
|
||||
for stmt in self.stmts {
|
||||
stmt.to_tokens(tokens);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,12 +204,12 @@ use proc_macro::TokenStream;
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, true)
|
||||
entry::main(args.into(), item.into(), true).into()
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime. This macro helps set up a `Runtime`
|
||||
/// without requiring the user to use [Runtime](../tokio/runtime/struct.Runtime.html) or
|
||||
/// [Builder](../tokio/runtime/struct.builder.html) directly.
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html) directly.
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
@@ -269,7 +269,7 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, false)
|
||||
entry::main(args.into(), item.into(), false).into()
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment.
|
||||
@@ -295,8 +295,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
///
|
||||
/// The `worker_threads` option configures the number of worker threads, and
|
||||
/// defaults to the number of cpus on the system. This is the default
|
||||
/// flavor.
|
||||
/// defaults to the number of cpus on the system.
|
||||
///
|
||||
/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
|
||||
/// flag.
|
||||
@@ -427,7 +426,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, true)
|
||||
entry::test(args.into(), item.into(), true).into()
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
@@ -442,7 +441,7 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
entry::test(args.into(), item.into(), false).into()
|
||||
}
|
||||
|
||||
/// Always fails with the error message below.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
use syn::{parse::Parser, Ident};
|
||||
|
||||
pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
// passed in is: `(_ _ _)` with one `_` per branch
|
||||
@@ -46,7 +46,7 @@ pub(crate) fn clean_pattern_macro(input: TokenStream) -> TokenStream {
|
||||
// If this isn't a pattern, we return the token stream as-is. The select!
|
||||
// macro is using it in a location requiring a pattern, so an error will be
|
||||
// emitted there.
|
||||
let mut input: syn::Pat = match syn::parse(input.clone()) {
|
||||
let mut input: syn::Pat = match syn::Pat::parse_single.parse(input.clone()) {
|
||||
Ok(it) => it,
|
||||
Err(_) => return input,
|
||||
};
|
||||
@@ -58,7 +58,6 @@ pub(crate) fn clean_pattern_macro(input: TokenStream) -> TokenStream {
|
||||
// Removes any occurrences of ref or mut in the provided pattern.
|
||||
fn clean_pattern(pat: &mut syn::Pat) {
|
||||
match pat {
|
||||
syn::Pat::Box(_box) => {}
|
||||
syn::Pat::Lit(_literal) => {}
|
||||
syn::Pat::Macro(_macro) => {}
|
||||
syn::Pat::Path(_path) => {}
|
||||
@@ -94,7 +93,7 @@ fn clean_pattern(pat: &mut syn::Pat) {
|
||||
}
|
||||
}
|
||||
syn::Pat::TupleStruct(tuple) => {
|
||||
for elem in tuple.pat.elems.iter_mut() {
|
||||
for elem in tuple.elems.iter_mut() {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
# 0.1.14 (April 26th, 2023)
|
||||
|
||||
This bugfix release bumps the minimum version of Tokio to 1.15, which is
|
||||
necessary for `timeout_repeating` to compile. ([#5657])
|
||||
|
||||
[#5657]: https://github.com/tokio-rs/tokio/pull/5657
|
||||
|
||||
# 0.1.13 (April 25th, 2023)
|
||||
|
||||
This release bumps the MSRV of tokio-stream to 1.56.
|
||||
|
||||
- stream: add "full" feature flag ([#5639])
|
||||
- stream: add `StreamExt::timeout_repeating` ([#5577])
|
||||
- stream: add `StreamNotifyClose` ([#4851])
|
||||
|
||||
[#4851]: https://github.com/tokio-rs/tokio/pull/4851
|
||||
[#5577]: https://github.com/tokio-rs/tokio/pull/5577
|
||||
[#5639]: https://github.com/tokio-rs/tokio/pull/5639
|
||||
|
||||
# 0.1.12 (January 20, 2023)
|
||||
|
||||
- time: remove `Unpin` bound on `Throttle` methods ([#5105])
|
||||
|
||||
+14
-4
@@ -4,9 +4,9 @@ name = "tokio-stream"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.12"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
version = "0.1.14"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
@@ -18,6 +18,16 @@ categories = ["asynchronous"]
|
||||
|
||||
[features]
|
||||
default = ["time"]
|
||||
|
||||
full = [
|
||||
"time",
|
||||
"net",
|
||||
"io-util",
|
||||
"fs",
|
||||
"sync",
|
||||
"signal"
|
||||
]
|
||||
|
||||
time = ["tokio/time"]
|
||||
net = ["tokio/net"]
|
||||
io-util = ["tokio/io-util"]
|
||||
@@ -28,7 +38,7 @@ signal = ["tokio/signal"]
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.8.0", path = "../tokio", features = ["sync"] }
|
||||
tokio = { version = "1.15.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -3,17 +3,8 @@
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::pin::Pin;
|
||||
|
||||
use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap};
|
||||
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
|
||||
|
||||
macro_rules! assert_ready_some {
|
||||
($($t:tt)*) => {
|
||||
match assert_ready!($($t)*) {
|
||||
Some(v) => v,
|
||||
None => panic!("expected `Some`, got `None`"),
|
||||
}
|
||||
};
|
||||
}
|
||||
use tokio_stream::{self as stream, Stream, StreamMap};
|
||||
use tokio_test::{assert_pending, assert_ready, task};
|
||||
|
||||
macro_rules! assert_ready_none {
|
||||
($($t:tt)*) => {
|
||||
@@ -28,7 +19,7 @@ fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item =
|
||||
Box::pin(s)
|
||||
}
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
fuzz_target!(|data: [bool; 64]| {
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct DidPoll<T> {
|
||||
@@ -45,11 +36,12 @@ fuzz_target!(|data: &[u8]| {
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
// Try the test with each possible length.
|
||||
for len in 0..data.len() {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let mut expect = 0;
|
||||
|
||||
for (i, is_empty) in data.iter().map(|x| *x != 0).enumerate() {
|
||||
for (i, is_empty) in data[..len].iter().copied().enumerate() {
|
||||
let inner = if is_empty {
|
||||
pin_box(stream::empty::<()>())
|
||||
} else {
|
||||
|
||||
@@ -63,12 +63,12 @@
|
||||
//! [`tokio-util`] provides the [`StreamReader`] and [`ReaderStream`]
|
||||
//! types when the io feature is enabled.
|
||||
//!
|
||||
//! [`tokio-util`]: https://docs.rs/tokio-util/0.4/tokio_util/codec/index.html
|
||||
//! [`tokio::io`]: https://docs.rs/tokio/1.0/tokio/io/index.html
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/1.0/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/1.0/tokio/io/trait.AsyncWrite.html
|
||||
//! [`ReaderStream`]: https://docs.rs/tokio-util/0.4/tokio_util/io/struct.ReaderStream.html
|
||||
//! [`StreamReader`]: https://docs.rs/tokio-util/0.4/tokio_util/io/struct.StreamReader.html
|
||||
//! [`tokio-util`]: https://docs.rs/tokio-util/latest/tokio_util/codec/index.html
|
||||
//! [`tokio::io`]: https://docs.rs/tokio/latest/tokio/io/index.html
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html
|
||||
//! [`ReaderStream`]: https://docs.rs/tokio-util/latest/tokio_util/io/struct.ReaderStream.html
|
||||
//! [`StreamReader`]: https://docs.rs/tokio-util/latest/tokio_util/io/struct.StreamReader.html
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
@@ -96,5 +96,8 @@ pub use pending::{pending, Pending};
|
||||
mod stream_map;
|
||||
pub use stream_map::StreamMap;
|
||||
|
||||
mod stream_close;
|
||||
pub use stream_close::StreamNotifyClose;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use futures_core::Stream;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use crate::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
/// A `Stream` that wraps the values in an `Option`.
|
||||
///
|
||||
/// Whenever the wrapped stream yields an item, this stream yields that item
|
||||
/// wrapped in `Some`. When the inner stream ends, then this stream first
|
||||
/// yields a `None` item, and then this stream will also end.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Using `StreamNotifyClose` to handle closed streams with `StreamMap`.
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct StreamNotifyClose<S> {
|
||||
#[pin]
|
||||
inner: Option<S>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StreamNotifyClose<S> {
|
||||
/// Create a new `StreamNotifyClose`.
|
||||
pub fn new(stream: S) -> Self {
|
||||
Self {
|
||||
inner: Some(stream),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get back the inner `Stream`.
|
||||
///
|
||||
/// Returns `None` if the stream has reached its end.
|
||||
pub fn into_inner(self) -> Option<S> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Stream for StreamNotifyClose<S>
|
||||
where
|
||||
S: Stream,
|
||||
{
|
||||
type Item = Option<S::Item>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// We can't invoke poll_next after it ended, so we unset the inner stream as a marker.
|
||||
match self
|
||||
.as_mut()
|
||||
.project()
|
||||
.inner
|
||||
.as_pin_mut()
|
||||
.map(|stream| S::poll_next(stream, cx))
|
||||
{
|
||||
Some(Poll::Ready(Some(item))) => Poll::Ready(Some(Some(item))),
|
||||
Some(Poll::Ready(None)) => {
|
||||
self.project().inner.set(None);
|
||||
Poll::Ready(Some(None))
|
||||
}
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if let Some(inner) = &self.inner {
|
||||
// We always return +1 because when there's stream there's atleast one more item.
|
||||
let (l, u) = inner.size_hint();
|
||||
(l.saturating_add(1), u.and_then(|u| u.checked_add(1)))
|
||||
} else {
|
||||
(0, Some(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,10 @@ use try_next::TryNext;
|
||||
|
||||
cfg_time! {
|
||||
pub(crate) mod timeout;
|
||||
pub(crate) mod timeout_repeating;
|
||||
use timeout::Timeout;
|
||||
use tokio::time::Duration;
|
||||
use timeout_repeating::TimeoutRepeating;
|
||||
use tokio::time::{Duration, Interval};
|
||||
mod throttle;
|
||||
use throttle::{throttle, Throttle};
|
||||
mod chunks_timeout;
|
||||
@@ -924,7 +926,9 @@ pub trait StreamExt: Stream {
|
||||
/// If the wrapped stream yields a value before the deadline is reached, the
|
||||
/// value is returned. Otherwise, an error is returned. The caller may decide
|
||||
/// to continue consuming the stream and will eventually get the next source
|
||||
/// stream value once it becomes available.
|
||||
/// stream value once it becomes available. See
|
||||
/// [`timeout_repeating`](StreamExt::timeout_repeating) for an alternative
|
||||
/// where the timeouts will repeat.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
@@ -971,6 +975,25 @@ pub trait StreamExt: Stream {
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Once a timeout error is received, no further events will be received
|
||||
/// unless the wrapped stream yields a value (timeouts do not repeat).
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::IntervalStream};
|
||||
/// use std::time::Duration;
|
||||
/// let interval_stream = IntervalStream::new(tokio::time::interval(Duration::from_millis(100)));
|
||||
/// let timeout_stream = interval_stream.timeout(Duration::from_millis(10));
|
||||
/// tokio::pin!(timeout_stream);
|
||||
///
|
||||
/// // Only one timeout will be received between values in the source stream.
|
||||
/// assert!(timeout_stream.try_next().await.is_ok());
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected one timeout");
|
||||
/// assert!(timeout_stream.try_next().await.is_ok(), "expected no more timeouts");
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(all(feature = "time"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn timeout(self, duration: Duration) -> Timeout<Self>
|
||||
@@ -980,6 +1003,95 @@ pub trait StreamExt: Stream {
|
||||
Timeout::new(self, duration)
|
||||
}
|
||||
|
||||
/// Applies a per-item timeout to the passed stream.
|
||||
///
|
||||
/// `timeout_repeating()` takes an [`Interval`](tokio::time::Interval) that
|
||||
/// controls the time each element of the stream has to complete before
|
||||
/// timing out.
|
||||
///
|
||||
/// If the wrapped stream yields a value before the deadline is reached, the
|
||||
/// value is returned. Otherwise, an error is returned. The caller may decide
|
||||
/// to continue consuming the stream and will eventually get the next source
|
||||
/// stream value once it becomes available. Unlike `timeout()`, if no value
|
||||
/// becomes available before the deadline is reached, additional errors are
|
||||
/// returned at the specified interval. See [`timeout`](StreamExt::timeout)
|
||||
/// for an alternative where the timeouts do not repeat.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This function consumes the stream passed into it and returns a
|
||||
/// wrapped version of it.
|
||||
///
|
||||
/// Polling the returned stream will continue to poll the inner stream even
|
||||
/// if one or more items time out.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// use std::time::Duration;
|
||||
/// # let int_stream = stream::iter(1..=3);
|
||||
///
|
||||
/// let int_stream = int_stream.timeout_repeating(tokio::time::interval(Duration::from_secs(1)));
|
||||
/// tokio::pin!(int_stream);
|
||||
///
|
||||
/// // When no items time out, we get the 3 elements in succession:
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(2)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(3)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
///
|
||||
/// // If the second item times out, we get an error and continue polling the stream:
|
||||
/// # let mut int_stream = stream::iter(vec![Ok(1), Err(()), Ok(2), Ok(3)]);
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert!(int_stream.try_next().await.is_err());
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(2)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(3)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
///
|
||||
/// // If we want to stop consuming the source stream the first time an
|
||||
/// // element times out, we can use the `take_while` operator:
|
||||
/// # let int_stream = stream::iter(vec![Ok(1), Err(()), Ok(2), Ok(3)]);
|
||||
/// let mut int_stream = int_stream.take_while(Result::is_ok);
|
||||
///
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(None));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Timeout errors will be continuously produced at the specified interval
|
||||
/// until the wrapped stream yields a value.
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// # async fn main() {
|
||||
/// use tokio_stream::{StreamExt, wrappers::IntervalStream};
|
||||
/// use std::time::Duration;
|
||||
/// let interval_stream = IntervalStream::new(tokio::time::interval(Duration::from_millis(23)));
|
||||
/// let timeout_stream = interval_stream.timeout_repeating(tokio::time::interval(Duration::from_millis(9)));
|
||||
/// tokio::pin!(timeout_stream);
|
||||
///
|
||||
/// // Multiple timeouts will be received between values in the source stream.
|
||||
/// assert!(timeout_stream.try_next().await.is_ok());
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected one timeout");
|
||||
/// assert!(timeout_stream.try_next().await.is_err(), "expected a second timeout");
|
||||
/// // Will eventually receive another value from the source stream...
|
||||
/// assert!(timeout_stream.try_next().await.is_ok(), "expected non-timeout");
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(all(feature = "time"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
fn timeout_repeating(self, interval: Interval) -> TimeoutRepeating<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
TimeoutRepeating::new(self, interval)
|
||||
}
|
||||
|
||||
/// Slows down a stream by enforcing a delay between items.
|
||||
///
|
||||
/// The underlying timer behind this utility has a granularity of one millisecond.
|
||||
|
||||
@@ -23,7 +23,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by `Timeout`.
|
||||
/// Error returned by `Timeout` and `TimeoutRepeating`.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Elapsed(());
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use crate::stream_ext::Fuse;
|
||||
use crate::{Elapsed, Stream};
|
||||
use tokio::time::Interval;
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Stream returned by the [`timeout_repeating`](super::StreamExt::timeout_repeating) method.
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
pub struct TimeoutRepeating<S> {
|
||||
#[pin]
|
||||
stream: Fuse<S>,
|
||||
#[pin]
|
||||
interval: Interval,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> TimeoutRepeating<S> {
|
||||
pub(super) fn new(stream: S, interval: Interval) -> Self {
|
||||
TimeoutRepeating {
|
||||
stream: Fuse::new(stream),
|
||||
interval,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for TimeoutRepeating<S> {
|
||||
type Item = Result<S::Item, Elapsed>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut me = self.project();
|
||||
|
||||
match me.stream.poll_next(cx) {
|
||||
Poll::Ready(v) => {
|
||||
if v.is_some() {
|
||||
me.interval.reset();
|
||||
}
|
||||
return Poll::Ready(v.map(Ok));
|
||||
}
|
||||
Poll::Pending => {}
|
||||
};
|
||||
|
||||
ready!(me.interval.poll_tick(cx));
|
||||
Poll::Ready(Some(Err(Elapsed::new())))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (lower, _) = self.stream.size_hint();
|
||||
|
||||
// The timeout stream may insert an error an infinite number of times.
|
||||
(lower, None)
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,18 @@ use std::task::{Context, Poll};
|
||||
/// to be merged, it may be advisable to use tasks sending values on a shared
|
||||
/// [`mpsc`] channel.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// `StreamMap` removes finished streams automatically, without alerting the user.
|
||||
/// In some scenarios, the caller would want to know on closed streams.
|
||||
/// To do this, use [`StreamNotifyClose`] as a wrapper to your stream.
|
||||
/// It will return None when the stream is closed.
|
||||
///
|
||||
/// [`StreamExt::merge`]: crate::StreamExt::merge
|
||||
/// [`mpsc`]: https://docs.rs/tokio/1.0/tokio/sync/mpsc/index.html
|
||||
/// [`pin!`]: https://docs.rs/tokio/1.0/tokio/macro.pin.html
|
||||
/// [`Box::pin`]: std::boxed::Box::pin
|
||||
/// [`StreamNotifyClose`]: crate::StreamNotifyClose
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -170,6 +178,28 @@ use std::task::{Context, Poll};
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Using `StreamNotifyClose` to handle closed streams with `StreamMap`.
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut map = StreamMap::new();
|
||||
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
/// map.insert(0, stream);
|
||||
/// map.insert(1, stream2);
|
||||
/// while let Some((key, val)) = map.next().await {
|
||||
/// match val {
|
||||
/// Some(val) => println!("got {val:?} from stream {key:?}"),
|
||||
/// None => println!("stream {key:?} closed"),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StreamMap<K, V> {
|
||||
/// Streams stored in the map
|
||||
@@ -568,7 +598,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> std::iter::FromIterator<(K, V)> for StreamMap<K, V>
|
||||
impl<K, V> FromIterator<(K, V)> for StreamMap<K, V>
|
||||
where
|
||||
K: Hash + Eq,
|
||||
{
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use tokio_stream::{StreamExt, StreamNotifyClose};
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_usage() {
|
||||
let mut stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
|
||||
|
||||
assert_eq!(stream.next().await, Some(Some(0)));
|
||||
assert_eq!(stream.next().await, Some(Some(1)));
|
||||
assert_eq!(stream.next().await, Some(None));
|
||||
assert_eq!(stream.next().await, None);
|
||||
}
|
||||
@@ -5,8 +5,8 @@ name = "tokio-test"
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.2"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
|
||||
@@ -310,6 +310,8 @@ impl Inner {
|
||||
|
||||
if now < until {
|
||||
break;
|
||||
} else {
|
||||
self.waiting = None;
|
||||
}
|
||||
} else {
|
||||
self.waiting = Some(Instant::now() + *dur);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::io;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::time::{Duration, Instant};
|
||||
use tokio_test::io::Builder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -84,3 +85,65 @@ async fn mock_panics_write_data_left() {
|
||||
use tokio_test::io::Builder;
|
||||
Builder::new().write(b"write").build();
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn wait() {
|
||||
const FIRST_WAIT: Duration = Duration::from_secs(1);
|
||||
|
||||
let mut mock = Builder::new()
|
||||
.wait(FIRST_WAIT)
|
||||
.read(b"hello ")
|
||||
.read(b"world!")
|
||||
.build();
|
||||
|
||||
let mut buf = [0; 256];
|
||||
|
||||
let start = Instant::now(); // record the time the read call takes
|
||||
//
|
||||
let n = mock.read(&mut buf).await.expect("read 1");
|
||||
assert_eq!(&buf[..n], b"hello ");
|
||||
println!("time elapsed after first read {:?}", start.elapsed());
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 2");
|
||||
assert_eq!(&buf[..n], b"world!");
|
||||
println!("time elapsed after second read {:?}", start.elapsed());
|
||||
|
||||
// make sure the .wait() instruction worked
|
||||
assert!(
|
||||
start.elapsed() >= FIRST_WAIT,
|
||||
"consuming the whole mock only took {}ms",
|
||||
start.elapsed().as_millis()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn multiple_wait() {
|
||||
const FIRST_WAIT: Duration = Duration::from_secs(1);
|
||||
const SECOND_WAIT: Duration = Duration::from_secs(1);
|
||||
|
||||
let mut mock = Builder::new()
|
||||
.wait(FIRST_WAIT)
|
||||
.read(b"hello ")
|
||||
.wait(SECOND_WAIT)
|
||||
.read(b"world!")
|
||||
.build();
|
||||
|
||||
let mut buf = [0; 256];
|
||||
|
||||
let start = Instant::now(); // record the time it takes to consume the mock
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 1");
|
||||
assert_eq!(&buf[..n], b"hello ");
|
||||
println!("time elapsed after first read {:?}", start.elapsed());
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 2");
|
||||
assert_eq!(&buf[..n], b"world!");
|
||||
println!("time elapsed after second read {:?}", start.elapsed());
|
||||
|
||||
// make sure the .wait() instruction worked
|
||||
assert!(
|
||||
start.elapsed() >= FIRST_WAIT + SECOND_WAIT,
|
||||
"consuming the whole mock only took {}ms",
|
||||
start.elapsed().as_millis()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
# 0.7.8 (April 25th, 2023)
|
||||
|
||||
This release bumps the MSRV of tokio-util to 1.56.
|
||||
|
||||
### Added
|
||||
|
||||
- time: add `DelayQueue::peek` ([#5569])
|
||||
|
||||
### Changed
|
||||
|
||||
This release contains one performance improvement:
|
||||
|
||||
- sync: try to lock the parent first in `CancellationToken` ([#5561])
|
||||
|
||||
### Fixed
|
||||
|
||||
- time: fix panic in `DelayQueue` ([#5630])
|
||||
|
||||
### Documented
|
||||
|
||||
- sync: improve `CancellationToken` doc on child tokens ([#5632])
|
||||
|
||||
[#5561]: https://github.com/tokio-rs/tokio/pull/5561
|
||||
[#5569]: https://github.com/tokio-rs/tokio/pull/5569
|
||||
[#5630]: https://github.com/tokio-rs/tokio/pull/5630
|
||||
[#5632]: https://github.com/tokio-rs/tokio/pull/5632
|
||||
|
||||
# 0.7.7 (February 12, 2023)
|
||||
|
||||
This release reverts the removal of the `Encoder` bound on the `FramedParts`
|
||||
|
||||
@@ -4,9 +4,9 @@ name = "tokio-util"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.7.x" git tag.
|
||||
version = "0.7.7"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
version = "0.7.8"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
|
||||
@@ -97,6 +97,8 @@ impl core::fmt::Debug for CancellationToken {
|
||||
}
|
||||
|
||||
impl Clone for CancellationToken {
|
||||
/// 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);
|
||||
CancellationToken {
|
||||
@@ -126,7 +128,8 @@ impl CancellationToken {
|
||||
}
|
||||
|
||||
/// Creates a `CancellationToken` which will get cancelled whenever the
|
||||
/// current token gets cancelled.
|
||||
/// 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
|
||||
/// returned in cancelled state.
|
||||
|
||||
@@ -151,47 +151,43 @@ fn with_locked_node_and_parent<F, Ret>(node: &Arc<TreeNode>, func: F) -> Ret
|
||||
where
|
||||
F: FnOnce(MutexGuard<'_, Inner>, Option<MutexGuard<'_, Inner>>) -> Ret,
|
||||
{
|
||||
let mut potential_parent = {
|
||||
let locked_node = node.inner.lock().unwrap();
|
||||
match locked_node.parent.clone() {
|
||||
Some(parent) => parent,
|
||||
// If we locked the node and its parent is `None`, we are in a valid state
|
||||
// and can return.
|
||||
None => return func(locked_node, None),
|
||||
}
|
||||
};
|
||||
use std::sync::TryLockError;
|
||||
|
||||
let mut locked_node = node.inner.lock().unwrap();
|
||||
|
||||
// Every time this fails, the number of ancestors of the node decreases,
|
||||
// so the loop must succeed after a finite number of iterations.
|
||||
loop {
|
||||
// Deadlock safety:
|
||||
//
|
||||
// Due to invariant #2, we know that we have to lock the parent first, and then the child.
|
||||
// This is true even if the potential_parent is no longer the current parent or even its
|
||||
// sibling, as the invariant still holds.
|
||||
let locked_parent = potential_parent.inner.lock().unwrap();
|
||||
let locked_node = node.inner.lock().unwrap();
|
||||
|
||||
let actual_parent = match locked_node.parent.clone() {
|
||||
Some(parent) => parent,
|
||||
// If we locked the node and its parent is `None`, we are in a valid state
|
||||
// and can return.
|
||||
None => {
|
||||
// Was the wrong parent, so unlock it before calling `func`
|
||||
drop(locked_parent);
|
||||
return func(locked_node, None);
|
||||
}
|
||||
// Look up the parent of the currently locked node.
|
||||
let potential_parent = match locked_node.parent.as_ref() {
|
||||
Some(potential_parent) => potential_parent.clone(),
|
||||
None => return func(locked_node, None),
|
||||
};
|
||||
|
||||
// Loop until we managed to lock both the node and its parent
|
||||
if Arc::ptr_eq(&actual_parent, &potential_parent) {
|
||||
return func(locked_node, Some(locked_parent));
|
||||
// Lock the parent. This may require unlocking the child first.
|
||||
let locked_parent = match potential_parent.inner.try_lock() {
|
||||
Ok(locked_parent) => locked_parent,
|
||||
Err(TryLockError::WouldBlock) => {
|
||||
drop(locked_node);
|
||||
// Deadlock safety:
|
||||
//
|
||||
// Due to invariant #2, the potential parent must come before
|
||||
// the child in the creation order. Therefore, we can safely
|
||||
// lock the child while holding the parent lock.
|
||||
let locked_parent = potential_parent.inner.lock().unwrap();
|
||||
locked_node = node.inner.lock().unwrap();
|
||||
locked_parent
|
||||
}
|
||||
Err(TryLockError::Poisoned(err)) => Err(err).unwrap(),
|
||||
};
|
||||
|
||||
// If we unlocked the child, then the parent may have changed. Check
|
||||
// that we still have the right parent.
|
||||
if let Some(actual_parent) = locked_node.parent.as_ref() {
|
||||
if Arc::ptr_eq(actual_parent, &potential_parent) {
|
||||
return func(locked_node, Some(locked_parent));
|
||||
}
|
||||
}
|
||||
|
||||
// Drop locked_parent before reassigning to potential_parent,
|
||||
// as potential_parent is borrowed in it
|
||||
drop(locked_node);
|
||||
drop(locked_parent);
|
||||
|
||||
potential_parent = actual_parent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,11 +239,7 @@ fn remove_child(parent: &mut Inner, mut node: MutexGuard<'_, Inner>) {
|
||||
|
||||
let len = parent.children.len();
|
||||
if 4 * len <= parent.children.capacity() {
|
||||
// equal to:
|
||||
// parent.children.shrink_to(2 * len);
|
||||
// but shrink_to was not yet stabilized in our minimal compatible version
|
||||
let old_children = std::mem::replace(&mut parent.children, Vec::with_capacity(2 * len));
|
||||
parent.children.extend(old_children);
|
||||
parent.children.shrink_to(2 * len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ enum State<T> {
|
||||
pub struct PollSender<T> {
|
||||
sender: Option<Sender<T>>,
|
||||
state: State<T>,
|
||||
acquire: ReusableBoxFuture<'static, Result<OwnedPermit<T>, PollSendError<T>>>,
|
||||
acquire: PollSenderFuture<T>,
|
||||
}
|
||||
|
||||
// Creates a future for acquiring a permit from the underlying channel. This is used to ensure
|
||||
@@ -64,13 +64,56 @@ async fn make_acquire_future<T>(
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> PollSender<T> {
|
||||
type InnerFuture<'a, T> = ReusableBoxFuture<'a, Result<OwnedPermit<T>, PollSendError<T>>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
// TODO: This should be replace with a type_alias_impl_trait to eliminate `'static` and all the transmutes
|
||||
struct PollSenderFuture<T>(InnerFuture<'static, T>);
|
||||
|
||||
impl<T> PollSenderFuture<T> {
|
||||
/// Create with an empty inner future with no `Send` bound.
|
||||
fn empty() -> Self {
|
||||
// We don't use `make_acquire_future` here because our relaxed bounds on `T` are not
|
||||
// compatible with the transitive bounds required by `Sender<T>`.
|
||||
Self(ReusableBoxFuture::new(async { unreachable!() }))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> PollSenderFuture<T> {
|
||||
/// Create with an empty inner future.
|
||||
fn new() -> Self {
|
||||
let v = InnerFuture::new(make_acquire_future(None));
|
||||
// This is safe because `make_acquire_future(None)` is actually `'static`
|
||||
Self(unsafe { mem::transmute::<InnerFuture<'_, T>, InnerFuture<'static, T>>(v) })
|
||||
}
|
||||
|
||||
/// Poll the inner future.
|
||||
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<Result<OwnedPermit<T>, PollSendError<T>>> {
|
||||
self.0.poll(cx)
|
||||
}
|
||||
|
||||
/// Replace the inner future.
|
||||
fn set(&mut self, sender: Option<Sender<T>>) {
|
||||
let inner: *mut InnerFuture<'static, T> = &mut self.0;
|
||||
let inner: *mut InnerFuture<'_, T> = inner.cast();
|
||||
// SAFETY: The `make_acquire_future(sender)` future must not exist after the type `T`
|
||||
// becomes invalid, and this casts away the type-level lifetime check for that. However, the
|
||||
// inner future is never moved out of this `PollSenderFuture<T>`, so the future will not
|
||||
// live longer than the `PollSenderFuture<T>` lives. A `PollSenderFuture<T>` is guaranteed
|
||||
// to not exist after the type `T` becomes invalid, because it is annotated with a `T`, so
|
||||
// this is ok.
|
||||
let inner = unsafe { &mut *inner };
|
||||
inner.set(make_acquire_future(sender));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send> PollSender<T> {
|
||||
/// Creates a new `PollSender`.
|
||||
pub fn new(sender: Sender<T>) -> Self {
|
||||
Self {
|
||||
sender: Some(sender.clone()),
|
||||
state: State::Idle(sender),
|
||||
acquire: ReusableBoxFuture::new(make_acquire_future(None)),
|
||||
acquire: PollSenderFuture::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +140,7 @@ impl<T: Send + 'static> PollSender<T> {
|
||||
State::Idle(sender) => {
|
||||
// Start trying to acquire a permit to reserve a slot for our send, and
|
||||
// immediately loop back around to poll it the first time.
|
||||
self.acquire.set(make_acquire_future(Some(sender)));
|
||||
self.acquire.set(Some(sender));
|
||||
(None, State::Acquiring)
|
||||
}
|
||||
State::Acquiring => match self.acquire.poll(cx) {
|
||||
@@ -194,7 +237,7 @@ impl<T: Send + 'static> PollSender<T> {
|
||||
match self.state {
|
||||
State::Idle(_) => self.state = State::Closed,
|
||||
State::Acquiring => {
|
||||
self.acquire.set(make_acquire_future(None));
|
||||
self.acquire.set(None);
|
||||
self.state = State::Closed;
|
||||
}
|
||||
_ => {}
|
||||
@@ -215,7 +258,7 @@ impl<T: Send + 'static> PollSender<T> {
|
||||
// We're currently trying to reserve a slot to send into.
|
||||
State::Acquiring => {
|
||||
// Replacing the future drops the in-flight one.
|
||||
self.acquire.set(make_acquire_future(None));
|
||||
self.acquire.set(None);
|
||||
|
||||
// If we haven't closed yet, we have to clone our stored sender since we have no way
|
||||
// to get it back from the acquire future we just dropped.
|
||||
@@ -255,9 +298,7 @@ impl<T> Clone for PollSender<T> {
|
||||
Self {
|
||||
sender,
|
||||
state,
|
||||
// We don't use `make_acquire_future` here because our relaxed bounds on `T` are not
|
||||
// compatible with the transitive bounds required by `Sender<T>`.
|
||||
acquire: ReusableBoxFuture::new(async { unreachable!() }),
|
||||
acquire: PollSenderFuture::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ use std::task::{self, Poll, Waker};
|
||||
/// performance and scalability benefits.
|
||||
///
|
||||
/// State associated with each entry is stored in a [`slab`]. This amortizes the cost of allocation,
|
||||
/// and allows reuse of the memory allocated for expired entires.
|
||||
/// and allows reuse of the memory allocated for expired entries.
|
||||
///
|
||||
/// Capacity can be checked using [`capacity`] and allocated preemptively by using
|
||||
/// the [`reserve`] method.
|
||||
@@ -874,6 +874,41 @@ impl<T> DelayQueue<T> {
|
||||
self.slab.compact();
|
||||
}
|
||||
|
||||
/// Gets the [`Key`] that [`poll_expired`] will pull out of the queue next, without
|
||||
/// pulling it out or waiting for the deadline to expire.
|
||||
///
|
||||
/// Entries that have already expired may be returned in any order, but it is
|
||||
/// guaranteed that this method returns them in the same order as when items
|
||||
/// are popped from the `DelayQueue`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
///
|
||||
/// let key1 = delay_queue.insert("foo", Duration::from_secs(10));
|
||||
/// let key2 = delay_queue.insert("bar", Duration::from_secs(5));
|
||||
/// let key3 = delay_queue.insert("baz", Duration::from_secs(15));
|
||||
///
|
||||
/// assert_eq!(delay_queue.peek().unwrap(), key2);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Key`]: struct@Key
|
||||
/// [`poll_expired`]: method@Self::poll_expired
|
||||
pub fn peek(&self) -> Option<Key> {
|
||||
use self::wheel::Stack;
|
||||
|
||||
self.expired.peek().or_else(|| self.wheel.peek())
|
||||
}
|
||||
|
||||
/// Returns the next time to poll as determined by the wheel
|
||||
fn next_deadline(&mut self) -> Option<Instant> {
|
||||
self.wheel
|
||||
@@ -1166,6 +1201,10 @@ impl<T> wheel::Stack for Stack<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<Self::Owned> {
|
||||
self.head
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store) {
|
||||
let key = *item;
|
||||
|
||||
@@ -140,11 +140,31 @@ impl<T: Stack> Level<T> {
|
||||
|
||||
// TODO: This can probably be simplified w/ power of 2 math
|
||||
let level_start = now - (now % level_range);
|
||||
let deadline = level_start + slot as u64 * slot_range;
|
||||
let mut deadline = level_start + slot as u64 * slot_range;
|
||||
if deadline < now {
|
||||
// A timer is in a slot "prior" to the current time. This can occur
|
||||
// because we do not have an infinite hierarchy of timer levels, and
|
||||
// eventually a timer scheduled for a very distant time might end up
|
||||
// being placed in a slot that is beyond the end of all of the
|
||||
// arrays.
|
||||
//
|
||||
// To deal with this, we first limit timers to being scheduled no
|
||||
// more than MAX_DURATION ticks in the future; that is, they're at
|
||||
// most one rotation of the top level away. Then, we force timers
|
||||
// that logically would go into the top+1 level, to instead go into
|
||||
// the top level's slots.
|
||||
//
|
||||
// What this means is that the top level's slots act as a
|
||||
// pseudo-ring buffer, and we rotate around them indefinitely. If we
|
||||
// compute a deadline before now, and it's the top level, it
|
||||
// therefore means we're actually looking at a slot in the future.
|
||||
debug_assert_eq!(self.level, super::NUM_LEVELS - 1);
|
||||
|
||||
deadline += level_range;
|
||||
}
|
||||
debug_assert!(
|
||||
deadline >= now,
|
||||
"deadline={}; now={}; level={}; slot={}; occupied={:b}",
|
||||
"deadline={:016X}; now={:016X}; level={}; slot={}; occupied={:b}",
|
||||
deadline,
|
||||
now,
|
||||
self.level,
|
||||
@@ -206,6 +226,10 @@ impl<T: Stack> Level<T> {
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn peek_entry_slot(&self, slot: usize) -> Option<T::Owned> {
|
||||
self.slot[slot].peek()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Level<T> {
|
||||
|
||||
@@ -139,6 +139,12 @@ where
|
||||
self.next_expiration().map(|expiration| expiration.deadline)
|
||||
}
|
||||
|
||||
/// Next key that will expire
|
||||
pub(crate) fn peek(&self) -> Option<T::Owned> {
|
||||
self.next_expiration()
|
||||
.and_then(|expiration| self.peek_entry(&expiration))
|
||||
}
|
||||
|
||||
/// Advances the timer up to the instant represented by `now`.
|
||||
pub(crate) fn poll(&mut self, now: u64, store: &mut T::Store) -> Option<T::Owned> {
|
||||
loop {
|
||||
@@ -244,6 +250,10 @@ where
|
||||
self.levels[expiration.level].pop_entry_slot(expiration.slot, store)
|
||||
}
|
||||
|
||||
fn peek_entry(&self, expiration: &Expiration) -> Option<T::Owned> {
|
||||
self.levels[expiration.level].peek_entry_slot(expiration.slot)
|
||||
}
|
||||
|
||||
fn level_for(&self, when: u64) -> usize {
|
||||
level_for(self.elapsed, when)
|
||||
}
|
||||
@@ -254,8 +264,11 @@ fn level_for(elapsed: u64, when: u64) -> usize {
|
||||
|
||||
// Mask in the trailing bits ignored by the level calculation in order to cap
|
||||
// the possible leading zeros
|
||||
let masked = elapsed ^ when | SLOT_MASK;
|
||||
|
||||
let mut masked = elapsed ^ when | SLOT_MASK;
|
||||
if masked >= MAX_DURATION {
|
||||
// Fudge the timer into the top level
|
||||
masked = MAX_DURATION - 1;
|
||||
}
|
||||
let leading_zeros = masked.leading_zeros() as usize;
|
||||
let significant = 63 - leading_zeros;
|
||||
significant / 6
|
||||
|
||||
@@ -22,6 +22,9 @@ pub(crate) trait Stack: Default {
|
||||
/// Pop an item from the stack
|
||||
fn pop(&mut self, store: &mut Self::Store) -> Option<Self::Owned>;
|
||||
|
||||
/// Peek into the stack.
|
||||
fn peek(&self) -> Option<Self::Owned>;
|
||||
|
||||
fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store);
|
||||
|
||||
fn when(item: &Self::Borrowed, store: &Self::Store) -> u64;
|
||||
|
||||
@@ -27,6 +27,29 @@ async fn simple() {
|
||||
send.send_item(42).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn simple_ref() {
|
||||
let v = vec![1, 2, 3i32];
|
||||
|
||||
let (send, mut recv) = channel(3);
|
||||
let mut send = PollSender::new(send);
|
||||
|
||||
for vi in v.iter() {
|
||||
let mut reserve = spawn(poll_fn(|cx| send.poll_reserve(cx)));
|
||||
assert_ready_ok!(reserve.poll());
|
||||
send.send_item(vi).unwrap();
|
||||
}
|
||||
|
||||
let mut reserve = spawn(poll_fn(|cx| send.poll_reserve(cx)));
|
||||
assert_pending!(reserve.poll());
|
||||
|
||||
assert_eq!(*recv.recv().await.unwrap(), 1);
|
||||
assert!(reserve.is_woken());
|
||||
assert_ready_ok!(reserve.poll());
|
||||
drop(recv);
|
||||
send.send_item(&42).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeated_poll_reserve() {
|
||||
let (send, mut recv) = channel::<i32>(1);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use futures::StreamExt;
|
||||
use tokio::time::{self, sleep, sleep_until, Duration, Instant};
|
||||
use tokio_test::{assert_pending, assert_ready, task};
|
||||
use tokio_util::time::DelayQueue;
|
||||
@@ -257,6 +258,10 @@ async fn reset_twice() {
|
||||
#[tokio::test]
|
||||
async fn repeatedly_reset_entry_inserted_as_expired() {
|
||||
time::pause();
|
||||
|
||||
// Instants before the start of the test seem to break in wasm.
|
||||
time::sleep(ms(1000)).await;
|
||||
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
let now = Instant::now();
|
||||
|
||||
@@ -556,6 +561,10 @@ async fn reset_later_after_slot_starts() {
|
||||
#[tokio::test]
|
||||
async fn reset_inserted_expired() {
|
||||
time::pause();
|
||||
|
||||
// Instants before the start of the test seem to break in wasm.
|
||||
time::sleep(ms(1000)).await;
|
||||
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
let now = Instant::now();
|
||||
|
||||
@@ -778,6 +787,22 @@ async fn compact_change_deadline() {
|
||||
assert!(entry.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn item_expiry_greater_than_wheel() {
|
||||
// This function tests that a delay queue that has existed for at least 2^36 milliseconds won't panic when a new item is inserted.
|
||||
let mut queue = DelayQueue::new();
|
||||
for _ in 0..2 {
|
||||
tokio::time::advance(Duration::from_millis(1 << 35)).await;
|
||||
queue.insert(0, Duration::from_millis(0));
|
||||
queue.next().await;
|
||||
}
|
||||
// This should not panic
|
||||
let no_panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
queue.insert(1, Duration::from_millis(1));
|
||||
}));
|
||||
assert!(no_panic.is_ok());
|
||||
}
|
||||
|
||||
#[cfg_attr(target_os = "wasi", ignore = "FIXME: Does not seem to work with WASI")]
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn remove_after_compact() {
|
||||
@@ -815,6 +840,44 @@ async fn remove_after_compact_poll() {
|
||||
assert!(panic.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn peek() {
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
let key = queue.insert_at("foo", now + ms(5));
|
||||
let key2 = queue.insert_at("bar", now);
|
||||
let key3 = queue.insert_at("baz", now + ms(10));
|
||||
|
||||
assert_eq!(queue.peek(), Some(key2));
|
||||
|
||||
sleep(ms(6)).await;
|
||||
|
||||
assert_eq!(queue.peek(), Some(key2));
|
||||
|
||||
let entry = assert_ready_some!(poll!(queue));
|
||||
assert_eq!(entry.get_ref(), &"bar");
|
||||
|
||||
assert_eq!(queue.peek(), Some(key));
|
||||
|
||||
let entry = assert_ready_some!(poll!(queue));
|
||||
assert_eq!(entry.get_ref(), &"foo");
|
||||
|
||||
assert_eq!(queue.peek(), Some(key3));
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
sleep(ms(5)).await;
|
||||
|
||||
assert_eq!(queue.peek(), Some(key3));
|
||||
|
||||
let entry = assert_ready_some!(poll!(queue));
|
||||
assert_eq!(entry.get_ref(), &"baz");
|
||||
|
||||
assert!(queue.peek().is_none());
|
||||
}
|
||||
|
||||
fn ms(n: u64) -> Duration {
|
||||
Duration::from_millis(n)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,106 @@
|
||||
# 1.28.1 (May 10th, 2023)
|
||||
|
||||
This release fixes a mistake in the build script that makes `AsFd`
|
||||
implementations unavailable on Rust 1.63. ([#5677])
|
||||
|
||||
[#5677]: https://github.com/tokio-rs/tokio/pull/5677
|
||||
|
||||
# 1.28.0 (April 25th, 2023)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `AsyncFd::async_io` ([#5542])
|
||||
- io: impl BufMut for ReadBuf ([#5590])
|
||||
- net: add `recv_buf` for `UdpSocket` and `UnixDatagram` ([#5583])
|
||||
- sync: add `OwnedSemaphorePermit::semaphore` ([#5618])
|
||||
- sync: add `same_channel` to broadcast channel ([#5607])
|
||||
- sync: add `watch::Receiver::wait_for` ([#5611])
|
||||
- task: add `JoinSet::spawn_blocking` and `JoinSet::spawn_blocking_on` ([#5612])
|
||||
|
||||
### Changed
|
||||
|
||||
- deps: update windows-sys to 0.48 ([#5591])
|
||||
- io: make `read_to_end` not grow unnecessarily ([#5610])
|
||||
- macros: make entrypoints more efficient ([#5621])
|
||||
- sync: improve Debug impl for `RwLock` ([#5647])
|
||||
- sync: reduce contention in `Notify` ([#5503])
|
||||
|
||||
### Fixed
|
||||
|
||||
- net: support `get_peer_cred` on AIX ([#5065])
|
||||
- sync: avoid deadlocks in `broadcast` with custom wakers ([#5578])
|
||||
|
||||
### Documented
|
||||
|
||||
- sync: fix typo in `Semaphore::MAX_PERMITS` ([#5645])
|
||||
- sync: fix typo in `tokio::sync::watch::Sender` docs ([#5587])
|
||||
|
||||
[#5065]: https://github.com/tokio-rs/tokio/pull/5065
|
||||
[#5503]: https://github.com/tokio-rs/tokio/pull/5503
|
||||
[#5542]: https://github.com/tokio-rs/tokio/pull/5542
|
||||
[#5578]: https://github.com/tokio-rs/tokio/pull/5578
|
||||
[#5583]: https://github.com/tokio-rs/tokio/pull/5583
|
||||
[#5587]: https://github.com/tokio-rs/tokio/pull/5587
|
||||
[#5590]: https://github.com/tokio-rs/tokio/pull/5590
|
||||
[#5591]: https://github.com/tokio-rs/tokio/pull/5591
|
||||
[#5607]: https://github.com/tokio-rs/tokio/pull/5607
|
||||
[#5610]: https://github.com/tokio-rs/tokio/pull/5610
|
||||
[#5611]: https://github.com/tokio-rs/tokio/pull/5611
|
||||
[#5612]: https://github.com/tokio-rs/tokio/pull/5612
|
||||
[#5618]: https://github.com/tokio-rs/tokio/pull/5618
|
||||
[#5621]: https://github.com/tokio-rs/tokio/pull/5621
|
||||
[#5645]: https://github.com/tokio-rs/tokio/pull/5645
|
||||
[#5647]: https://github.com/tokio-rs/tokio/pull/5647
|
||||
|
||||
# 1.27.0 (March 27th, 2023)
|
||||
|
||||
This release bumps the MSRV of Tokio to 1.56. ([#5559])
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `async_io` helper method to sockets ([#5512])
|
||||
- io: add implementations of `AsFd`/`AsHandle`/`AsSocket` ([#5514], [#5540])
|
||||
- net: add `UdpSocket::peek_sender()` ([#5520])
|
||||
- sync: add `RwLockWriteGuard::{downgrade_map, try_downgrade_map}` ([#5527])
|
||||
- task: add `JoinHandle::abort_handle` ([#5543])
|
||||
|
||||
### Changed
|
||||
|
||||
- io: use `memchr` from `libc` ([#5558])
|
||||
- macros: accept path as crate rename in `#[tokio::main]` ([#5557])
|
||||
- macros: update to syn 2.0.0 ([#5572])
|
||||
- time: don't register for a wakeup when `Interval` returns `Ready` ([#5553])
|
||||
|
||||
### Fixed
|
||||
|
||||
- fs: fuse std iterator in `ReadDir` ([#5555])
|
||||
- tracing: fix `spawn_blocking` location fields ([#5573])
|
||||
- time: clean up redundant check in `Wheel::poll()` ([#5574])
|
||||
|
||||
### Documented
|
||||
|
||||
- macros: define cancellation safety ([#5525])
|
||||
- io: add details to docs of `tokio::io::copy[_buf]` ([#5575])
|
||||
- io: refer to `ReaderStream` and `StreamReader` in module docs ([#5576])
|
||||
|
||||
[#5512]: https://github.com/tokio-rs/tokio/pull/5512
|
||||
[#5514]: https://github.com/tokio-rs/tokio/pull/5514
|
||||
[#5520]: https://github.com/tokio-rs/tokio/pull/5520
|
||||
[#5525]: https://github.com/tokio-rs/tokio/pull/5525
|
||||
[#5527]: https://github.com/tokio-rs/tokio/pull/5527
|
||||
[#5540]: https://github.com/tokio-rs/tokio/pull/5540
|
||||
[#5543]: https://github.com/tokio-rs/tokio/pull/5543
|
||||
[#5553]: https://github.com/tokio-rs/tokio/pull/5553
|
||||
[#5555]: https://github.com/tokio-rs/tokio/pull/5555
|
||||
[#5557]: https://github.com/tokio-rs/tokio/pull/5557
|
||||
[#5558]: https://github.com/tokio-rs/tokio/pull/5558
|
||||
[#5559]: https://github.com/tokio-rs/tokio/pull/5559
|
||||
[#5572]: https://github.com/tokio-rs/tokio/pull/5572
|
||||
[#5573]: https://github.com/tokio-rs/tokio/pull/5573
|
||||
[#5574]: https://github.com/tokio-rs/tokio/pull/5574
|
||||
[#5575]: https://github.com/tokio-rs/tokio/pull/5575
|
||||
[#5576]: https://github.com/tokio-rs/tokio/pull/5576
|
||||
|
||||
# 1.26.0 (March 1st, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
+14
-13
@@ -6,9 +6,9 @@ name = "tokio"
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.x.y" git tag.
|
||||
version = "1.26.0"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
version = "1.28.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
@@ -42,7 +42,7 @@ full = [
|
||||
]
|
||||
|
||||
fs = []
|
||||
io-util = ["memchr", "bytes"]
|
||||
io-util = ["bytes"]
|
||||
# stdin, stdout, stderr
|
||||
io-std = []
|
||||
macros = ["tokio-macros"]
|
||||
@@ -97,25 +97,29 @@ stats = []
|
||||
autocfg = "1.1"
|
||||
|
||||
[dependencies]
|
||||
tokio-macros = { version = "1.7.0", path = "../tokio-macros", optional = true }
|
||||
tokio-macros = { version = "~2.1.0", path = "../tokio-macros", optional = true }
|
||||
|
||||
pin-project-lite = "0.2.0"
|
||||
|
||||
# Everything else is optional...
|
||||
bytes = { version = "1.0.0", optional = true }
|
||||
memchr = { version = "2.2", optional = true }
|
||||
mio = { version = "0.8.4", optional = true }
|
||||
num_cpus = { version = "1.8.0", optional = true }
|
||||
parking_lot = { version = "0.12.0", optional = true }
|
||||
|
||||
[target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dependencies]
|
||||
socket2 = { version = "0.4.4", optional = true, features = [ "all" ] }
|
||||
socket2 = { version = "0.4.9", optional = true, features = [ "all" ] }
|
||||
|
||||
# Currently unstable. The API exposed by these features may be broken at any time.
|
||||
# Requires `--cfg tokio_unstable` to enable.
|
||||
[target.'cfg(tokio_unstable)'.dependencies]
|
||||
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true } # Not in full
|
||||
|
||||
# Currently unstable. The API exposed by these features may be broken at any time.
|
||||
# Requires `--cfg tokio_unstable` to enable.
|
||||
[target.'cfg(tokio_taskdump)'.dependencies]
|
||||
backtrace = { version = "0.3.58" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = { version = "0.2.42", optional = true }
|
||||
signal-hook-registry = { version = "1.1.1", optional = true }
|
||||
@@ -125,19 +129,16 @@ libc = { version = "0.2.42" }
|
||||
nix = { version = "0.26", default-features = false, features = ["fs", "socket"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.45"
|
||||
version = "0.48"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(docsrs)'.dependencies.windows-sys]
|
||||
version = "0.45"
|
||||
version = "0.48"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Security_Authorization",
|
||||
]
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.ntapi]
|
||||
version = "0.3.6"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = { version = "0.4.0", path = "../tokio-test" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
@@ -146,7 +147,7 @@ mockall = "0.11.1"
|
||||
async-stream = "0.3"
|
||||
|
||||
[target.'cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))'.dev-dependencies]
|
||||
socket2 = "0.4"
|
||||
socket2 = "0.4.9"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[target.'cfg(not(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown")))'.dev-dependencies]
|
||||
|
||||
+18
-4
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.26.0", features = ["full"] }
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -187,7 +187,20 @@ When updating this, also update:
|
||||
|
||||
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
|
||||
least** 6 months. When increasing the MSRV, the new Rust version must have been
|
||||
released at least six months ago. The current MSRV is 1.49.0.
|
||||
released at least six months ago. The current MSRV is 1.56.0.
|
||||
|
||||
Note that the MSRV is not increased automatically, and only as part of a minor
|
||||
release. The MSRV history for past minor releases can be found below:
|
||||
|
||||
* 1.27 to now - Rust 1.56
|
||||
* 1.17 to 1.26 - Rust 1.49
|
||||
* 1.15 to 1.16 - Rust 1.46
|
||||
* 1.0 to 1.14 - Rust 1.45
|
||||
|
||||
Note that although we try to avoid the situation where a dependency transitively
|
||||
increases the MSRV of Tokio, we do not guarantee that this does not happen.
|
||||
However, every minor release will have some set of versions of dependencies that
|
||||
works with the MSRV of that minor release.
|
||||
|
||||
## Release schedule
|
||||
|
||||
@@ -202,8 +215,9 @@ 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.18.x` - LTS release until June 2023
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
* `1.18.x` - LTS release until June 2023. (MSRV 1.49)
|
||||
* `1.20.x` - LTS release until September 2023. (MSRV 1.49)
|
||||
* `1.25.x` - LTS release until March 2024. (MSRV 1.49)
|
||||
|
||||
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
|
||||
|
||||
+38
-31
@@ -10,13 +10,6 @@ const CONST_THREAD_LOCAL_PROBE: &str = r#"
|
||||
}
|
||||
"#;
|
||||
|
||||
const ADDR_OF_PROBE: &str = r#"
|
||||
{
|
||||
let my_var = 10;
|
||||
::std::ptr::addr_of!(my_var)
|
||||
}
|
||||
"#;
|
||||
|
||||
const CONST_MUTEX_NEW_PROBE: &str = r#"
|
||||
{
|
||||
static MY_MUTEX: ::std::sync::Mutex<i32> = ::std::sync::Mutex::new(1);
|
||||
@@ -24,6 +17,20 @@ const CONST_MUTEX_NEW_PROBE: &str = r#"
|
||||
}
|
||||
"#;
|
||||
|
||||
const AS_FD_PROBE: &str = r#"
|
||||
{
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::prelude::AsFd as _;
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::prelude::AsSocket as _;
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(target_os = "wasi")]
|
||||
use std::os::wasi::prelude::AsFd as _;
|
||||
}
|
||||
"#;
|
||||
|
||||
const TARGET_HAS_ATOMIC_PROBE: &str = r#"
|
||||
{
|
||||
#[cfg(target_has_atomic = "ptr")]
|
||||
@@ -40,9 +47,9 @@ const TARGET_ATOMIC_U64_PROBE: &str = r#"
|
||||
|
||||
fn main() {
|
||||
let mut enable_const_thread_local = false;
|
||||
let mut enable_addr_of = false;
|
||||
let mut enable_target_has_atomic = false;
|
||||
let mut enable_const_mutex_new = false;
|
||||
let mut enable_as_fd = false;
|
||||
let mut target_needs_atomic_u64_fallback = false;
|
||||
|
||||
match AutoCfg::new() {
|
||||
@@ -67,21 +74,6 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// The `addr_of` and `addr_of_mut` macros were stabilized in 1.51.
|
||||
if ac.probe_rustc_version(1, 52) {
|
||||
enable_addr_of = true;
|
||||
} else if ac.probe_rustc_version(1, 51) {
|
||||
// This compiler claims to be 1.51, but there are some nightly
|
||||
// compilers that claim to be 1.51 without supporting the
|
||||
// feature. Explicitly probe to check if code using them
|
||||
// compiles.
|
||||
//
|
||||
// The oldest nightly that supports the feature is 2021-01-31.
|
||||
if ac.probe_expression(ADDR_OF_PROBE) {
|
||||
enable_addr_of = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The `target_has_atomic` cfg was stabilized in 1.60.
|
||||
if ac.probe_rustc_version(1, 61) {
|
||||
enable_target_has_atomic = true;
|
||||
@@ -117,6 +109,21 @@ fn main() {
|
||||
enable_const_mutex_new = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The `AsFd` family of traits were made stable in 1.63.
|
||||
if ac.probe_rustc_version(1, 64) {
|
||||
enable_as_fd = true;
|
||||
} else if ac.probe_rustc_version(1, 63) {
|
||||
// This compiler claims to be 1.63, but there are some nightly
|
||||
// compilers that claim to be 1.63 without supporting the
|
||||
// feature. Explicitly probe to check if code using them
|
||||
// compiles.
|
||||
//
|
||||
// The oldest nightly that supports the feature is 2022-06-16.
|
||||
if ac.probe_expression(AS_FD_PROBE) {
|
||||
enable_as_fd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
@@ -138,14 +145,6 @@ fn main() {
|
||||
autocfg::emit("tokio_no_const_thread_local")
|
||||
}
|
||||
|
||||
if !enable_addr_of {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
//
|
||||
// RUSTFLAGS="--cfg tokio_no_addr_of"
|
||||
autocfg::emit("tokio_no_addr_of")
|
||||
}
|
||||
|
||||
if !enable_target_has_atomic {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
@@ -162,6 +161,14 @@ fn main() {
|
||||
autocfg::emit("tokio_no_const_mutex_new")
|
||||
}
|
||||
|
||||
if !enable_as_fd {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
//
|
||||
// RUSTFLAGS="--cfg tokio_no_as_fd"
|
||||
autocfg::emit("tokio_no_as_fd");
|
||||
}
|
||||
|
||||
if target_needs_atomic_u64_fallback {
|
||||
// To disable this feature on compilers that support it, you can
|
||||
// explicitly pass this flag with the following environment variable:
|
||||
|
||||
+40
-1
@@ -13,7 +13,7 @@ pub mod windows {
|
||||
|
||||
/// See [std::os::windows::io::AsRawHandle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html)
|
||||
pub trait AsRawHandle {
|
||||
/// See [std::os::windows::io::FromRawHandle::from_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html#tymethod.as_raw_handle)
|
||||
/// See [std::os::windows::io::AsRawHandle::as_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html#tymethod.as_raw_handle)
|
||||
fn as_raw_handle(&self) -> RawHandle;
|
||||
}
|
||||
|
||||
@@ -22,5 +22,44 @@ pub mod windows {
|
||||
/// See [std::os::windows::io::FromRawHandle::from_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawHandle.html#tymethod.from_raw_handle)
|
||||
unsafe fn from_raw_handle(handle: RawHandle) -> Self;
|
||||
}
|
||||
|
||||
/// See [std::os::windows::io::RawSocket](https://doc.rust-lang.org/std/os/windows/io/type.RawSocket.html)
|
||||
pub type RawSocket = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [std::os::windows::io::AsRawSocket](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawSocket.html)
|
||||
pub trait AsRawSocket {
|
||||
/// See [std::os::windows::io::AsRawSocket::as_raw_socket](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawSocket.html#tymethod.as_raw_socket)
|
||||
fn as_raw_socket(&self) -> RawSocket;
|
||||
}
|
||||
|
||||
/// See [std::os::windows::io::FromRawSocket](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawSocket.html)
|
||||
pub trait FromRawSocket {
|
||||
/// See [std::os::windows::io::FromRawSocket::from_raw_socket](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawSocket.html#tymethod.from_raw_socket)
|
||||
unsafe fn from_raw_socket(sock: RawSocket) -> Self;
|
||||
}
|
||||
|
||||
/// See [std::os::windows::io::IntoRawSocket](https://doc.rust-lang.org/std/os/windows/io/trait.IntoRawSocket.html)
|
||||
pub trait IntoRawSocket {
|
||||
/// See [std::os::windows::io::IntoRawSocket::into_raw_socket](https://doc.rust-lang.org/std/os/windows/io/trait.IntoRawSocket.html#tymethod.into_raw_socket)
|
||||
fn into_raw_socket(self) -> RawSocket;
|
||||
}
|
||||
|
||||
/// See [std::os::windows::io::BorrowedHandle](https://doc.rust-lang.org/std/os/windows/io/struct.BorrowedHandle.html)
|
||||
pub type BorrowedHandle<'handle> = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [std::os::windows::io::AsHandle](https://doc.rust-lang.org/std/os/windows/io/trait.AsHandle.html)
|
||||
pub trait AsHandle {
|
||||
/// See [std::os::windows::io::AsHandle::as_handle](https://doc.rust-lang.org/std/os/windows/io/trait.AsHandle.html#tymethod.as_handle)
|
||||
fn as_handle(&self) -> BorrowedHandle<'_>;
|
||||
}
|
||||
|
||||
/// See [std::os::windows::io::BorrowedSocket](https://doc.rust-lang.org/std/os/windows/io/struct.BorrowedSocket.html)
|
||||
pub type BorrowedSocket<'socket> = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [std::os::windows::io::AsSocket](https://doc.rust-lang.org/std/os/windows/io/trait.AsSocket.html)
|
||||
pub trait AsSocket {
|
||||
/// See [std::os::windows::io::AsSocket::as_socket](https://doc.rust-lang.org/std/os/windows/io/trait.AsSocket.html#tymethod.as_socket)
|
||||
fn as_socket(&self) -> BorrowedSocket<'_>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
-12
@@ -498,6 +498,7 @@ impl AsyncRead for File {
|
||||
cx: &mut Context<'_>,
|
||||
dst: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let me = self.get_mut();
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
@@ -594,6 +595,7 @@ impl AsyncSeek for File {
|
||||
}
|
||||
|
||||
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let inner = self.inner.get_mut();
|
||||
|
||||
loop {
|
||||
@@ -629,6 +631,7 @@ impl AsyncWrite for File {
|
||||
cx: &mut Context<'_>,
|
||||
src: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let me = self.get_mut();
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
@@ -695,11 +698,13 @@ impl AsyncWrite for File {
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let inner = self.inner.get_mut();
|
||||
inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
self.poll_flush(cx)
|
||||
}
|
||||
}
|
||||
@@ -725,6 +730,15 @@ impl std::os::unix::io::AsRawFd for File {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(tokio_no_as_fd)))]
|
||||
impl std::os::unix::io::AsFd for File {
|
||||
fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> {
|
||||
unsafe {
|
||||
std::os::unix::io::BorrowedFd::borrow_raw(std::os::unix::io::AsRawFd::as_raw_fd(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::FromRawFd for File {
|
||||
unsafe fn from_raw_fd(fd: std::os::unix::io::RawFd) -> Self {
|
||||
@@ -732,17 +746,32 @@ impl std::os::unix::io::FromRawFd for File {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::AsRawHandle for File {
|
||||
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
|
||||
self.std.as_raw_handle()
|
||||
}
|
||||
}
|
||||
cfg_windows! {
|
||||
use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsHandle, BorrowedHandle};
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::FromRawHandle for File {
|
||||
unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
|
||||
StdFile::from_raw_handle(handle).into()
|
||||
impl AsRawHandle for File {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.std.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for File {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe {
|
||||
BorrowedHandle::borrow_raw(
|
||||
AsRawHandle::as_raw_handle(self),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawHandle for File {
|
||||
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
|
||||
StdFile::from_raw_handle(handle).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,8 +779,18 @@ impl Inner {
|
||||
async fn complete_inflight(&mut self) {
|
||||
use crate::future::poll_fn;
|
||||
|
||||
if let Err(e) = poll_fn(|cx| Pin::new(&mut *self).poll_flush(cx)).await {
|
||||
self.last_write_err = Some(e.kind());
|
||||
poll_fn(|cx| self.poll_complete_inflight(cx)).await
|
||||
}
|
||||
|
||||
fn poll_complete_inflight(&mut self, cx: &mut Context<'_>) -> Poll<()> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
match self.poll_flush(cx) {
|
||||
Poll::Ready(Err(e)) => {
|
||||
self.last_write_err = Some(e.kind());
|
||||
Poll::Ready(())
|
||||
}
|
||||
Poll::Ready(Ok(())) => Poll::Ready(()),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -115,9 +115,7 @@ feature! {
|
||||
pub use self::symlink::symlink;
|
||||
}
|
||||
|
||||
feature! {
|
||||
#![windows]
|
||||
|
||||
cfg_windows! {
|
||||
mod symlink_dir;
|
||||
pub use self::symlink_dir::symlink_dir;
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ use mock_open_options::MockOpenOptions as StdOpenOptions;
|
||||
#[cfg(not(test))]
|
||||
use std::fs::OpenOptions as StdOpenOptions;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
|
||||
/// Options and flags which can be used to configure how a file is opened.
|
||||
///
|
||||
/// This builder exposes the ability to configure how a [`File`] is opened and
|
||||
@@ -399,8 +404,6 @@ impl OpenOptions {
|
||||
feature! {
|
||||
#![unix]
|
||||
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
||||
impl OpenOptions {
|
||||
/// Sets the mode bits that a new file will be created with.
|
||||
///
|
||||
@@ -464,11 +467,7 @@ feature! {
|
||||
}
|
||||
}
|
||||
|
||||
feature! {
|
||||
#![windows]
|
||||
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
|
||||
cfg_windows! {
|
||||
impl OpenOptions {
|
||||
/// Overrides the `dwDesiredAccess` argument to the call to [`CreateFile`]
|
||||
/// with the specified value.
|
||||
|
||||
+21
-22
@@ -35,9 +35,9 @@ pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
|
||||
asyncify(|| -> io::Result<ReadDir> {
|
||||
let mut std = std::fs::read_dir(path)?;
|
||||
let mut buf = VecDeque::with_capacity(CHUNK_SIZE);
|
||||
ReadDir::next_chunk(&mut buf, &mut std);
|
||||
let remain = ReadDir::next_chunk(&mut buf, &mut std);
|
||||
|
||||
Ok(ReadDir(State::Idle(Some((buf, std)))))
|
||||
Ok(ReadDir(State::Idle(Some((buf, std, remain)))))
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -66,8 +66,8 @@ pub struct ReadDir(State);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Idle(Option<(VecDeque<io::Result<DirEntry>>, std::fs::ReadDir)>),
|
||||
Pending(JoinHandle<(VecDeque<io::Result<DirEntry>>, std::fs::ReadDir)>),
|
||||
Idle(Option<(VecDeque<io::Result<DirEntry>>, std::fs::ReadDir, bool)>),
|
||||
Pending(JoinHandle<(VecDeque<io::Result<DirEntry>>, std::fs::ReadDir, bool)>),
|
||||
}
|
||||
|
||||
impl ReadDir {
|
||||
@@ -103,38 +103,35 @@ impl ReadDir {
|
||||
loop {
|
||||
match self.0 {
|
||||
State::Idle(ref mut data) => {
|
||||
let (buf, _) = data.as_mut().unwrap();
|
||||
let (buf, _, ref remain) = data.as_mut().unwrap();
|
||||
|
||||
if let Some(ent) = buf.pop_front() {
|
||||
return Poll::Ready(ent.map(Some));
|
||||
};
|
||||
} else if !remain {
|
||||
return Poll::Ready(Ok(None));
|
||||
}
|
||||
|
||||
let (mut buf, mut std) = data.take().unwrap();
|
||||
let (mut buf, mut std, _) = data.take().unwrap();
|
||||
|
||||
self.0 = State::Pending(spawn_blocking(move || {
|
||||
ReadDir::next_chunk(&mut buf, &mut std);
|
||||
(buf, std)
|
||||
let remain = ReadDir::next_chunk(&mut buf, &mut std);
|
||||
(buf, std, remain)
|
||||
}));
|
||||
}
|
||||
State::Pending(ref mut rx) => {
|
||||
let (mut buf, std) = ready!(Pin::new(rx).poll(cx))?;
|
||||
|
||||
let ret = match buf.pop_front() {
|
||||
Some(Ok(x)) => Ok(Some(x)),
|
||||
Some(Err(e)) => Err(e),
|
||||
None => Ok(None),
|
||||
};
|
||||
|
||||
self.0 = State::Idle(Some((buf, std)));
|
||||
|
||||
return Poll::Ready(ret);
|
||||
self.0 = State::Idle(Some(ready!(Pin::new(rx).poll(cx))?));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_chunk(buf: &mut VecDeque<io::Result<DirEntry>>, std: &mut std::fs::ReadDir) {
|
||||
for ret in std.by_ref().take(CHUNK_SIZE) {
|
||||
fn next_chunk(buf: &mut VecDeque<io::Result<DirEntry>>, std: &mut std::fs::ReadDir) -> bool {
|
||||
for _ in 0..CHUNK_SIZE {
|
||||
let ret = match std.next() {
|
||||
Some(ret) => ret,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let success = ret.is_ok();
|
||||
|
||||
buf.push_back(ret.map(|std| DirEntry {
|
||||
@@ -152,6 +149,8 @@ impl ReadDir {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::path::Path;
|
||||
///
|
||||
/// This is an async version of [`std::os::windows::fs::symlink_dir`][std]
|
||||
///
|
||||
/// [std]: std::os::windows::fs::symlink_dir
|
||||
/// [std]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html
|
||||
pub async fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::path::Path;
|
||||
///
|
||||
/// This is an async version of [`std::os::windows::fs::symlink_file`][std]
|
||||
///
|
||||
/// [std]: std::os::windows::fs::symlink_file
|
||||
/// [std]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html
|
||||
pub async fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
|
||||
@@ -508,6 +508,109 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
pub async fn writable_mut<'a>(&'a mut self) -> io::Result<AsyncFdReadyMutGuard<'a, T>> {
|
||||
self.readiness_mut(Interest::WRITABLE).await
|
||||
}
|
||||
|
||||
/// Reads or writes from the file descriptor using a user-provided IO operation.
|
||||
///
|
||||
/// The `async_io` method is a convenience utility that waits for the file
|
||||
/// descriptor to become ready, and then executes the provided IO operation.
|
||||
/// Since file descriptors may be marked ready spuriously, the closure will
|
||||
/// be called repeatedly until it returns something other than a
|
||||
/// [`WouldBlock`] error. This is done using the following loop:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::io::{self, Result};
|
||||
/// # struct Dox<T> { inner: T }
|
||||
/// # impl<T> Dox<T> {
|
||||
/// # async fn writable(&self) -> Result<&Self> {
|
||||
/// # Ok(self)
|
||||
/// # }
|
||||
/// # fn try_io<R>(&self, _: impl FnMut(&T) -> Result<R>) -> Result<Result<R>> {
|
||||
/// # panic!()
|
||||
/// # }
|
||||
/// async fn async_io<R>(&self, mut f: impl FnMut(&T) -> io::Result<R>) -> io::Result<R> {
|
||||
/// loop {
|
||||
/// // or `readable` if called with the read interest.
|
||||
/// let guard = self.writable().await?;
|
||||
///
|
||||
/// match guard.try_io(&mut f) {
|
||||
/// Ok(result) => return result,
|
||||
/// Err(_would_block) => continue,
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// The closure should only return a [`WouldBlock`] error if it has performed
|
||||
/// an IO operation on the file descriptor that failed due to the file descriptor not being
|
||||
/// ready. Returning a [`WouldBlock`] error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the file descriptor to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio [`AsyncFd`] type, as this will mess with the
|
||||
/// readiness flag and can cause the file descriptor to behave incorrectly.
|
||||
///
|
||||
/// This method is not intended to be used with combined interests.
|
||||
/// The closure should perform only one type of IO operation, so it should not
|
||||
/// require more than one ready state. This method may panic or sleep forever
|
||||
/// if it is called with a combined interest.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This example sends some bytes on the inner [`std::net::UdpSocket`]. The `async_io`
|
||||
/// method waits for readiness, and retries if the send operation does block. This example
|
||||
/// is equivalent to the one given for [`try_io`].
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::io::{Interest, unix::AsyncFd};
|
||||
///
|
||||
/// use std::io;
|
||||
/// use std::net::UdpSocket;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let socket = UdpSocket::bind("0.0.0.0:8080")?;
|
||||
/// socket.set_nonblocking(true)?;
|
||||
/// let async_fd = AsyncFd::new(socket)?;
|
||||
///
|
||||
/// let written = async_fd
|
||||
/// .async_io(Interest::WRITABLE, |inner| inner.send(&[1, 2]))
|
||||
/// .await?;
|
||||
///
|
||||
/// println!("wrote {written} bytes");
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`try_io`]: AsyncFdReadyGuard::try_io
|
||||
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
|
||||
pub async fn async_io<R>(
|
||||
&self,
|
||||
interest: Interest,
|
||||
mut f: impl FnMut(&T) -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
self.registration
|
||||
.async_io(interest, || f(self.get_ref()))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Reads or writes from the file descriptor using a user-provided IO operation.
|
||||
///
|
||||
/// The behavior is the same as [`async_io`], except that the closure can mutate the inner
|
||||
/// value of the [`AsyncFd`].
|
||||
///
|
||||
/// [`async_io`]: AsyncFd::async_io
|
||||
pub async fn async_io_mut<R>(
|
||||
&mut self,
|
||||
interest: Interest,
|
||||
mut f: impl FnMut(&mut T) -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
self.registration
|
||||
.async_io(interest, || f(self.inner.as_mut().unwrap()))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRawFd> AsRawFd for AsyncFd<T> {
|
||||
@@ -516,6 +619,13 @@ impl<T: AsRawFd> AsRawFd for AsyncFd<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl<T: AsRawFd> std::os::unix::io::AsFd for AsyncFd<T> {
|
||||
fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> {
|
||||
unsafe { std::os::unix::io::BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::fmt::Debug + AsRawFd> std::fmt::Debug for AsyncFd<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AsyncFd")
|
||||
@@ -571,6 +681,43 @@ impl<'a, Inner: AsRawFd> AsyncFdReadyGuard<'a, Inner> {
|
||||
/// `AsyncFdReadyGuard` no longer expresses the readiness state that was queried to
|
||||
/// create this `AsyncFdReadyGuard`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This example sends some bytes to the inner [`std::net::UdpSocket`]. Waiting
|
||||
/// for write-readiness and retrying when the send operation does block are explicit.
|
||||
/// This example can be written more succinctly using [`AsyncFd::async_io`].
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::io::unix::AsyncFd;
|
||||
///
|
||||
/// use std::io;
|
||||
/// use std::net::UdpSocket;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let socket = UdpSocket::bind("0.0.0.0:8080")?;
|
||||
/// socket.set_nonblocking(true)?;
|
||||
/// let async_fd = AsyncFd::new(socket)?;
|
||||
///
|
||||
/// let written = loop {
|
||||
/// let mut guard = async_fd.writable().await?;
|
||||
/// match guard.try_io(|inner| inner.get_ref().send(&[1, 2])) {
|
||||
/// Ok(result) => {
|
||||
/// break result?;
|
||||
/// }
|
||||
/// Err(_would_block) => {
|
||||
/// // try_io already cleared the file descriptor's readiness state
|
||||
/// continue;
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
///
|
||||
/// println!("wrote {written} bytes");
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
|
||||
// Alias for old name in 0.x
|
||||
#[cfg_attr(docsrs, doc(alias = "with_io"))]
|
||||
|
||||
+20
-14
@@ -130,19 +130,23 @@
|
||||
//! other words, these types must never block the thread, and instead the
|
||||
//! current task is notified when the I/O resource is ready.
|
||||
//!
|
||||
//! ## Conversion to and from Sink/Stream
|
||||
//! ## Conversion to and from Stream/Sink
|
||||
//!
|
||||
//! It is often convenient to encapsulate the reading and writing of
|
||||
//! bytes and instead work with a [`Sink`] or [`Stream`] of some data
|
||||
//! type that is encoded as bytes and/or decoded from bytes. Tokio
|
||||
//! provides some utility traits in the [tokio-util] crate that
|
||||
//! abstract the asynchronous buffering that is required and allows
|
||||
//! you to write [`Encoder`] and [`Decoder`] functions working with a
|
||||
//! buffer of bytes, and then use that ["codec"] to transform anything
|
||||
//! that implements [`AsyncRead`] and [`AsyncWrite`] into a `Sink`/`Stream` of
|
||||
//! your structured data.
|
||||
//! It is often convenient to encapsulate the reading and writing of bytes in a
|
||||
//! [`Stream`] or [`Sink`] of data.
|
||||
//!
|
||||
//! [tokio-util]: https://docs.rs/tokio-util/0.6/tokio_util/codec/index.html
|
||||
//! Tokio provides simple wrappers for converting [`AsyncRead`] to [`Stream`]
|
||||
//! and vice-versa in the [tokio-util] crate, see [`ReaderStream`] and
|
||||
//! [`StreamReader`].
|
||||
//!
|
||||
//! There are also utility traits that abstract the asynchronous buffering
|
||||
//! necessary to write your own adaptors for encoding and decoding bytes to/from
|
||||
//! your structured data, allowing to transform something that implements
|
||||
//! [`AsyncRead`]/[`AsyncWrite`] into a [`Stream`]/[`Sink`], see [`Decoder`] and
|
||||
//! [`Encoder`] in the [tokio-util::codec] module.
|
||||
//!
|
||||
//! [tokio-util]: https://docs.rs/tokio-util
|
||||
//! [tokio-util::codec]: https://docs.rs/tokio-util/latest/tokio_util/codec/index.html
|
||||
//!
|
||||
//! # Standard input and output
|
||||
//!
|
||||
@@ -167,9 +171,11 @@
|
||||
//! [`AsyncWrite`]: trait@AsyncWrite
|
||||
//! [`AsyncReadExt`]: trait@AsyncReadExt
|
||||
//! [`AsyncWriteExt`]: trait@AsyncWriteExt
|
||||
//! ["codec"]: https://docs.rs/tokio-util/0.6/tokio_util/codec/index.html
|
||||
//! [`Encoder`]: https://docs.rs/tokio-util/0.6/tokio_util/codec/trait.Encoder.html
|
||||
//! [`Decoder`]: https://docs.rs/tokio-util/0.6/tokio_util/codec/trait.Decoder.html
|
||||
//! ["codec"]: https://docs.rs/tokio-util/latest/tokio_util/codec/index.html
|
||||
//! [`Encoder`]: https://docs.rs/tokio-util/latest/tokio_util/codec/trait.Encoder.html
|
||||
//! [`Decoder`]: https://docs.rs/tokio-util/latest/tokio_util/codec/trait.Decoder.html
|
||||
//! [`ReaderStream`]: https://docs.rs/tokio-util/latest/tokio_util/io/struct.ReaderStream.html
|
||||
//! [`StreamReader`]: https://docs.rs/tokio-util/latest/tokio_util/io/struct.StreamReader.html
|
||||
//! [`Error`]: struct@Error
|
||||
//! [`ErrorKind`]: enum@ErrorKind
|
||||
//! [`Result`]: type@Result
|
||||
|
||||
@@ -270,6 +270,33 @@ impl<'a> ReadBuf<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "io-util")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
|
||||
unsafe impl<'a> bytes::BufMut for ReadBuf<'a> {
|
||||
fn remaining_mut(&self) -> usize {
|
||||
self.remaining()
|
||||
}
|
||||
|
||||
// SAFETY: The caller guarantees that at least `cnt` unfilled bytes have been initialized.
|
||||
unsafe fn advance_mut(&mut self, cnt: usize) {
|
||||
self.assume_init(cnt);
|
||||
self.advance(cnt);
|
||||
}
|
||||
|
||||
fn chunk_mut(&mut self) -> &mut bytes::buf::UninitSlice {
|
||||
// SAFETY: No region of `unfilled` will be deinitialized because it is
|
||||
// exposed as an `UninitSlice`, whose API guarantees that the memory is
|
||||
// never deinitialized.
|
||||
let unfilled = unsafe { self.unfilled_mut() };
|
||||
let len = unfilled.len();
|
||||
let ptr = unfilled.as_mut_ptr() as *mut u8;
|
||||
|
||||
// SAFETY: The pointer is valid for `len` bytes because it comes from a
|
||||
// slice of that length.
|
||||
unsafe { bytes::buf::UninitSlice::from_raw_parts_mut(ptr, len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ReadBuf<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ReadBuf")
|
||||
|
||||
+34
-7
@@ -74,16 +74,43 @@ cfg_io_std! {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::AsRawFd for Stderr {
|
||||
fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
|
||||
std::io::stderr().as_raw_fd()
|
||||
mod sys {
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use super::Stderr;
|
||||
|
||||
impl AsRawFd for Stderr {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
std::io::stderr().as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for Stderr {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::AsRawHandle for Stderr {
|
||||
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
|
||||
std::io::stderr().as_raw_handle()
|
||||
cfg_windows! {
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsHandle, BorrowedHandle};
|
||||
use crate::os::windows::io::{AsRawHandle, RawHandle};
|
||||
|
||||
impl AsRawHandle for Stderr {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
std::io::stderr().as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for Stderr {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-7
@@ -49,16 +49,43 @@ cfg_io_std! {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::AsRawFd for Stdin {
|
||||
fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
|
||||
std::io::stdin().as_raw_fd()
|
||||
mod sys {
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use super::Stdin;
|
||||
|
||||
impl AsRawFd for Stdin {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
std::io::stdin().as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for Stdin {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::AsRawHandle for Stdin {
|
||||
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
|
||||
std::io::stdin().as_raw_handle()
|
||||
cfg_windows! {
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsHandle, BorrowedHandle};
|
||||
use crate::os::windows::io::{AsRawHandle, RawHandle};
|
||||
|
||||
impl AsRawHandle for Stdin {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
std::io::stdin().as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for Stdin {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-7
@@ -73,16 +73,43 @@ cfg_io_std! {
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::AsRawFd for Stdout {
|
||||
fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
|
||||
std::io::stdout().as_raw_fd()
|
||||
mod sys {
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use super::Stdout;
|
||||
|
||||
impl AsRawFd for Stdout {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
std::io::stdout().as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for Stdout {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::AsRawHandle for Stdout {
|
||||
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
|
||||
std::io::stdout().as_raw_handle()
|
||||
cfg_windows! {
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsHandle, BorrowedHandle};
|
||||
use crate::os::windows::io::{AsRawHandle, RawHandle};
|
||||
|
||||
impl AsRawHandle for Stdout {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
std::io::stdout().as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for Stdout {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ cfg_io_util! {
|
||||
/// [`next_line`] method.
|
||||
/// * Use [`tokio_util::codec::LinesCodec`][LinesCodec].
|
||||
///
|
||||
/// [LinesCodec]: https://docs.rs/tokio-util/0.6/tokio_util/codec/struct.LinesCodec.html
|
||||
/// [LinesCodec]: https://docs.rs/tokio-util/latest/tokio_util/codec/struct.LinesCodec.html
|
||||
/// [`read_until`]: Self::read_until
|
||||
/// [`lines`]: Self::lines
|
||||
/// [`next_line`]: crate::io::Lines::next_line
|
||||
|
||||
@@ -153,14 +153,22 @@ cfg_io_util! {
|
||||
///
|
||||
/// This function returns a future that will continuously read data from
|
||||
/// `reader` and then write it into `writer` in a streaming fashion until
|
||||
/// `reader` returns EOF.
|
||||
/// `reader` returns EOF or fails.
|
||||
///
|
||||
/// On success, the total number of bytes that were copied from `reader` to
|
||||
/// `writer` is returned.
|
||||
///
|
||||
/// This is an asynchronous version of [`std::io::copy`][std].
|
||||
///
|
||||
/// A heap-allocated copy buffer with 8 KB is created to take data from the
|
||||
/// reader to the writer, check [`copy_buf`] if you want an alternative for
|
||||
/// [`AsyncBufRead`]. You can use `copy_buf` with [`BufReader`] to change the
|
||||
/// buffer capacity.
|
||||
///
|
||||
/// [std]: std::io::copy
|
||||
/// [`copy_buf`]: crate::io::copy_buf
|
||||
/// [`AsyncBufRead`]: crate::io::AsyncBufRead
|
||||
/// [`BufReader`]: crate::io::BufReader
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
||||
@@ -24,11 +24,17 @@ cfg_io_util! {
|
||||
///
|
||||
/// This function returns a future that will continuously read data from
|
||||
/// `reader` and then write it into `writer` in a streaming fashion until
|
||||
/// `reader` returns EOF.
|
||||
/// `reader` returns EOF or fails.
|
||||
///
|
||||
/// On success, the total number of bytes that were copied from `reader` to
|
||||
/// `writer` is returned.
|
||||
///
|
||||
/// This is a [`tokio::io::copy`] alternative for [`AsyncBufRead`] readers
|
||||
/// with no extra buffer allocation, since [`AsyncBufRead`] allow access
|
||||
/// to the reader's inner buffer.
|
||||
///
|
||||
/// [`tokio::io::copy`]: crate::io::copy
|
||||
/// [`AsyncBufRead`]: crate::io::AsyncBufRead
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::io::util::vec_with_initialized::{into_read_buf_parts, VecU8, VecWithInitialized};
|
||||
use crate::io::AsyncRead;
|
||||
use crate::io::{AsyncRead, ReadBuf};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::marker::PhantomPinned;
|
||||
use std::mem;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
@@ -67,16 +67,47 @@ fn poll_read_to_end<V: VecU8, R: AsyncRead + ?Sized>(
|
||||
// has 4 bytes while still making large reads if the reader does have a ton
|
||||
// of data to return. Simply tacking on an extra DEFAULT_BUF_SIZE space every
|
||||
// time is 4,500 times (!) slower than this if the reader has a very small
|
||||
// amount of data to return.
|
||||
buf.reserve(32);
|
||||
// amount of data to return. When the vector is full with its starting
|
||||
// capacity, we first try to read into a small buffer to see if we reached
|
||||
// an EOF. This only happens when the starting capacity is >= NUM_BYTES, since
|
||||
// we allocate at least NUM_BYTES each time. This avoids the unnecessary
|
||||
// allocation that we attempt before reading into the vector.
|
||||
|
||||
const NUM_BYTES: usize = 32;
|
||||
let try_small_read = buf.try_small_read_first(NUM_BYTES);
|
||||
|
||||
// Get a ReadBuf into the vector.
|
||||
let mut read_buf = buf.get_read_buf();
|
||||
let mut read_buf;
|
||||
let poll_result;
|
||||
|
||||
let filled_before = read_buf.filled().len();
|
||||
let poll_result = read.poll_read(cx, &mut read_buf);
|
||||
let filled_after = read_buf.filled().len();
|
||||
let n = filled_after - filled_before;
|
||||
let n = if try_small_read {
|
||||
// Read some bytes using a small read.
|
||||
let mut small_buf: [MaybeUninit<u8>; NUM_BYTES] = [MaybeUninit::uninit(); NUM_BYTES];
|
||||
let mut small_read_buf = ReadBuf::uninit(&mut small_buf);
|
||||
poll_result = read.poll_read(cx, &mut small_read_buf);
|
||||
let to_write = small_read_buf.filled();
|
||||
|
||||
// Ensure we have enough space to fill our vector with what we read.
|
||||
read_buf = buf.get_read_buf();
|
||||
if to_write.len() > read_buf.remaining() {
|
||||
buf.reserve(NUM_BYTES);
|
||||
read_buf = buf.get_read_buf();
|
||||
}
|
||||
read_buf.put_slice(to_write);
|
||||
|
||||
to_write.len()
|
||||
} else {
|
||||
// Ensure we have enough space for reading.
|
||||
buf.reserve(NUM_BYTES);
|
||||
read_buf = buf.get_read_buf();
|
||||
|
||||
// Read data directly into vector.
|
||||
let filled_before = read_buf.filled().len();
|
||||
poll_result = read.poll_read(cx, &mut read_buf);
|
||||
|
||||
// Compute the number of bytes read.
|
||||
read_buf.filled().len() - filled_before
|
||||
};
|
||||
|
||||
// Update the length of the vector using the result of poll_read.
|
||||
let read_buf_parts = into_read_buf_parts(read_buf);
|
||||
@@ -87,11 +118,11 @@ fn poll_read_to_end<V: VecU8, R: AsyncRead + ?Sized>(
|
||||
// In this case, nothing should have been read. However we still
|
||||
// update the vector in case the poll_read call initialized parts of
|
||||
// the vector's unused capacity.
|
||||
debug_assert_eq!(filled_before, filled_after);
|
||||
debug_assert_eq!(n, 0);
|
||||
Poll::Pending
|
||||
}
|
||||
Poll::Ready(Err(err)) => {
|
||||
debug_assert_eq!(filled_before, filled_after);
|
||||
debug_assert_eq!(n, 0);
|
||||
Poll::Ready(Err(err))
|
||||
}
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(n)),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::io::AsyncBufRead;
|
||||
use crate::util::memchr;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::future::Future;
|
||||
|
||||
@@ -28,6 +28,7 @@ pub(crate) struct VecWithInitialized<V> {
|
||||
// The number of initialized bytes in the vector.
|
||||
// Always between `vec.len()` and `vec.capacity()`.
|
||||
num_initialized: usize,
|
||||
starting_capacity: usize,
|
||||
}
|
||||
|
||||
impl VecWithInitialized<Vec<u8>> {
|
||||
@@ -47,6 +48,7 @@ where
|
||||
// to its length are initialized.
|
||||
Self {
|
||||
num_initialized: vec.as_mut().len(),
|
||||
starting_capacity: vec.as_ref().capacity(),
|
||||
vec,
|
||||
}
|
||||
}
|
||||
@@ -111,6 +113,15 @@ where
|
||||
vec.set_len(parts.len);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a boolean telling the caller to try reading into a small local buffer first if true.
|
||||
// Doing so would avoid overallocating when vec is filled to capacity and we reached EOF.
|
||||
pub(crate) fn try_small_read_first(&self, num_bytes: usize) -> bool {
|
||||
let vec = self.vec.as_ref();
|
||||
vec.capacity() - vec.len() < num_bytes
|
||||
&& self.starting_capacity == vec.capacity()
|
||||
&& self.starting_capacity >= num_bytes
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ReadBufParts {
|
||||
|
||||
@@ -487,6 +487,21 @@ compile_error!("Tokio's build script has incorrectly detected wasm.");
|
||||
))]
|
||||
compile_error!("Only features sync,macros,io-util,rt,time are supported on wasm.");
|
||||
|
||||
#[cfg(all(not(tokio_unstable), tokio_taskdump))]
|
||||
compile_error!("The `tokio_taskdump` feature requires `--cfg tokio_unstable`.");
|
||||
|
||||
#[cfg(all(
|
||||
tokio_taskdump,
|
||||
not(all(
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))
|
||||
))]
|
||||
compile_error!(
|
||||
"The `tokio_taskdump` feature is only currently supported on \
|
||||
linux, on `aarch64`, `x86` and `x86_64`."
|
||||
);
|
||||
|
||||
// Includes re-exports used by macros.
|
||||
//
|
||||
// This module is not intended to be part of the public API. In general, any
|
||||
@@ -552,6 +567,20 @@ cfg_time! {
|
||||
pub mod time;
|
||||
}
|
||||
|
||||
mod trace {
|
||||
cfg_taskdump! {
|
||||
pub(crate) use crate::runtime::task::trace::trace_leaf;
|
||||
}
|
||||
|
||||
cfg_not_taskdump! {
|
||||
#[inline(always)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn trace_leaf(_: &mut std::task::Context<'_>) -> std::task::Poll<()> {
|
||||
std::task::Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod util;
|
||||
|
||||
/// Due to the `Stream` trait's inclusion in `std` landing later than Tokio's 1.0
|
||||
@@ -658,5 +687,6 @@ cfg_macros! {
|
||||
#[cfg(test)]
|
||||
fn is_unpin<T: Unpin>() {}
|
||||
|
||||
/// fuzz test (fuzz_linked_list)
|
||||
#[cfg(fuzzing)]
|
||||
pub mod fuzz;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! This module defines a macro that lets you go from a raw pointer to a struct
|
||||
//! to a raw pointer to a field of the struct.
|
||||
|
||||
#[cfg(not(tokio_no_addr_of))]
|
||||
macro_rules! generate_addr_of_methods {
|
||||
(
|
||||
impl<$($gen:ident)*> $struct_name:ty {$(
|
||||
@@ -21,33 +20,3 @@ macro_rules! generate_addr_of_methods {
|
||||
)*}
|
||||
};
|
||||
}
|
||||
|
||||
// The `addr_of_mut!` macro is only available for MSRV at least 1.51.0. This
|
||||
// version of the macro uses a workaround for older versions of rustc.
|
||||
#[cfg(tokio_no_addr_of)]
|
||||
macro_rules! generate_addr_of_methods {
|
||||
(
|
||||
impl<$($gen:ident)*> $struct_name:ty {$(
|
||||
$(#[$attrs:meta])*
|
||||
$vis:vis unsafe fn $fn_name:ident(self: NonNull<Self>) -> NonNull<$field_type:ty> {
|
||||
&self$(.$field_name:tt)+
|
||||
}
|
||||
)*}
|
||||
) => {
|
||||
impl<$($gen)*> $struct_name {$(
|
||||
$(#[$attrs])*
|
||||
$vis unsafe fn $fn_name(me: ::core::ptr::NonNull<Self>) -> ::core::ptr::NonNull<$field_type> {
|
||||
let me = me.as_ptr();
|
||||
let me_u8 = me as *mut u8;
|
||||
|
||||
let field_offset = {
|
||||
let me_ref = &*me;
|
||||
let field_ref_u8 = (&me_ref $(.$field_name)+ ) as *const $field_type as *const u8;
|
||||
field_ref_u8.offset_from(me_u8)
|
||||
};
|
||||
|
||||
::core::ptr::NonNull::new_unchecked(me_u8.offset(field_offset).cast())
|
||||
}
|
||||
)*}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,18 @@ macro_rules! feature {
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables Windows-specific code.
|
||||
/// Use this macro instead of `cfg(windows)` to generate docs properly.
|
||||
macro_rules! cfg_windows {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(any(all(doc, docsrs), windows))]
|
||||
#[cfg_attr(docsrs, doc(cfg(windows)))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables enter::block_on.
|
||||
macro_rules! cfg_block_on {
|
||||
($($item:item)*) => {
|
||||
@@ -361,6 +373,44 @@ macro_rules! cfg_not_rt_multi_thread {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_taskdump {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
feature = "rt",
|
||||
target_os = "linux",
|
||||
any(
|
||||
target_arch = "aarch64",
|
||||
target_arch = "x86",
|
||||
target_arch = "x86_64"
|
||||
)
|
||||
))]
|
||||
$item
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cfg_not_taskdump {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(not(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
feature = "rt",
|
||||
target_os = "linux",
|
||||
any(
|
||||
target_arch = "aarch64",
|
||||
target_arch = "x86",
|
||||
target_arch = "x86_64"
|
||||
)
|
||||
)))]
|
||||
$item
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cfg_test_util {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
macro_rules! if_loom {
|
||||
($($t:tt)*) => {{
|
||||
#[cfg(loom)]
|
||||
const LOOM: bool = true;
|
||||
#[cfg(not(loom))]
|
||||
const LOOM: bool = false;
|
||||
|
||||
if LOOM {
|
||||
{
|
||||
$($t)*
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -131,6 +131,13 @@
|
||||
/// correctly even if it is restarted while waiting at an `.await`, then it is
|
||||
/// cancellation safe.
|
||||
///
|
||||
/// Cancellation safety can be defined in the following way: If you have a
|
||||
/// future that has not yet completed, then it must be a no-op to drop that
|
||||
/// future and recreate it. This definition is motivated by the situation where
|
||||
/// a `select!` is used in a loop. Without this guarantee, you would lose your
|
||||
/// progress when another branch completes and you restart the `select!` by
|
||||
/// going around the loop.
|
||||
///
|
||||
/// Be aware that cancelling something that is not cancellation safe is not
|
||||
/// necessarily wrong. For example, if you are cancelling a task because the
|
||||
/// application is shutting down, then you probably don't care that partially
|
||||
|
||||
@@ -5,7 +5,6 @@ cfg_not_wasi! {
|
||||
use crate::net::{to_socket_addrs, ToSocketAddrs};
|
||||
}
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::{self, SocketAddr};
|
||||
@@ -407,6 +406,13 @@ mod sys {
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for TcpListener {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_unstable! {
|
||||
@@ -420,17 +426,31 @@ cfg_unstable! {
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for TcpListener {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod sys {
|
||||
use super::TcpListener;
|
||||
use std::os::windows::prelude::*;
|
||||
cfg_windows! {
|
||||
use crate::os::windows::io::{AsRawSocket, RawSocket};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsSocket, BorrowedSocket};
|
||||
|
||||
impl AsRawSocket for TcpListener {
|
||||
fn as_raw_socket(&self) -> RawSocket {
|
||||
self.io.as_raw_socket()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsSocket for TcpListener {
|
||||
fn as_socket(&self) -> BorrowedSocket<'_> {
|
||||
unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+87
-23
@@ -4,12 +4,18 @@ use std::fmt;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[cfg(all(unix, not(tokio_no_as_fd)))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
|
||||
use std::time::Duration;
|
||||
|
||||
cfg_windows! {
|
||||
use crate::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsSocket, BorrowedSocket};
|
||||
}
|
||||
|
||||
cfg_net! {
|
||||
/// A TCP socket that has not yet been converted to a `TcpStream` or
|
||||
/// `TcpListener`.
|
||||
@@ -398,6 +404,51 @@ impl TcpSocket {
|
||||
self.inner.linger()
|
||||
}
|
||||
|
||||
/// Sets the value of the `TCP_NODELAY` option on this socket.
|
||||
///
|
||||
/// If set, this option disables the Nagle algorithm. This means that segments are always
|
||||
/// sent as soon as possible, even if there is only a small amount of data. When not set,
|
||||
/// data is buffered until there is a sufficient amount to send out, thereby avoiding
|
||||
/// the frequent sending of small packets.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::TcpSocket;
|
||||
///
|
||||
/// # async fn dox() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let socket = TcpSocket::new_v4()?;
|
||||
///
|
||||
/// println!("{:?}", socket.nodelay()?);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> {
|
||||
self.inner.set_nodelay(nodelay)
|
||||
}
|
||||
|
||||
/// Gets the value of the `TCP_NODELAY` option on this socket.
|
||||
///
|
||||
/// For more information about this option, see [`set_nodelay`].
|
||||
///
|
||||
/// [`set_nodelay`]: TcpSocket::set_nodelay
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::TcpSocket;
|
||||
///
|
||||
/// # async fn dox() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let stream = TcpSocket::new_v4()?;
|
||||
///
|
||||
/// stream.set_nodelay(true)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn nodelay(&self) -> io::Result<bool> {
|
||||
self.inner.nodelay()
|
||||
}
|
||||
|
||||
/// Gets the value of the `IP_TOS` option for this socket.
|
||||
///
|
||||
/// For more information about this option, see [`set_tos`].
|
||||
@@ -737,6 +788,13 @@ impl AsRawFd for TcpSocket {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(tokio_no_as_fd)))]
|
||||
impl AsFd for TcpSocket {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl FromRawFd for TcpSocket {
|
||||
/// Converts a `RawFd` to a `TcpSocket`.
|
||||
@@ -758,30 +816,36 @@ impl IntoRawFd for TcpSocket {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl IntoRawSocket for TcpSocket {
|
||||
fn into_raw_socket(self) -> RawSocket {
|
||||
self.inner.into_raw_socket()
|
||||
cfg_windows! {
|
||||
impl IntoRawSocket for TcpSocket {
|
||||
fn into_raw_socket(self) -> RawSocket {
|
||||
self.inner.into_raw_socket()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl AsRawSocket for TcpSocket {
|
||||
fn as_raw_socket(&self) -> RawSocket {
|
||||
self.inner.as_raw_socket()
|
||||
impl AsRawSocket for TcpSocket {
|
||||
fn as_raw_socket(&self) -> RawSocket {
|
||||
self.inner.as_raw_socket()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl FromRawSocket for TcpSocket {
|
||||
/// Converts a `RawSocket` to a `TcpStream`.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// The caller is responsible for ensuring that the socket is in
|
||||
/// non-blocking mode.
|
||||
unsafe fn from_raw_socket(socket: RawSocket) -> TcpSocket {
|
||||
let inner = socket2::Socket::from_raw_socket(socket);
|
||||
TcpSocket { inner }
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsSocket for TcpSocket {
|
||||
fn as_socket(&self) -> BorrowedSocket<'_> {
|
||||
unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawSocket for TcpSocket {
|
||||
/// Converts a `RawSocket` to a `TcpStream`.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// The caller is responsible for ensuring that the socket is in
|
||||
/// non-blocking mode.
|
||||
unsafe fn from_raw_socket(socket: RawSocket) -> TcpSocket {
|
||||
let inner = socket2::Socket::from_raw_socket(socket);
|
||||
TcpSocket { inner }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +141,9 @@ impl ReadHalf<'_> {
|
||||
|
||||
/// Waits for any of the requested ready states.
|
||||
///
|
||||
/// This function is usually paired with `try_read()` or `try_write()`. It
|
||||
/// can be used to concurrently read / write to the same socket on a single
|
||||
/// task without splitting the socket.
|
||||
/// This function is usually paired with [`try_read()`]. It can be used instead
|
||||
/// of [`readable()`] to check the returned ready set for [`Ready::READABLE`]
|
||||
/// and [`Ready::READ_CLOSED`] events.
|
||||
///
|
||||
/// The function may complete without the socket being ready. This is a
|
||||
/// false-positive and attempting an operation will return with
|
||||
@@ -153,6 +153,9 @@ impl ReadHalf<'_> {
|
||||
///
|
||||
/// This function is equivalent to [`TcpStream::ready`].
|
||||
///
|
||||
/// [`try_read()`]: Self::try_read
|
||||
/// [`readable()`]: Self::readable
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. Once a readiness event occurs, the method
|
||||
@@ -275,9 +278,9 @@ impl ReadHalf<'_> {
|
||||
impl WriteHalf<'_> {
|
||||
/// Waits for any of the requested ready states.
|
||||
///
|
||||
/// This function is usually paired with `try_read()` or `try_write()`. It
|
||||
/// can be used to concurrently read / write to the same socket on a single
|
||||
/// task without splitting the socket.
|
||||
/// This function is usually paired with [`try_write()`]. It can be used instead
|
||||
/// of [`writable()`] to check the returned ready set for [`Ready::WRITABLE`]
|
||||
/// and [`Ready::WRITE_CLOSED`] events.
|
||||
///
|
||||
/// The function may complete without the socket being ready. This is a
|
||||
/// false-positive and attempting an operation will return with
|
||||
@@ -287,6 +290,9 @@ impl WriteHalf<'_> {
|
||||
///
|
||||
/// This function is equivalent to [`TcpStream::ready`].
|
||||
///
|
||||
/// [`try_write()`]: Self::try_write
|
||||
/// [`writable()`]: Self::writable
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. Once a readiness event occurs, the method
|
||||
|
||||
@@ -196,9 +196,9 @@ impl OwnedReadHalf {
|
||||
|
||||
/// Waits for any of the requested ready states.
|
||||
///
|
||||
/// This function is usually paired with `try_read()` or `try_write()`. It
|
||||
/// can be used to concurrently read / write to the same socket on a single
|
||||
/// task without splitting the socket.
|
||||
/// This function is usually paired with [`try_read()`]. It can be used instead
|
||||
/// of [`readable()`] to check the returned ready set for [`Ready::READABLE`]
|
||||
/// and [`Ready::READ_CLOSED`] events.
|
||||
///
|
||||
/// The function may complete without the socket being ready. This is a
|
||||
/// false-positive and attempting an operation will return with
|
||||
@@ -208,6 +208,9 @@ impl OwnedReadHalf {
|
||||
///
|
||||
/// This function is equivalent to [`TcpStream::ready`].
|
||||
///
|
||||
/// [`try_read()`]: Self::try_read
|
||||
/// [`readable()`]: Self::readable
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. Once a readiness event occurs, the method
|
||||
@@ -357,9 +360,9 @@ impl OwnedWriteHalf {
|
||||
|
||||
/// Waits for any of the requested ready states.
|
||||
///
|
||||
/// This function is usually paired with `try_read()` or `try_write()`. It
|
||||
/// can be used to concurrently read / write to the same socket on a single
|
||||
/// task without splitting the socket.
|
||||
/// This function is usually paired with [`try_write()`]. It can be used instead
|
||||
/// of [`writable()`] to check the returned ready set for [`Ready::WRITABLE`]
|
||||
/// and [`Ready::WRITE_CLOSED`] events.
|
||||
///
|
||||
/// The function may complete without the socket being ready. This is a
|
||||
/// false-positive and attempting an operation will return with
|
||||
@@ -369,6 +372,9 @@ impl OwnedWriteHalf {
|
||||
///
|
||||
/// This function is equivalent to [`TcpStream::ready`].
|
||||
///
|
||||
/// [`try_write()`]: Self::try_write
|
||||
/// [`writable()`]: Self::writable
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. Once a readiness event occurs, the method
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf, Ready};
|
||||
use crate::net::tcp::split::{split, ReadHalf, WriteHalf};
|
||||
use crate::net::tcp::split_owned::{split_owned, OwnedReadHalf, OwnedWriteHalf};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
@@ -1016,6 +1015,42 @@ impl TcpStream {
|
||||
.try_io(interest, || self.io.try_io(f))
|
||||
}
|
||||
|
||||
/// Reads or writes from the socket using a user-provided IO operation.
|
||||
///
|
||||
/// The readiness of the socket is awaited and when the socket is ready,
|
||||
/// the provided closure is called. The closure should attempt to perform
|
||||
/// IO operation on the socket by manually calling the appropriate syscall.
|
||||
/// If the operation fails because the socket is not actually ready,
|
||||
/// then the closure should return a `WouldBlock` error. In such case the
|
||||
/// readiness flag is cleared and the socket readiness is awaited again.
|
||||
/// This loop is repeated until the closure returns an `Ok` or an error
|
||||
/// other than `WouldBlock`.
|
||||
///
|
||||
/// The closure should only return a `WouldBlock` error if it has performed
|
||||
/// an IO operation on the socket that failed due to the socket not being
|
||||
/// ready. Returning a `WouldBlock` error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the socket to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio `TcpStream` type, as this will mess with the
|
||||
/// readiness flag and can cause the socket to behave incorrectly.
|
||||
///
|
||||
/// This method is not intended to be used with combined interests.
|
||||
/// The closure should perform only one type of IO operation, so it should not
|
||||
/// require more than one ready state. This method may panic or sleep forever
|
||||
/// if it is called with a combined interest.
|
||||
pub async fn async_io<R>(
|
||||
&self,
|
||||
interest: Interest,
|
||||
mut f: impl FnMut() -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(interest, || self.io.try_io(&mut f))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Receives data on the socket from the remote address to which it is
|
||||
/// connected, without removing that data from the queue. On success,
|
||||
/// returns the number of bytes peeked.
|
||||
@@ -1342,18 +1377,32 @@ mod sys {
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for TcpStream {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod sys {
|
||||
use super::TcpStream;
|
||||
use std::os::windows::prelude::*;
|
||||
cfg_windows! {
|
||||
use crate::os::windows::io::{AsRawSocket, RawSocket};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsSocket, BorrowedSocket};
|
||||
|
||||
impl AsRawSocket for TcpStream {
|
||||
fn as_raw_socket(&self) -> RawSocket {
|
||||
self.io.as_raw_socket()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsSocket for TcpStream {
|
||||
fn as_socket(&self) -> BorrowedSocket<'_> {
|
||||
unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(tokio_unstable, tokio_wasi))]
|
||||
@@ -1366,4 +1415,11 @@ mod sys {
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for TcpStream {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+350
-17
@@ -1,7 +1,6 @@
|
||||
use crate::io::{Interest, PollEvented, ReadBuf, Ready};
|
||||
use crate::net::{to_socket_addrs, ToSocketAddrs};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::{self, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
@@ -826,7 +825,7 @@ impl UdpSocket {
|
||||
/// address to which it is connected. On success, returns the number of
|
||||
/// bytes read.
|
||||
///
|
||||
/// The function must be called with valid byte array buf of sufficient size
|
||||
/// This method must be called with valid byte array buf of sufficient size
|
||||
/// to hold the message bytes. If a message is too long to fit in the
|
||||
/// supplied buffer, excess bytes may be discarded.
|
||||
///
|
||||
@@ -882,10 +881,12 @@ impl UdpSocket {
|
||||
/// Tries to receive data from the stream into the provided buffer, advancing the
|
||||
/// buffer's internal cursor, returning how many bytes were read.
|
||||
///
|
||||
/// The function must be called with valid byte array buf of sufficient size
|
||||
/// This method must be called with valid byte array buf of sufficient size
|
||||
/// to hold the message bytes. If a message is too long to fit in the
|
||||
/// supplied buffer, excess bytes may be discarded.
|
||||
///
|
||||
/// This method can be used even if `buf` is uninitialized.
|
||||
///
|
||||
/// When there is no pending data, `Err(io::ErrorKind::WouldBlock)` is
|
||||
/// returned. This function is usually paired with `readable()`.
|
||||
///
|
||||
@@ -932,10 +933,10 @@ impl UdpSocket {
|
||||
let dst =
|
||||
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
|
||||
|
||||
// Safety: We trust `UdpSocket::recv` to have filled up `n` bytes in the
|
||||
// buffer.
|
||||
let n = (*self.io).recv(dst)?;
|
||||
|
||||
// Safety: We trust `UdpSocket::recv` to have filled up `n` bytes in the
|
||||
// buffer.
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
@@ -944,16 +945,75 @@ impl UdpSocket {
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to receive a single datagram message on the socket. On success,
|
||||
/// returns the number of bytes read and the origin.
|
||||
/// Receives a single datagram message on the socket from the remote address
|
||||
/// to which it is connected, advancing the buffer's internal cursor,
|
||||
/// returning how many bytes were read.
|
||||
///
|
||||
/// The function must be called with valid byte array buf of sufficient size
|
||||
/// This method must be called with valid byte array buf of sufficient size
|
||||
/// to hold the message bytes. If a message is too long to fit in the
|
||||
/// supplied buffer, excess bytes may be discarded.
|
||||
///
|
||||
/// This method can be used even if `buf` is uninitialized.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::UdpSocket;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// // Connect to a peer
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:8080").await?;
|
||||
/// socket.connect("127.0.0.1:8081").await?;
|
||||
///
|
||||
/// let mut buf = Vec::with_capacity(512);
|
||||
/// let len = socket.recv_buf(&mut buf).await?;
|
||||
///
|
||||
/// println!("received {} bytes {:?}", len, &buf[..len]);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn recv_buf<B: BufMut>(&self, buf: &mut B) -> io::Result<usize> {
|
||||
self.io.registration().async_io(Interest::READABLE, || {
|
||||
let dst = buf.chunk_mut();
|
||||
let dst =
|
||||
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
|
||||
|
||||
let n = (*self.io).recv(dst)?;
|
||||
|
||||
// Safety: We trust `UdpSocket::recv` to have filled up `n` bytes in the
|
||||
// buffer.
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
}).await
|
||||
}
|
||||
|
||||
/// Tries to receive a single datagram message on the socket. On success,
|
||||
/// returns the number of bytes read and the origin.
|
||||
///
|
||||
/// This method must be called with valid byte array buf of sufficient size
|
||||
/// to hold the message bytes. If a message is too long to fit in the
|
||||
/// supplied buffer, excess bytes may be discarded.
|
||||
///
|
||||
/// This method can be used even if `buf` is uninitialized.
|
||||
///
|
||||
/// When there is no pending data, `Err(io::ErrorKind::WouldBlock)` is
|
||||
/// returned. This function is usually paired with `readable()`.
|
||||
///
|
||||
/// # Notes
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -996,10 +1056,10 @@ impl UdpSocket {
|
||||
let dst =
|
||||
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
|
||||
|
||||
// Safety: We trust `UdpSocket::recv_from` to have filled up `n` bytes in the
|
||||
// buffer.
|
||||
let (n, addr) = (*self.io).recv_from(dst)?;
|
||||
|
||||
// Safety: We trust `UdpSocket::recv_from` to have filled up `n` bytes in the
|
||||
// buffer.
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
@@ -1007,6 +1067,62 @@ impl UdpSocket {
|
||||
Ok((n, addr))
|
||||
})
|
||||
}
|
||||
|
||||
/// Receives a single datagram message on the socket, advancing the
|
||||
/// buffer's internal cursor, returning how many bytes were read and the origin.
|
||||
///
|
||||
/// This method must be called with valid byte array buf of sufficient size
|
||||
/// to hold the message bytes. If a message is too long to fit in the
|
||||
/// supplied buffer, excess bytes may be discarded.
|
||||
///
|
||||
/// This method can be used even if `buf` is uninitialized.
|
||||
///
|
||||
/// # Notes
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::UdpSocket;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// // Connect to a peer
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:8080").await?;
|
||||
/// socket.connect("127.0.0.1:8081").await?;
|
||||
///
|
||||
/// let mut buf = Vec::with_capacity(512);
|
||||
/// let (len, addr) = socket.recv_buf_from(&mut buf).await?;
|
||||
///
|
||||
/// println!("received {:?} bytes from {:?}", len, addr);
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn recv_buf_from<B: BufMut>(&self, buf: &mut B) -> io::Result<(usize, SocketAddr)> {
|
||||
self.io.registration().async_io(Interest::READABLE, || {
|
||||
let dst = buf.chunk_mut();
|
||||
let dst =
|
||||
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
|
||||
|
||||
let (n, addr) = (*self.io).recv_from(dst)?;
|
||||
|
||||
// Safety: We trust `UdpSocket::recv_from` to have filled up `n` bytes in the
|
||||
// buffer.
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
|
||||
Ok((n,addr))
|
||||
}).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends data on the socket to the given address. On success, returns the
|
||||
@@ -1177,6 +1293,15 @@ impl UdpSocket {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Notes
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
|
||||
self.io
|
||||
.registration()
|
||||
@@ -1201,6 +1326,15 @@ impl UdpSocket {
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may encounter any standard I/O error except `WouldBlock`.
|
||||
///
|
||||
/// # Notes
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
pub fn poll_recv_from(
|
||||
&self,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -1226,13 +1360,23 @@ impl UdpSocket {
|
||||
/// Tries to receive a single datagram message on the socket. On success,
|
||||
/// returns the number of bytes read and the origin.
|
||||
///
|
||||
/// The function must be called with valid byte array buf of sufficient size
|
||||
/// This method must be called with valid byte array buf of sufficient size
|
||||
/// to hold the message bytes. If a message is too long to fit in the
|
||||
/// supplied buffer, excess bytes may be discarded.
|
||||
///
|
||||
/// When there is no pending data, `Err(io::ErrorKind::WouldBlock)` is
|
||||
/// returned. This function is usually paired with `readable()`.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -1319,6 +1463,42 @@ impl UdpSocket {
|
||||
.try_io(interest, || self.io.try_io(f))
|
||||
}
|
||||
|
||||
/// Reads or writes from the socket using a user-provided IO operation.
|
||||
///
|
||||
/// The readiness of the socket is awaited and when the socket is ready,
|
||||
/// the provided closure is called. The closure should attempt to perform
|
||||
/// IO operation on the socket by manually calling the appropriate syscall.
|
||||
/// If the operation fails because the socket is not actually ready,
|
||||
/// then the closure should return a `WouldBlock` error. In such case the
|
||||
/// readiness flag is cleared and the socket readiness is awaited again.
|
||||
/// This loop is repeated until the closure returns an `Ok` or an error
|
||||
/// other than `WouldBlock`.
|
||||
///
|
||||
/// The closure should only return a `WouldBlock` error if it has performed
|
||||
/// an IO operation on the socket that failed due to the socket not being
|
||||
/// ready. Returning a `WouldBlock` error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the socket to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio `UdpSocket` type, as this will mess with the
|
||||
/// readiness flag and can cause the socket to behave incorrectly.
|
||||
///
|
||||
/// This method is not intended to be used with combined interests.
|
||||
/// The closure should perform only one type of IO operation, so it should not
|
||||
/// require more than one ready state. This method may panic or sleep forever
|
||||
/// if it is called with a combined interest.
|
||||
pub async fn async_io<R>(
|
||||
&self,
|
||||
interest: Interest,
|
||||
mut f: impl FnMut() -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(interest, || self.io.try_io(&mut f))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Receives data from the socket, without removing it from the input queue.
|
||||
/// On success, returns the number of bytes read and the address from whence
|
||||
/// the data came.
|
||||
@@ -1331,6 +1511,17 @@ impl UdpSocket {
|
||||
/// Make sure to always use a sufficiently large buffer to hold the
|
||||
/// maximum UDP packet size, which can be up to 65536 bytes in size.
|
||||
///
|
||||
/// MacOS will return an error if you pass a zero-sized buffer.
|
||||
///
|
||||
/// If you're merely interested in learning the sender of the data at the head of the queue,
|
||||
/// try [`peek_sender`].
|
||||
///
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -1349,6 +1540,9 @@ impl UdpSocket {
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`peek_sender`]: method@Self::peek_sender
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
pub async fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
|
||||
self.io
|
||||
.registration()
|
||||
@@ -1357,7 +1551,7 @@ impl UdpSocket {
|
||||
}
|
||||
|
||||
/// Receives data from the socket, without removing it from the input queue.
|
||||
/// On success, returns the number of bytes read.
|
||||
/// On success, returns the sending address of the datagram.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
@@ -1371,6 +1565,17 @@ impl UdpSocket {
|
||||
/// Make sure to always use a sufficiently large buffer to hold the
|
||||
/// maximum UDP packet size, which can be up to 65536 bytes in size.
|
||||
///
|
||||
/// MacOS will return an error if you pass a zero-sized buffer.
|
||||
///
|
||||
/// If you're merely interested in learning the sender of the data at the head of the queue,
|
||||
/// try [`poll_peek_sender`].
|
||||
///
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// The function returns:
|
||||
@@ -1382,6 +1587,9 @@ impl UdpSocket {
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may encounter any standard I/O error except `WouldBlock`.
|
||||
///
|
||||
/// [`poll_peek_sender`]: method@Self::poll_peek_sender
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
pub fn poll_peek_from(
|
||||
&self,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -1404,6 +1612,117 @@ impl UdpSocket {
|
||||
Poll::Ready(Ok(addr))
|
||||
}
|
||||
|
||||
/// Tries to receive data on the socket without removing it from the input queue.
|
||||
/// On success, returns the number of bytes read and the sending address of the
|
||||
/// datagram.
|
||||
///
|
||||
/// When there is no pending data, `Err(io::ErrorKind::WouldBlock)` is
|
||||
/// returned. This function is usually paired with `readable()`.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// On Windows, if the data is larger than the buffer specified, the buffer
|
||||
/// is filled with the first part of the data, and peek returns the error
|
||||
/// WSAEMSGSIZE(10040). The excess data is lost.
|
||||
/// Make sure to always use a sufficiently large buffer to hold the
|
||||
/// maximum UDP packet size, which can be up to 65536 bytes in size.
|
||||
///
|
||||
/// MacOS will return an error if you pass a zero-sized buffer.
|
||||
///
|
||||
/// If you're merely interested in learning the sender of the data at the head of the queue,
|
||||
/// try [`try_peek_sender`].
|
||||
///
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [`try_peek_sender`]: method@Self::try_peek_sender
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
pub fn try_peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::READABLE, || self.io.peek_from(buf))
|
||||
}
|
||||
|
||||
/// Retrieve the sender of the data at the head of the input queue, waiting if empty.
|
||||
///
|
||||
/// This is equivalent to calling [`peek_from`] with a zero-sized buffer,
|
||||
/// but suppresses the `WSAEMSGSIZE` error on Windows and the "invalid argument" error on macOS.
|
||||
///
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [`peek_from`]: method@Self::peek_from
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
pub async fn peek_sender(&self) -> io::Result<SocketAddr> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(Interest::READABLE, || self.peek_sender_inner())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieve the sender of the data at the head of the input queue,
|
||||
/// scheduling a wakeup if empty.
|
||||
///
|
||||
/// This is equivalent to calling [`poll_peek_from`] with a zero-sized buffer,
|
||||
/// but suppresses the `WSAEMSGSIZE` error on Windows and the "invalid argument" error on macOS.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Note that on multiple calls to a `poll_*` method in the recv direction, only the
|
||||
/// `Waker` from the `Context` passed to the most recent call will be scheduled to
|
||||
/// receive a wakeup.
|
||||
///
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [`poll_peek_from`]: method@Self::poll_peek_from
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
pub fn poll_peek_sender(&self, cx: &mut Context<'_>) -> Poll<io::Result<SocketAddr>> {
|
||||
self.io
|
||||
.registration()
|
||||
.poll_read_io(cx, || self.peek_sender_inner())
|
||||
}
|
||||
|
||||
/// Try to retrieve the sender of the data at the head of the input queue.
|
||||
///
|
||||
/// When there is no pending data, `Err(io::ErrorKind::WouldBlock)` is
|
||||
/// returned. This function is usually paired with `readable()`.
|
||||
///
|
||||
/// Note that the socket address **cannot** be implicitly trusted, because it is relatively
|
||||
/// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack].
|
||||
/// Because UDP is stateless and does not validate the origin of a packet,
|
||||
/// the attacker does not need to be able to intercept traffic in order to interfere.
|
||||
/// It is important to be aware of this when designing your application-level protocol.
|
||||
///
|
||||
/// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection
|
||||
pub fn try_peek_sender(&self) -> io::Result<SocketAddr> {
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::READABLE, || self.peek_sender_inner())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peek_sender_inner(&self) -> io::Result<SocketAddr> {
|
||||
self.io.try_io(|| {
|
||||
self.as_socket()
|
||||
.peek_sender()?
|
||||
// May be `None` if the platform doesn't populate the sender for some reason.
|
||||
// In testing, that only occurred on macOS if you pass a zero-sized buffer,
|
||||
// but the implementation of `Socket::peek_sender()` covers that.
|
||||
.as_socket()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "sender not available"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the value of the `SO_BROADCAST` option for this socket.
|
||||
///
|
||||
/// For more information about this option, see [`set_broadcast`].
|
||||
@@ -1691,7 +2010,7 @@ impl fmt::Debug for UdpSocket {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix))]
|
||||
#[cfg(unix)]
|
||||
mod sys {
|
||||
use super::UdpSocket;
|
||||
use std::os::unix::prelude::*;
|
||||
@@ -1701,16 +2020,30 @@ mod sys {
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for UdpSocket {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod sys {
|
||||
use super::UdpSocket;
|
||||
use std::os::windows::prelude::*;
|
||||
cfg_windows! {
|
||||
use crate::os::windows::io::{AsRawSocket, RawSocket};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsSocket, BorrowedSocket};
|
||||
|
||||
impl AsRawSocket for UdpSocket {
|
||||
fn as_raw_socket(&self) -> RawSocket {
|
||||
self.io.as_raw_socket()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsSocket for UdpSocket {
|
||||
fn as_socket(&self) -> BorrowedSocket<'_> {
|
||||
unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::io::{Interest, PollEvented, ReadBuf, Ready};
|
||||
use crate::net::unix::SocketAddr;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::Shutdown;
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::unix::net;
|
||||
use std::path::Path;
|
||||
@@ -90,6 +91,7 @@ cfg_net_unix! {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg_attr(docsrs, doc(alias = "uds"))]
|
||||
pub struct UnixDatagram {
|
||||
io: PollEvented<mio::net::UnixDatagram>,
|
||||
}
|
||||
@@ -806,6 +808,8 @@ impl UnixDatagram {
|
||||
cfg_io_util! {
|
||||
/// Tries to receive data from the socket without waiting.
|
||||
///
|
||||
/// This method can be used even if `buf` is uninitialized.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -865,9 +869,64 @@ impl UnixDatagram {
|
||||
Ok((n, SocketAddr(addr)))
|
||||
}
|
||||
|
||||
/// Receives from the socket, advances the
|
||||
/// buffer's internal cursor and returns how many bytes were read and the origin.
|
||||
///
|
||||
/// This method can be used even if `buf` is uninitialized.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::error::Error;
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// use tokio::net::UnixDatagram;
|
||||
/// use tempfile::tempdir;
|
||||
///
|
||||
/// // We use a temporary directory so that the socket
|
||||
/// // files left by the bound sockets will get cleaned up.
|
||||
/// let tmp = tempdir()?;
|
||||
///
|
||||
/// // Bind each socket to a filesystem path
|
||||
/// let tx_path = tmp.path().join("tx");
|
||||
/// let tx = UnixDatagram::bind(&tx_path)?;
|
||||
/// let rx_path = tmp.path().join("rx");
|
||||
/// let rx = UnixDatagram::bind(&rx_path)?;
|
||||
///
|
||||
/// let bytes = b"hello world";
|
||||
/// tx.send_to(bytes, &rx_path).await?;
|
||||
///
|
||||
/// let mut buf = Vec::with_capacity(24);
|
||||
/// let (size, addr) = rx.recv_buf_from(&mut buf).await?;
|
||||
///
|
||||
/// let dgram = &buf[..size];
|
||||
/// assert_eq!(dgram, bytes);
|
||||
/// assert_eq!(addr.as_pathname().unwrap(), &tx_path);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn recv_buf_from<B: BufMut>(&self, buf: &mut B) -> io::Result<(usize, SocketAddr)> {
|
||||
self.io.registration().async_io(Interest::READABLE, || {
|
||||
let dst = buf.chunk_mut();
|
||||
let dst =
|
||||
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
|
||||
|
||||
// Safety: We trust `UnixDatagram::recv_from` to have filled up `n` bytes in the
|
||||
// buffer.
|
||||
let (n, addr) = (*self.io).recv_from(dst)?;
|
||||
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
Ok((n,SocketAddr(addr)))
|
||||
}).await
|
||||
}
|
||||
|
||||
/// Tries to read data from the stream into the provided buffer, advancing the
|
||||
/// buffer's internal cursor, returning how many bytes were read.
|
||||
///
|
||||
/// This method can be used even if `buf` is uninitialized.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -925,6 +984,52 @@ impl UnixDatagram {
|
||||
Ok(n)
|
||||
})
|
||||
}
|
||||
|
||||
/// Receives data from the socket from the address to which it is connected,
|
||||
/// advancing the buffer's internal cursor, returning how many bytes were read.
|
||||
///
|
||||
/// This method can be used even if `buf` is uninitialized.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use std::error::Error;
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// use tokio::net::UnixDatagram;
|
||||
///
|
||||
/// // Create the pair of sockets
|
||||
/// let (sock1, sock2) = UnixDatagram::pair()?;
|
||||
///
|
||||
/// // Since the sockets are paired, the paired send/recv
|
||||
/// // functions can be used
|
||||
/// let bytes = b"hello world";
|
||||
/// sock1.send(bytes).await?;
|
||||
///
|
||||
/// let mut buff = Vec::with_capacity(24);
|
||||
/// let size = sock2.recv_buf(&mut buff).await?;
|
||||
///
|
||||
/// let dgram = &buff[..size];
|
||||
/// assert_eq!(dgram, bytes);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn recv_buf<B: BufMut>(&self, buf: &mut B) -> io::Result<usize> {
|
||||
self.io.registration().async_io(Interest::READABLE, || {
|
||||
let dst = buf.chunk_mut();
|
||||
let dst =
|
||||
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
|
||||
|
||||
// Safety: We trust `UnixDatagram::recv_from` to have filled up `n` bytes in the
|
||||
// buffer.
|
||||
let n = (*self.io).recv(dst)?;
|
||||
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
Ok(n)
|
||||
}).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends data on the socket to the specified address.
|
||||
@@ -1260,6 +1365,42 @@ impl UnixDatagram {
|
||||
.try_io(interest, || self.io.try_io(f))
|
||||
}
|
||||
|
||||
/// Reads or writes from the socket using a user-provided IO operation.
|
||||
///
|
||||
/// The readiness of the socket is awaited and when the socket is ready,
|
||||
/// the provided closure is called. The closure should attempt to perform
|
||||
/// IO operation on the socket by manually calling the appropriate syscall.
|
||||
/// If the operation fails because the socket is not actually ready,
|
||||
/// then the closure should return a `WouldBlock` error. In such case the
|
||||
/// readiness flag is cleared and the socket readiness is awaited again.
|
||||
/// This loop is repeated until the closure returns an `Ok` or an error
|
||||
/// other than `WouldBlock`.
|
||||
///
|
||||
/// The closure should only return a `WouldBlock` error if it has performed
|
||||
/// an IO operation on the socket that failed due to the socket not being
|
||||
/// ready. Returning a `WouldBlock` error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the socket to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio `UnixDatagram` type, as this will mess with the
|
||||
/// readiness flag and can cause the socket to behave incorrectly.
|
||||
///
|
||||
/// This method is not intended to be used with combined interests.
|
||||
/// The closure should perform only one type of IO operation, so it should not
|
||||
/// require more than one ready state. This method may panic or sleep forever
|
||||
/// if it is called with a combined interest.
|
||||
pub async fn async_io<R>(
|
||||
&self,
|
||||
interest: Interest,
|
||||
mut f: impl FnMut() -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(interest, || self.io.try_io(&mut f))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns the local address that this socket is bound to.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -1436,3 +1577,10 @@ impl AsRawFd for UnixDatagram {
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for UnixDatagram {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::io::{Interest, PollEvented};
|
||||
use crate::net::unix::{SocketAddr, UnixStream};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::unix::net;
|
||||
use std::path::Path;
|
||||
@@ -44,6 +45,7 @@ cfg_net_unix! {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(docsrs, doc(alias = "uds"))]
|
||||
pub struct UnixListener {
|
||||
io: PollEvented<mio::net::UnixListener>,
|
||||
}
|
||||
@@ -208,3 +210,10 @@ impl AsRawFd for UnixListener {
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for UnixListener {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ use mio::unix::pipe as mio_pipe;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::os::unix::fs::{FileTypeExt, OpenOptionsExt};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
@@ -662,6 +664,13 @@ impl AsRawFd for Sender {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for Sender {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Reading end of a Unix pipe.
|
||||
///
|
||||
/// It can be constructed from a FIFO file with [`OpenOptions::open_receiver`].
|
||||
@@ -1161,6 +1170,13 @@ impl AsRawFd for Receiver {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for Receiver {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if file is a FIFO
|
||||
fn is_fifo(file: &File) -> io::Result<bool> {
|
||||
Ok(file.metadata()?.file_type().is_fifo())
|
||||
|
||||
@@ -55,9 +55,20 @@ pub(crate) fn split(stream: &mut UnixStream) -> (ReadHalf<'_>, WriteHalf<'_>) {
|
||||
impl ReadHalf<'_> {
|
||||
/// Wait for any of the requested ready states.
|
||||
///
|
||||
/// This function is usually paired with `try_read()` or `try_write()`. It
|
||||
/// can be used to concurrently read / write to the same socket on a single
|
||||
/// task without splitting the socket.
|
||||
/// This function is usually paired with [`try_read()`]. It can be used instead
|
||||
/// of [`readable()`] to check the returned ready set for [`Ready::READABLE`]
|
||||
/// and [`Ready::READ_CLOSED`] events.
|
||||
///
|
||||
/// The function may complete without the socket being ready. This is a
|
||||
/// false-positive and attempting an operation will return with
|
||||
/// `io::ErrorKind::WouldBlock`. The function can also return with an empty
|
||||
/// [`Ready`] set, so you should always check the returned value and possibly
|
||||
/// wait again if the requested states are not set.
|
||||
///
|
||||
/// This function is equivalent to [`UnixStream::ready`].
|
||||
///
|
||||
/// [`try_read()`]: Self::try_read
|
||||
/// [`readable()`]: Self::readable
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
@@ -178,9 +189,9 @@ impl ReadHalf<'_> {
|
||||
impl WriteHalf<'_> {
|
||||
/// Waits for any of the requested ready states.
|
||||
///
|
||||
/// This function is usually paired with `try_read()` or `try_write()`. It
|
||||
/// can be used to concurrently read / write to the same socket on a single
|
||||
/// task without splitting the socket.
|
||||
/// This function is usually paired with [`try_write()`]. It can be used instead
|
||||
/// of [`writable()`] to check the returned ready set for [`Ready::WRITABLE`]
|
||||
/// and [`Ready::WRITE_CLOSED`] events.
|
||||
///
|
||||
/// The function may complete without the socket being ready. This is a
|
||||
/// false-positive and attempting an operation will return with
|
||||
@@ -188,6 +199,11 @@ impl WriteHalf<'_> {
|
||||
/// [`Ready`] set, so you should always check the returned value and possibly
|
||||
/// wait again if the requested states are not set.
|
||||
///
|
||||
/// This function is equivalent to [`UnixStream::ready`].
|
||||
///
|
||||
/// [`try_write()`]: Self::try_write
|
||||
/// [`writable()`]: Self::writable
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. Once a readiness event occurs, the method
|
||||
|
||||
@@ -110,9 +110,9 @@ impl OwnedReadHalf {
|
||||
|
||||
/// Waits for any of the requested ready states.
|
||||
///
|
||||
/// This function is usually paired with `try_read()` or `try_write()`. It
|
||||
/// can be used to concurrently read / write to the same socket on a single
|
||||
/// task without splitting the socket.
|
||||
/// This function is usually paired with [`try_read()`]. It can be used instead
|
||||
/// of [`readable()`] to check the returned ready set for [`Ready::READABLE`]
|
||||
/// and [`Ready::READ_CLOSED`] events.
|
||||
///
|
||||
/// The function may complete without the socket being ready. This is a
|
||||
/// false-positive and attempting an operation will return with
|
||||
@@ -120,6 +120,11 @@ impl OwnedReadHalf {
|
||||
/// [`Ready`] set, so you should always check the returned value and possibly
|
||||
/// wait again if the requested states are not set.
|
||||
///
|
||||
/// This function is equivalent to [`UnixStream::ready`].
|
||||
///
|
||||
/// [`try_read()`]: Self::try_read
|
||||
/// [`readable()`]: Self::readable
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. Once a readiness event occurs, the method
|
||||
@@ -267,9 +272,9 @@ impl OwnedWriteHalf {
|
||||
|
||||
/// Waits for any of the requested ready states.
|
||||
///
|
||||
/// This function is usually paired with `try_read()` or `try_write()`. It
|
||||
/// can be used to concurrently read / write to the same socket on a single
|
||||
/// task without splitting the socket.
|
||||
/// This function is usually paired with [`try_write()`]. It can be used instead
|
||||
/// of [`writable()`] to check the returned ready set for [`Ready::WRITABLE`]
|
||||
/// and [`Ready::WRITE_CLOSED`] events.
|
||||
///
|
||||
/// The function may complete without the socket being ready. This is a
|
||||
/// false-positive and attempting an operation will return with
|
||||
@@ -277,6 +282,11 @@ impl OwnedWriteHalf {
|
||||
/// [`Ready`] set, so you should always check the returned value and possibly
|
||||
/// wait again if the requested states are not set.
|
||||
///
|
||||
/// This function is equivalent to [`UnixStream::ready`].
|
||||
///
|
||||
/// [`try_write()`]: Self::try_write
|
||||
/// [`writable()`]: Self::writable
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. Once a readiness event occurs, the method
|
||||
|
||||
@@ -5,10 +5,11 @@ use crate::net::unix::split_owned::{split_owned, OwnedReadHalf, OwnedWriteHalf};
|
||||
use crate::net::unix::ucred::{self, UCred};
|
||||
use crate::net::unix::SocketAddr;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::Shutdown;
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::unix::net;
|
||||
use std::path::Path;
|
||||
@@ -33,6 +34,7 @@ cfg_net_unix! {
|
||||
///
|
||||
/// [`shutdown()`]: fn@crate::io::AsyncWriteExt::shutdown
|
||||
/// [`UnixListener::accept`]: crate::net::UnixListener::accept
|
||||
#[cfg_attr(docsrs, doc(alias = "uds"))]
|
||||
pub struct UnixStream {
|
||||
io: PollEvented<mio::net::UnixStream>,
|
||||
}
|
||||
@@ -706,6 +708,42 @@ impl UnixStream {
|
||||
.try_io(interest, || self.io.try_io(f))
|
||||
}
|
||||
|
||||
/// Reads or writes from the socket using a user-provided IO operation.
|
||||
///
|
||||
/// The readiness of the socket is awaited and when the socket is ready,
|
||||
/// the provided closure is called. The closure should attempt to perform
|
||||
/// IO operation on the socket by manually calling the appropriate syscall.
|
||||
/// If the operation fails because the socket is not actually ready,
|
||||
/// then the closure should return a `WouldBlock` error. In such case the
|
||||
/// readiness flag is cleared and the socket readiness is awaited again.
|
||||
/// This loop is repeated until the closure returns an `Ok` or an error
|
||||
/// other than `WouldBlock`.
|
||||
///
|
||||
/// The closure should only return a `WouldBlock` error if it has performed
|
||||
/// an IO operation on the socket that failed due to the socket not being
|
||||
/// ready. Returning a `WouldBlock` error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the socket to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio `UnixStream` type, as this will mess with the
|
||||
/// readiness flag and can cause the socket to behave incorrectly.
|
||||
///
|
||||
/// This method is not intended to be used with combined interests.
|
||||
/// The closure should perform only one type of IO operation, so it should not
|
||||
/// require more than one ready state. This method may panic or sleep forever
|
||||
/// if it is called with a combined interest.
|
||||
pub async fn async_io<R>(
|
||||
&self,
|
||||
interest: Interest,
|
||||
mut f: impl FnMut() -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
self.io
|
||||
.registration()
|
||||
.async_io(interest, || self.io.try_io(&mut f))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates new `UnixStream` from a `std::os::unix::net::UnixStream`.
|
||||
///
|
||||
/// This function is intended to be used to wrap a UnixStream from the
|
||||
@@ -1000,3 +1038,10 @@ impl AsRawFd for UnixStream {
|
||||
self.io.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for UnixStream {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ pub(crate) use self::impl_macos::get_peer_cred;
|
||||
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
|
||||
pub(crate) use self::impl_solaris::get_peer_cred;
|
||||
|
||||
#[cfg(target_os = "aix")]
|
||||
pub(crate) use self::impl_aix::get_peer_cred;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "openbsd"))]
|
||||
pub(crate) mod impl_linux {
|
||||
use crate::net::unix::{self, UnixStream};
|
||||
@@ -250,3 +253,31 @@ pub(crate) mod impl_solaris {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "aix")]
|
||||
pub(crate) mod impl_aix {
|
||||
use crate::net::unix::UnixStream;
|
||||
use std::io;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
pub(crate) fn get_peer_cred(sock: &UnixStream) -> io::Result<super::UCred> {
|
||||
unsafe {
|
||||
let raw_fd = sock.as_raw_fd();
|
||||
|
||||
let mut uid = std::mem::MaybeUninit::uninit();
|
||||
let mut gid = std::mem::MaybeUninit::uninit();
|
||||
|
||||
let ret = libc::getpeereid(raw_fd, uid.as_mut_ptr(), gid.as_mut_ptr());
|
||||
|
||||
if ret == 0 {
|
||||
Ok(super::UCred {
|
||||
uid: uid.assume_init(),
|
||||
gid: gid.assume_init(),
|
||||
pid: None,
|
||||
})
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use std::ptr;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf, Ready};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsHandle, BorrowedHandle};
|
||||
use crate::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle};
|
||||
|
||||
cfg_io_util! {
|
||||
@@ -851,6 +853,39 @@ impl NamedPipeServer {
|
||||
) -> io::Result<R> {
|
||||
self.io.registration().try_io(interest, f)
|
||||
}
|
||||
|
||||
/// Reads or writes from the pipe using a user-provided IO operation.
|
||||
///
|
||||
/// The readiness of the pipe is awaited and when the pipe is ready,
|
||||
/// the provided closure is called. The closure should attempt to perform
|
||||
/// IO operation on the pipe by manually calling the appropriate syscall.
|
||||
/// If the operation fails because the pipe is not actually ready,
|
||||
/// then the closure should return a `WouldBlock` error. In such case the
|
||||
/// readiness flag is cleared and the pipe readiness is awaited again.
|
||||
/// This loop is repeated until the closure returns an `Ok` or an error
|
||||
/// other than `WouldBlock`.
|
||||
///
|
||||
/// The closure should only return a `WouldBlock` error if it has performed
|
||||
/// an IO operation on the pipe that failed due to the pipe not being
|
||||
/// ready. Returning a `WouldBlock` error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the pipe to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio `NamedPipeServer` type, as this will mess with the
|
||||
/// readiness flag and can cause the pipe to behave incorrectly.
|
||||
///
|
||||
/// This method is not intended to be used with combined interests.
|
||||
/// The closure should perform only one type of IO operation, so it should not
|
||||
/// require more than one ready state. This method may panic or sleep forever
|
||||
/// if it is called with a combined interest.
|
||||
pub async fn async_io<R>(
|
||||
&self,
|
||||
interest: Interest,
|
||||
f: impl FnMut() -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
self.io.registration().async_io(interest, f).await
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for NamedPipeServer {
|
||||
@@ -895,6 +930,13 @@ impl AsRawHandle for NamedPipeServer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for NamedPipeServer {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Windows named pipe] client.
|
||||
///
|
||||
/// Constructed using [`ClientOptions::open`].
|
||||
@@ -1601,6 +1643,39 @@ impl NamedPipeClient {
|
||||
) -> io::Result<R> {
|
||||
self.io.registration().try_io(interest, f)
|
||||
}
|
||||
|
||||
/// Reads or writes from the pipe using a user-provided IO operation.
|
||||
///
|
||||
/// The readiness of the pipe is awaited and when the pipe is ready,
|
||||
/// the provided closure is called. The closure should attempt to perform
|
||||
/// IO operation on the pipe by manually calling the appropriate syscall.
|
||||
/// If the operation fails because the pipe is not actually ready,
|
||||
/// then the closure should return a `WouldBlock` error. In such case the
|
||||
/// readiness flag is cleared and the pipe readiness is awaited again.
|
||||
/// This loop is repeated until the closure returns an `Ok` or an error
|
||||
/// other than `WouldBlock`.
|
||||
///
|
||||
/// The closure should only return a `WouldBlock` error if it has performed
|
||||
/// an IO operation on the pipe that failed due to the pipe not being
|
||||
/// ready. Returning a `WouldBlock` error in any other situation will
|
||||
/// incorrectly clear the readiness flag, which can cause the pipe to
|
||||
/// behave incorrectly.
|
||||
///
|
||||
/// The closure should not perform the IO operation using any of the methods
|
||||
/// defined on the Tokio `NamedPipeClient` type, as this will mess with the
|
||||
/// readiness flag and can cause the pipe to behave incorrectly.
|
||||
///
|
||||
/// This method is not intended to be used with combined interests.
|
||||
/// The closure should perform only one type of IO operation, so it should not
|
||||
/// require more than one ready state. This method may panic or sleep forever
|
||||
/// if it is called with a combined interest.
|
||||
pub async fn async_io<R>(
|
||||
&self,
|
||||
interest: Interest,
|
||||
f: impl FnMut() -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
self.io.registration().async_io(interest, f).await
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for NamedPipeClient {
|
||||
@@ -1645,6 +1720,13 @@ impl AsRawHandle for NamedPipeClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for NamedPipeClient {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder structure for construct a named pipe with named pipe-specific
|
||||
/// options. This is required to use for named pipe servers who wants to modify
|
||||
/// pipe-related options.
|
||||
|
||||
+94
-35
@@ -156,7 +156,6 @@
|
||||
//! ```no_run
|
||||
//! use tokio::join;
|
||||
//! use tokio::process::Command;
|
||||
//! use std::convert::TryInto;
|
||||
//! use std::process::Stdio;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
@@ -217,7 +216,7 @@
|
||||
//! from being spawned.
|
||||
//!
|
||||
//! The tokio runtime will, on a best-effort basis, attempt to reap and clean up
|
||||
//! any process which it has spawned. No additional guarantees are made with regards
|
||||
//! any process which it has spawned. No additional guarantees are made with regard to
|
||||
//! how quickly or how often this procedure will take place.
|
||||
//!
|
||||
//! It is recommended to avoid dropping a [`Child`] process handle before it has been
|
||||
@@ -245,22 +244,26 @@ mod kill;
|
||||
use crate::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use crate::process::kill::Kill;
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::OsStr;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::process::{Command as StdCommand, ExitStatus, Output, Stdio};
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::CommandExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
cfg_windows! {
|
||||
use crate::os::windows::io::{AsRawHandle, RawHandle};
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use crate::os::windows::io::{AsHandle, BorrowedHandle};
|
||||
}
|
||||
|
||||
/// This structure mimics the API of [`std::process::Command`] found in the standard library, but
|
||||
/// replaces functions that create a process with an asynchronous variant. The main provided
|
||||
/// asynchronous functions are [spawn](Command::spawn), [status](Command::status), and
|
||||
@@ -397,6 +400,22 @@ impl Command {
|
||||
self
|
||||
}
|
||||
|
||||
/// Append literal text to the command line without any quoting or escaping.
|
||||
///
|
||||
/// This is useful for passing arguments to `cmd.exe /c`, which doesn't follow
|
||||
/// `CommandLineToArgvW` escaping rules.
|
||||
///
|
||||
/// **Note**: This is an [unstable API][unstable] but will be stabilised once
|
||||
/// tokio's MSRV is sufficiently new. See [the documentation on
|
||||
/// unstable features][unstable] for details about using unstable features.
|
||||
#[cfg(windows)]
|
||||
#[cfg(tokio_unstable)]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(windows, tokio_unstable))))]
|
||||
pub fn raw_arg<S: AsRef<OsStr>>(&mut self, text_to_append_as_is: S) -> &mut Command {
|
||||
self.std.raw_arg(text_to_append_as_is);
|
||||
self
|
||||
}
|
||||
|
||||
/// Inserts or updates an environment variable mapping.
|
||||
///
|
||||
/// Note that environment variable names are case-insensitive (but case-preserving) on Windows,
|
||||
@@ -631,7 +650,7 @@ impl Command {
|
||||
/// operation, the resulting zombie process cannot be `.await`ed inside of the
|
||||
/// destructor to avoid blocking other tasks. The tokio runtime will, on a
|
||||
/// best-effort basis, attempt to reap and clean up such processes in the
|
||||
/// background, but makes no additional guarantees are made with regards
|
||||
/// background, but no additional guarantees are made with regard to
|
||||
/// how quickly or how often this procedure will take place.
|
||||
///
|
||||
/// If stronger guarantees are required, it is recommended to avoid dropping
|
||||
@@ -642,16 +661,16 @@ impl Command {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [process creation flags][1] to be passed to `CreateProcess`.
|
||||
///
|
||||
/// These will always be ORed with `CREATE_UNICODE_ENVIRONMENT`.
|
||||
///
|
||||
/// [1]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
|
||||
#[cfg(windows)]
|
||||
#[cfg_attr(docsrs, doc(cfg(windows)))]
|
||||
pub fn creation_flags(&mut self, flags: u32) -> &mut Command {
|
||||
self.std.creation_flags(flags);
|
||||
self
|
||||
cfg_windows! {
|
||||
/// Sets the [process creation flags][1] to be passed to `CreateProcess`.
|
||||
///
|
||||
/// These will always be ORed with `CREATE_UNICODE_ENVIRONMENT`.
|
||||
///
|
||||
/// [1]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
|
||||
pub fn creation_flags(&mut self, flags: u32) -> &mut Command {
|
||||
self.std.creation_flags(flags);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the child process's user ID. This translates to a
|
||||
@@ -811,7 +830,7 @@ impl Command {
|
||||
/// from being spawned.
|
||||
///
|
||||
/// The tokio runtime will, on a best-effort basis, attempt to reap and clean up
|
||||
/// any process which it has spawned. No additional guarantees are made with regards
|
||||
/// any process which it has spawned. No additional guarantees are made with regard to
|
||||
/// how quickly or how often this procedure will take place.
|
||||
///
|
||||
/// It is recommended to avoid dropping a [`Child`] process handle before it has been
|
||||
@@ -1082,13 +1101,14 @@ impl Child {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the raw handle of the process associated with this child while
|
||||
/// it is still running. Returns `None` if the child has exited.
|
||||
#[cfg(windows)]
|
||||
pub fn raw_handle(&self) -> Option<RawHandle> {
|
||||
match &self.child {
|
||||
FusedChild::Child(c) => Some(c.inner.as_raw_handle()),
|
||||
FusedChild::Done(_) => None,
|
||||
cfg_windows! {
|
||||
/// Extracts the raw handle of the process associated with this child while
|
||||
/// it is still running. Returns `None` if the child has exited.
|
||||
pub fn raw_handle(&self) -> Option<RawHandle> {
|
||||
match &self.child {
|
||||
FusedChild::Child(c) => Some(c.inner.as_raw_handle()),
|
||||
FusedChild::Done(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,7 +1343,7 @@ impl ChildStdin {
|
||||
}
|
||||
|
||||
impl ChildStdout {
|
||||
/// Creates an asynchronous `ChildStderr` from a synchronous one.
|
||||
/// Creates an asynchronous `ChildStdout` from a synchronous one.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -1428,6 +1448,8 @@ impl TryInto<Stdio> for ChildStderr {
|
||||
|
||||
#[cfg(unix)]
|
||||
mod sys {
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use super::{ChildStderr, ChildStdin, ChildStdout};
|
||||
@@ -1438,42 +1460,79 @@ mod sys {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for ChildStdin {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for ChildStdout {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.inner.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for ChildStdout {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for ChildStderr {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.inner.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for ChildStderr {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod sys {
|
||||
use std::os::windows::io::{AsRawHandle, RawHandle};
|
||||
|
||||
use super::{ChildStderr, ChildStdin, ChildStdout};
|
||||
|
||||
cfg_windows! {
|
||||
impl AsRawHandle for ChildStdin {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.inner.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for ChildStdin {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawHandle for ChildStdout {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.inner.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for ChildStdout {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawHandle for ChildStderr {
|
||||
fn as_raw_handle(&self) -> RawHandle {
|
||||
self.inner.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsHandle for ChildStderr {
|
||||
fn as_handle(&self) -> BorrowedHandle<'_> {
|
||||
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(loom)))]
|
||||
|
||||
@@ -39,6 +39,8 @@ use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::pin::Pin;
|
||||
use std::process::{Child as StdChild, ExitStatus, Stdio};
|
||||
@@ -194,6 +196,13 @@ impl AsRawFd for Pipe {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for Pipe {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn convert_to_stdio(io: ChildStdio) -> io::Result<Stdio> {
|
||||
let mut fd = io.inner.into_inner()?.fd;
|
||||
|
||||
@@ -246,6 +255,13 @@ impl AsRawFd for ChildStdio {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_no_as_fd))]
|
||||
impl AsFd for ChildStdio {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for ChildStdio {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
|
||||
@@ -36,10 +36,9 @@ use windows_sys::{
|
||||
DuplicateHandle, BOOLEAN, DUPLICATE_SAME_ACCESS, HANDLE, INVALID_HANDLE_VALUE,
|
||||
},
|
||||
Win32::System::Threading::{
|
||||
GetCurrentProcess, RegisterWaitForSingleObject, UnregisterWaitEx, WT_EXECUTEINWAITTHREAD,
|
||||
WT_EXECUTEONLYONCE,
|
||||
GetCurrentProcess, RegisterWaitForSingleObject, UnregisterWaitEx, INFINITE,
|
||||
WT_EXECUTEINWAITTHREAD, WT_EXECUTEONLYONCE,
|
||||
},
|
||||
Win32::System::WindowsProgramming::INFINITE,
|
||||
};
|
||||
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
|
||||
@@ -371,7 +371,9 @@ impl Spawner {
|
||||
task.name = %name.unwrap_or_default(),
|
||||
task.id = id.as_u64(),
|
||||
"fn" = %std::any::type_name::<F>(),
|
||||
spawn.location = %format_args!("{}:{}:{}", location.file(), location.line(), location.column()),
|
||||
loc.file = location.file(),
|
||||
loc.line = location.line(),
|
||||
loc.col = location.column(),
|
||||
);
|
||||
fut.instrument(span)
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::runtime::handle::Handle;
|
||||
use crate::runtime::{blocking, driver, Callback, Runtime};
|
||||
use crate::runtime::{blocking, driver, Callback, HistogramBuilder, Runtime};
|
||||
use crate::util::rand::{RngSeed, RngSeedGenerator};
|
||||
|
||||
use std::fmt;
|
||||
@@ -95,6 +95,12 @@ pub struct Builder {
|
||||
/// Specify a random number generator seed to provide deterministic results
|
||||
pub(super) seed_generator: RngSeedGenerator,
|
||||
|
||||
/// When true, enables task poll count histogram instrumentation.
|
||||
pub(super) metrics_poll_count_histogram_enable: bool,
|
||||
|
||||
/// Configures the task poll count histogram
|
||||
pub(super) metrics_poll_count_histogram: HistogramBuilder,
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
pub(super) unhandled_panic: UnhandledPanic,
|
||||
}
|
||||
@@ -268,6 +274,10 @@ impl Builder {
|
||||
#[cfg(tokio_unstable)]
|
||||
unhandled_panic: UnhandledPanic::Ignore,
|
||||
|
||||
metrics_poll_count_histogram_enable: false,
|
||||
|
||||
metrics_poll_count_histogram: Default::default(),
|
||||
|
||||
disable_lifo_slot: false,
|
||||
}
|
||||
}
|
||||
@@ -877,6 +887,133 @@ impl Builder {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_metrics! {
|
||||
/// Enables tracking the distribution of task poll times.
|
||||
///
|
||||
/// Task poll times are not instrumented by default as doing so requires
|
||||
/// calling [`Instant::now()`] twice per task poll, which could add
|
||||
/// measurable overhead. Use the [`Handle::metrics()`] to access the
|
||||
/// metrics data.
|
||||
///
|
||||
/// The histogram uses fixed bucket sizes. In other words, the histogram
|
||||
/// buckets are not dynamic based on input values. Use the
|
||||
/// `metrics_poll_count_histogram_` builder methods to configure the
|
||||
/// histogram details.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::runtime;
|
||||
///
|
||||
/// let rt = runtime::Builder::new_multi_thread()
|
||||
/// .enable_metrics_poll_count_histogram()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
/// # // Test default values here
|
||||
/// # fn us(n: u64) -> std::time::Duration { std::time::Duration::from_micros(n) }
|
||||
/// # let m = rt.handle().metrics();
|
||||
/// # assert_eq!(m.poll_count_histogram_num_buckets(), 10);
|
||||
/// # assert_eq!(m.poll_count_histogram_bucket_range(0), us(0)..us(100));
|
||||
/// # assert_eq!(m.poll_count_histogram_bucket_range(1), us(100)..us(200));
|
||||
/// ```
|
||||
///
|
||||
/// [`Handle::metrics()`]: crate::runtime::Handle::metrics
|
||||
/// [`Instant::now()`]: std::time::Instant::now
|
||||
pub fn enable_metrics_poll_count_histogram(&mut self) -> &mut Self {
|
||||
self.metrics_poll_count_histogram_enable = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the histogram scale for tracking the distribution of task poll
|
||||
/// times.
|
||||
///
|
||||
/// Tracking the distribution of task poll times can be done using a
|
||||
/// linear or log scale. When using linear scale, each histogram bucket
|
||||
/// will represent the same range of poll times. When using log scale,
|
||||
/// each histogram bucket will cover a range twice as big as the
|
||||
/// previous bucket.
|
||||
///
|
||||
/// **Default:** linear scale.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::runtime::{self, HistogramScale};
|
||||
///
|
||||
/// let rt = runtime::Builder::new_multi_thread()
|
||||
/// .enable_metrics_poll_count_histogram()
|
||||
/// .metrics_poll_count_histogram_scale(HistogramScale::Log)
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
pub fn metrics_poll_count_histogram_scale(&mut self, histogram_scale: crate::runtime::HistogramScale) -> &mut Self {
|
||||
self.metrics_poll_count_histogram.scale = histogram_scale;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the histogram resolution for tracking the distribution of task
|
||||
/// poll times.
|
||||
///
|
||||
/// The resolution is the histogram's first bucket's range. When using a
|
||||
/// linear histogram scale, each bucket will cover the same range. When
|
||||
/// using a log scale, each bucket will cover a range twice as big as
|
||||
/// the previous bucket. In the log case, the resolution represents the
|
||||
/// smallest bucket range.
|
||||
///
|
||||
/// Note that, when using log scale, the resolution is rounded up to the
|
||||
/// nearest power of 2 in nanoseconds.
|
||||
///
|
||||
/// **Default:** 100 microseconds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::runtime;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let rt = runtime::Builder::new_multi_thread()
|
||||
/// .enable_metrics_poll_count_histogram()
|
||||
/// .metrics_poll_count_histogram_resolution(Duration::from_micros(100))
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
pub fn metrics_poll_count_histogram_resolution(&mut self, resolution: Duration) -> &mut Self {
|
||||
assert!(resolution > Duration::from_secs(0));
|
||||
// Sanity check the argument and also make the cast below safe.
|
||||
assert!(resolution <= Duration::from_secs(1));
|
||||
|
||||
let resolution = resolution.as_nanos() as u64;
|
||||
self.metrics_poll_count_histogram.resolution = resolution;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the number of buckets for the histogram tracking the
|
||||
/// distribution of task poll times.
|
||||
///
|
||||
/// The last bucket tracks all greater values that fall out of other
|
||||
/// ranges. So, configuring the histogram using a linear scale,
|
||||
/// resolution of 50ms, and 10 buckets, the 10th bucket will track task
|
||||
/// polls that take more than 450ms to complete.
|
||||
///
|
||||
/// **Default:** 10
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::runtime;
|
||||
///
|
||||
/// let rt = runtime::Builder::new_multi_thread()
|
||||
/// .enable_metrics_poll_count_histogram()
|
||||
/// .metrics_poll_count_histogram_buckets(15)
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
pub fn metrics_poll_count_histogram_buckets(&mut self, buckets: usize) -> &mut Self {
|
||||
self.metrics_poll_count_histogram.num_buckets = buckets;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn build_current_thread_runtime(&mut self) -> io::Result<Runtime> {
|
||||
use crate::runtime::scheduler::{self, CurrentThread};
|
||||
use crate::runtime::{runtime::Scheduler, Config};
|
||||
@@ -909,6 +1046,7 @@ impl Builder {
|
||||
unhandled_panic: self.unhandled_panic.clone(),
|
||||
disable_lifo_slot: self.disable_lifo_slot,
|
||||
seed_generator: seed_generator_1,
|
||||
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -922,6 +1060,14 @@ impl Builder {
|
||||
blocking_pool,
|
||||
))
|
||||
}
|
||||
|
||||
fn metrics_poll_count_histogram_builder(&self) -> Option<HistogramBuilder> {
|
||||
if self.metrics_poll_count_histogram_enable {
|
||||
Some(self.metrics_poll_count_histogram.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_io_driver! {
|
||||
@@ -1050,6 +1196,7 @@ cfg_rt_multi_thread! {
|
||||
unhandled_panic: self.unhandled_panic.clone(),
|
||||
disable_lifo_slot: self.disable_lifo_slot,
|
||||
seed_generator: seed_generator_1,
|
||||
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ pub(crate) struct Config {
|
||||
/// deterministic way.
|
||||
pub(crate) seed_generator: RngSeedGenerator,
|
||||
|
||||
/// How to build poll time histograms
|
||||
pub(crate) metrics_poll_count_histogram: Option<crate::runtime::HistogramBuilder>,
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
/// How to respond to unhandled task panics.
|
||||
pub(crate) unhandled_panic: crate::runtime::UnhandledPanic,
|
||||
|
||||
@@ -12,6 +12,10 @@ cfg_rt! {
|
||||
use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
|
||||
cfg_taskdump! {
|
||||
use crate::runtime::task::trace;
|
||||
}
|
||||
}
|
||||
|
||||
struct Context {
|
||||
@@ -45,6 +49,15 @@ struct Context {
|
||||
/// Tracks the amount of "work" a task may still do before yielding back to
|
||||
/// the sheduler
|
||||
budget: Cell<coop::Budget>,
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
feature = "rt",
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
trace: trace::Context,
|
||||
}
|
||||
|
||||
tokio_thread_local! {
|
||||
@@ -75,6 +88,19 @@ tokio_thread_local! {
|
||||
rng: FastRand::new(RngSeed::new()),
|
||||
|
||||
budget: Cell::new(coop::Budget::unconstrained()),
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
feature = "rt",
|
||||
target_os = "linux",
|
||||
any(
|
||||
target_arch = "aarch64",
|
||||
target_arch = "x86",
|
||||
target_arch = "x86_64"
|
||||
)
|
||||
))]
|
||||
trace: trace::Context::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,6 +404,14 @@ cfg_rt! {
|
||||
matches!(self, EnterRuntime::Entered { .. })
|
||||
}
|
||||
}
|
||||
|
||||
cfg_taskdump! {
|
||||
/// SAFETY: Callers of this function must ensure that trace frames always
|
||||
/// form a valid linked list.
|
||||
pub(crate) unsafe fn with_trace<R>(f: impl FnOnce(&trace::Context) -> R) -> R {
|
||||
CONTEXT.with(|c| f(&c.trace))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forces the current "entered" state to be cleared while the closure
|
||||
|
||||
@@ -169,7 +169,7 @@ cfg_coop! {
|
||||
/// that the budget empties appropriately.
|
||||
///
|
||||
/// Note that `RestoreOnPending` restores the budget **as it was before `poll_proceed`**.
|
||||
/// Therefore, if the budget is _fCURRENT.withurther_ adjusted between when `poll_proceed` returns and
|
||||
/// Therefore, if the budget is _further_ adjusted between when `poll_proceed` returns and
|
||||
/// `RestRestoreOnPending` is dropped, those adjustments are erased unless the caller indicates
|
||||
/// that progress was made.
|
||||
#[inline]
|
||||
|
||||
@@ -11,8 +11,14 @@ impl Defer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn defer(&mut self, waker: Waker) {
|
||||
self.deferred.push(waker);
|
||||
pub(crate) fn defer(&mut self, waker: &Waker) {
|
||||
// If the same task adds itself a bunch of times, then only add it once.
|
||||
if let Some(last) = self.deferred.last() {
|
||||
if last.will_wake(waker) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.deferred.push(waker.clone());
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
@@ -24,4 +30,9 @@ impl Defer {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(tokio_taskdump)]
|
||||
pub(crate) fn take_deferred(&mut self) -> Vec<Waker> {
|
||||
std::mem::take(&mut self.deferred)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//! Snapshots of runtime state.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// A snapshot of a runtime's state.
|
||||
#[derive(Debug)]
|
||||
pub struct Dump {
|
||||
tasks: Tasks,
|
||||
}
|
||||
|
||||
/// Snapshots of tasks.
|
||||
#[derive(Debug)]
|
||||
pub struct Tasks {
|
||||
tasks: Vec<Task>,
|
||||
}
|
||||
|
||||
/// A snapshot of a task.
|
||||
#[derive(Debug)]
|
||||
pub struct Task {
|
||||
trace: Trace,
|
||||
}
|
||||
|
||||
/// An execution trace of a task's last poll.
|
||||
#[derive(Debug)]
|
||||
pub struct Trace {
|
||||
inner: super::task::trace::Trace,
|
||||
}
|
||||
|
||||
impl Dump {
|
||||
pub(crate) fn new(tasks: Vec<Task>) -> Self {
|
||||
Self {
|
||||
tasks: Tasks { tasks },
|
||||
}
|
||||
}
|
||||
|
||||
/// Tasks in this snapshot.
|
||||
pub fn tasks(&self) -> &Tasks {
|
||||
&self.tasks
|
||||
}
|
||||
}
|
||||
|
||||
impl Tasks {
|
||||
/// Iterate over tasks.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Task> {
|
||||
self.tasks.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub(crate) fn new(trace: super::task::trace::Trace) -> Self {
|
||||
Self {
|
||||
trace: Trace { inner: trace },
|
||||
}
|
||||
}
|
||||
|
||||
/// A trace of this task's state.
|
||||
pub fn trace(&self) -> &Trace {
|
||||
&self.trace
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Trace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
@@ -252,6 +252,15 @@ impl Handle {
|
||||
/// [`tokio::time`]: crate::time
|
||||
#[track_caller]
|
||||
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
feature = "rt",
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
let future = super::task::trace::Trace::root(future);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let future =
|
||||
crate::util::trace::task(future, "block_on", None, super::task::Id::next().as_u64());
|
||||
@@ -274,6 +283,14 @@ impl Handle {
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
let id = crate::runtime::task::Id::next();
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
feature = "rt",
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
let future = super::task::trace::Trace::root(future);
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let future = crate::util::trace::task(future, "task", _name, id.as_u64());
|
||||
self.inner.spawn(future, id)
|
||||
@@ -321,6 +338,20 @@ cfg_metrics! {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_taskdump! {
|
||||
impl Handle {
|
||||
/// Capture a snapshot of this runtime's state.
|
||||
pub fn dump(&self) -> crate::runtime::Dump {
|
||||
match &self.inner {
|
||||
scheduler::Handle::CurrentThread(handle) => handle.dump(),
|
||||
#[cfg(all(feature = "rt-multi-thread", not(tokio_wasi)))]
|
||||
scheduler::Handle::MultiThread(_) =>
|
||||
unimplemented!("taskdumps are unsupported on the multi-thread runtime"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by `try_current` when no Runtime has been started
|
||||
#[derive(Debug)]
|
||||
pub struct TryCurrentError {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user