mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
165
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83d82f97eb | ||
|
|
1b1b9dc7e3 | ||
|
|
012c848401 | ||
|
|
48c55768fd | ||
|
|
657fd883d2 | ||
|
|
6b076a2743 | ||
|
|
910a1e2fcf | ||
|
|
6d25a00145 | ||
|
|
78bf8a9e5e | ||
|
|
b8af5aad16 | ||
|
|
2e62374e4a | ||
|
|
56c4365584 | ||
|
|
fb0d305a7a | ||
|
|
848482d2bb | ||
|
|
00af6eff77 | ||
|
|
b7290910f7 | ||
|
|
af6c87a045 | ||
|
|
6257712d68 | ||
|
|
c5d52c17ae | ||
|
|
2a54ad01d0 | ||
|
|
cb18b0a231 | ||
|
|
7ccd3e0c6d | ||
|
|
e63d0f10bf | ||
|
|
a2941e48be | ||
|
|
1c8d22c18b | ||
|
|
cbb3c155dd | ||
|
|
038c4d9999 | ||
|
|
7b24b22901 | ||
|
|
779b9c19d5 | ||
|
|
1204da7300 | ||
|
|
e75ca93d30 | ||
|
|
15712018da | ||
|
|
076d77c186 | ||
|
|
e2853c1b49 | ||
|
|
8f0103f6c5 | ||
|
|
fb4d43017d | ||
|
|
1e14ef0093 | ||
|
|
a8b6353535 | ||
|
|
c748f4965e | ||
|
|
a96dab1089 | ||
|
|
79a7e78c0d | ||
|
|
7c12e41d07 | ||
|
|
7a99f87df2 | ||
|
|
0b2c9b8bab | ||
|
|
98c8c38e96 | ||
|
|
080d52902f | ||
|
|
e87ff8a83a | ||
|
|
1605279abf | ||
|
|
25258d572a | ||
|
|
8ddb58bf6c | ||
|
|
4b032a25a4 | ||
|
|
edd172cd32 | ||
|
|
9877fa2a97 | ||
|
|
0f898a3148 | ||
|
|
d6a9ef5333 | ||
|
|
2a180188c6 | ||
|
|
9f9db7da63 | ||
|
|
d274ef3748 | ||
|
|
5e6d4c7999 | ||
|
|
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"
|
||||
|
||||
+175
-65
@@ -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
|
||||
@@ -42,7 +42,6 @@ jobs:
|
||||
- test-unstable
|
||||
- miri
|
||||
- asan
|
||||
- semver
|
||||
- cross-check
|
||||
- cross-test
|
||||
- no-atomic-u64
|
||||
@@ -56,13 +55,29 @@ jobs:
|
||||
- check-readme
|
||||
- test-hyper
|
||||
- x86_64-fortanix-unknown-sgx
|
||||
- check-redox
|
||||
- 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 }}
|
||||
@@ -191,8 +209,59 @@ jobs:
|
||||
# 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
|
||||
@@ -224,31 +294,29 @@ jobs:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: asan
|
||||
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1
|
||||
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1 --nocapture
|
||||
env:
|
||||
RUSTFLAGS: -Z sanitizer=address
|
||||
RUSTFLAGS: -Z sanitizer=address --cfg tokio_no_tuning_tests
|
||||
# Ignore `trybuild` errors as they are irrelevant and flaky on nightly
|
||||
TRYBUILD: overwrite
|
||||
|
||||
semver:
|
||||
name: semver
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
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
|
||||
# Re-enable this after the next release.
|
||||
#
|
||||
#semver:
|
||||
# name: semver
|
||||
# needs: basics
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - name: Check semver
|
||||
# uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
# with:
|
||||
# rust-toolchain: ${{ env.rust_stable }}
|
||||
# release-type: minor
|
||||
|
||||
cross-check:
|
||||
name: cross-check
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -273,14 +341,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
|
||||
@@ -297,18 +368,19 @@ jobs:
|
||||
# First run with all features (including parking_lot)
|
||||
- run: cross test -p tokio --all-features --target ${{ matrix.target }} --tests
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 ${{ matrix.rustflags }}
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
|
||||
# Now run without parking_lot
|
||||
- name: Remove `parking_lot` from `full` feature
|
||||
run: sed -i '0,/parking_lot/{/parking_lot/d;}' tokio/Cargo.toml
|
||||
# The `tokio_no_parking_lot` cfg is here to ensure the `sed` above does not silently break.
|
||||
- run: cross test -p tokio --features full,test-util --target ${{ matrix.target }} --tests
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 --cfg tokio_no_parking_lot ${{ matrix.rustflags }}
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 --cfg tokio_no_parking_lot --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
|
||||
|
||||
# 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
|
||||
@@ -323,20 +395,21 @@ jobs:
|
||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||
with:
|
||||
target: i686-unknown-linux-gnu
|
||||
- run: cargo test -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
|
||||
- run: cargo test -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features -- --test-threads 1 --nocapture
|
||||
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
|
||||
- run: cargo hack build -p tokio --feature-powerset --depth 2 --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -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
|
||||
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 --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
|
||||
@@ -349,12 +422,17 @@ jobs:
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
- name: check --feature-powerset
|
||||
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
run: cargo hack check --all --feature-powerset --depth 2 --keep-going
|
||||
# Try with unstable feature flags
|
||||
- name: check --feature-powerset --unstable
|
||||
run: cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||
run: cargo hack check --all --feature-powerset --depth 2 --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 --keep-going
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
|
||||
|
||||
minrust:
|
||||
name: minrust
|
||||
@@ -366,19 +444,6 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ env.rust_min }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# First compile just the main tokio crate with minrust and newest version
|
||||
# of all dependencies, then pin once_cell and compile the rest of the
|
||||
# crates with the pinned once_cell version.
|
||||
#
|
||||
# This is necessary because tokio-util transitively depends on once_cell,
|
||||
# which is not compatible with the current minrust after the 1.15.0
|
||||
# release.
|
||||
- name: "check -p tokio --all-features"
|
||||
run: cargo check -p tokio --all-features
|
||||
env:
|
||||
RUSTFLAGS: "" # remove -Dwarnings
|
||||
- name: "pin once_cell version"
|
||||
run: cargo update -p once_cell --precise 1.14.0
|
||||
- name: "check --workspace --all-features"
|
||||
run: cargo check --workspace --all-features
|
||||
env:
|
||||
@@ -386,6 +451,7 @@ jobs:
|
||||
|
||||
minimal-versions:
|
||||
name: minimal-versions
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -406,7 +472,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.
|
||||
@@ -430,8 +496,8 @@ jobs:
|
||||
- name: "rustfmt --check"
|
||||
# Workaround for rust-lang/cargo#7732
|
||||
run: |
|
||||
if ! rustfmt --check --edition 2018 $(git ls-files '*.rs'); then
|
||||
printf "Please run \`rustfmt --edition 2018 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
if ! rustfmt --check --edition 2021 $(git ls-files '*.rs'); then
|
||||
printf "Please run \`rustfmt --edition 2021 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -463,11 +529,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 +563,7 @@ jobs:
|
||||
|
||||
test-hyper:
|
||||
name: Test hyper
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -529,6 +597,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
|
||||
@@ -543,8 +612,24 @@ jobs:
|
||||
run: cargo build --target x86_64-fortanix-unknown-sgx --features rt,sync
|
||||
working-directory: tokio
|
||||
|
||||
check-redox:
|
||||
name: build tokio for redox-os
|
||||
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 }}
|
||||
target: x86_64-unknown-redox
|
||||
- name: check tokio on redox
|
||||
run: cargo check --target x86_64-unknown-redox --all-features
|
||||
working-directory: tokio
|
||||
|
||||
wasm32-unknown-unknown:
|
||||
name: test tokio for wasm32-unknown-unknown
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -561,6 +646,7 @@ jobs:
|
||||
|
||||
wasm32-wasi:
|
||||
name: wasm32-wasi
|
||||
needs: basics
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -607,25 +693,49 @@ jobs:
|
||||
working-directory: tests-integration
|
||||
|
||||
check-external-types:
|
||||
name: check-external-types
|
||||
name: check-external-types (${{ matrix.os }})
|
||||
needs: basics
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust nightly-2022-11-16
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
rust:
|
||||
# `check-external-types` requires a specific Rust nightly version. See
|
||||
# the README for details: https://github.com/awslabs/cargo-check-external-types
|
||||
toolchain: nightly-2022-11-16
|
||||
- nightly-2023-05-31
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v1
|
||||
with:
|
||||
tool: [email protected]
|
||||
- name: check-external-types
|
||||
run: |
|
||||
set -x
|
||||
cargo install cargo-check-external-types --locked --version 0.1.6
|
||||
cargo check-external-types --all-features --config external-types.toml
|
||||
run: 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: 2
|
||||
- scope: loom_pool::group_b
|
||||
max_preemptions: 2
|
||||
- scope: loom_pool::group_c
|
||||
max_preemptions: 2
|
||||
- scope: loom_pool::group_d
|
||||
max_preemptions: 2
|
||||
- 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
|
||||
|
||||
+29
-8
@@ -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
|
||||
@@ -170,10 +173,10 @@ command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2018 $(git ls-files '*.rs')
|
||||
rustfmt --check --edition 2021 $(git ls-files '*.rs')
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
@@ -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
|
||||
|
||||
@@ -213,7 +230,7 @@ integration tests in the crate and follow the style.
|
||||
|
||||
Some of our crates include a set of fuzz tests, this will be marked by a
|
||||
directory `fuzz`. It is a good idea to run fuzz tests after each change.
|
||||
To get started with fuzz testing you'll need to install
|
||||
To get started with fuzz testing you'll need to install
|
||||
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
|
||||
|
||||
`cargo install cargo-fuzz`
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
|
||||
resolver = "2"
|
||||
members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
|
||||
@@ -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.29.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
|
||||
|
||||
+10
-1
@@ -2,7 +2,7 @@
|
||||
name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
test-util = ["tokio/test-util"]
|
||||
@@ -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);
|
||||
@@ -6,13 +6,16 @@ use tokio::runtime::{self, Runtime};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn spawn_many(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const NUM_WORKERS: usize = 4;
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const STALL_DUR: Duration = Duration::from_micros(10);
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
@@ -38,6 +41,99 @@ 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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and yield. Yielding is a
|
||||
// lower notification priority than spawning / regular notification.
|
||||
fn spawn_many_remote_busy1(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
let flag = Arc::new(AtomicBool::new(true));
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(2 * NUM_WORKERS) {
|
||||
let flag = flag.clone();
|
||||
rt.spawn(async move {
|
||||
while flag.load(Relaxed) {
|
||||
tokio::task::yield_now().await;
|
||||
stall();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and spawn new high-CPU
|
||||
// tasks. Spawning goes via a higher notification priority than yielding.
|
||||
fn spawn_many_remote_busy2(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
let flag = Arc::new(AtomicBool::new(true));
|
||||
|
||||
// Spawn some tasks to keep the runtimes busy
|
||||
for _ in 0..(NUM_WORKERS) {
|
||||
let flag = flag.clone();
|
||||
fn iter(flag: Arc<AtomicBool>) {
|
||||
tokio::spawn(async {
|
||||
if flag.load(Relaxed) {
|
||||
stall();
|
||||
iter(flag);
|
||||
}
|
||||
});
|
||||
}
|
||||
rt.spawn(async {
|
||||
iter(flag);
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
fn yield_many(b: &mut Bencher) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS: usize = 200;
|
||||
@@ -140,12 +236,28 @@ 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,);
|
||||
fn stall() {
|
||||
let now = Instant::now();
|
||||
while now.elapsed() < STALL_DUR {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy1,
|
||||
spawn_many_remote_busy2,
|
||||
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);
|
||||
+6
-2
@@ -2,7 +2,7 @@
|
||||
name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead, and delete the **path**.
|
||||
@@ -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,90 @@
|
||||
//! This example demonstrates tokio's experimental task dumping functionality.
|
||||
//! This application deadlocks. Input CTRL+C to display traces of each task, or
|
||||
//! input CTRL+C twice within 1 second to quit.
|
||||
|
||||
#[cfg(all(
|
||||
tokio_unstable,
|
||||
tokio_taskdump,
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
|
||||
))]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Barrier;
|
||||
|
||||
#[inline(never)]
|
||||
async fn a(barrier: Arc<Barrier>) {
|
||||
b(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn b(barrier: Arc<Barrier>) {
|
||||
c(barrier).await
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
async fn c(barrier: Arc<Barrier>) {
|
||||
barrier.wait().await;
|
||||
}
|
||||
|
||||
// Prints a task dump upon receipt of CTRL+C, or returns if CTRL+C is
|
||||
// inputted twice within a second.
|
||||
async fn dump_or_quit() {
|
||||
use tokio::time::{timeout, Duration, Instant};
|
||||
let handle = tokio::runtime::Handle::current();
|
||||
let mut last_signal: Option<Instant> = None;
|
||||
// wait for CTRL+C
|
||||
while let Ok(_) = tokio::signal::ctrl_c().await {
|
||||
// exit if a CTRL+C is inputted twice within 1 second
|
||||
if let Some(time_since_last_signal) = last_signal.map(|i| i.elapsed()) {
|
||||
if time_since_last_signal < Duration::from_secs(1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
last_signal = Some(Instant::now());
|
||||
|
||||
// capture a dump, and print each trace
|
||||
println!("{:-<80}", "");
|
||||
if let Ok(dump) = timeout(Duration::from_secs(2), handle.dump()).await {
|
||||
for (i, task) in dump.tasks().iter().enumerate() {
|
||||
let trace = task.trace();
|
||||
println!("TASK {i}:");
|
||||
println!("{trace}\n");
|
||||
}
|
||||
} else {
|
||||
println!("Task dumping timed out. Use a native debugger (like gdb) to debug the deadlock.");
|
||||
}
|
||||
println!("{:-<80}", "");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
}
|
||||
}
|
||||
|
||||
println!("This program has a deadlock.");
|
||||
println!("Input CTRL+C to print a task dump.");
|
||||
println!("Input CTRL+C twice within 1 second to exit.");
|
||||
|
||||
// oops! this barrier waits for one more task than will ever come.
|
||||
let barrier = Arc::new(Barrier::new(3));
|
||||
|
||||
let task_1 = tokio::spawn(a(barrier.clone()));
|
||||
let task_2 = tokio::spawn(a(barrier));
|
||||
|
||||
tokio::select!(
|
||||
_ = dump_or_quit() => {},
|
||||
_ = task_1 => {},
|
||||
_ = task_2 => {},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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")
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "stress-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tests-build"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -7,7 +7,6 @@ use tokio::process::{Child, Command};
|
||||
use tokio_test::assert_ok;
|
||||
|
||||
use futures::future::{self, FutureExt};
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
|
||||
@@ -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"
|
||||
edition = "2018"
|
||||
rust-version = "1.49"
|
||||
version = "2.1.0"
|
||||
edition = "2021"
|
||||
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"] }
|
||||
|
||||
+171
-74
@@ -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,23 +340,24 @@ 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 {
|
||||
rt = quote! { #rt.worker_threads(#v) };
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) };
|
||||
}
|
||||
if let Some(v) = config.start_paused {
|
||||
rt = quote! { #rt.start_paused(#v) };
|
||||
rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) };
|
||||
}
|
||||
|
||||
let header = if is_test {
|
||||
@@ -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])
|
||||
|
||||
+15
-5
@@ -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"]
|
||||
@@ -27,8 +37,8 @@ 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"] }
|
||||
pin-project-lite = "0.2.7"
|
||||
tokio = { version = "1.15.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tokio-stream-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
@@ -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"
|
||||
@@ -34,13 +34,13 @@ rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
|
||||
__docs_rs = ["futures-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.22.0", path = "../tokio", features = ["sync"] }
|
||||
tokio = { version = "1.28.0", path = "../tokio", features = ["sync"] }
|
||||
bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
futures-io = { version = "0.3.0", optional = true }
|
||||
futures-util = { version = "0.3.0", optional = true }
|
||||
pin-project-lite = "0.2.0"
|
||||
pin-project-lite = "0.2.7"
|
||||
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
|
||||
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
@@ -56,6 +56,7 @@ async-stream = "0.3.0"
|
||||
futures = "0.3.0"
|
||||
futures-test = "0.3.5"
|
||||
parking_lot = "0.12.0"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -227,6 +227,8 @@ impl<T: tokio::io::AsyncSeek> futures_io::AsyncSeek for Compat<T> {
|
||||
pos: io::SeekFrom,
|
||||
) -> Poll<io::Result<u64>> {
|
||||
if self.seek_pos != Some(pos) {
|
||||
// Ensure previous seeks have finished before starting a new one
|
||||
ready!(self.as_mut().project().inner.poll_complete(cx))?;
|
||||
self.as_mut().project().inner.start_seek(pos)?;
|
||||
*self.as_mut().project().seek_pos = Some(pos);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,42 @@ impl<R: AsyncRead, F: FnMut(&[u8])> AsyncRead for InspectReader<R, F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncWrite, F> AsyncWrite for InspectReader<R, F> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::result::Result<usize, std::io::Error>> {
|
||||
self.project().reader.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), std::io::Error>> {
|
||||
self.project().reader.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<std::result::Result<(), std::io::Error>> {
|
||||
self.project().reader.poll_shutdown(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<Result<usize>> {
|
||||
self.project().reader.poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.reader.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// An adapter that lets you inspect the data that's being written.
|
||||
///
|
||||
@@ -132,3 +168,13 @@ impl<W: AsyncWrite, F: FnMut(&[u8])> AsyncWrite for InspectWriter<W, F> {
|
||||
self.writer.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: AsyncRead, F> AsyncRead for InspectWriter<W, F> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
self.project().writer.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ pub mod sync;
|
||||
|
||||
pub mod either;
|
||||
|
||||
pub use bytes;
|
||||
|
||||
#[cfg(any(feature = "io", feature = "codec"))]
|
||||
mod util {
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,6 +316,60 @@ where
|
||||
self.insert(key, task);
|
||||
}
|
||||
|
||||
/// Spawn the blocking code on the blocking threadpool and store it in this `JoinMap` with the provided
|
||||
/// key.
|
||||
///
|
||||
/// If a task previously existed in the `JoinMap` for this key, that task
|
||||
/// will be cancelled and replaced with the new one. The previous task will
|
||||
/// be removed from the `JoinMap`; a subsequent call to [`join_next`] will
|
||||
/// *not* return a cancelled [`JoinError`] for that task.
|
||||
///
|
||||
/// Note that blocking tasks cannot be cancelled after execution starts.
|
||||
/// Replaced blocking tasks will still run to completion if the task has begun
|
||||
/// to execute when it is replaced. A blocking task which is replaced before
|
||||
/// it has been scheduled on a blocking worker thread will be cancelled.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if called outside of a Tokio runtime.
|
||||
///
|
||||
/// [`join_next`]: Self::join_next
|
||||
#[track_caller]
|
||||
pub fn spawn_blocking<F>(&mut self, key: K, f: F)
|
||||
where
|
||||
F: FnOnce() -> V,
|
||||
F: Send + 'static,
|
||||
V: Send,
|
||||
{
|
||||
let task = self.tasks.spawn_blocking(f);
|
||||
self.insert(key, task)
|
||||
}
|
||||
|
||||
/// Spawn the blocking code on the blocking threadpool of the provided runtime and store it in this
|
||||
/// `JoinMap` with the provided key.
|
||||
///
|
||||
/// If a task previously existed in the `JoinMap` for this key, that task
|
||||
/// will be cancelled and replaced with the new one. The previous task will
|
||||
/// be removed from the `JoinMap`; a subsequent call to [`join_next`] will
|
||||
/// *not* return a cancelled [`JoinError`] for that task.
|
||||
///
|
||||
/// Note that blocking tasks cannot be cancelled after execution starts.
|
||||
/// Replaced blocking tasks will still run to completion if the task has begun
|
||||
/// to execute when it is replaced. A blocking task which is replaced before
|
||||
/// it has been scheduled on a blocking worker thread will be cancelled.
|
||||
///
|
||||
/// [`join_next`]: Self::join_next
|
||||
#[track_caller]
|
||||
pub fn spawn_blocking_on<F>(&mut self, key: K, f: F, handle: &Handle)
|
||||
where
|
||||
F: FnOnce() -> V,
|
||||
F: Send + 'static,
|
||||
V: Send,
|
||||
{
|
||||
let task = self.tasks.spawn_blocking_on(f, handle);
|
||||
self.insert(key, task);
|
||||
}
|
||||
|
||||
/// Spawn the provided task on the current [`LocalSet`] and store it in this
|
||||
/// `JoinMap` with the provided key.
|
||||
///
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#![cfg(all(feature = "compat"))]
|
||||
#![cfg(not(target_os = "wasi"))] // WASI does not support all fs operations
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures_io::SeekFrom;
|
||||
use futures_util::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::fs::OpenOptions;
|
||||
use tokio_util::compat::TokioAsyncWriteCompatExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn compat_file_seek() -> futures_util::io::Result<()> {
|
||||
let temp_file = NamedTempFile::new()?;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(temp_file)
|
||||
.await?
|
||||
.compat_write();
|
||||
|
||||
file.write_all(&[0, 1, 2, 3, 4, 5]).await?;
|
||||
file.write_all(&[6, 7]).await?;
|
||||
|
||||
assert_eq!(file.stream_position().await?, 8);
|
||||
|
||||
// Modify elements at position 2.
|
||||
assert_eq!(file.seek(SeekFrom::Start(2)).await?, 2);
|
||||
file.write_all(&[8, 9]).await?;
|
||||
|
||||
file.flush().await?;
|
||||
|
||||
// Verify we still have 8 elements.
|
||||
assert_eq!(file.seek(SeekFrom::End(0)).await?, 8);
|
||||
// Seek back to the start of the file to read and verify contents.
|
||||
file.seek(SeekFrom::Start(0)).await?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let num_bytes = file.read_to_end(&mut buf).await?;
|
||||
assert_eq!(&buf[..num_bytes], &[0, 1, 8, 9, 4, 5, 6, 7]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -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,189 @@
|
||||
# 1.29.1 (June 29, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- rt: fix nesting two `block_in_place` with a `block_on` between ([#5837])
|
||||
|
||||
[#5837]: https://github.com/tokio-rs/tokio/pull/5837
|
||||
|
||||
# 1.29.0 (June 27, 2023)
|
||||
|
||||
Technically a breaking change, the `Send` implementation is removed from
|
||||
`runtime::EnterGuard`. This change fixes a bug and should not impact most users.
|
||||
|
||||
### Breaking
|
||||
|
||||
- rt: `EnterGuard` should not be `Send` ([#5766])
|
||||
|
||||
### Fixed
|
||||
|
||||
- fs: reduce blocking ops in `fs::read_dir` ([#5653])
|
||||
- rt: fix possible starvation ([#5686], [#5712])
|
||||
- rt: fix stacked borrows issue in `JoinSet` ([#5693])
|
||||
- rt: panic if `EnterGuard` dropped incorrect order ([#5772])
|
||||
- time: do not overflow to signal value ([#5710])
|
||||
- fs: wait for in-flight ops before cloning `File` ([#5803])
|
||||
|
||||
### Changed
|
||||
|
||||
- rt: reduce time to poll tasks scheduled from outside the runtime ([#5705], [#5720])
|
||||
|
||||
### Added
|
||||
|
||||
- net: add uds doc alias for unix sockets ([#5659])
|
||||
- rt: add metric for number of tasks ([#5628])
|
||||
- sync: implement more traits for channel errors ([#5666])
|
||||
- net: add nodelay methods on TcpSocket ([#5672])
|
||||
- sync: add `broadcast::Receiver::blocking_recv` ([#5690])
|
||||
- process: add `raw_arg` method to `Command` ([#5704])
|
||||
- io: support PRIORITY epoll events ([#5566])
|
||||
- task: add `JoinSet::poll_join_next` ([#5721])
|
||||
- net: add support for Redox OS ([#5790])
|
||||
|
||||
|
||||
### Unstable
|
||||
|
||||
- rt: add the ability to dump task backtraces ([#5608], [#5676], [#5708], [#5717])
|
||||
- rt: instrument task poll times with a histogram ([#5685])
|
||||
|
||||
[#5766]: https://github.com/tokio-rs/tokio/pull/5766
|
||||
[#5653]: https://github.com/tokio-rs/tokio/pull/5653
|
||||
[#5686]: https://github.com/tokio-rs/tokio/pull/5686
|
||||
[#5712]: https://github.com/tokio-rs/tokio/pull/5712
|
||||
[#5693]: https://github.com/tokio-rs/tokio/pull/5693
|
||||
[#5772]: https://github.com/tokio-rs/tokio/pull/5772
|
||||
[#5710]: https://github.com/tokio-rs/tokio/pull/5710
|
||||
[#5803]: https://github.com/tokio-rs/tokio/pull/5803
|
||||
[#5705]: https://github.com/tokio-rs/tokio/pull/5705
|
||||
[#5720]: https://github.com/tokio-rs/tokio/pull/5720
|
||||
[#5659]: https://github.com/tokio-rs/tokio/pull/5659
|
||||
[#5628]: https://github.com/tokio-rs/tokio/pull/5628
|
||||
[#5666]: https://github.com/tokio-rs/tokio/pull/5666
|
||||
[#5672]: https://github.com/tokio-rs/tokio/pull/5672
|
||||
[#5690]: https://github.com/tokio-rs/tokio/pull/5690
|
||||
[#5704]: https://github.com/tokio-rs/tokio/pull/5704
|
||||
[#5566]: https://github.com/tokio-rs/tokio/pull/5566
|
||||
[#5721]: https://github.com/tokio-rs/tokio/pull/5721
|
||||
[#5790]: https://github.com/tokio-rs/tokio/pull/5790
|
||||
[#5608]: https://github.com/tokio-rs/tokio/pull/5608
|
||||
[#5676]: https://github.com/tokio-rs/tokio/pull/5676
|
||||
[#5708]: https://github.com/tokio-rs/tokio/pull/5708
|
||||
[#5717]: https://github.com/tokio-rs/tokio/pull/5717
|
||||
[#5685]: https://github.com/tokio-rs/tokio/pull/5685
|
||||
|
||||
# 1.28.2 (May 28, 2023)
|
||||
|
||||
Forward ports 1.18.6 changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: disable default features for mio ([#5728])
|
||||
|
||||
[#5728]: https://github.com/tokio-rs/tokio/pull/5728
|
||||
|
||||
# 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
|
||||
@@ -81,6 +267,16 @@
|
||||
[#5513]: https://github.com/tokio-rs/tokio/pull/5513
|
||||
[#5517]: https://github.com/tokio-rs/tokio/pull/5517
|
||||
|
||||
# 1.25.1 (May 28, 2023)
|
||||
|
||||
Forward ports 1.18.6 changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: disable default features for mio ([#5728])
|
||||
|
||||
[#5728]: https://github.com/tokio-rs/tokio/pull/5728
|
||||
|
||||
# 1.25.0 (January 28, 2023)
|
||||
|
||||
### Fixed
|
||||
@@ -417,6 +613,16 @@ wasm32-wasi target is given unstable support for the `net` feature.
|
||||
[#4956]: https://github.com/tokio-rs/tokio/pull/4956
|
||||
[#4959]: https://github.com/tokio-rs/tokio/pull/4959
|
||||
|
||||
# 1.20.5 (May 28, 2023)
|
||||
|
||||
Forward ports 1.18.6 changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: disable default features for mio ([#5728])
|
||||
|
||||
[#5728]: https://github.com/tokio-rs/tokio/pull/5728
|
||||
|
||||
# 1.20.4 (January 17, 2023)
|
||||
|
||||
Forward ports 1.18.5 changes.
|
||||
@@ -563,6 +769,14 @@ This release fixes a bug in `Notified::enable`. ([#4747])
|
||||
[#4729]: https://github.com/tokio-rs/tokio/pull/4729
|
||||
[#4739]: https://github.com/tokio-rs/tokio/pull/4739
|
||||
|
||||
# 1.18.6 (May 28, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- deps: disable default features for mio ([#5728])
|
||||
|
||||
[#5728]: https://github.com/tokio-rs/tokio/pull/5728
|
||||
|
||||
# 1.18.5 (January 17, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
+19
-18
@@ -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.29.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,47 +97,48 @@ 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"
|
||||
pin-project-lite = "0.2.7"
|
||||
|
||||
# Everything else is optional...
|
||||
bytes = { version = "1.0.0", optional = true }
|
||||
memchr = { version = "2.2", optional = true }
|
||||
mio = { version = "0.8.4", optional = true }
|
||||
mio = { version = "0.8.6", optional = true, default-features = false }
|
||||
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 }
|
||||
libc = { version = "0.2.145", optional = true }
|
||||
signal-hook-registry = { version = "1.1.1", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
libc = { version = "0.2.42" }
|
||||
libc = { version = "0.2.145" }
|
||||
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"
|
||||
[target.'cfg(windows)'.dev-dependencies.windows-sys]
|
||||
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.29.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:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "tokio-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
+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<'_>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+52
-12
@@ -399,6 +399,7 @@ impl File {
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn try_clone(&self) -> io::Result<File> {
|
||||
self.inner.lock().await.complete_inflight().await;
|
||||
let std = self.std.clone();
|
||||
let std_file = asyncify(move || std.try_clone()).await?;
|
||||
Ok(File::from_std(std_file))
|
||||
@@ -498,6 +499,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 +596,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 +632,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 +699,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 +731,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 +747,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 +780,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.
|
||||
|
||||
+30
-25
@@ -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 {
|
||||
@@ -142,7 +139,9 @@ impl ReadDir {
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku",
|
||||
target_os = "vxworks"
|
||||
target_os = "vxworks",
|
||||
target_os = "nto",
|
||||
target_os = "vita",
|
||||
)))]
|
||||
file_type: std.file_type().ok(),
|
||||
std: Arc::new(std),
|
||||
@@ -152,6 +151,8 @@ impl ReadDir {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +202,9 @@ pub struct DirEntry {
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku",
|
||||
target_os = "vxworks"
|
||||
target_os = "vxworks",
|
||||
target_os = "nto",
|
||||
target_os = "vita",
|
||||
)))]
|
||||
file_type: Option<FileType>,
|
||||
std: Arc<std::fs::DirEntry>,
|
||||
@@ -332,7 +335,9 @@ impl DirEntry {
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "haiku",
|
||||
target_os = "vxworks"
|
||||
target_os = "vxworks",
|
||||
target_os = "nto",
|
||||
target_os = "vita",
|
||||
)))]
|
||||
if let Some(file_type) = self.file_type {
|
||||
return Ok(file_type);
|
||||
|
||||
@@ -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();
|
||||
|
||||
+563
-20
@@ -1,4 +1,4 @@
|
||||
use crate::io::Interest;
|
||||
use crate::io::{Interest, Ready};
|
||||
use crate::runtime::io::{ReadyEvent, Registration};
|
||||
use crate::runtime::scheduler;
|
||||
|
||||
@@ -201,13 +201,14 @@ pub struct AsyncFdReadyMutGuard<'a, T: AsRawFd> {
|
||||
event: Option<ReadyEvent>,
|
||||
}
|
||||
|
||||
const ALL_INTEREST: Interest = Interest::READABLE.add(Interest::WRITABLE);
|
||||
|
||||
impl<T: AsRawFd> AsyncFd<T> {
|
||||
/// Creates an AsyncFd backed by (and taking ownership of) an object
|
||||
/// implementing [`AsRawFd`]. The backing file descriptor is cached at the
|
||||
/// time of creation.
|
||||
///
|
||||
/// Only configures the [`Interest::READABLE`] and [`Interest::WRITABLE`] interests. For more
|
||||
/// control, use [`AsyncFd::with_interest`].
|
||||
///
|
||||
/// This method must be called in the context of a tokio runtime.
|
||||
///
|
||||
/// # Panics
|
||||
@@ -220,11 +221,12 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
where
|
||||
T: AsRawFd,
|
||||
{
|
||||
Self::with_interest(inner, ALL_INTEREST)
|
||||
Self::with_interest(inner, Interest::READABLE | Interest::WRITABLE)
|
||||
}
|
||||
|
||||
/// Creates new instance as `new` with additional ability to customize interest,
|
||||
/// allowing to specify whether file descriptor will be polled for read, write or both.
|
||||
/// Creates an AsyncFd backed by (and taking ownership of) an object
|
||||
/// implementing [`AsRawFd`], with a specific [`Interest`]. The backing
|
||||
/// file descriptor is cached at the time of creation.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -440,7 +442,96 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
.into()
|
||||
}
|
||||
|
||||
async fn readiness(&self, interest: Interest) -> io::Result<AsyncFdReadyGuard<'_, T>> {
|
||||
/// Waits for any of the requested ready states, returning a
|
||||
/// [`AsyncFdReadyGuard`] that must be dropped to resume
|
||||
/// polling for the requested ready states.
|
||||
///
|
||||
/// The function may complete without the file descriptor 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.
|
||||
///
|
||||
/// When an IO operation does return `io::ErrorKind::WouldBlock`, the readiness must be cleared.
|
||||
/// When a combined interest is used, it is important to clear only the readiness
|
||||
/// that is actually observed to block. For instance when the combined
|
||||
/// interest `Interest::READABLE | Interest::WRITABLE` is used, and a read blocks, only
|
||||
/// read readiness should be cleared using the [`AsyncFdReadyGuard::clear_ready_matching`] method:
|
||||
/// `guard.clear_ready_matching(Ready::READABLE)`.
|
||||
/// Also clearing the write readiness in this case would be incorrect. The [`AsyncFdReadyGuard::clear_ready`]
|
||||
/// method clears all readiness flags.
|
||||
///
|
||||
/// This method takes `&self`, so it is possible to call this method
|
||||
/// concurrently with other methods on this struct. This method only
|
||||
/// provides shared access to the inner IO resource when handling the
|
||||
/// [`AsyncFdReadyGuard`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Concurrently read and write to a [`std::net::TcpStream`] on the same task without
|
||||
/// splitting.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use std::error::Error;
|
||||
/// use std::io;
|
||||
/// use std::io::{Read, Write};
|
||||
/// use std::net::TcpStream;
|
||||
/// use tokio::io::unix::AsyncFd;
|
||||
/// use tokio::io::{Interest, Ready};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let stream = TcpStream::connect("127.0.0.1:8080")?;
|
||||
/// stream.set_nonblocking(true)?;
|
||||
/// let stream = AsyncFd::new(stream)?;
|
||||
///
|
||||
/// loop {
|
||||
/// let mut guard = stream
|
||||
/// .ready(Interest::READABLE | Interest::WRITABLE)
|
||||
/// .await?;
|
||||
///
|
||||
/// if guard.ready().is_readable() {
|
||||
/// let mut data = vec![0; 1024];
|
||||
/// // Try to read data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match stream.get_ref().read(&mut data) {
|
||||
/// Ok(n) => {
|
||||
/// println!("read {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// // a read has blocked, but a write might still succeed.
|
||||
/// // clear only the read readiness.
|
||||
/// guard.clear_ready_matching(Ready::READABLE);
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// if guard.ready().is_writable() {
|
||||
/// // Try to write data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match stream.get_ref().write(b"hello world") {
|
||||
/// Ok(n) => {
|
||||
/// println!("write {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// // a write has blocked, but a read might still succeed.
|
||||
/// // clear only the write readiness.
|
||||
/// guard.clear_ready_matching(Ready::WRITABLE);
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn ready(&self, interest: Interest) -> io::Result<AsyncFdReadyGuard<'_, T>> {
|
||||
let event = self.registration.readiness(interest).await?;
|
||||
|
||||
Ok(AsyncFdReadyGuard {
|
||||
@@ -449,7 +540,94 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
})
|
||||
}
|
||||
|
||||
async fn readiness_mut(
|
||||
/// Waits for any of the requested ready states, returning a
|
||||
/// [`AsyncFdReadyMutGuard`] that must be dropped to resume
|
||||
/// polling for the requested ready states.
|
||||
///
|
||||
/// The function may complete without the file descriptor 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.
|
||||
///
|
||||
/// When an IO operation does return `io::ErrorKind::WouldBlock`, the readiness must be cleared.
|
||||
/// When a combined interest is used, it is important to clear only the readiness
|
||||
/// that is actually observed to block. For instance when the combined
|
||||
/// interest `Interest::READABLE | Interest::WRITABLE` is used, and a read blocks, only
|
||||
/// read readiness should be cleared using the [`AsyncFdReadyMutGuard::clear_ready_matching`] method:
|
||||
/// `guard.clear_ready_matching(Ready::READABLE)`.
|
||||
/// Also clearing the write readiness in this case would be incorrect.
|
||||
/// The [`AsyncFdReadyMutGuard::clear_ready`] method clears all readiness flags.
|
||||
///
|
||||
/// This method takes `&mut self`, so it is possible to access the inner IO
|
||||
/// resource mutably when handling the [`AsyncFdReadyMutGuard`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Concurrently read and write to a [`std::net::TcpStream`] on the same task without
|
||||
/// splitting.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use std::error::Error;
|
||||
/// use std::io;
|
||||
/// use std::io::{Read, Write};
|
||||
/// use std::net::TcpStream;
|
||||
/// use tokio::io::unix::AsyncFd;
|
||||
/// use tokio::io::{Interest, Ready};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let stream = TcpStream::connect("127.0.0.1:8080")?;
|
||||
/// stream.set_nonblocking(true)?;
|
||||
/// let mut stream = AsyncFd::new(stream)?;
|
||||
///
|
||||
/// loop {
|
||||
/// let mut guard = stream
|
||||
/// .ready_mut(Interest::READABLE | Interest::WRITABLE)
|
||||
/// .await?;
|
||||
///
|
||||
/// if guard.ready().is_readable() {
|
||||
/// let mut data = vec![0; 1024];
|
||||
/// // Try to read data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match guard.get_inner_mut().read(&mut data) {
|
||||
/// Ok(n) => {
|
||||
/// println!("read {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// // a read has blocked, but a write might still succeed.
|
||||
/// // clear only the read readiness.
|
||||
/// guard.clear_ready_matching(Ready::READABLE);
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// if guard.ready().is_writable() {
|
||||
/// // Try to write data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match guard.get_inner_mut().write(b"hello world") {
|
||||
/// Ok(n) => {
|
||||
/// println!("write {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// // a write has blocked, but a read might still succeed.
|
||||
/// // clear only the write readiness.
|
||||
/// guard.clear_ready_matching(Ready::WRITABLE);
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn ready_mut(
|
||||
&mut self,
|
||||
interest: Interest,
|
||||
) -> io::Result<AsyncFdReadyMutGuard<'_, T>> {
|
||||
@@ -471,7 +649,7 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
/// [`AsyncFdReadyGuard`].
|
||||
#[allow(clippy::needless_lifetimes)] // The lifetime improves rustdoc rendering.
|
||||
pub async fn readable<'a>(&'a self) -> io::Result<AsyncFdReadyGuard<'a, T>> {
|
||||
self.readiness(Interest::READABLE).await
|
||||
self.ready(Interest::READABLE).await
|
||||
}
|
||||
|
||||
/// Waits for the file descriptor to become readable, returning a
|
||||
@@ -482,7 +660,7 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
/// resource mutably when handling the [`AsyncFdReadyMutGuard`].
|
||||
#[allow(clippy::needless_lifetimes)] // The lifetime improves rustdoc rendering.
|
||||
pub async fn readable_mut<'a>(&'a mut self) -> io::Result<AsyncFdReadyMutGuard<'a, T>> {
|
||||
self.readiness_mut(Interest::READABLE).await
|
||||
self.ready_mut(Interest::READABLE).await
|
||||
}
|
||||
|
||||
/// Waits for the file descriptor to become writable, returning a
|
||||
@@ -495,7 +673,7 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
/// [`AsyncFdReadyGuard`].
|
||||
#[allow(clippy::needless_lifetimes)] // The lifetime improves rustdoc rendering.
|
||||
pub async fn writable<'a>(&'a self) -> io::Result<AsyncFdReadyGuard<'a, T>> {
|
||||
self.readiness(Interest::WRITABLE).await
|
||||
self.ready(Interest::WRITABLE).await
|
||||
}
|
||||
|
||||
/// Waits for the file descriptor to become writable, returning a
|
||||
@@ -506,7 +684,110 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
/// resource mutably when handling the [`AsyncFdReadyMutGuard`].
|
||||
#[allow(clippy::needless_lifetimes)] // The lifetime improves rustdoc rendering.
|
||||
pub async fn writable_mut<'a>(&'a mut self) -> io::Result<AsyncFdReadyMutGuard<'a, T>> {
|
||||
self.readiness_mut(Interest::WRITABLE).await
|
||||
self.ready_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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +797,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")
|
||||
@@ -531,22 +819,117 @@ impl<T: AsRawFd> Drop for AsyncFd<T> {
|
||||
}
|
||||
|
||||
impl<'a, Inner: AsRawFd> AsyncFdReadyGuard<'a, Inner> {
|
||||
/// Indicates to tokio that the file descriptor is no longer ready. The
|
||||
/// internal readiness flag will be cleared, and tokio will wait for the
|
||||
/// Indicates to tokio that the file descriptor is no longer ready. All
|
||||
/// internal readiness flags will be cleared, and tokio will wait for the
|
||||
/// next edge-triggered readiness notification from the OS.
|
||||
///
|
||||
/// This function is commonly used with guards returned by [`AsyncFd::readable`] and
|
||||
/// [`AsyncFd::writable`].
|
||||
///
|
||||
/// It is critical that this function not be called unless your code
|
||||
/// _actually observes_ that the file descriptor is _not_ ready. Do not call
|
||||
/// it simply because, for example, a read succeeded; it should be called
|
||||
/// when a read is observed to block.
|
||||
///
|
||||
/// [`drop`]: method@std::mem::drop
|
||||
pub fn clear_ready(&mut self) {
|
||||
if let Some(event) = self.event.take() {
|
||||
self.async_fd.registration.clear_readiness(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates to tokio that the file descriptor no longer has a specific readiness.
|
||||
/// The internal readiness flag will be cleared, and tokio will wait for the
|
||||
/// next edge-triggered readiness notification from the OS.
|
||||
///
|
||||
/// This function is useful in combination with the [`AsyncFd::ready`] method when a
|
||||
/// combined interest like `Interest::READABLE | Interest::WRITABLE` is used.
|
||||
///
|
||||
/// It is critical that this function not be called unless your code
|
||||
/// _actually observes_ that the file descriptor is _not_ ready for the provided `Ready`.
|
||||
/// Do not call it simply because, for example, a read succeeded; it should be called
|
||||
/// when a read is observed to block. Only clear the specific readiness that is observed to
|
||||
/// block. For example when a read blocks when using a combined interest,
|
||||
/// only clear `Ready::READABLE`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Concurrently read and write to a [`std::net::TcpStream`] on the same task without
|
||||
/// splitting.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use std::error::Error;
|
||||
/// use std::io;
|
||||
/// use std::io::{Read, Write};
|
||||
/// use std::net::TcpStream;
|
||||
/// use tokio::io::unix::AsyncFd;
|
||||
/// use tokio::io::{Interest, Ready};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let stream = TcpStream::connect("127.0.0.1:8080")?;
|
||||
/// stream.set_nonblocking(true)?;
|
||||
/// let stream = AsyncFd::new(stream)?;
|
||||
///
|
||||
/// loop {
|
||||
/// let mut guard = stream
|
||||
/// .ready(Interest::READABLE | Interest::WRITABLE)
|
||||
/// .await?;
|
||||
///
|
||||
/// if guard.ready().is_readable() {
|
||||
/// let mut data = vec![0; 1024];
|
||||
/// // Try to read data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match stream.get_ref().read(&mut data) {
|
||||
/// Ok(n) => {
|
||||
/// println!("read {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// // a read has blocked, but a write might still succeed.
|
||||
/// // clear only the read readiness.
|
||||
/// guard.clear_ready_matching(Ready::READABLE);
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// if guard.ready().is_writable() {
|
||||
/// // Try to write data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match stream.get_ref().write(b"hello world") {
|
||||
/// Ok(n) => {
|
||||
/// println!("write {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// // a write has blocked, but a read might still succeed.
|
||||
/// // clear only the write readiness.
|
||||
/// guard.clear_ready_matching(Ready::WRITABLE);
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn clear_ready_matching(&mut self, ready: Ready) {
|
||||
if let Some(mut event) = self.event.take() {
|
||||
self.async_fd
|
||||
.registration
|
||||
.clear_readiness(event.with_ready(ready));
|
||||
|
||||
// the event is no longer ready for the readiness that was just cleared
|
||||
event.ready = event.ready - ready;
|
||||
|
||||
if !event.ready.is_empty() {
|
||||
self.event = Some(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This method should be invoked when you intentionally want to keep the
|
||||
/// ready flag asserted.
|
||||
///
|
||||
@@ -556,6 +939,20 @@ impl<'a, Inner: AsRawFd> AsyncFdReadyGuard<'a, Inner> {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/// Get the [`Ready`] value associated with this guard.
|
||||
///
|
||||
/// This method will return the empty readiness state if
|
||||
/// [`AsyncFdReadyGuard::clear_ready`] has been called on
|
||||
/// the guard.
|
||||
///
|
||||
/// [`Ready`]: crate::io::Ready
|
||||
pub fn ready(&self) -> Ready {
|
||||
match &self.event {
|
||||
Some(event) => event.ready,
|
||||
None => Ready::EMPTY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the provided IO operation.
|
||||
///
|
||||
/// If `f` returns a [`WouldBlock`] error, the readiness state associated
|
||||
@@ -571,6 +968,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"))]
|
||||
@@ -604,22 +1038,117 @@ impl<'a, Inner: AsRawFd> AsyncFdReadyGuard<'a, Inner> {
|
||||
}
|
||||
|
||||
impl<'a, Inner: AsRawFd> AsyncFdReadyMutGuard<'a, Inner> {
|
||||
/// Indicates to tokio that the file descriptor is no longer ready. The
|
||||
/// internal readiness flag will be cleared, and tokio will wait for the
|
||||
/// Indicates to tokio that the file descriptor is no longer ready. All
|
||||
/// internal readiness flags will be cleared, and tokio will wait for the
|
||||
/// next edge-triggered readiness notification from the OS.
|
||||
///
|
||||
/// This function is commonly used with guards returned by [`AsyncFd::readable_mut`] and
|
||||
/// [`AsyncFd::writable_mut`].
|
||||
///
|
||||
/// It is critical that this function not be called unless your code
|
||||
/// _actually observes_ that the file descriptor is _not_ ready. Do not call
|
||||
/// it simply because, for example, a read succeeded; it should be called
|
||||
/// when a read is observed to block.
|
||||
///
|
||||
/// [`drop`]: method@std::mem::drop
|
||||
pub fn clear_ready(&mut self) {
|
||||
if let Some(event) = self.event.take() {
|
||||
self.async_fd.registration.clear_readiness(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates to tokio that the file descriptor no longer has a specific readiness.
|
||||
/// The internal readiness flag will be cleared, and tokio will wait for the
|
||||
/// next edge-triggered readiness notification from the OS.
|
||||
///
|
||||
/// This function is useful in combination with the [`AsyncFd::ready_mut`] method when a
|
||||
/// combined interest like `Interest::READABLE | Interest::WRITABLE` is used.
|
||||
///
|
||||
/// It is critical that this function not be called unless your code
|
||||
/// _actually observes_ that the file descriptor is _not_ ready for the provided `Ready`.
|
||||
/// Do not call it simply because, for example, a read succeeded; it should be called
|
||||
/// when a read is observed to block. Only clear the specific readiness that is observed to
|
||||
/// block. For example when a read blocks when using a combined interest,
|
||||
/// only clear `Ready::READABLE`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Concurrently read and write to a [`std::net::TcpStream`] on the same task without
|
||||
/// splitting.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use std::error::Error;
|
||||
/// use std::io;
|
||||
/// use std::io::{Read, Write};
|
||||
/// use std::net::TcpStream;
|
||||
/// use tokio::io::unix::AsyncFd;
|
||||
/// use tokio::io::{Interest, Ready};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let stream = TcpStream::connect("127.0.0.1:8080")?;
|
||||
/// stream.set_nonblocking(true)?;
|
||||
/// let mut stream = AsyncFd::new(stream)?;
|
||||
///
|
||||
/// loop {
|
||||
/// let mut guard = stream
|
||||
/// .ready_mut(Interest::READABLE | Interest::WRITABLE)
|
||||
/// .await?;
|
||||
///
|
||||
/// if guard.ready().is_readable() {
|
||||
/// let mut data = vec![0; 1024];
|
||||
/// // Try to read data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match guard.get_inner_mut().read(&mut data) {
|
||||
/// Ok(n) => {
|
||||
/// println!("read {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// // a read has blocked, but a write might still succeed.
|
||||
/// // clear only the read readiness.
|
||||
/// guard.clear_ready_matching(Ready::READABLE);
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// if guard.ready().is_writable() {
|
||||
/// // Try to write data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match guard.get_inner_mut().write(b"hello world") {
|
||||
/// Ok(n) => {
|
||||
/// println!("write {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// // a write has blocked, but a read might still succeed.
|
||||
/// // clear only the write readiness.
|
||||
/// guard.clear_ready_matching(Ready::WRITABLE);
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn clear_ready_matching(&mut self, ready: Ready) {
|
||||
if let Some(mut event) = self.event.take() {
|
||||
self.async_fd
|
||||
.registration
|
||||
.clear_readiness(event.with_ready(ready));
|
||||
|
||||
// the event is no longer ready for the readiness that was just cleared
|
||||
event.ready = event.ready - ready;
|
||||
|
||||
if !event.ready.is_empty() {
|
||||
self.event = Some(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This method should be invoked when you intentionally want to keep the
|
||||
/// ready flag asserted.
|
||||
///
|
||||
@@ -629,6 +1158,20 @@ impl<'a, Inner: AsRawFd> AsyncFdReadyMutGuard<'a, Inner> {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/// Get the [`Ready`] value associated with this guard.
|
||||
///
|
||||
/// This method will return the empty readiness state if
|
||||
/// [`AsyncFdReadyGuard::clear_ready`] has been called on
|
||||
/// the guard.
|
||||
///
|
||||
/// [`Ready`]: super::Ready
|
||||
pub fn ready(&self) -> Ready {
|
||||
match &self.event {
|
||||
Some(event) => event.ready,
|
||||
None => Ready::EMPTY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the provided IO operation.
|
||||
///
|
||||
/// If `f` returns a [`WouldBlock`] error, the readiness state associated
|
||||
|
||||
@@ -44,6 +44,11 @@ impl Interest {
|
||||
/// Writable interest includes write-closed events.
|
||||
pub const WRITABLE: Interest = Interest(mio::Interest::WRITABLE);
|
||||
|
||||
/// Returns a `Interest` set representing priority completion interests.
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
|
||||
pub const PRIORITY: Interest = Interest(mio::Interest::PRIORITY);
|
||||
|
||||
/// Returns true if the value includes readable interest.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -78,6 +83,25 @@ impl Interest {
|
||||
self.0.is_writable()
|
||||
}
|
||||
|
||||
/// Returns true if the value includes priority interest.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::io::Interest;
|
||||
///
|
||||
/// assert!(!Interest::READABLE.is_priority());
|
||||
/// assert!(Interest::PRIORITY.is_priority());
|
||||
///
|
||||
/// let both = Interest::READABLE | Interest::PRIORITY;
|
||||
/// assert!(both.is_priority());
|
||||
/// ```
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
|
||||
pub const fn is_priority(self) -> bool {
|
||||
self.0.is_priority()
|
||||
}
|
||||
|
||||
/// Add together two `Interest` values.
|
||||
///
|
||||
/// This function works from a `const` context.
|
||||
@@ -104,6 +128,8 @@ impl Interest {
|
||||
match self {
|
||||
Interest::READABLE => Ready::READABLE | Ready::READ_CLOSED,
|
||||
Interest::WRITABLE => Ready::WRITABLE | Ready::WRITE_CLOSED,
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
Interest::PRIORITY => Ready::PRIORITY | Ready::READ_CLOSED,
|
||||
_ => Ready::EMPTY,
|
||||
}
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
@@ -73,6 +73,9 @@ cfg_io_driver! {
|
||||
impl<E: Source> PollEvented<E> {
|
||||
/// Creates a new `PollEvented` associated with the default reactor.
|
||||
///
|
||||
/// The returned `PollEvented` has readable and writable interests. For more control, use
|
||||
/// [`Self::new_with_interest`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if thread-local runtime is not set.
|
||||
|
||||
@@ -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")
|
||||
|
||||
+51
-4
@@ -7,6 +7,8 @@ const READABLE: usize = 0b0_01;
|
||||
const WRITABLE: usize = 0b0_10;
|
||||
const READ_CLOSED: usize = 0b0_0100;
|
||||
const WRITE_CLOSED: usize = 0b0_1000;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
const PRIORITY: usize = 0b1_0000;
|
||||
|
||||
/// Describes the readiness state of an I/O resources.
|
||||
///
|
||||
@@ -31,7 +33,17 @@ impl Ready {
|
||||
/// Returns a `Ready` representing write closed readiness.
|
||||
pub const WRITE_CLOSED: Ready = Ready(WRITE_CLOSED);
|
||||
|
||||
/// Returns a `Ready` representing priority readiness.
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
|
||||
pub const PRIORITY: Ready = Ready(PRIORITY);
|
||||
|
||||
/// Returns a `Ready` representing readiness for all operations.
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub const ALL: Ready = Ready(READABLE | WRITABLE | READ_CLOSED | WRITE_CLOSED | PRIORITY);
|
||||
|
||||
/// Returns a `Ready` representing readiness for all operations.
|
||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||
pub const ALL: Ready = Ready(READABLE | WRITABLE | READ_CLOSED | WRITE_CLOSED);
|
||||
|
||||
// Must remain crate-private to avoid adding a public dependency on Mio.
|
||||
@@ -65,6 +77,13 @@ impl Ready {
|
||||
ready |= Ready::WRITE_CLOSED;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
{
|
||||
if event.is_priority() {
|
||||
ready |= Ready::PRIORITY;
|
||||
}
|
||||
}
|
||||
|
||||
ready
|
||||
}
|
||||
|
||||
@@ -144,6 +163,23 @@ impl Ready {
|
||||
self.contains(Ready::WRITE_CLOSED)
|
||||
}
|
||||
|
||||
/// Returns `true` if the value includes priority `readiness`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::io::Ready;
|
||||
///
|
||||
/// assert!(!Ready::EMPTY.is_priority());
|
||||
/// assert!(!Ready::WRITABLE.is_priority());
|
||||
/// assert!(Ready::PRIORITY.is_priority());
|
||||
/// ```
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "android"))))]
|
||||
pub fn is_priority(self) -> bool {
|
||||
self.contains(Ready::PRIORITY)
|
||||
}
|
||||
|
||||
/// Returns true if `self` is a superset of `other`.
|
||||
///
|
||||
/// `other` may represent more than one readiness operations, in which case
|
||||
@@ -191,6 +227,12 @@ cfg_io_readiness! {
|
||||
ready |= Ready::WRITE_CLOSED;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
if interest.is_priority() {
|
||||
ready |= Ready::PRIORITY;
|
||||
ready |= Ready::READ_CLOSED;
|
||||
}
|
||||
|
||||
ready
|
||||
}
|
||||
|
||||
@@ -240,11 +282,16 @@ impl ops::Sub<Ready> for Ready {
|
||||
|
||||
impl fmt::Debug for Ready {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Ready")
|
||||
.field("is_readable", &self.is_readable())
|
||||
let mut fmt = fmt.debug_struct("Ready");
|
||||
|
||||
fmt.field("is_readable", &self.is_readable())
|
||||
.field("is_writable", &self.is_writable())
|
||||
.field("is_read_closed", &self.is_read_closed())
|
||||
.field("is_write_closed", &self.is_write_closed())
|
||||
.finish()
|
||||
.field("is_write_closed", &self.is_write_closed());
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fmt.field("is_priority", &self.is_priority());
|
||||
|
||||
fmt.finish()
|
||||
}
|
||||
}
|
||||
|
||||
+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
|
||||
///
|
||||
|
||||
@@ -53,6 +53,7 @@ impl AsyncRead for Empty {
|
||||
cx: &mut Context<'_>,
|
||||
_: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
ready!(poll_proceed_and_make_progress(cx));
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
@@ -61,6 +62,7 @@ impl AsyncRead for Empty {
|
||||
impl AsyncBufRead for Empty {
|
||||
#[inline]
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
ready!(poll_proceed_and_make_progress(cx));
|
||||
Poll::Ready(Ok(&[]))
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ impl AsyncRead for Pipe {
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
|
||||
|
||||
let ret = self.poll_read_internal(cx, buf);
|
||||
@@ -249,6 +250,7 @@ impl AsyncRead for Pipe {
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
self.poll_read_internal(cx, buf)
|
||||
}
|
||||
}
|
||||
@@ -261,6 +263,7 @@ impl AsyncWrite for Pipe {
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
|
||||
|
||||
let ret = self.poll_write_internal(cx, buf);
|
||||
@@ -277,6 +280,7 @@ impl AsyncWrite for Pipe {
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
self.poll_write_internal(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,40 @@ cfg_time! {
|
||||
pub mod time;
|
||||
}
|
||||
|
||||
mod trace {
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "sync"), allow(dead_code))]
|
||||
pub(crate) fn async_trace_leaf() -> impl Future<Output = ()> {
|
||||
struct Trace;
|
||||
|
||||
impl Future for Trace {
|
||||
type Output = ();
|
||||
|
||||
#[inline(always)]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||
trace_leaf(cx)
|
||||
}
|
||||
}
|
||||
|
||||
Trace
|
||||
}
|
||||
}
|
||||
|
||||
mod util;
|
||||
|
||||
/// Due to the `Stream` trait's inclusion in `std` landing later than Tokio's 1.0
|
||||
@@ -658,5 +707,6 @@ cfg_macros! {
|
||||
#[cfg(test)]
|
||||
fn is_unpin<T: Unpin>() {}
|
||||
|
||||
/// fuzz test (fuzz_linked_list)
|
||||
#[cfg(fuzzing)]
|
||||
pub mod fuzz;
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
//! A `Barrier` that provides `wait_timeout`.
|
||||
//!
|
||||
//! This implementation mirrors that of the Rust standard library.
|
||||
|
||||
use crate::loom::sync::{Condvar, Mutex};
|
||||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// A barrier enables multiple threads to synchronize the beginning
|
||||
/// of some computation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::{Arc, Barrier};
|
||||
/// use std::thread;
|
||||
///
|
||||
/// let mut handles = Vec::with_capacity(10);
|
||||
/// let barrier = Arc::new(Barrier::new(10));
|
||||
/// for _ in 0..10 {
|
||||
/// let c = Arc::clone(&barrier);
|
||||
/// // The same messages will be printed together.
|
||||
/// // You will NOT see any interleaving.
|
||||
/// handles.push(thread::spawn(move|| {
|
||||
/// println!("before wait");
|
||||
/// c.wait();
|
||||
/// println!("after wait");
|
||||
/// }));
|
||||
/// }
|
||||
/// // Wait for other threads to finish.
|
||||
/// for handle in handles {
|
||||
/// handle.join().unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
pub(crate) struct Barrier {
|
||||
lock: Mutex<BarrierState>,
|
||||
cvar: Condvar,
|
||||
num_threads: usize,
|
||||
}
|
||||
|
||||
// The inner state of a double barrier
|
||||
struct BarrierState {
|
||||
count: usize,
|
||||
generation_id: usize,
|
||||
}
|
||||
|
||||
/// A `BarrierWaitResult` is returned by [`Barrier::wait()`] when all threads
|
||||
/// in the [`Barrier`] have rendezvoused.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::Barrier;
|
||||
///
|
||||
/// let barrier = Barrier::new(1);
|
||||
/// let barrier_wait_result = barrier.wait();
|
||||
/// ```
|
||||
pub(crate) struct BarrierWaitResult(bool);
|
||||
|
||||
impl fmt::Debug for Barrier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Barrier").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Barrier {
|
||||
/// Creates a new barrier that can block a given number of threads.
|
||||
///
|
||||
/// A barrier will block `n`-1 threads which call [`wait()`] and then wake
|
||||
/// up all threads at once when the `n`th thread calls [`wait()`].
|
||||
///
|
||||
/// [`wait()`]: Barrier::wait
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::Barrier;
|
||||
///
|
||||
/// let barrier = Barrier::new(10);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub(crate) fn new(n: usize) -> Barrier {
|
||||
Barrier {
|
||||
lock: Mutex::new(BarrierState {
|
||||
count: 0,
|
||||
generation_id: 0,
|
||||
}),
|
||||
cvar: Condvar::new(),
|
||||
num_threads: n,
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocks the current thread until all threads have rendezvoused here.
|
||||
///
|
||||
/// Barriers are re-usable after all threads have rendezvoused once, and can
|
||||
/// be used continuously.
|
||||
///
|
||||
/// A single (arbitrary) thread will receive a [`BarrierWaitResult`] that
|
||||
/// returns `true` from [`BarrierWaitResult::is_leader()`] when returning
|
||||
/// from this function, and all other threads will receive a result that
|
||||
/// will return `false` from [`BarrierWaitResult::is_leader()`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::{Arc, Barrier};
|
||||
/// use std::thread;
|
||||
///
|
||||
/// let mut handles = Vec::with_capacity(10);
|
||||
/// let barrier = Arc::new(Barrier::new(10));
|
||||
/// for _ in 0..10 {
|
||||
/// let c = Arc::clone(&barrier);
|
||||
/// // The same messages will be printed together.
|
||||
/// // You will NOT see any interleaving.
|
||||
/// handles.push(thread::spawn(move|| {
|
||||
/// println!("before wait");
|
||||
/// c.wait();
|
||||
/// println!("after wait");
|
||||
/// }));
|
||||
/// }
|
||||
/// // Wait for other threads to finish.
|
||||
/// for handle in handles {
|
||||
/// handle.join().unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
pub(crate) fn wait(&self) -> BarrierWaitResult {
|
||||
let mut lock = self.lock.lock();
|
||||
let local_gen = lock.generation_id;
|
||||
lock.count += 1;
|
||||
if lock.count < self.num_threads {
|
||||
// We need a while loop to guard against spurious wakeups.
|
||||
// https://en.wikipedia.org/wiki/Spurious_wakeup
|
||||
while local_gen == lock.generation_id {
|
||||
lock = self.cvar.wait(lock).unwrap();
|
||||
}
|
||||
BarrierWaitResult(false)
|
||||
} else {
|
||||
lock.count = 0;
|
||||
lock.generation_id = lock.generation_id.wrapping_add(1);
|
||||
self.cvar.notify_all();
|
||||
BarrierWaitResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocks the current thread until all threads have rendezvoused here for
|
||||
/// at most `timeout` duration.
|
||||
pub(crate) fn wait_timeout(&self, timeout: Duration) -> Option<BarrierWaitResult> {
|
||||
// This implementation mirrors `wait`, but with each blocking operation
|
||||
// replaced by a timeout-amenable alternative.
|
||||
|
||||
let deadline = Instant::now() + timeout;
|
||||
|
||||
// Acquire `self.lock` with at most `timeout` duration.
|
||||
let mut lock = loop {
|
||||
if let Some(guard) = self.lock.try_lock() {
|
||||
break guard;
|
||||
} else if Instant::now() > deadline {
|
||||
return None;
|
||||
} else {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
};
|
||||
|
||||
// Shrink the `timeout` to account for the time taken to acquire `lock`.
|
||||
let timeout = deadline.saturating_duration_since(Instant::now());
|
||||
|
||||
let local_gen = lock.generation_id;
|
||||
lock.count += 1;
|
||||
if lock.count < self.num_threads {
|
||||
// We need a while loop to guard against spurious wakeups.
|
||||
// https://en.wikipedia.org/wiki/Spurious_wakeup
|
||||
while local_gen == lock.generation_id {
|
||||
let (guard, timeout_result) = self.cvar.wait_timeout(lock, timeout).unwrap();
|
||||
lock = guard;
|
||||
if timeout_result.timed_out() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(BarrierWaitResult(false))
|
||||
} else {
|
||||
lock.count = 0;
|
||||
lock.generation_id = lock.generation_id.wrapping_add(1);
|
||||
self.cvar.notify_all();
|
||||
Some(BarrierWaitResult(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BarrierWaitResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BarrierWaitResult")
|
||||
.field("is_leader", &self.is_leader())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BarrierWaitResult {
|
||||
/// Returns `true` if this thread is the "leader thread" for the call to
|
||||
/// [`Barrier::wait()`].
|
||||
///
|
||||
/// Only one thread will have `true` returned from their result, all other
|
||||
/// threads will have `false` returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::Barrier;
|
||||
///
|
||||
/// let barrier = Barrier::new(1);
|
||||
/// let barrier_wait_result = barrier.wait();
|
||||
/// println!("{:?}", barrier_wait_result.is_leader());
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub(crate) fn is_leader(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ mod atomic_u16;
|
||||
mod atomic_u32;
|
||||
mod atomic_u64;
|
||||
mod atomic_usize;
|
||||
mod barrier;
|
||||
mod mutex;
|
||||
#[cfg(feature = "parking_lot")]
|
||||
mod parking_lot;
|
||||
@@ -76,6 +77,8 @@ pub(crate) mod sync {
|
||||
|
||||
pub(crate) use std::sync::atomic::{fence, AtomicBool, AtomicPtr, AtomicU8, Ordering};
|
||||
}
|
||||
|
||||
pub(crate) use super::barrier::Barrier;
|
||||
}
|
||||
|
||||
pub(crate) mod sys {
|
||||
|
||||
@@ -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)*
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -23,10 +23,6 @@ cfg_trace! {
|
||||
mod trace;
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
#[cfg(feature = "rt")]
|
||||
pub(crate) mod scoped_tls;
|
||||
|
||||
cfg_macros! {
|
||||
#[macro_use]
|
||||
mod select;
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
use crate::loom::thread::LocalKey;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::marker;
|
||||
|
||||
/// Sets a reference as a thread-local.
|
||||
macro_rules! scoped_thread_local {
|
||||
($(#[$attrs:meta])* $vis:vis static $name:ident: $ty:ty) => (
|
||||
$(#[$attrs])*
|
||||
$vis static $name: $crate::macros::scoped_tls::ScopedKey<$ty>
|
||||
= $crate::macros::scoped_tls::ScopedKey {
|
||||
inner: {
|
||||
tokio_thread_local!(static FOO: ::std::cell::Cell<*const ()> = const {
|
||||
std::cell::Cell::new(::std::ptr::null())
|
||||
});
|
||||
&FOO
|
||||
},
|
||||
_marker: ::std::marker::PhantomData,
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
/// Type representing a thread local storage key corresponding to a reference
|
||||
/// to the type parameter `T`.
|
||||
pub(crate) struct ScopedKey<T> {
|
||||
pub(crate) inner: &'static LocalKey<Cell<*const ()>>,
|
||||
pub(crate) _marker: marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for ScopedKey<T> {}
|
||||
|
||||
impl<T> ScopedKey<T> {
|
||||
/// Inserts a value into this scoped thread local storage slot for a
|
||||
/// duration of a closure.
|
||||
pub(crate) fn set<F, R>(&'static self, t: &T, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
struct Reset {
|
||||
key: &'static LocalKey<Cell<*const ()>>,
|
||||
val: *const (),
|
||||
}
|
||||
|
||||
impl Drop for Reset {
|
||||
fn drop(&mut self) {
|
||||
self.key.with(|c| c.set(self.val));
|
||||
}
|
||||
}
|
||||
|
||||
let prev = self.inner.with(|c| {
|
||||
let prev = c.get();
|
||||
c.set(t as *const _ as *const ());
|
||||
prev
|
||||
});
|
||||
|
||||
let _reset = Reset {
|
||||
key: self.inner,
|
||||
val: prev,
|
||||
};
|
||||
|
||||
f()
|
||||
}
|
||||
|
||||
/// Gets a value out of this scoped variable.
|
||||
pub(crate) fn with<F, R>(&'static self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(Option<&T>) -> R,
|
||||
{
|
||||
let val = self.inner.with(|c| c.get());
|
||||
|
||||
if val.is_null() {
|
||||
f(None)
|
||||
} else {
|
||||
unsafe { f(Some(&*(val as *const 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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user