Compare commits

..
Author SHA1 Message Date
Alice Ryhl e3cb931f79 update macros_type_mismatch 2025-04-24 14:57:59 +02:00
Alice Ryhl c8cdeda37b Merge branch 'master' into ci-beta-1.87 2025-04-24 14:50:34 +02:00
Alice Ryhl 1ea9ce11d4 ci: fix cfg!(miri) declarations in tests (#7286) 2025-04-24 14:50:19 +02:00
Alice Ryhl 4d4d12613b chore: prepare tokio-util v0.7.15 (#7283) 2025-04-23 13:13:48 +02:00
Alan Somers 5490267a79 fs: update the mockall dev dependency to 0.13.0 (#7234) 2025-04-23 11:13:10 +02:00
Alice Ryhl a86e576043 ci: try with 1.87 beta 2025-04-22 13:27:11 +02:00
Nicholas Skinsacos 1434b32b5a examples: improve echo example consistency (#7256) 2025-04-18 18:02:25 +03:30
Carl Lerche 159a3b2c85 rt(unstable): remove alt multi-threaded runtime (#7275)
The alternative multi-threaded runtime started as an experiment. We have been
unable to find real-world benefit. Work has halted on this effort, so lets get
rid of it.
2025-04-17 13:33:55 -07:00
M.Amin Rayej ce87dcfbf0 runtime: document the queue behavior of spawn_blocking (#7269) 2025-04-17 22:49:29 +03:30
Yichi Zhang d41d49d202 metrics: fix panic comment in max_error docs (#7273) 2025-04-18 00:40:59 +09:00
Paul Mabileau 7a6c424f6e process: add Command::spawn_with (#7249)
Signed-off-by: Paul Mabileau <[email protected]>
2025-04-16 14:07:36 +02:00
Conrad Ludgate c3037adac9 task: properly handle removed entries in JoinMap (#7264) 2025-04-15 16:33:20 +02:00
Nicholas Skinsacos 964fd06e0f benches: add helper functions for building runtimes (#7260) 2025-04-14 12:23:00 +03:30
Paolo Barbolini 817fa605ee fs: avoid some copies in tokio::fs::write (#7199) 2025-04-08 15:43:38 +02:00
Alex Bakon 77de684ed9 runtime: mark runtime::Handle unwind-safe (#7230) 2025-04-08 13:38:04 +02:00
Alice Ryhl 83d550e511 changelog: fix release date of v1.44.2 (#7248) 2025-04-08 10:15:58 +02:00
Alice Ryhl 1b3d3e7cd6 Merge 'tokio-1.43.1-fix-release-date' into 'master' (#7247) 2025-04-08 10:15:23 +02:00
Alan Somers 2a8c551631 tokio: update mio-aio dev dependency to 1.0 (#7235)
This eliminates a duplicate dependency on mio
2025-04-06 12:15:37 +02:00
Carl Lerche 676630785b Merge branch 'tokio-1.44.x' into forward-port-1.44.x 2025-04-05 08:20:03 -07:00
Carl Lerche ec4b1d7215 chore: forward port 1.43.x 2025-04-04 16:13:58 -07:00
Carl Lerche e3c3a56718 Merge branch 'tokio-1.43.x' into forward-port-1.43.x 2025-04-04 16:11:53 -07:00
jimmycathy 0ec4d0db4d docs: remove redundant words in comment (#7224) 2025-03-16 18:08:25 +09:00
Jamie d83ba30d8d task: explicitly state that TaskTracker does not abort tasks on Drop (#7223) 2025-03-14 14:48:54 +00:00
LongYinan f339587b27 deps: update hashbrown to 0.15 (#7219) 2025-03-14 09:47:18 +01:00
Alice Ryhl b663abe091 chore: update tokio-util version number (#7215) 2025-03-13 11:22:34 +01:00
Motoyuki Kimura 9a11efc262 chore: prepare tokio-util v0.7.14 (#7215) 2025-03-13 11:20:52 +01:00
Alice Ryhl d760b26666 Merge tokio-1.44.1 into master (#7218) 2025-03-13 09:44:03 +01:00
Alice Ryhl d413c9c02a chore: prepare Tokio v1.44.1 (#7217) 2025-03-13 09:13:25 +01:00
Carl Lerche addbfb9204 rt: skip defer queue in block_in_place context (#7216) 2025-03-13 08:18:13 +01:00
Motoyuki Kimura 5687043328 test: remove unused dependencies (#7214) 2025-03-13 01:28:01 +09:00
Vitaly Shukela 72c87a7724 test: add io::Builder::name for better panic messages (#7212)
Introduce tokio_test::io::Builder::name to configure
name of the mock object, to include in panic messages.

Also show number of remaining actions or action index
in some cases to help debugging failed tests.
2025-03-13 00:34:09 +09:00
Alphyr 8507e28f89 Remove an old custom OnceCell implementation in favor of std (#7208) 2025-03-11 08:15:26 +01:00
Alphyr 7efcab43c9 Do not require Unpin for some trait impls (#7204) 2025-03-11 08:14:22 +01:00
Ty Larrabee e4a39d2ef6 sync: add CancellationToken::run_until_cancelled_owned (#7081) 2025-03-10 14:24:25 +01:00
Owen Leung afd3678f89 metrics: stabilize worker_total_busy_duration (#6899) 2025-03-10 10:29:45 +01:00
Alice Ryhl 8182ecf262 chore: prepare Tokio v1.44.0 (#7202) 2025-03-07 21:11:03 +01:00
Motoyuki Kimura a258bff701 ci: enable printing in multi thread loom tests (#7200) 2025-03-07 13:33:16 +01:00
Stepan Koltsov e076d21f67 process: clarify Child::kill behavior (#7162) 2025-03-06 14:59:51 +03:30
Noah Kennedy 042433cdcc net: debug_assert on creating a tokio socket from a blocking one (#7166)
See #5595 and #7172.

This adds a debug assertion that checks that a supplied underlying std socket is set to nonblocking mode when constructing a tokio socket object from such an object.

This only works on unix.
2025-03-05 18:10:30 +00:00
M.Amin Rayej 0284d1b5c8 macros: make select! budget-aware (#7164) 2025-03-05 01:07:18 +03:30
Carl Lerche 710bc8071e rt: coop should yield using waker defer strategy (#7185) 2025-03-04 15:02:43 +01:00
Alice Ryhl a2b12bd579 readme: adjust release schedule to once per month (#7191) 2025-03-03 14:04:42 +03:30
Jonathan Hiles e7b593cbee process: fix grammar of the ChildStdin struct doc comment (#7192) 2025-03-03 13:01:35 +03:30
kilavvy 3aaf4a5377 coop: adjust grammar in tests/coop_budget.rs (#7173) 2025-03-03 09:59:03 +01:00
Alice Ryhl 8e741c1c0e tokio: mark 1.43 as LTS (#7189) 2025-03-03 09:54:26 +01:00
dlzht 47d46455bd util: optimize buffer reserve for AnyDelimiterCodec::encode (#7188) 2025-03-02 19:53:19 +03:30
Adriano Mourão 20c1fdc678 runtime: consistently use worker_threads instead of core_threads (#7186) 2025-02-28 10:19:24 +01:00
Josh Triplett 638ce93591 io: add read_exact_arc to safely read a new uninitialized Arc (#7165) 2025-02-27 14:07:49 +01:00
Kyle Cotton c853991b1e io: swap reader/writer in simplex doc test (#7176) 2025-02-25 14:49:19 +00:00
Josh Triplett 6d410f6c90 util: fix example of Buf implementor in StreamReader docs (#7167) 2025-02-21 09:22:16 +03:30
Finomnis a27575f284 signal: fix CTRL_CLOSE, CTRL_LOGOFF, CTRL_SHUTDOWN on windows (#7122) 2025-02-19 12:27:13 +01:00
Stepan Koltsov 13fbdace66 process: add test for Child::kill after Child::wait (#7163) 2025-02-19 00:52:50 +03:30
Timo 4380c3d821 sync: Added WeakSender to sync::broadcast::channel (#7100) 2025-02-17 21:24:29 +01:00
katelyn martin 383da87313 sync: implement oneshot::Receiver::is_empty() (#7153) 2025-02-17 02:01:11 +03:30
katelyn martin 17117b591e sync: implement oneshot::Receiver::is_terminated() (#7152) 2025-02-16 22:55:50 +03:30
Nathaniel Bajo aa70f6c5f0 io: add documentation for SyncIoBridge with examples and alternatives (#6815) 2025-02-16 19:01:52 +01:00
Dylan Laufenberg 67c343d9e9 docs: fix nesting of next sections under examples (#7159)
Promote the feature flags and supported platforms sections out from
under examples, as they are not examples. Adjust their subsections
accordingly. Expose these subsections via navigation sidebar.
2025-02-15 23:08:13 +01:00
Oleksandr Babak 34cdcc7d87 macros: docs about select! alternatives (#7110) 2025-02-15 22:49:23 +01:00
Stepan Koltsov 8e134172dd process: calling start_kill on exited child should not fail (#7160) 2025-02-15 13:48:22 +03:30
M.Amin Rayej 605ef578df coop: expose coop as a public module (#7116) 2025-02-14 18:56:12 +03:30
M.Amin Rayej 9b578f0c9d ci: bump freeBSD image version (#7158) 2025-02-14 17:37:16 +03:30
M.Amin Rayej 0a15768380 io: clean up buffer casts (#7142) 2025-02-10 19:57:25 +03:30
Motoyuki Kimura eb1a2ee990 net: rename the argument for send_to (#7146) 2025-02-08 15:05:05 +03:30
M.Amin Rayej 8713d39228 process: add example for reading Child stdout (#7141) 2025-02-08 13:31:02 +03:30
Florian Gäbler 7e27911911 fs: align symlink and hardlink parameter names with std (#7143) 2025-02-07 23:40:15 +03:30
Alice Ryhl 4b3da20c98 fs: empty reads on File should not start a background read (#7139) 2025-02-06 01:37:29 +03:30
Jason Gin b8ac94ed70 rt: add before and after task poll callbacks (#7120)
Add callbacks for poll start and stop, enabling users to instrument these points
in the runtime's life cycle.
2025-01-30 21:14:00 +00:00
Oliver Wanglerandow 5086e56dcb io: implemented get_ref and get_mut for SyncIoBridge (#7128)
Co-authored-by: ow <[email protected]>
2025-01-28 15:28:07 +01:00
Ariel Ben-YehudaandAriel Ben-Yehuda 2671ffb55b tracing: make the task tracing API unstable piblkc (#6972)
* make self-tracing public

* address review comments

* try to fix doctest

* adjust imports to fit standard

* more documentation

---------

Co-authored-by: Ariel Ben-Yehuda <[email protected]>
2025-01-27 13:09:23 -08:00
Taiki Endo 7f09959b0a chore: use [lints] to address unexpected_cfgs lint (#7124) 2025-01-25 17:46:21 +01:00
Taiki Endo fb7dec0e95 ci: test AArch64/Armv7hf Linux on ubuntu-22.04-arm runner (#7123) 2025-01-25 11:17:37 +01:00
M.Amin Rayej ee19b0ed73 net: fix warnings when building the docs (#7113) 2025-01-22 11:48:43 +01:00
Josh McKinney c081dfe3ce macros: characterization tests for ? operator fail (#7069)
When a `?` operator is used in a tokio entry point function (wrapped in
`#[tokio::main]`), which has a Option or Result return type, but where
the function does not actually return that type correctly, currently the
compiler returns two errors instead of just one. The first of which is
incorrect and only exists due to the macro expanding to an async block.

```
cannot use the `?` operator in an async block that returns `()`
```

This commit is a characterization test for this behavior to help show
when it's fixed (or even changed for better / worse)
2025-01-22 10:55:00 +01:00
M.Amin Rayej 21a13f9eea runtime: clean up magic number in registration set (#7112) 2025-01-21 14:40:32 +01:00
Motoyuki Kimura a82bdeebe9 sync: handle panic during mpsc drop (#7094) 2025-01-13 18:36:51 +01:00
Evan Rittenhouse 435e39001b sync: fix sync::broadcast::Sender<T>::closed() doctest (#7090)
The test's previous iteration could sometimes flake since we didn't
await the completion of the first task. Since the tasks only existed to
`move` the relevant `rx`'s in, to force a drop, we can omit them
entirely and drop the `rx`s via `drop()`. This prevents any
scheduling-related flakes.
2025-01-12 12:33:07 +01:00
Marshall Lee dabae570b1 ci: add spellcheck.dic validation (#7062) 2025-01-10 13:39:23 +01:00
29 6bd3be2e45 process: add Command::get_kill_on_drop() (#7086) 2025-01-10 12:35:13 +01:00
Alice Ryhl 6fc1a8c8da ci: fix ci error about wasm32-wasip1 (#7085) 2025-01-10 18:59:35 +09:00
Evan Rittenhouse 5c8cd33820 sync: add broadcast::Sender::closed (#6685) 2025-01-09 16:37:49 +01:00
199 changed files with 3900 additions and 8647 deletions
-25
View File
@@ -1,25 +0,0 @@
version: 2.1
jobs:
test-arm:
machine:
image: default
resource_class: arm.medium
environment:
# Change to pin rust version
RUST_STABLE: stable
steps:
- checkout
- run:
name: Install Rust
command: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o rustup.sh
chmod +x rustup.sh
./rustup.sh -y --default-toolchain $RUST_STABLE
source "$HOME"/.cargo/env
# Only run Tokio tests
- run: cargo test --all-features -p tokio
workflows:
ci:
jobs:
- test-arm
+2 -2
View File
@@ -3,8 +3,8 @@ auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
freebsd_instance:
image_family: freebsd-14-2
env:
RUST_STABLE: stable
RUST_NIGHTLY: nightly-2024-05-05
RUST_STABLE: beta
RUST_NIGHTLY: nightly-2025-01-25
RUSTFLAGS: -D warnings
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
+34 -26
View File
@@ -15,10 +15,10 @@ env:
RUST_BACKTRACE: 1
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
# Change to specific Rust release to pin
rust_stable: stable
rust_nightly: nightly-2024-05-05
rust_stable: beta
rust_nightly: nightly-2025-01-25
# Pin a specific miri version
rust_miri_nightly: nightly-2024-10-21
rust_miri_nightly: nightly-2025-01-25
rust_clippy: '1.77'
# When updating this, also update:
# - README.md
@@ -540,15 +540,19 @@ jobs:
cross-test-with-parking_lot:
needs: basics
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- target: i686-unknown-linux-gnu
os: ubuntu-latest
rustflags: --cfg tokio_taskdump
- target: armv5te-unknown-linux-gnueabi
os: ubuntu-latest
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved
rustflags: --cfg tokio_taskdump
steps:
- uses: actions/checkout@v4
@@ -580,15 +584,19 @@ jobs:
cross-test-without-parking_lot:
needs: basics
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- target: i686-unknown-linux-gnu
os: ubuntu-latest
rustflags: --cfg tokio_taskdump
- target: armv5te-unknown-linux-gnueabi
os: ubuntu-latest
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved
rustflags: --cfg tokio_taskdump
steps:
- uses: actions/checkout@v4
@@ -1093,23 +1101,6 @@ jobs:
run: cargo check-external-types --all-features
working-directory: tokio
check-unexpected-lints-cfgs:
name: check unexpected lints and cfgs
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_nightly }}
- name: don't allow warnings
run: sed -i '/#!\[allow(unknown_lints, unexpected_cfgs)\]/d' */src/lib.rs */tests/*.rs
- name: check for unknown lints and cfgs
run: cargo check --all-features --tests
env:
RUSTFLAGS: -Dwarnings --check-cfg=cfg(loom,tokio_unstable,tokio_taskdump,fuzzing,mio_unsupported_force_poll_poll,tokio_internal_mt_counters,fs,tokio_no_parking_lot,tokio_no_tuning_tests) -Funexpected_cfgs -Funknown_lints
check-fuzzing:
name: check-fuzzing
needs: basics
@@ -1147,8 +1138,25 @@ jobs:
- uses: actions/checkout@v4
- name: Make sure dictionary words are sorted and unique
run: |
# `sed` removes the first line (number of words) and
# the last line (new line).
FILE="spellcheck.dic"
# Verify the first line is an integer.
first_line=$(head -n 1 "$FILE")
if ! [[ "$first_line" =~ ^[0-9]+$ ]]; then
echo "Error: The first line of $FILE must be an integer, but got: '$first_line'"
exit 1
fi
expected_count="$first_line"
# Check that the number of lines matches the integer.
# xargs (with no arguments) will strip leading/trailing whitespacefrom wc's output.
actual_count=$(sed '1d' "$FILE" | wc -l | xargs)
if [ "$expected_count" -ne "$actual_count" ]; then
echo "Error: The number of lines ($actual_count) does not match $expected_count."
exit 1
fi
# `sed` removes the first line (number of words).
#
# `sort` makes sure everything in between is sorted
# and contains no duplicates.
@@ -1158,10 +1166,10 @@ jobs:
# environments.
(
sed '1d; $d' spellcheck.dic | LC_ALL=en_US.UTF8 sort -uc
sed '1d' $FILE | LC_ALL=en_US.UTF8 sort -uc
) || {
echo "Dictionary is not in sorted order. Correct order is:"
LC_ALL=en_US.UTF8 sort -u <(sed '1d; $d' spellcheck.dic)
LC_ALL=en_US.UTF8 sort -u <(sed '1d' $FILE)
false
}
- name: Run cargo-spellcheck
+3 -3
View File
@@ -17,7 +17,7 @@ env:
LOOM_MAX_BRANCHES: 10000
RUST_BACKTRACE: 1
# Change to specific Rust release to pin
rust_stable: stable
rust_stable: beta
permissions:
contents: read
@@ -91,7 +91,7 @@ jobs:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: loom ${{ matrix.scope }}
run: cargo test --lib --release --features full -- $SCOPE
run: cargo test --lib --release --features full -- --nocapture $SCOPE
working-directory: tokio
env:
SCOPE: ${{ matrix.scope }}
@@ -116,7 +116,7 @@ jobs:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: loom ${{ matrix.scope }}
run: cargo test --lib --release --features full -- $SCOPE
run: cargo test --lib --release --features full -- --nocapture $SCOPE
working-directory: tokio
env:
SCOPE: ${{ matrix.scope }}
Generated
-2181
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.43.2", features = ["full"] }
tokio = { version = "1.44.2", features = ["full"] }
```
Then, on your main.rs:
@@ -205,8 +205,8 @@ works with the MSRV of that minor release.
## Release schedule
Tokio doesn't follow a fixed release schedule, but we typically make one to two
new minor releases each month. We make patch releases for bugfixes as necessary.
Tokio doesn't follow a fixed release schedule, but we typically make one minor
release each month. We make patch releases for bugfixes as necessary.
## Bug patching policy
@@ -216,8 +216,8 @@ warrants a patch release with a fix for the bug, it will be backported and
released as a new patch release for each LTS minor version. Our current LTS
releases are:
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
* `1.38.x` - LTS release until July 2025. (MSRV 1.63)
* `1.43.x` - LTS release until March 2026. (MSRV 1.70)
Each LTS release will continue to receive backported fixes for at least a year.
If you wish to use a fixed minor release in your project, we recommend that you
@@ -227,7 +227,7 @@ To use a fixed minor version, you can specify the version with a tilde. For
example, to specify that you wish to use the newest `1.32.x` patch release, you
can use the following dependency specification:
```text
tokio = { version = "~1.32", features = [...] }
tokio = { version = "~1.38", features = [...] }
```
### Previous LTS releases
@@ -238,6 +238,7 @@ tokio = { version = "~1.32", features = [...] }
* `1.20.x` - LTS release until September 2023.
* `1.25.x` - LTS release until March 2024.
* `1.32.x` - LTS release until September 2024.
* `1.36.x` - LTS release until March 2025.
## License
+3
View File
@@ -95,3 +95,6 @@ harness = false
name = "time_timeout"
path = "time_timeout.rs"
harness = false
[lints]
workspace = true
+19 -14
View File
@@ -10,10 +10,21 @@ async fn work() -> usize {
black_box(val)
}
fn basic_scheduler_spawn(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_current_thread()
fn single_rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
.unwrap()
}
fn multi_rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.build()
.unwrap()
}
fn basic_scheduler_spawn(c: &mut Criterion) {
let runtime = single_rt();
c.bench_function("basic_scheduler_spawn", |b| {
b.iter(|| {
@@ -26,9 +37,7 @@ fn basic_scheduler_spawn(c: &mut Criterion) {
}
fn basic_scheduler_spawn10(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let runtime = single_rt();
c.bench_function("basic_scheduler_spawn10", |b| {
b.iter(|| {
@@ -46,10 +55,8 @@ fn basic_scheduler_spawn10(c: &mut Criterion) {
}
fn threaded_scheduler_spawn(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.build()
.unwrap();
let runtime = multi_rt();
c.bench_function("threaded_scheduler_spawn", |b| {
b.iter(|| {
runtime.block_on(async {
@@ -61,10 +68,8 @@ fn threaded_scheduler_spawn(c: &mut Criterion) {
}
fn threaded_scheduler_spawn10(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.build()
.unwrap();
let runtime = multi_rt();
c.bench_function("threaded_scheduler_spawn10", |b| {
b.iter(|| {
runtime.block_on(async {
+10 -6
View File
@@ -33,17 +33,21 @@ name = "chat"
path = "chat.rs"
[[example]]
name = "connect"
path = "connect.rs"
name = "connect-tcp"
path = "connect-tcp.rs"
[[example]]
name = "connect-udp"
path = "connect-udp.rs"
[[example]]
name = "echo-tcp"
path = "echo-tcp.rs"
[[example]]
name = "echo-udp"
path = "echo-udp.rs"
[[example]]
name = "echo"
path = "echo.rs"
[[example]]
name = "hello_world"
path = "hello_world.rs"
+1 -1
View File
@@ -10,7 +10,7 @@ cargo run --example $name
```
A good starting point for the examples would be [`hello_world`](hello_world.rs)
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
and [`echo-tcp`](echo-tcp.rs). Additionally [the tokio website][tokioweb] contains
additional guides for some of the examples.
For a larger "real world" example, see the [`mini-redis`][redis] repository.
+71
View File
@@ -0,0 +1,71 @@
//! An example of hooking up stdin/stdout to a TCP stream.
//!
//! This example will connect to a socket address specified in the argument list
//! and then forward all data read on stdin to the server, printing out all data
//! received on stdout. Each line entered on stdin will be translated to a TCP
//! packet which is then sent to the remote address.
//!
//! Note that this is not currently optimized for performance, especially
//! around buffer management. Rather it's intended to show an example of
//! working with a client.
//!
//! This example can be quite useful when interacting with the other examples in
//! this repository! Many of them recommend running this as a simple "hook up
//! stdin/stdout to a server" to get up and running.
#![warn(rust_2018_idioms)]
use tokio::io::{stdin, stdout};
use tokio::net::TcpStream;
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
use bytes::Bytes;
use futures::{future, Sink, SinkExt, Stream, StreamExt};
use std::env;
use std::error::Error;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Parse what address we're going to connect to
let args = env::args().skip(1).collect::<Vec<_>>();
let addr = args
.first()
.ok_or("this program requires at least one argument")?;
let addr = addr.parse::<SocketAddr>()?;
let stdin = FramedRead::new(stdin(), BytesCodec::new());
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
connect(&addr, stdin, stdout).await?;
Ok(())
}
pub async fn connect(
addr: &SocketAddr,
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
mut stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
) -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect(addr).await?;
let (r, w) = stream.split();
let mut sink = FramedWrite::new(w, BytesCodec::new());
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
// on the event of an Error, log the error and end the stream
let mut stream = FramedRead::new(r, BytesCodec::new())
.filter_map(|i| match i {
//BytesMut into Bytes
Ok(i) => future::ready(Some(i.freeze())),
Err(e) => {
println!("failed to read from socket; error={e}");
future::ready(None)
}
})
.map(Ok);
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
(Err(e), _) | (_, Err(e)) => Err(e.into()),
_ => Ok(()),
}
}
+91
View File
@@ -0,0 +1,91 @@
//! An example of hooking up stdin/stdout to a UDP stream.
//!
//! This example will connect to a socket address specified in the argument list
//! and then forward all data read on stdin to the server, printing out all data
//! received on stdout. Each line entered on stdin will be translated to a UDP
//! packet which is then sent to the remote address.
//!
//! Note that this is not currently optimized for performance, especially
//! around buffer management. Rather it's intended to show an example of
//! working with a client.
//!
//! This example can be quite useful when interacting with the other examples in
//! this repository! Many of them recommend running this as a simple "hook up
//! stdin/stdout to a server" to get up and running.
#![warn(rust_2018_idioms)]
use tokio::io::{stdin, stdout};
use tokio::net::UdpSocket;
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
use bytes::Bytes;
use futures::{Sink, SinkExt, Stream, StreamExt};
use std::env;
use std::error::Error;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Parse what address we're going to connect to
let args = env::args().skip(1).collect::<Vec<_>>();
let addr = args
.first()
.ok_or("this program requires at least one argument")?;
let addr = addr.parse::<SocketAddr>()?;
let stdin = FramedRead::new(stdin(), BytesCodec::new());
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
connect(&addr, stdin, stdout).await?;
Ok(())
}
pub async fn connect(
addr: &SocketAddr,
stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
) -> Result<(), Box<dyn Error>> {
// We'll bind our UDP socket to a local IP/port, but for now we
// basically let the OS pick both of those.
let bind_addr = if addr.ip().is_ipv4() {
"0.0.0.0:0"
} else {
"[::]:0"
};
let socket = UdpSocket::bind(&bind_addr).await?;
socket.connect(addr).await?;
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
Ok(())
}
async fn send(
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
writer: &UdpSocket,
) -> Result<(), std::io::Error> {
while let Some(item) = stdin.next().await {
let buf = item?;
writer.send(&buf[..]).await?;
}
Ok(())
}
async fn recv(
mut stdout: impl Sink<Bytes, Error = std::io::Error> + Unpin,
reader: &UdpSocket,
) -> Result<(), std::io::Error> {
loop {
let mut buf = vec![0; 1024];
let n = reader.recv(&mut buf[..]).await?;
if n > 0 {
stdout.send(Bytes::from(buf)).await?;
}
}
}
-147
View File
@@ -1,147 +0,0 @@
//! An example of hooking up stdin/stdout to either a TCP or UDP stream.
//!
//! This example will connect to a socket address specified in the argument list
//! and then forward all data read on stdin to the server, printing out all data
//! received on stdout. An optional `--udp` argument can be passed to specify
//! that the connection should be made over UDP instead of TCP, translating each
//! line entered on stdin to a UDP packet to be sent to the remote address.
//!
//! Note that this is not currently optimized for performance, especially
//! around buffer management. Rather it's intended to show an example of
//! working with a client.
//!
//! This example can be quite useful when interacting with the other examples in
//! this repository! Many of them recommend running this as a simple "hook up
//! stdin/stdout to a server" to get up and running.
#![warn(rust_2018_idioms)]
use futures::StreamExt;
use tokio::io;
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
use std::env;
use std::error::Error;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Determine if we're going to run in TCP or UDP mode
let mut args = env::args().skip(1).collect::<Vec<_>>();
let tcp = match args.iter().position(|a| a == "--udp") {
Some(i) => {
args.remove(i);
false
}
None => true,
};
// Parse what address we're going to connect to
let addr = args
.first()
.ok_or("this program requires at least one argument")?;
let addr = addr.parse::<SocketAddr>()?;
let stdin = FramedRead::new(io::stdin(), BytesCodec::new());
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
let stdout = FramedWrite::new(io::stdout(), BytesCodec::new());
if tcp {
tcp::connect(&addr, stdin, stdout).await?;
} else {
udp::connect(&addr, stdin, stdout).await?;
}
Ok(())
}
mod tcp {
use bytes::Bytes;
use futures::{future, Sink, SinkExt, Stream, StreamExt};
use std::{error::Error, io, net::SocketAddr};
use tokio::net::TcpStream;
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
pub async fn connect(
addr: &SocketAddr,
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
) -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect(addr).await?;
let (r, w) = stream.split();
let mut sink = FramedWrite::new(w, BytesCodec::new());
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
// on the event of an Error, log the error and end the stream
let mut stream = FramedRead::new(r, BytesCodec::new())
.filter_map(|i| match i {
//BytesMut into Bytes
Ok(i) => future::ready(Some(i.freeze())),
Err(e) => {
println!("failed to read from socket; error={e}");
future::ready(None)
}
})
.map(Ok);
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
(Err(e), _) | (_, Err(e)) => Err(e.into()),
_ => Ok(()),
}
}
}
mod udp {
use bytes::Bytes;
use futures::{Sink, SinkExt, Stream, StreamExt};
use std::error::Error;
use std::io;
use std::net::SocketAddr;
use tokio::net::UdpSocket;
pub async fn connect(
addr: &SocketAddr,
stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
) -> Result<(), Box<dyn Error>> {
// We'll bind our UDP socket to a local IP/port, but for now we
// basically let the OS pick both of those.
let bind_addr = if addr.ip().is_ipv4() {
"0.0.0.0:0"
} else {
"[::]:0"
};
let socket = UdpSocket::bind(&bind_addr).await?;
socket.connect(addr).await?;
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
Ok(())
}
async fn send(
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
writer: &UdpSocket,
) -> Result<(), io::Error> {
while let Some(item) = stdin.next().await {
let buf = item?;
writer.send(&buf[..]).await?;
}
Ok(())
}
async fn recv(
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
reader: &UdpSocket,
) -> Result<(), io::Error> {
loop {
let mut buf = vec![0; 1024];
let n = reader.recv(&mut buf[..]).await?;
if n > 0 {
stdout.send(Bytes::from(buf)).await?;
}
}
}
}
-2
View File
@@ -1,5 +1,3 @@
#![allow(unknown_lints, unexpected_cfgs)]
//! This example demonstrates tokio's experimental task dumping functionality.
//! This application deadlocks. Input CTRL+C to display traces of each task, or
//! input CTRL+C twice within 1 second to quit.
+3 -3
View File
@@ -9,13 +9,13 @@
//!
//! To see this server in action, you can run this in one terminal:
//!
//! cargo run --example echo
//! cargo run --example echo-tcp
//!
//! and in another terminal you can run:
//!
//! cargo run --example connect 127.0.0.1:8080
//! cargo run --example connect-tcp 127.0.0.1:8080
//!
//! Each line you type in to the `connect` terminal should be echo'd back to
//! Each line you type in to the `connect-tcp` terminal should be echo'd back to
//! you! If you open up multiple terminals running the `connect` example you
//! should be able to see them all make progress simultaneously.
+2 -2
View File
@@ -6,9 +6,9 @@
//!
//! and in another terminal you can run:
//!
//! cargo run --example connect -- --udp 127.0.0.1:8080
//! cargo run --example connect-udp 127.0.0.1:8080
//!
//! Each line you type in to the `nc` terminal should be echo'd back to you!
//! Each line you type in to the `connect-udp` terminal should be echo'd back to you!
#![warn(rust_2018_idioms)]
+2 -2
View File
@@ -13,9 +13,9 @@
//!
//! and in another terminal you can run:
//!
//! cargo run --example connect 127.0.0.1:8080
//! cargo run --example connect-tcp 127.0.0.1:8080
//!
//! Each line you type in to the `connect` terminal should be written to terminal!
//! Each line you type in to the `connect-tcp` terminal should be written to terminal!
//!
//! Minimal js example:
//!
+2 -2
View File
@@ -11,11 +11,11 @@
//!
//! This in another terminal
//!
//! cargo run --example echo
//! cargo run --example echo-tcp
//!
//! And finally this in another terminal
//!
//! cargo run --example connect 127.0.0.1:8081
//! cargo run --example connect-tcp 127.0.0.1:8081
//!
//! This final terminal will connect to our proxy, which will in turn connect to
//! the echo server, and you'll be able to see data flowing between them.
+2 -2
View File
@@ -12,9 +12,9 @@
//!
//! and next in another windows run:
//!
//! cargo run --example connect 127.0.0.1:8080
//! cargo run --example connect-tcp 127.0.0.1:8080
//!
//! In the `connect` window you can type in commands where when you hit enter
//! In the `connect-tcp` window you can type in commands where when you hit enter
//! you'll get a response from the server for that command. An example session
//! is:
//!
+4 -1
View File
@@ -1,4 +1,4 @@
298
302
&
+
<
@@ -70,6 +70,7 @@ connectionless
coroutines
cpu
cpus
customizable
Customizable
datagram
Datagram
@@ -78,6 +79,7 @@ deallocate
deallocated
Deallocates
debuginfo
decrement
decrementing
demangled
dequeued
@@ -168,6 +170,7 @@ mio's
miri
misconfigured
mock's
monomorphization
mpmc
mpsc
multi
+3
View File
@@ -13,3 +13,6 @@ tokio = { version = "1.0.0", path = "../tokio/", features = ["full"] }
[dev-dependencies]
rand = "0.8"
[lints]
workspace = true
+3
View File
@@ -15,3 +15,6 @@ tokio = { version = "1.0.0", path = "../tokio", optional = true }
[dev-dependencies]
trybuild = "1.0"
[lints]
workspace = true
@@ -23,6 +23,40 @@ async fn extra_semicolon() -> Result<(), ()> {
Ok(());
}
/// This test is a characterization test for the `?` operator.
///
/// See <https://github.com/tokio-rs/tokio/issues/6930#issuecomment-2572502517> for more details.
///
/// It should fail with a single error message about the return type of the function, but instead
/// if fails with an extra error message due to the `?` operator being used within the async block
/// rather than the original function.
///
/// ```text
/// 28 | None?;
/// | ^ cannot use the `?` operator in an async block that returns `()`
/// ```
#[tokio::main]
async fn question_mark_operator_with_invalid_option() -> Option<()> {
None?;
}
/// This test is a characterization test for the `?` operator.
///
/// See <https://github.com/tokio-rs/tokio/issues/6930#issuecomment-2572502517> for more details.
///
/// It should fail with a single error message about the return type of the function, but instead
/// if fails with an extra error message due to the `?` operator being used within the async block
/// rather than the original function.
///
/// ```text
/// 33 | Ok(())?;
/// | ^ cannot use the `?` operator in an async block that returns `()`
/// ```
#[tokio::main]
async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
Ok(())?;
}
// https://github.com/tokio-rs/tokio/issues/4635
#[allow(redundant_semicolons)]
#[rustfmt::skip]
@@ -49,11 +49,64 @@ help: try adding an expression at the end of the block
24 + Ok(())
|
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:32:5
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> tests/fail/macros_type_mismatch.rs:40:9
|
30 | async fn issue_4635() {
38 | #[tokio::main]
| -------------- this function should return `Result` or `Option` to accept `?`
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
40 | None?;
| ^ cannot use the `?` operator in an async block that returns `()`
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:40:5
|
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
| ---------- expected `Option<()>` because of return type
40 | None?;
| ^^^^^^ expected `Option<()>`, found `()`
|
= note: expected enum `Option<()>`
found unit type `()`
help: try adding an expression at the end of the block
|
40 ~ None?;;
41 + None
|
40 ~ None?;;
41 + Some(())
|
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> tests/fail/macros_type_mismatch.rs:57:11
|
55 | #[tokio::main]
| -------------- this function should return `Result` or `Option` to accept `?`
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
57 | Ok(())?;
| ^ cannot use the `?` operator in an async block that returns `()`
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:57:5
|
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
| -------------- expected `Result<(), ()>` because of return type
57 | Ok(())?;
| ^^^^^^^^ expected `Result<(), ()>`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
help: try adding an expression at the end of the block
|
57 ~ Ok(())?;;
58 + Ok(())
|
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:66:5
|
64 | async fn issue_4635() {
| - help: try adding a return type: `-> i32`
31 | return 1;
32 | ;
65 | return 1;
66 | ;
| ^ expected `()`, found integer
+3
View File
@@ -61,3 +61,6 @@ tokio-test = { version = "0.4", path = "../tokio-test", optional = true }
doc-comment = "0.3.1"
futures = { version = "0.3.0", features = ["async-await"] }
bytes = "1.0.0"
[lints]
workspace = true
+3
View File
@@ -31,3 +31,6 @@ tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
[package.metadata.docs.rs]
all-features = true
[lints]
workspace = true
-5
View File
@@ -1,4 +1,3 @@
#![allow(unknown_lints, unexpected_cfgs)]
#![allow(clippy::needless_doctest_main)]
#![warn(
missing_debug_implementations,
@@ -211,7 +210,6 @@ use proc_macro::TokenStream;
/// This option is only compatible with the `current_thread` runtime.
///
/// ```no_run
/// # #![allow(unknown_lints, unexpected_cfgs)]
/// #[cfg(tokio_unstable)]
/// #[tokio::main(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
/// async fn main() {
@@ -226,7 +224,6 @@ use proc_macro::TokenStream;
/// Equivalent code not using `#[tokio::main]`
///
/// ```no_run
/// # #![allow(unknown_lints, unexpected_cfgs)]
/// #[cfg(tokio_unstable)]
/// fn main() {
/// tokio::runtime::Builder::new_current_thread()
@@ -480,7 +477,6 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// This option is only compatible with the `current_thread` runtime.
///
/// ```no_run
/// # #![allow(unknown_lints, unexpected_cfgs)]
/// #[cfg(tokio_unstable)]
/// #[tokio::test(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
/// async fn my_test() {
@@ -495,7 +491,6 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// Equivalent code not using `#[tokio::test]`
///
/// ```no_run
/// # #![allow(unknown_lints, unexpected_cfgs)]
/// #[cfg(tokio_unstable)]
/// #[test]
/// fn my_test() {
+3
View File
@@ -56,3 +56,6 @@ rustdoc-args = ["--cfg", "docsrs"]
# This should allow `docsrs` to be read across projects, so that `tokio-stream`
# can pick up stubbed types exported by `tokio`.
rustc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
-1
View File
@@ -1,4 +1,3 @@
#![allow(unknown_lints, unexpected_cfgs)]
#![allow(
clippy::cognitive_complexity,
clippy::large_enum_variant,
+1 -1
View File
@@ -17,7 +17,7 @@ unsafe impl<T> Sync for Pending<T> {}
///
/// The returned stream is never ready. Attempting to call
/// [`next()`](crate::StreamExt::next) will never complete. Use
/// [`stream::empty()`](super::empty()) to obtain a stream that is is
/// [`stream::empty()`](super::empty()) to obtain a stream that is
/// immediately empty but returns no values.
///
/// # Examples
+3 -3
View File
@@ -19,9 +19,6 @@ categories = ["asynchronous", "development-tools::testing"]
[dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
tokio-stream = { version = "0.1.1", path = "../tokio-stream" }
async-stream = "0.3.3"
bytes = "1.0.0"
futures-core = "0.3.0"
[dev-dependencies]
@@ -30,3 +27,6 @@ futures-util = "0.3.0"
[package.metadata.docs.rs]
all-features = true
[lints]
workspace = true
+53 -9
View File
@@ -52,6 +52,7 @@ pub struct Handle {
pub struct Builder {
// Sequence of actions for the Mock to take
actions: VecDeque<Action>,
name: String,
}
#[derive(Debug, Clone)]
@@ -71,6 +72,7 @@ struct Inner {
sleep: Option<Pin<Box<Sleep>>>,
read_wait: Option<Waker>,
rx: UnboundedReceiverStream<Action>,
name: String,
}
impl Builder {
@@ -127,6 +129,12 @@ impl Builder {
self
}
/// Set name of the mock IO object to include in panic messages and debug output
pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
self.name = name.into();
self
}
/// Build a `Mock` value according to the defined script.
pub fn build(&mut self) -> Mock {
let (mock, _) = self.build_with_handle();
@@ -135,7 +143,7 @@ impl Builder {
/// Build a `Mock` value paired with a handle
pub fn build_with_handle(&mut self) -> (Mock, Handle) {
let (inner, handle) = Inner::new(self.actions.clone());
let (inner, handle) = Inner::new(self.actions.clone(), self.name.clone());
let mock = Mock { inner };
@@ -184,7 +192,7 @@ impl Handle {
}
impl Inner {
fn new(actions: VecDeque<Action>) -> (Inner, Handle) {
fn new(actions: VecDeque<Action>, name: String) -> (Inner, Handle) {
let (tx, rx) = mpsc::unbounded_channel();
let rx = UnboundedReceiverStream::new(rx);
@@ -195,6 +203,7 @@ impl Inner {
read_wait: None,
rx,
waiting: None,
name,
};
let handle = Handle { tx };
@@ -256,7 +265,7 @@ impl Inner {
Action::Write(ref mut expect) => {
let n = cmp::min(src.len(), expect.len());
assert_eq!(&src[..n], &expect[..n]);
assert_eq!(&src[..n], &expect[..n], "name={} i={}", self.name, i);
// Drop data that was matched
expect.drain(..n);
@@ -418,7 +427,7 @@ impl AsyncWrite for Mock {
self.inner.actions.push_back(action);
}
Poll::Ready(None) => {
panic!("unexpected write");
panic!("unexpected write {}", self.pmsg());
}
}
}
@@ -429,7 +438,7 @@ impl AsyncWrite for Mock {
let until = Instant::now() + rem;
self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
} else {
panic!("unexpected WouldBlock");
panic!("unexpected WouldBlock {}", self.pmsg());
}
}
Ok(0) => {
@@ -445,7 +454,7 @@ impl AsyncWrite for Mock {
continue;
}
None => {
panic!("unexpected write");
panic!("unexpected write {}", self.pmsg());
}
}
}
@@ -475,8 +484,16 @@ impl Drop for Mock {
}
self.inner.actions.iter().for_each(|a| match a {
Action::Read(data) => assert!(data.is_empty(), "There is still data left to read."),
Action::Write(data) => assert!(data.is_empty(), "There is still data left to write."),
Action::Read(data) => assert!(
data.is_empty(),
"There is still data left to read. {}",
self.pmsg()
),
Action::Write(data) => assert!(
data.is_empty(),
"There is still data left to write. {}",
self.pmsg()
),
_ => (),
});
}
@@ -505,6 +522,33 @@ fn is_task_ctx() -> bool {
impl fmt::Debug for Inner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Inner {{...}}")
if self.name.is_empty() {
write!(f, "Inner {{...}}")
} else {
write!(f, "Inner {{name={}, ...}}", self.name)
}
}
}
struct PanicMsgSnippet<'a>(&'a Inner);
impl<'a> fmt::Display for PanicMsgSnippet<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.name.is_empty() {
write!(f, "({} actions remain)", self.0.actions.len())
} else {
write!(
f,
"(name {}, {} actions remain)",
self.0.name,
self.0.actions.len()
)
}
}
}
impl Mock {
fn pmsg(&self) -> PanicMsgSnippet<'_> {
PanicMsgSnippet(&self.inner)
}
}
-1
View File
@@ -1,4 +1,3 @@
#![allow(unknown_lints, unexpected_cfgs)]
#![warn(
missing_debug_implementations,
missing_docs,
+56
View File
@@ -1,3 +1,59 @@
# 0.7.15 (April 23rd, 2025)
### Fixed
- task: properly handle removed entries in `JoinMap` ([#7264])
### Updated
- deps: update hashbrown to 0.15 ([#7219])
### Documented
- task: explicitly state that `TaskTracker` does not abort tasks on Drop ([#7223])
[#7219]: https://github.com/tokio-rs/tokio/pull/7219
[#7223]: https://github.com/tokio-rs/tokio/pull/7223
[#7264]: https://github.com/tokio-rs/tokio/pull/7264
# 0.7.14 (March 12th, 2025)
### Added
- io: add `get_ref` and `get_mut` for `SyncIoBridge` ([#7128])
- io: add `read_exact_arc` ([#7165])
- sync: add `CancellationToken::run_until_cancelled_owned` ([#7081])
### Changed
- codec: optimize buffer reserve for `AnyDelimiterCodec::encode` ([#7188])
- either: enable `Either` to use underlying `AsyncWrite` implementation ([#7025])
### Fixed
- codec: fix typo in API docs ([#7044])
- util: fix example in `StreamReader` docs ([#7167])
### Documented
- io: add docs for `SyncIoBridge` with examples and alternatives ([#6815])
### Internal
- io: clean up buffer casts ([#7142])
- task: run `spawn_pinned` tests with miri ([#7023])
[#6815]: https://github.com/tokio-rs/tokio/pull/6815
[#7023]: https://github.com/tokio-rs/tokio/pull/7023
[#7025]: https://github.com/tokio-rs/tokio/pull/7025
[#7044]: https://github.com/tokio-rs/tokio/pull/7044
[#7081]: https://github.com/tokio-rs/tokio/pull/7081
[#7128]: https://github.com/tokio-rs/tokio/pull/7128
[#7142]: https://github.com/tokio-rs/tokio/pull/7142
[#7165]: https://github.com/tokio-rs/tokio/pull/7165
[#7167]: https://github.com/tokio-rs/tokio/pull/7167
[#7188]: https://github.com/tokio-rs/tokio/pull/7188
# 0.7.13 (December 4th, 2024)
### Fixed
+6 -3
View File
@@ -4,7 +4,7 @@ name = "tokio-util"
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-util-0.7.x" git tag.
version = "0.7.13"
version = "0.7.15"
edition = "2021"
rust-version = "1.70"
authors = ["Tokio Contributors <[email protected]>"]
@@ -35,7 +35,7 @@ __docs_rs = ["futures-util"]
[dependencies]
tokio = { version = "1.28.0", path = "../tokio", features = ["sync"] }
bytes = "1.0.0"
bytes = "1.5.0"
futures-core = "0.3.0"
futures-sink = "0.3.0"
futures-io = { version = "0.3.0", optional = true }
@@ -45,7 +45,7 @@ slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
tracing = { version = "0.1.29", default-features = false, features = ["std"], optional = true }
[target.'cfg(tokio_unstable)'.dependencies]
hashbrown = { version = "0.14.0", default-features = false, optional = true }
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry"], optional = true }
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
@@ -68,3 +68,6 @@ rustc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"]
[package.metadata.playground]
features = ["full"]
[lints]
workspace = true
+1 -1
View File
@@ -217,7 +217,7 @@ where
fn encode(&mut self, chunk: T, buf: &mut BytesMut) -> Result<(), AnyDelimiterCodecError> {
let chunk = chunk.as_ref();
buf.reserve(chunk.len() + 1);
buf.reserve(chunk.len() + self.sequence_writer.len());
buf.put(chunk.as_bytes());
buf.put(self.sequence_writer.as_ref());
+3
View File
@@ -18,6 +18,9 @@ mod sink_writer;
mod stream_reader;
cfg_io_util! {
mod read_arc;
pub use self::read_arc::read_exact_arc;
mod sync_bridge;
pub use self::sync_bridge::SyncIoBridge;
}
+44
View File
@@ -0,0 +1,44 @@
use std::io;
use std::mem::MaybeUninit;
use std::sync::Arc;
use tokio::io::{AsyncRead, AsyncReadExt};
/// Read data from an `AsyncRead` into an `Arc`.
///
/// This uses `Arc::new_uninit_slice` and reads into the resulting uninitialized `Arc`.
///
/// # Example
///
/// ```
/// # #[tokio::main]
/// # async fn main() -> std::io::Result<()> {
/// use tokio_util::io::read_exact_arc;
///
/// let read = tokio::io::repeat(42);
///
/// let arc = read_exact_arc(read, 4).await?;
///
/// assert_eq!(&arc[..], &[42; 4]);
/// # Ok(())
/// # }
/// ```
pub async fn read_exact_arc<R: AsyncRead>(read: R, len: usize) -> io::Result<Arc<[u8]>> {
tokio::pin!(read);
// TODO(MSRV 1.82): When bumping MSRV, switch to `Arc::new_uninit_slice(len)`. The following is
// equivalent, and generates the same assembly, but works without requiring MSRV 1.82.
let arc: Arc<[MaybeUninit<u8>]> = (0..len).map(|_| MaybeUninit::uninit()).collect();
// TODO(MSRV future): Use `Arc::get_mut_unchecked` once it's stabilized.
// SAFETY: We're the only owner of the `Arc`, and we keep the `Arc` valid throughout this loop
// as we write through this reference.
let mut buf = unsafe { &mut *(Arc::as_ptr(&arc) as *mut [MaybeUninit<u8>]) };
while !buf.is_empty() {
if read.read_buf(&mut buf).await? == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "early eof"));
}
}
// TODO(MSRV 1.82): When bumping MSRV, switch to `arc.assume_init()`. The following is
// equivalent, and generates the same assembly, but works without requiring MSRV 1.82.
// SAFETY: This changes `[MaybeUninit<u8>]` to `[u8]`, and we've initialized all the bytes in
// the loop above.
Ok(unsafe { Arc::from_raw(Arc::into_raw(arc) as *const [u8]) })
}
+1 -1
View File
@@ -169,7 +169,7 @@ where
/// Convert a stream of byte chunks into an [`AsyncRead`].
///
/// The item should be a [`Result`] with the ok variant being something that
/// implements the [`Buf`] trait (e.g. `Vec<u8>` or `Bytes`). The error
/// implements the [`Buf`] trait (e.g. `Cursor<Vec<u8>>` or `Bytes`). The error
/// should be convertible into an [io error].
///
/// [`Result`]: std::result::Result
+264 -1
View File
@@ -5,7 +5,258 @@ use tokio::io::{
};
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
/// a [`tokio::io::AsyncWrite`] synchronously as a [`std::io::Write`].
///
/// # Alternatives
///
/// In many cases, there are better alternatives to using `SyncIoBridge`, especially
/// if you want to avoid blocking the async runtime. Consider the following scenarios:
///
/// When hashing data, using `SyncIoBridge` can lead to suboptimal performance and
/// might not fully leverage the async capabilities of the system.
///
/// ### Why It Matters:
///
/// `SyncIoBridge` allows you to use asynchronous I/O operations in an synchronous
/// context by blocking the current thread. However, this can be inefficient because:
/// - **Inefficient Resource Usage**: `SyncIoBridge` takes up an entire OS thread,
/// which is inefficient compared to asynchronous code that can multiplex many
/// tasks on a single thread.
/// - **Thread Pool Saturation**: Excessive use of `SyncIoBridge` can exhaust the
/// async runtime's thread pool, reducing the number of threads available for
/// other tasks and impacting overall performance.
/// - **Missed Concurrency Benefits**: By using synchronous operations with
/// `SyncIoBridge`, you lose the ability to interleave tasks efficiently,
/// which is a key advantage of asynchronous programming.
///
/// ## Example 1: Hashing Data
///
/// The use of `SyncIoBridge` is unnecessary when hashing data. Instead, you can
/// process the data asynchronously by reading it into memory, which avoids blocking
/// the async runtime.
///
/// There are two strategies for avoiding `SyncIoBridge` when hashing data. When
/// the data fits into memory, the easiest is to read the data into a `Vec<u8>`
/// and hash it:
///
/// Explanation: This example demonstrates how to asynchronously read data from a
/// reader into memory and hash it using a synchronous hashing function. The
/// `SyncIoBridge` is avoided, ensuring that the async runtime is not blocked.
/// ```rust
/// use tokio::io::AsyncReadExt;
/// use tokio::io::AsyncRead;
/// use std::io::Cursor;
/// # mod blake3 { pub fn hash(_: &[u8]) {} }
///
/// async fn hash_contents(mut reader: impl AsyncRead + Unpin) -> Result<(), std::io::Error> {
/// // Read all data from the reader into a Vec<u8>.
/// let mut data = Vec::new();
/// reader.read_to_end(&mut data).await?;
///
/// // Hash the data using the blake3 hashing function.
/// let hash = blake3::hash(&data);
///
/// Ok(hash)
///}
///
/// #[tokio::main]
/// async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// hash_contents(reader).await
/// }
/// ```
///
/// When the data doesn't fit into memory, the hashing library will usually
/// provide a `hasher` that you can repeatedly call `update` on to hash the data
/// one chunk at the time.
///
/// Explanation: This example demonstrates how to asynchronously stream data in
/// chunks for hashing. Each chunk is read asynchronously, and the hash is updated
/// incrementally. This avoids blocking and improves performance over using
/// `SyncIoBridge`.
///
/// ```rust
/// use tokio::io::AsyncReadExt;
/// use tokio::io::AsyncRead;
/// use std::io::Cursor;
/// # struct Hasher;
/// # impl Hasher { pub fn update(&mut self, _: &[u8]) {} pub fn finalize(&self) {} }
///
/// /// Asynchronously streams data from an async reader, processes it in chunks,
/// /// and hashes the data incrementally.
/// async fn hash_stream(mut reader: impl AsyncRead + Unpin, mut hasher: Hasher) -> Result<(), std::io::Error> {
/// // Create a buffer to read data into, sized for performance.
/// let mut data = vec![0; 64 * 1024];
/// loop {
/// // Read data from the reader into the buffer.
/// let len = reader.read(&mut data).await?;
/// if len == 0 { break; } // Exit loop if no more data.
///
/// // Update the hash with the data read.
/// hasher.update(&data[..len]);
/// }
///
/// // Finalize the hash after all data has been processed.
/// let hash = hasher.finalize();
///
/// Ok(hash)
///}
///
/// #[tokio::main]
/// async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// let hasher = Hasher;
/// hash_stream(reader, hasher).await
/// }
/// ```
///
///
/// ## Example 2: Compressing Data
///
/// When compressing data, the use of `SyncIoBridge` is unnecessary as it introduces
/// blocking and inefficient code. Instead, you can utilize an async compression library
/// such as the [`async-compression`](https://docs.rs/async-compression/latest/async_compression/)
/// crate, which is built to handle asynchronous data streams efficiently.
///
/// Explanation: This example shows how to asynchronously compress data using an
/// async compression library. By reading and writing asynchronously, it avoids
/// blocking and is more efficient than using `SyncIoBridge` with a non-async
/// compression library.
///
/// ```ignore
/// use async_compression::tokio::write::GzipEncoder;
/// use std::io::Cursor;
/// use tokio::io::AsyncRead;
///
/// /// Asynchronously compresses data from an async reader using Gzip and an async encoder.
/// async fn compress_data(mut reader: impl AsyncRead + Unpin) -> Result<(), std::io::Error> {
/// let writer = tokio::io::sink();
///
/// // Create a Gzip encoder that wraps the writer.
/// let mut encoder = GzipEncoder::new(writer);
///
/// // Copy data from the reader to the encoder, compressing it.
/// tokio::io::copy(&mut reader, &mut encoder).await?;
///
/// Ok(())
///}
///
/// #[tokio::main]
/// async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// compress_data(reader).await?;
///
/// Ok(())
/// }
/// ```
///
///
/// ## Example 3: Parsing Data Formats
///
///
/// `SyncIoBridge` is not ideal when parsing data formats such as `JSON`, as it
/// blocks async operations. A more efficient approach is to read data asynchronously
/// into memory and then `deserialize` it, avoiding unnecessary synchronization overhead.
///
/// Explanation: This example shows how to asynchronously read data into memory
/// and then parse it as `JSON`. By avoiding `SyncIoBridge`, the asynchronous runtime
/// remains unblocked, leading to better performance when working with asynchronous
/// I/O streams.
///
/// ```rust,no_run
/// use tokio::io::AsyncRead;
/// use tokio::io::AsyncReadExt;
/// use std::io::Cursor;
/// # mod serde {
/// # pub trait DeserializeOwned: 'static {}
/// # impl<T: 'static> DeserializeOwned for T {}
/// # }
/// # mod serde_json {
/// # use super::serde::DeserializeOwned;
/// # pub fn from_slice<T: DeserializeOwned>(_: &[u8]) -> Result<T, std::io::Error> {
/// # unimplemented!()
/// # }
/// # }
/// # #[derive(Debug)] struct MyStruct;
///
///
/// async fn parse_json(mut reader: impl AsyncRead + Unpin) -> Result<MyStruct, std::io::Error> {
/// // Read all data from the reader into a Vec<u8>.
/// let mut data = Vec::new();
/// reader.read_to_end(&mut data).await?;
///
/// // Deserialize the data from the Vec<u8> into a MyStruct instance.
/// let value: MyStruct = serde_json::from_slice(&data)?;
///
/// Ok(value)
///}
///
/// #[tokio::main]
/// async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// parse_json(reader).await?;
/// Ok(())
/// }
/// ```
///
/// ## Correct Usage of `SyncIoBridge` inside `spawn_blocking`
///
/// `SyncIoBridge` is mainly useful when you need to interface with synchronous
/// libraries from an asynchronous context.
///
/// Explanation: This example shows how to use `SyncIoBridge` inside a `spawn_blocking`
/// task to safely perform synchronous I/O without blocking the async runtime. The
/// `spawn_blocking` ensures that the synchronous code is offloaded to a dedicated
/// thread pool, preventing it from interfering with the async tasks.
///
/// ```rust
/// use tokio::task::spawn_blocking;
/// use tokio_util::io::SyncIoBridge;
/// use tokio::io::AsyncRead;
/// use std::marker::Unpin;
/// use std::io::Cursor;
///
/// /// Wraps an async reader with `SyncIoBridge` and performs synchronous I/O operations in a blocking task.
/// async fn process_sync_io(reader: impl AsyncRead + Unpin + Send + 'static) -> Result<Vec<u8>, std::io::Error> {
/// // Wrap the async reader with `SyncIoBridge` to allow synchronous reading.
/// let mut sync_reader = SyncIoBridge::new(reader);
///
/// // Spawn a blocking task to perform synchronous I/O operations.
/// let result = spawn_blocking(move || {
/// // Create an in-memory buffer to hold the copied data.
/// let mut buffer = Vec::new();
/// // Copy data from the sync_reader to the buffer.
/// std::io::copy(&mut sync_reader, &mut buffer)?;
/// // Return the buffer containing the copied data.
/// Ok::<_, std::io::Error>(buffer)
/// })
/// .await??;
///
/// // Return the result from the blocking task.
/// Ok(result)
///}
///
/// #[tokio::main]
/// async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// let result = process_sync_io(reader).await?;
///
/// // You can use `result` here as needed.
///
/// Ok(())
/// }
/// ```
///
#[derive(Debug)]
pub struct SyncIoBridge<T> {
src: T,
@@ -154,3 +405,15 @@ impl<T: Unpin> SyncIoBridge<T> {
self.src
}
}
impl<T> AsMut<T> for SyncIoBridge<T> {
fn as_mut(&mut self) -> &mut T {
&mut self.src
}
}
impl<T> AsRef<T> for SyncIoBridge<T> {
fn as_ref(&self) -> &T {
&self.src
}
}
-1
View File
@@ -1,4 +1,3 @@
#![allow(unknown_lints, unexpected_cfgs)]
#![allow(clippy::needless_doctest_main)]
#![warn(
missing_debug_implementations,
+16
View File
@@ -287,6 +287,22 @@ impl CancellationToken {
}
.await
}
/// Runs a future to completion and returns its result wrapped inside of an `Option`
/// unless the `CancellationToken` is cancelled. In that case the function returns
/// `None` and the future gets dropped.
///
/// The function takes self by value and returns a future that owns the token.
///
/// # Cancel safety
///
/// This method is only cancel safe if `fut` is cancel safe.
pub async fn run_until_cancelled_owned<F>(self, fut: F) -> Option<F::Output>
where
F: Future,
{
self.run_until_cancelled(fut).await
}
}
// ===== impl WaitForCancellationFuture =====
+12 -9
View File
@@ -469,16 +469,19 @@ where
///
/// [`tokio::select!`]: tokio::select
pub async fn join_next(&mut self) -> Option<(K, Result<V, JoinError>)> {
let (res, id) = match self.tasks.join_next_with_id().await {
Some(Ok((id, output))) => (Ok(output), id),
Some(Err(e)) => {
let id = e.id();
(Err(e), id)
loop {
let (res, id) = match self.tasks.join_next_with_id().await {
Some(Ok((id, output))) => (Ok(output), id),
Some(Err(e)) => {
let id = e.id();
(Err(e), id)
}
None => return None,
};
if let Some(key) = self.remove_by_id(id) {
break Some((key, res));
}
None => return None,
};
let key = self.remove_by_id(id)?;
Some((key, res))
}
}
/// Aborts all tasks and waits for them to finish shutting down.
+2
View File
@@ -53,6 +53,8 @@ use tokio::{
/// `TaskTracker`, this does not happen. Once tasks exit, they are immediately removed from the
/// `TaskTracker`.
///
/// Note that unlike [`JoinSet`], dropping a `TaskTracker` does not abort the tasks.
///
/// # Examples
///
/// For more examples, please see the topic page on [graceful shutdown].
+4 -3
View File
@@ -5,13 +5,13 @@ use tokio::{io::ReadBuf, net::UdpSocket};
use bytes::{BufMut, BytesMut};
use futures_sink::Sink;
use std::io;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
use std::{
borrow::Borrow,
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
};
use std::{io, mem::MaybeUninit};
/// A unified [`Stream`] and [`Sink`] interface to an underlying `UdpSocket`, using
/// the `Encoder` and `Decoder` traits to encode and decode frames.
@@ -83,7 +83,7 @@ where
let addr = {
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
// transparent wrapper around `[MaybeUninit<u8>]`.
let buf = unsafe { &mut *(pin.rd.chunk_mut() as *mut _ as *mut [MaybeUninit<u8>]) };
let buf = unsafe { pin.rd.chunk_mut().as_uninit_slice_mut() };
let mut read = ReadBuf::uninit(buf);
let ptr = read.filled().as_ptr();
let res = ready!(pin.socket.borrow().poll_recv_from(cx, &mut read));
@@ -91,9 +91,10 @@ where
assert_eq!(ptr, read.filled().as_ptr());
let addr = res?;
let filled = read.filled().len();
// Safety: This is guaranteed to be the number of initialized (and read) bytes due
// to the invariants provided by `ReadBuf::filled`.
unsafe { pin.rd.advance_mut(read.filled().len()) };
unsafe { pin.rd.advance_mut(filled) };
addr
};
+1 -2
View File
@@ -2,7 +2,6 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use bytes::{Buf, BufMut};
use std::io::{self, IoSlice};
use std::mem::MaybeUninit;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
@@ -59,7 +58,7 @@ pub fn poll_read_buf<T: AsyncRead + ?Sized, B: BufMut>(
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
// transparent wrapper around `[MaybeUninit<u8>]`.
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
let dst = unsafe { dst.as_uninit_slice_mut() };
let mut buf = ReadBuf::uninit(dst);
let ptr = buf.filled().as_ptr();
ready!(io.poll_read(cx, &mut buf)?);
@@ -493,3 +493,58 @@ fn run_until_cancelled_test() {
);
}
}
#[test]
fn run_until_cancelled_owned_test() {
let (waker, _) = new_count_waker();
{
let token = CancellationToken::new();
let to_cancel = token.clone();
let takes_ownership = move |token: CancellationToken| {
token.run_until_cancelled_owned(std::future::pending::<()>())
};
let fut = takes_ownership(token);
pin!(fut);
assert_eq!(
Poll::Pending,
fut.as_mut().poll(&mut Context::from_waker(&waker))
);
to_cancel.cancel();
assert_eq!(
Poll::Ready(None),
fut.as_mut().poll(&mut Context::from_waker(&waker))
);
}
{
let (tx, rx) = oneshot::channel::<()>();
let token = CancellationToken::new();
let takes_ownership = move |token: CancellationToken, rx: oneshot::Receiver<()>| {
token.run_until_cancelled_owned(async move {
rx.await.unwrap();
42
})
};
let fut = takes_ownership(token, rx);
pin!(fut);
assert_eq!(
Poll::Pending,
fut.as_mut().poll(&mut Context::from_waker(&waker))
);
tx.send(()).unwrap();
assert_eq!(
Poll::Ready(Some(42)),
fut.as_mut().poll(&mut Context::from_waker(&waker))
);
}
}
+46 -1
View File
@@ -1,4 +1,3 @@
#![allow(unknown_lints, unexpected_cfgs)]
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "rt", tokio_unstable))]
@@ -298,3 +297,49 @@ async fn abort_all() {
assert!(was_seen);
}
}
#[tokio::test]
async fn duplicate_keys() {
let mut map = JoinMap::new();
map.spawn(1, async { 1 });
map.spawn(1, async { 2 });
assert_eq!(map.len(), 1);
let (key, res) = map.join_next().await.unwrap();
assert_eq!(key, 1);
assert_eq!(res.unwrap(), 2);
assert!(map.join_next().await.is_none());
}
#[tokio::test]
async fn duplicate_keys2() {
let (send, recv) = oneshot::channel::<()>();
let mut map = JoinMap::new();
map.spawn(1, async { 1 });
map.spawn(1, async {
recv.await.unwrap();
2
});
assert_eq!(map.len(), 1);
tokio::select! {
biased;
res = map.join_next() => match res {
Some((_key, res)) => panic!("Task {res:?} exited."),
None => panic!("Phantom task completeion."),
},
() = tokio::task::yield_now() => {},
}
send.send(()).unwrap();
let (key, res) = map.join_next().await.unwrap();
assert_eq!(key, 1);
assert_eq!(res.unwrap(), 2);
assert!(map.join_next().await.is_none());
}
+96 -3
View File
@@ -1,10 +1,103 @@
# 1.43.2 (August 1st, 2025)
# 1.44.2 (April 5th, 2025)
This release fixes a soundness issue in the broadcast channel. The channel
accepts values that are `Send` but `!Sync`. Previously, the channel called
`clone()` on these values without synchronizing. This release fixes the channel
by synchronizing calls to `.clone()` (Thanks Austin Bonander for finding and
reporting the issue).
### Fixed
- process: fix panic from spurious pidfd wakeup ([#7494])
- sync: synchronize `clone()` call in broadcast channel ([#7232])
[#7494]: https://github.com/tokio-rs/tokio/pull/7494
[#7232]: https://github.com/tokio-rs/tokio/pull/7232
# 1.44.1 (March 13th, 2025)
### Fixed
- rt: skip defer queue in `block_in_place` context ([#7216])
[#7216]: https://github.com/tokio-rs/tokio/pull/7216
# 1.44.0 (March 7th, 2025)
This release changes the `from_std` method on sockets to panic if a blocking
socket is provided. We determined this change is not a breaking change as Tokio is not
intended to operate using blocking sockets. Doing so results in runtime hangs and
should be considered a bug. Accidentally passing a blocking socket to Tokio is one
of the most common user mistakes. If this change causes an issue for you, please
comment on [#7172].
### Added
- coop: add `task::coop` module ([#7116])
- process: add `Command::get_kill_on_drop()` ([#7086])
- sync: add `broadcast::Sender::closed` ([#6685], [#7090])
- sync: add `broadcast::WeakSender` ([#7100])
- sync: add `oneshot::Receiver::is_empty()` ([#7153])
- sync: add `oneshot::Receiver::is_terminated()` ([#7152])
### Fixed
- fs: empty reads on `File` should not start a background read ([#7139])
- process: calling `start_kill` on exited child should not fail ([#7160])
- signal: fix `CTRL_CLOSE`, `CTRL_LOGOFF`, `CTRL_SHUTDOWN` on windows ([#7122])
- sync: properly handle panic during mpsc drop ([#7094])
### Changes
- runtime: clean up magic number in registration set ([#7112])
- coop: make coop yield using waker defer strategy ([#7185])
- macros: make `select!` budget-aware ([#7164])
- net: panic when passing a blocking socket to `from_std` ([#7166])
- io: clean up buffer casts ([#7142])
### Changes to unstable APIs
- rt: add before and after task poll callbacks ([#7120])
- tracing: make the task tracing API unstable public ([#6972])
### Documented
- docs: fix nesting of sections in top-level docs ([#7159])
- fs: rename symlink and hardlink parameter names ([#7143])
- io: swap reader/writer in simplex doc test ([#7176])
- macros: docs about `select!` alternatives ([#7110])
- net: rename the argument for `send_to` ([#7146])
- process: add example for reading `Child` stdout ([#7141])
- process: clarify `Child::kill` behavior ([#7162])
- process: fix grammar of the `ChildStdin` struct doc comment ([#7192])
- runtime: consistently use `worker_threads` instead of `core_threads` ([#7186])
[#6685]: https://github.com/tokio-rs/tokio/pull/6685
[#6972]: https://github.com/tokio-rs/tokio/pull/6972
[#7086]: https://github.com/tokio-rs/tokio/pull/7086
[#7090]: https://github.com/tokio-rs/tokio/pull/7090
[#7094]: https://github.com/tokio-rs/tokio/pull/7094
[#7100]: https://github.com/tokio-rs/tokio/pull/7100
[#7110]: https://github.com/tokio-rs/tokio/pull/7110
[#7112]: https://github.com/tokio-rs/tokio/pull/7112
[#7116]: https://github.com/tokio-rs/tokio/pull/7116
[#7120]: https://github.com/tokio-rs/tokio/pull/7120
[#7122]: https://github.com/tokio-rs/tokio/pull/7122
[#7139]: https://github.com/tokio-rs/tokio/pull/7139
[#7141]: https://github.com/tokio-rs/tokio/pull/7141
[#7142]: https://github.com/tokio-rs/tokio/pull/7142
[#7143]: https://github.com/tokio-rs/tokio/pull/7143
[#7146]: https://github.com/tokio-rs/tokio/pull/7146
[#7152]: https://github.com/tokio-rs/tokio/pull/7152
[#7153]: https://github.com/tokio-rs/tokio/pull/7153
[#7159]: https://github.com/tokio-rs/tokio/pull/7159
[#7160]: https://github.com/tokio-rs/tokio/pull/7160
[#7162]: https://github.com/tokio-rs/tokio/pull/7162
[#7164]: https://github.com/tokio-rs/tokio/pull/7164
[#7166]: https://github.com/tokio-rs/tokio/pull/7166
[#7172]: https://github.com/tokio-rs/tokio/pull/7172
[#7176]: https://github.com/tokio-rs/tokio/pull/7176
[#7185]: https://github.com/tokio-rs/tokio/pull/7185
[#7186]: https://github.com/tokio-rs/tokio/pull/7186
[#7192]: https://github.com/tokio-rs/tokio/pull/7192
# 1.43.1 (April 5th, 2025)
+5 -4
View File
@@ -6,7 +6,7 @@ name = "tokio"
# - README.md
# - Update CHANGELOG.md.
# - Create "v1.x.y" git tag.
version = "1.43.2"
version = "1.44.2"
edition = "2021"
rust-version = "1.70"
authors = ["Tokio Contributors <[email protected]>"]
@@ -91,7 +91,7 @@ tokio-macros = { version = "~2.5.0", path = "../tokio-macros", optional = true }
pin-project-lite = "0.2.11"
# Everything else is optional...
bytes = { version = "1.1.0", optional = true }
bytes = { version = "1.2.1", optional = true }
mio = { version = "1.0.1", optional = true, default-features = false }
parking_lot = { version = "0.12.0", optional = true }
@@ -131,8 +131,9 @@ features = [
tokio-test = { version = "0.4.0", path = "../tokio-test" }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
futures = { version = "0.3.0", features = ["async-await"] }
mockall = "0.11.1"
mockall = "0.13.0"
async-stream = "0.3"
futures-concurrency = "7.6.3"
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
socket2 = "0.5.5"
@@ -146,7 +147,7 @@ rand = "0.8.0"
wasm-bindgen-test = "0.3.0"
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
mio-aio = { version = "0.9.0", features = ["tokio"] }
mio-aio = { version = "1", features = ["tokio"] }
[target.'cfg(loom)'.dev-dependencies]
loom = { version = "0.7", features = ["futures", "checkpoint"] }
+6 -5
View File
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.43.2", features = ["full"] }
tokio = { version = "1.44.2", features = ["full"] }
```
Then, on your main.rs:
@@ -205,8 +205,8 @@ works with the MSRV of that minor release.
## Release schedule
Tokio doesn't follow a fixed release schedule, but we typically make one to two
new minor releases each month. We make patch releases for bugfixes as necessary.
Tokio doesn't follow a fixed release schedule, but we typically make one minor
release each month. We make patch releases for bugfixes as necessary.
## Bug patching policy
@@ -216,8 +216,8 @@ warrants a patch release with a fix for the bug, it will be backported and
released as a new patch release for each LTS minor version. Our current LTS
releases are:
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
* `1.38.x` - LTS release until July 2025. (MSRV 1.63)
* `1.43.x` - LTS release until March 2026. (MSRV 1.70)
Each LTS release will continue to receive backported fixes for at least a year.
If you wish to use a fixed minor release in your project, we recommend that you
@@ -227,7 +227,7 @@ To use a fixed minor version, you can specify the version with a tilde. For
example, to specify that you wish to use the newest `1.32.x` patch release, you
can use the following dependency specification:
```text
tokio = { version = "~1.32", features = [...] }
tokio = { version = "~1.38", features = [...] }
```
### Previous LTS releases
@@ -238,6 +238,7 @@ tokio = { version = "~1.32", features = [...] }
* `1.20.x` - LTS release until September 2023.
* `1.25.x` - LTS release until March 2024.
* `1.32.x` - LTS release until September 2024.
* `1.36.x` - LTS release until March 2025.
## License
+7 -7
View File
@@ -24,21 +24,21 @@ pub enum NotDefinedHere {}
impl mio::event::Source for NotDefinedHere {
fn register(
&mut self,
registry: &mio::Registry,
token: mio::Token,
interests: mio::Interest,
_registry: &mio::Registry,
_token: mio::Token,
_interests: mio::Interest,
) -> std::io::Result<()> {
Ok(())
}
fn reregister(
&mut self,
registry: &mio::Registry,
token: mio::Token,
interests: mio::Interest,
_registry: &mio::Registry,
_token: mio::Token,
_interests: mio::Interest,
) -> std::io::Result<()> {
Ok(())
}
fn deregister(&mut self, registry: &mio::Registry) -> std::io::Result<()> {
fn deregister(&mut self, _registry: &mio::Registry) -> std::io::Result<()> {
Ok(())
}
}
+2 -1
View File
@@ -587,6 +587,7 @@ impl AsyncRead for File {
dst: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
ready!(crate::trace::trace_leaf(cx));
let me = self.get_mut();
let inner = me.inner.get_mut();
@@ -595,7 +596,7 @@ impl AsyncRead for File {
State::Idle(ref mut buf_cell) => {
let mut buf = buf_cell.take().unwrap();
if !buf.is_empty() {
if !buf.is_empty() || dst.remaining() == 0 {
buf.copy_to(dst);
*buf_cell = Some(buf);
return Poll::Ready(Ok(()));
+6 -6
View File
@@ -7,7 +7,7 @@ use std::path::Path;
///
/// This is an async version of [`std::fs::hard_link`].
///
/// The `dst` path will be a link pointing to the `src` path. Note that systems
/// The `link` path will be a link pointing to the `original` path. Note that systems
/// often require these two paths to both be located on the same filesystem.
///
/// # Platform-specific behavior
@@ -23,7 +23,7 @@ use std::path::Path;
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * The `src` path is not a file or doesn't exist.
/// * The `original` path is not a file or doesn't exist.
///
/// # Examples
///
@@ -36,9 +36,9 @@ use std::path::Path;
/// Ok(())
/// }
/// ```
pub async fn hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
let src = src.as_ref().to_owned();
let dst = dst.as_ref().to_owned();
pub async fn hard_link(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
let original = original.as_ref().to_owned();
let link = link.as_ref().to_owned();
asyncify(move || std::fs::hard_link(src, dst)).await
asyncify(move || std::fs::hard_link(original, link)).await
}
+7
View File
@@ -62,6 +62,13 @@ impl Read for MockFile {
impl Read for &'_ MockFile {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
// Placate Miri. Tokio will call this method with an uninitialized
// buffer, which is ok because std::io::Read::read implementations don't usually read
// from their input buffers. But Mockall 0.12-0.13 will try to Debug::fmt the
// buffer, even if there is no failure, triggering an uninitialized data access alert from
// Miri. Initialize the data here just to prevent those Miri alerts.
// This can be removed after upgrading to Mockall 0.14.
dst.fill(0);
self.inner_read(dst)
}
}
+5 -5
View File
@@ -5,12 +5,12 @@ use std::path::Path;
/// Creates a new symbolic link on the filesystem.
///
/// The `dst` path will be a symbolic link pointing to the `src` path.
/// The `link` path will be a symbolic link pointing to the `original` path.
///
/// This is an async version of [`std::os::unix::fs::symlink`].
pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
let src = src.as_ref().to_owned();
let dst = dst.as_ref().to_owned();
pub async fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
let original = original.as_ref().to_owned();
let link = link.as_ref().to_owned();
asyncify(move || std::os::unix::fs::symlink(src, dst)).await
asyncify(move || std::os::unix::fs::symlink(original, link)).await
}
+5 -5
View File
@@ -5,15 +5,15 @@ use std::path::Path;
/// Creates a new directory symlink on the filesystem.
///
/// The `dst` path will be a directory symbolic link pointing to the `src`
/// The `link` path will be a directory symbolic link pointing to the `original`
/// path.
///
/// This is an async version of [`std::os::windows::fs::symlink_dir`][std]
///
/// [std]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html
pub async fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
let src = src.as_ref().to_owned();
let dst = dst.as_ref().to_owned();
pub async fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
let original = original.as_ref().to_owned();
let link = link.as_ref().to_owned();
asyncify(move || std::os::windows::fs::symlink_dir(src, dst)).await
asyncify(move || std::os::windows::fs::symlink_dir(original, link)).await
}
+5 -5
View File
@@ -5,15 +5,15 @@ use std::path::Path;
/// Creates a new file symbolic link on the filesystem.
///
/// The `dst` path will be a file symbolic link pointing to the `src`
/// The `link` path will be a file symbolic link pointing to the `original`
/// path.
///
/// This is an async version of [`std::os::windows::fs::symlink_file`][std]
///
/// [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();
pub async fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
let original = original.as_ref().to_owned();
let link = link.as_ref().to_owned();
asyncify(move || std::os::windows::fs::symlink_file(src, dst)).await
asyncify(move || std::os::windows::fs::symlink_file(original, link)).await
}
+1 -1
View File
@@ -25,7 +25,7 @@ use std::{io, path::Path};
/// ```
pub async fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> io::Result<()> {
let path = path.as_ref().to_owned();
let contents = contents.as_ref().to_owned();
let contents = crate::util::as_ref::upgrade(contents);
asyncify(move || std::fs::write(path, contents)).await
}
+3 -3
View File
@@ -84,15 +84,15 @@ impl<T: ?Sized + AsyncBufRead + Unpin> AsyncBufRead for &mut T {
impl<P> AsyncBufRead for Pin<P>
where
P: DerefMut + Unpin,
P: DerefMut,
P::Target: AsyncBufRead,
{
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
self.get_mut().as_mut().poll_fill_buf(cx)
crate::util::pin_as_deref_mut(self).poll_fill_buf(cx)
}
fn consume(self: Pin<&mut Self>, amt: usize) {
self.get_mut().as_mut().consume(amt);
crate::util::pin_as_deref_mut(self).consume(amt);
}
}
+2 -2
View File
@@ -80,7 +80,7 @@ impl<T: ?Sized + AsyncRead + Unpin> AsyncRead for &mut T {
impl<P> AsyncRead for Pin<P>
where
P: DerefMut + Unpin,
P: DerefMut,
P::Target: AsyncRead,
{
fn poll_read(
@@ -88,7 +88,7 @@ where
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
self.get_mut().as_mut().poll_read(cx, buf)
crate::util::pin_as_deref_mut(self).poll_read(cx, buf)
}
}
+3 -3
View File
@@ -68,15 +68,15 @@ impl<T: ?Sized + AsyncSeek + Unpin> AsyncSeek for &mut T {
impl<P> AsyncSeek for Pin<P>
where
P: DerefMut + Unpin,
P: DerefMut,
P::Target: AsyncSeek,
{
fn start_seek(self: Pin<&mut Self>, pos: SeekFrom) -> io::Result<()> {
self.get_mut().as_mut().start_seek(pos)
crate::util::pin_as_deref_mut(self).start_seek(pos)
}
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
self.get_mut().as_mut().poll_complete(cx)
crate::util::pin_as_deref_mut(self).poll_complete(cx)
}
}
+5 -5
View File
@@ -224,7 +224,7 @@ impl<T: ?Sized + AsyncWrite + Unpin> AsyncWrite for &mut T {
impl<P> AsyncWrite for Pin<P>
where
P: DerefMut + Unpin,
P: DerefMut,
P::Target: AsyncWrite,
{
fn poll_write(
@@ -232,7 +232,7 @@ where
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
self.get_mut().as_mut().poll_write(cx, buf)
crate::util::pin_as_deref_mut(self).poll_write(cx, buf)
}
fn poll_write_vectored(
@@ -240,7 +240,7 @@ where
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<io::Result<usize>> {
self.get_mut().as_mut().poll_write_vectored(cx, bufs)
crate::util::pin_as_deref_mut(self).poll_write_vectored(cx, bufs)
}
fn is_write_vectored(&self) -> bool {
@@ -248,11 +248,11 @@ where
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.get_mut().as_mut().poll_flush(cx)
crate::util::pin_as_deref_mut(self).poll_flush(cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.get_mut().as_mut().poll_shutdown(cx)
crate::util::pin_as_deref_mut(self).poll_shutdown(cx)
}
}
+9 -1
View File
@@ -125,7 +125,7 @@ impl<E: Source> PollEvented<E> {
}
/// Returns a reference to the registration.
#[cfg(any(feature = "net", all(feature = "process", target_os = "linux")))]
#[cfg(feature = "net")]
pub(crate) fn registration(&self) -> &Registration {
&self.registration
}
@@ -138,6 +138,14 @@ impl<E: Source> PollEvented<E> {
Ok(inner)
}
#[cfg(all(feature = "process", target_os = "linux"))]
pub(crate) fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.registration
.poll_read_ready(cx)
.map_err(io::Error::from)
.map_ok(|_| ())
}
/// Re-register under new runtime with `interest`.
#[cfg(all(feature = "process", target_os = "linux"))]
pub(crate) fn reregister(&mut self, interest: Interest) -> io::Result<()> {
+1 -1
View File
@@ -94,7 +94,7 @@ impl CopyBuffer {
feature = "time",
))]
// Keep track of task budget
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
let coop = ready!(crate::task::coop::poll_proceed(cx));
loop {
// If there is some space left in our buffer, then we try to read some
// data to continue, thus maximizing the chances of a large write.
+5 -5
View File
@@ -197,8 +197,8 @@ impl Drop for DuplexStream {
/// ```
/// # async fn ex() -> std::io::Result<()> {
/// # use tokio::io::{AsyncReadExt, AsyncWriteExt};
/// let (writer, reader) = tokio::io::simplex(64);
/// let mut simplex_stream = writer.unsplit(reader);
/// let (reader, writer) = tokio::io::simplex(64);
/// let mut simplex_stream = reader.unsplit(writer);
/// simplex_stream.write_all(b"hello").await?;
///
/// let mut buf = [0u8; 5];
@@ -332,7 +332,7 @@ impl AsyncRead for SimplexStream {
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
ready!(crate::trace::trace_leaf(cx));
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
let coop = ready!(crate::task::coop::poll_proceed(cx));
let ret = self.poll_read_internal(cx, buf);
if ret.is_ready() {
@@ -362,7 +362,7 @@ impl AsyncWrite for SimplexStream {
buf: &[u8],
) -> Poll<std::io::Result<usize>> {
ready!(crate::trace::trace_leaf(cx));
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
let coop = ready!(crate::task::coop::poll_proceed(cx));
let ret = self.poll_write_internal(cx, buf);
if ret.is_ready() {
@@ -390,7 +390,7 @@ impl AsyncWrite for SimplexStream {
bufs: &[std::io::IoSlice<'_>],
) -> Poll<Result<usize, std::io::Error>> {
ready!(crate::trace::trace_leaf(cx));
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
let coop = ready!(crate::task::coop::poll_proceed(cx));
let ret = self.poll_write_vectored_internal(cx, bufs);
if ret.is_ready() {
+1 -1
View File
@@ -88,7 +88,7 @@ cfg_io_util! {
cfg_coop! {
fn poll_proceed_and_make_progress(cx: &mut std::task::Context<'_>) -> std::task::Poll<()> {
let coop = std::task::ready!(crate::runtime::coop::poll_proceed(cx));
let coop = std::task::ready!(crate::task::coop::poll_proceed(cx));
coop.made_progress();
std::task::Poll::Ready(())
}
+1 -2
View File
@@ -41,7 +41,6 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
use crate::io::ReadBuf;
use std::mem::MaybeUninit;
let me = self.project();
@@ -51,7 +50,7 @@ where
let n = {
let dst = me.buf.chunk_mut();
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
let dst = unsafe { dst.as_uninit_slice_mut() };
let mut buf = ReadBuf::uninit(dst);
let ptr = buf.filled().as_ptr();
ready!(Pin::new(me.reader).poll_read(cx, &mut buf)?);
+5 -6
View File
@@ -1,4 +1,3 @@
#![allow(unknown_lints, unexpected_cfgs)]
#![allow(
clippy::cognitive_complexity,
clippy::large_enum_variant,
@@ -303,7 +302,7 @@
//! }
//! ```
//!
//! ## Feature flags
//! # Feature flags
//!
//! Tokio uses a set of [feature flags] to reduce the amount of compiled code. It
//! is possible to just enable certain features over others. By default, Tokio
@@ -341,7 +340,7 @@
//! _Note: `AsyncRead` and `AsyncWrite` traits do not require any features and are
//! always available._
//!
//! ### Unstable features
//! ## Unstable features
//!
//! Some feature flags are only available when specifying the `tokio_unstable` flag:
//!
@@ -393,7 +392,7 @@
//! [unstable features]: https://internals.rust-lang.org/t/feature-request-unstable-opt-in-non-transitive-crate-features/16193#why-not-a-crate-feature-2
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section
//!
//! ## Supported platforms
//! # Supported platforms
//!
//! Tokio currently guarantees support for the following platforms:
//!
@@ -419,7 +418,7 @@
//!
//! [mio-supported]: https://crates.io/crates/mio#platforms
//!
//! ### `WASM` support
//! ## `WASM` support
//!
//! Tokio has some limited support for the `WASM` platform. Without the
//! `tokio_unstable` flag, the following features are supported:
@@ -441,7 +440,7 @@
//! immediately instead of blocking forever. On platforms that don't support
//! time, this means that the runtime can never be idle in any way.
//!
//! ### Unstable `WASM` support
//! ## Unstable `WASM` support
//!
//! Tokio also has unstable support for some additional `WASM` features. This
//! requires the use of the `tokio_unstable` flag.
@@ -1,10 +1,10 @@
use super::AtomicU64;
use crate::loom::sync::{atomic::Ordering, Mutex};
use crate::util::once_cell::OnceCell;
use std::sync::OnceLock;
pub(crate) struct StaticAtomicU64 {
init: u64,
cell: OnceCell<Mutex<u64>>,
cell: OnceLock<Mutex<u64>>,
}
impl AtomicU64 {
@@ -19,7 +19,7 @@ impl StaticAtomicU64 {
pub(crate) const fn new(val: u64) -> StaticAtomicU64 {
StaticAtomicU64 {
init: val,
cell: OnceCell::new(),
cell: OnceLock::new(),
}
}
@@ -52,6 +52,6 @@ impl StaticAtomicU64 {
}
fn inner(&self) -> &Mutex<u64> {
self.cell.get(|| Mutex::new(self.init))
self.cell.get_or_init(|| Mutex::new(self.init))
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::{self, MutexGuard, TryLockError};
/// Adapter for `std::Mutex` that removes the poisoning aspects
/// from its api.
/// from its API.
#[derive(Debug)]
pub(crate) struct Mutex<T: ?Sized>(sync::Mutex<T>);
+14
View File
@@ -602,3 +602,17 @@ macro_rules! cfg_is_wasm_not_wasi {
)*
}
}
/// Use this macro to provide two different implementations of the same API — one for stable
/// builds and one for unstable builds.
macro_rules! cfg_metrics_variant {
(stable: {$($stable_code:tt)*}, unstable: {$($unstable_code:tt)*}) => {
cfg_not_unstable_metrics! {
$($stable_code)*
}
cfg_unstable_metrics! {
$($unstable_code)*
}
}
}
+151
View File
@@ -398,6 +398,153 @@ macro_rules! doc {
/// }
/// }
/// ```
/// # Alternatives from the Ecosystem
///
/// The `select!` macro is a powerful tool for managing multiple asynchronous
/// branches, enabling tasks to run concurrently within the same thread. However,
/// its use can introduce challenges, particularly around cancellation safety, which
/// can lead to subtle and hard-to-debug errors. For many use cases, ecosystem
/// alternatives may be preferable as they mitigate these concerns by offering
/// clearer syntax, more predictable control flow, and reducing the need to manually
/// handle issues like fuse semantics or cancellation safety.
///
/// ## Merging Streams
///
/// For cases where `loop { select! { ... } }` is used to poll multiple tasks,
/// stream merging offers a concise alternative, inherently handle cancellation-safe
/// processing, removing the risk of data loss. Libraries such as [`tokio_stream`],
/// [`futures::stream`] and [`futures_concurrency`] provide tools for merging
/// streams and handling their outputs sequentially.
///
/// [`tokio_stream`]: https://docs.rs/tokio-stream/latest/tokio_stream/
/// [`futures::stream`]: https://docs.rs/futures/latest/futures/stream/
/// [`futures_concurrency`]: https://docs.rs/futures-concurrency/latest/futures_concurrency/
///
/// ### Example with `select!`
///
/// ```
/// struct File;
/// struct Channel;
/// struct Socket;
///
/// impl Socket {
/// async fn read_packet(&mut self) -> Vec<u8> {
/// vec![]
/// }
/// }
///
/// async fn read_send(_file: &mut File, _channel: &mut Channel) {
/// // do work that is not cancel safe
/// }
///
/// #[tokio::main]
/// async fn main() {
/// // open our IO types
/// let mut file = File;
/// let mut channel = Channel;
/// let mut socket = Socket;
///
/// loop {
/// tokio::select! {
/// _ = read_send(&mut file, &mut channel) => { /* ... */ },
/// _data = socket.read_packet() => { /* ... */ }
/// _ = futures::future::ready(()) => break
/// }
/// }
/// }
///
/// ```
///
/// ### Moving to `merge`
///
/// By using merge, you can unify multiple asynchronous tasks into a single stream,
/// eliminating the need to manage tasks manually and reducing the risk of
/// unintended behavior like data loss.
///
/// ```
/// use std::pin::pin;
///
/// use futures::stream::unfold;
/// use tokio_stream::StreamExt;
///
/// struct File;
/// struct Channel;
/// struct Socket;
///
/// impl Socket {
/// async fn read_packet(&mut self) -> Vec<u8> {
/// vec![]
/// }
/// }
///
/// async fn read_send(_file: &mut File, _channel: &mut Channel) {
/// // do work that is not cancel safe
/// }
///
/// enum Message {
/// Stop,
/// Sent,
/// Data(Vec<u8>),
/// }
///
/// #[tokio::main]
/// async fn main() {
/// // open our IO types
/// let file = File;
/// let channel = Channel;
/// let socket = Socket;
///
/// let a = unfold((file, channel), |(mut file, mut channel)| async {
/// read_send(&mut file, &mut channel).await;
/// Some((Message::Sent, (file, channel)))
/// });
/// let b = unfold(socket, |mut socket| async {
/// let data = socket.read_packet().await;
/// Some((Message::Data(data), socket))
/// });
/// let c = tokio_stream::iter([Message::Stop]);
///
/// let mut s = pin!(a.merge(b).merge(c));
/// while let Some(msg) = s.next().await {
/// match msg {
/// Message::Data(_data) => { /* ... */ }
/// Message::Sent => continue,
/// Message::Stop => break,
/// }
/// }
/// }
/// ```
///
/// ## Racing Futures
///
/// If you need to wait for the first completion among several asynchronous tasks,
/// ecosystem utilities such as
/// [`futures`](https://docs.rs/futures/latest/futures/),
/// [`futures-lite`](https://docs.rs/futures-lite/latest/futures_lite/) or
/// [`futures-concurrency`](https://docs.rs/futures-concurrency/latest/futures_concurrency/)
/// provide streamlined syntax for racing futures:
///
/// - [`futures_concurrency::future::Race`](https://docs.rs/futures-concurrency/latest/futures_concurrency/future/trait.Race.html)
/// - [`futures::select`](https://docs.rs/futures/latest/futures/macro.select.html)
/// - [`futures::stream::select_all`](https://docs.rs/futures/latest/futures/stream/select_all/index.html) (for streams)
/// - [`futures_lite::future::or`](https://docs.rs/futures-lite/latest/futures_lite/future/fn.or.html)
/// - [`futures_lite::future::race`](https://docs.rs/futures-lite/latest/futures_lite/future/fn.race.html)
///
/// ```
/// use futures_concurrency::future::Race;
///
/// #[tokio::main]
/// async fn main() {
/// let task_a = async { Ok("ok") };
/// let task_b = async { Err("error") };
/// let result = (task_a, task_b).race().await;
///
/// match result {
/// Ok(output) => println!("First task completed with: {output}"),
/// Err(err) => eprintln!("Error occurred: {err}"),
/// }
/// }
/// ```
#[macro_export]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
$select
@@ -513,6 +660,10 @@ doc! {macro_rules! select {
let mut futures = &mut futures;
$crate::macros::support::poll_fn(|cx| {
// Return `Pending` when the task budget is depleted since budget-aware futures
// are going to yield anyway and other futures will not cooperate.
::std::task::ready!($crate::macros::support::poll_budget_available(cx));
// Track if any branch returns pending. If no branch completes
// **or** returns pending, this implies that all branches are
// disabled.
+17 -1
View File
@@ -7,8 +7,24 @@ cfg_macros! {
pub fn thread_rng_n(n: u32) -> u32 {
crate::runtime::context::thread_rng_n(n)
}
cfg_coop! {
#[doc(hidden)]
#[inline]
pub fn poll_budget_available(cx: &mut Context<'_>) -> Poll<()> {
crate::task::coop::poll_budget_available(cx)
}
}
cfg_not_coop! {
#[doc(hidden)]
#[inline]
pub fn poll_budget_available(_: &mut Context<'_>) -> Poll<()> {
Poll::Ready(())
}
}
}
pub use std::future::{Future, IntoFuture};
pub use std::pin::Pin;
pub use std::task::Poll;
pub use std::task::{Context, Poll};
+8 -2
View File
@@ -1,5 +1,6 @@
use crate::io::{Interest, PollEvented};
use crate::net::tcp::TcpStream;
use crate::util::check_socket_for_blocking;
cfg_not_wasi! {
use crate::net::{to_socket_addrs, ToSocketAddrs};
@@ -83,13 +84,12 @@ impl TcpListener {
/// # Examples
///
/// ```no_run
/// # if cfg!(miri) { return } // No `socket` in miri.
/// use tokio::net::TcpListener;
///
/// use std::io;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// let listener = TcpListener::bind("127.0.0.1:2345").await?;
///
/// // use the listener
@@ -209,6 +209,10 @@ impl TcpListener {
/// will block the thread, which will cause unexpected behavior.
/// Non-blocking mode can be set using [`set_nonblocking`].
///
/// Passing a listener in blocking mode is always erroneous,
/// and the behavior in that case may change in the future.
/// For example, it could panic.
///
/// [`set_nonblocking`]: std::net::TcpListener::set_nonblocking
///
/// # Examples
@@ -236,6 +240,8 @@ impl TcpListener {
/// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function.
#[track_caller]
pub fn from_std(listener: net::TcpListener) -> io::Result<TcpListener> {
check_socket_for_blocking(&listener)?;
let io = mio::net::TcpListener::from_std(listener);
let io = PollEvented::new(io)?;
Ok(TcpListener { io })
+1 -1
View File
@@ -743,12 +743,12 @@ impl TcpSocket {
/// # Examples
///
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// use tokio::net::TcpSocket;
/// use socket2::{Domain, Socket, Type};
///
/// #[tokio::main]
/// async fn main() -> std::io::Result<()> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// let socket2_socket = Socket::new(Domain::IPV4, Type::STREAM, None)?;
/// socket2_socket.set_nonblocking(true)?;
///
+8 -1
View File
@@ -7,6 +7,7 @@ cfg_not_wasi! {
use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf, Ready};
use crate::net::tcp::split::{split, ReadHalf, WriteHalf};
use crate::net::tcp::split_owned::{split_owned, OwnedReadHalf, OwnedWriteHalf};
use crate::util::check_socket_for_blocking;
use std::fmt;
use std::io;
@@ -173,6 +174,10 @@ impl TcpStream {
/// will block the thread, which will cause unexpected behavior.
/// Non-blocking mode can be set using [`set_nonblocking`].
///
/// Passing a listener in blocking mode is always erroneous,
/// and the behavior in that case may change in the future.
/// For example, it could panic.
///
/// [`set_nonblocking`]: std::net::TcpStream::set_nonblocking
///
/// # Examples
@@ -200,6 +205,8 @@ impl TcpStream {
/// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function.
#[track_caller]
pub fn from_std(stream: std::net::TcpStream) -> io::Result<TcpStream> {
check_socket_for_blocking(&stream)?;
let io = mio::net::TcpStream::from_std(stream);
let io = PollEvented::new(io)?;
Ok(TcpStream { io })
@@ -213,7 +220,6 @@ impl TcpStream {
/// # Examples
///
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// use std::error::Error;
/// use std::io::Read;
/// use tokio::net::TcpListener;
@@ -222,6 +228,7 @@ impl TcpStream {
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// let mut data = [0u8; 12];
/// # if false {
/// let listener = TcpListener::bind("127.0.0.1:34254").await?;
+12 -5
View File
@@ -1,5 +1,6 @@
use crate::io::{Interest, PollEvented, ReadBuf, Ready};
use crate::net::{to_socket_addrs, ToSocketAddrs};
use crate::util::check_socket_for_blocking;
use std::fmt;
use std::io;
@@ -134,12 +135,12 @@ impl UdpSocket {
/// # Example
///
/// ```no_run
/// # if cfg!(miri) { return } // No `socket` in miri.
/// use tokio::net::UdpSocket;
/// use std::io;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// let sock = UdpSocket::bind("0.0.0.0:8080").await?;
/// // use `sock`
/// # let _ = sock;
@@ -192,6 +193,10 @@ impl UdpSocket {
/// will block the thread, which will cause unexpected behavior.
/// Non-blocking mode can be set using [`set_nonblocking`].
///
/// Passing a listener in blocking mode is always erroneous,
/// and the behavior in that case may change in the future.
/// For example, it could panic.
///
/// [`set_nonblocking`]: std::net::UdpSocket::set_nonblocking
///
/// # Panics
@@ -220,6 +225,8 @@ impl UdpSocket {
/// ```
#[track_caller]
pub fn from_std(socket: net::UdpSocket) -> io::Result<UdpSocket> {
check_socket_for_blocking(&socket)?;
let io = mio::net::UdpSocket::from_std(socket);
UdpSocket::new(io)
}
@@ -296,12 +303,12 @@ impl UdpSocket {
/// # Example
///
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// use tokio::net::UdpSocket;
///
/// # use std::{io, net::SocketAddr};
/// # #[tokio::main]
/// # async fn main() -> io::Result<()> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// let addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
/// let peer = "127.0.0.1:11100".parse::<SocketAddr>().unwrap();
/// let sock = UdpSocket::bind(addr).await?;
@@ -1164,8 +1171,8 @@ impl UdpSocket {
/// Ok(())
/// }
/// ```
pub async fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], target: A) -> io::Result<usize> {
let mut addrs = to_socket_addrs(target).await?;
pub async fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> io::Result<usize> {
let mut addrs = to_socket_addrs(addr).await?;
match addrs.next() {
Some(target) => self.send_to_addr(buf, target).await,
@@ -2123,12 +2130,12 @@ impl UdpSocket {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// use tokio::net::UdpSocket;
/// use std::io;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// // Create a socket
/// let socket = UdpSocket::bind("0.0.0.0:8080").await?;
///
+26 -19
View File
@@ -1,5 +1,6 @@
use crate::io::{Interest, PollEvented, ReadBuf, Ready};
use crate::net::unix::SocketAddr;
use crate::util::check_socket_for_blocking;
use std::fmt;
use std::io;
@@ -35,10 +36,10 @@ cfg_net_unix! {
/// # Examples
/// Using named sockets, associated with a filesystem path:
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -68,10 +69,10 @@ cfg_net_unix! {
///
/// Using unnamed sockets, created as a pair
/// ```
/// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No SOCK_DGRAM for `socketpair` in miri.
/// use tokio::net::UnixDatagram;
///
/// // Create the pair of sockets
@@ -373,10 +374,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -406,10 +407,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No SOCK_DGRAM for `socketpair` in miri.
/// use tokio::net::UnixDatagram;
///
/// // Create the pair of sockets
@@ -449,6 +450,10 @@ impl UnixDatagram {
/// will block the thread, which will cause unexpected behavior.
/// Non-blocking mode can be set using [`set_nonblocking`].
///
/// Passing a listener in blocking mode is always erroneous,
/// and the behavior in that case may change in the future.
/// For example, it could panic.
///
/// [`set_nonblocking`]: std::os::unix::net::UnixDatagram::set_nonblocking
///
/// # Panics
@@ -461,10 +466,10 @@ impl UnixDatagram {
/// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function.
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use std::os::unix::net::UnixDatagram as StdUDS;
/// use tempfile::tempdir;
@@ -484,6 +489,8 @@ impl UnixDatagram {
/// ```
#[track_caller]
pub fn from_std(datagram: net::UnixDatagram) -> io::Result<UnixDatagram> {
check_socket_for_blocking(&datagram)?;
let socket = mio::net::UnixDatagram::from_std(datagram);
let io = PollEvented::new(socket)?;
Ok(UnixDatagram { io })
@@ -526,10 +533,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -566,10 +573,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -611,10 +618,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No SOCK_DGRAM for `socketpair` in miri.
/// use tokio::net::UnixDatagram;
///
/// // Create the pair of sockets
@@ -742,10 +749,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No SOCK_DGRAM for `socketpair` in miri.
/// use tokio::net::UnixDatagram;
///
/// // Create the pair of sockets
@@ -893,10 +900,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -1010,10 +1017,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No SOCK_DGRAM for `socketpair` in miri.
/// use tokio::net::UnixDatagram;
///
/// // Create the pair of sockets
@@ -1061,10 +1068,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -1112,10 +1119,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -1429,10 +1436,10 @@ impl UnixDatagram {
/// # Examples
/// For a socket bound to a local path
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -1452,10 +1459,10 @@ impl UnixDatagram {
///
/// For an unbound socket
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
///
/// // Create an unbound socket
@@ -1477,10 +1484,10 @@ impl UnixDatagram {
/// # Examples
/// For a peer with a local path
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
/// use tempfile::tempdir;
///
@@ -1503,10 +1510,10 @@ impl UnixDatagram {
///
/// For an unbound peer
/// ```
/// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No SOCK_DGRAM for `socketpair` in miri.
/// use tokio::net::UnixDatagram;
///
/// // Create the pair of sockets
@@ -1525,10 +1532,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// use tokio::net::UnixDatagram;
///
/// // Create an unbound socket
@@ -1553,10 +1560,10 @@ impl UnixDatagram {
///
/// # Examples
/// ```
/// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri.
/// # use std::error::Error;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No SOCK_DGRAM for `socketpair` in miri.
/// use tokio::net::UnixDatagram;
/// use std::net::Shutdown;
///
+7
View File
@@ -1,5 +1,6 @@
use crate::io::{Interest, PollEvented};
use crate::net::unix::{SocketAddr, UnixStream};
use crate::util::check_socket_for_blocking;
use std::fmt;
use std::io;
@@ -106,6 +107,10 @@ impl UnixListener {
/// will block the thread, which will cause unexpected behavior.
/// Non-blocking mode can be set using [`set_nonblocking`].
///
/// Passing a listener in blocking mode is always erroneous,
/// and the behavior in that case may change in the future.
/// For example, it could panic.
///
/// [`set_nonblocking`]: std::os::unix::net::UnixListener::set_nonblocking
///
/// # Examples
@@ -133,6 +138,8 @@ impl UnixListener {
/// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function.
#[track_caller]
pub fn from_std(listener: net::UnixListener) -> io::Result<UnixListener> {
check_socket_for_blocking(&listener)?;
let listener = mio::net::UnixListener::from_std(listener);
let io = PollEvented::new(listener)?;
Ok(UnixListener { io })
+8 -1
View File
@@ -3,6 +3,7 @@ use crate::net::unix::split::{split, ReadHalf, WriteHalf};
use crate::net::unix::split_owned::{split_owned, OwnedReadHalf, OwnedWriteHalf};
use crate::net::unix::ucred::{self, UCred};
use crate::net::unix::SocketAddr;
use crate::util::check_socket_for_blocking;
use std::fmt;
use std::future::poll_fn;
@@ -791,6 +792,10 @@ impl UnixStream {
/// will block the thread, which will cause unexpected behavior.
/// Non-blocking mode can be set using [`set_nonblocking`].
///
/// Passing a listener in blocking mode is always erroneous,
/// and the behavior in that case may change in the future.
/// For example, it could panic.
///
/// [`set_nonblocking`]: std::os::unix::net::UnixStream::set_nonblocking
///
/// # Examples
@@ -818,6 +823,8 @@ impl UnixStream {
/// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function.
#[track_caller]
pub fn from_std(stream: net::UnixStream) -> io::Result<UnixStream> {
check_socket_for_blocking(&stream)?;
let stream = mio::net::UnixStream::from_std(stream);
let io = PollEvented::new(stream)?;
@@ -833,7 +840,6 @@ impl UnixStream {
/// # Examples
///
/// ```
/// # if cfg!(miri) { return } // No `socket` in miri.
/// use std::error::Error;
/// use std::io::Read;
/// use tokio::net::UnixListener;
@@ -842,6 +848,7 @@ impl UnixStream {
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn Error>> {
/// # if cfg!(miri) { return Ok(()); } // No `socket` in miri.
/// let dir = tempfile::tempdir().unwrap();
/// let bind_path = dir.path().join("bind_path");
///
+164 -11
View File
@@ -249,7 +249,7 @@ use std::future::Future;
use std::io;
use std::path::Path;
use std::pin::Pin;
use std::process::{Command as StdCommand, ExitStatus, Output, Stdio};
use std::process::{Child as StdChild, Command as StdCommand, ExitStatus, Output, Stdio};
use std::task::{ready, Context, Poll};
#[cfg(unix)]
@@ -810,7 +810,6 @@ impl Command {
/// Basic usage:
///
/// ```no_run
/// # if cfg!(miri) { return } // No `pidfd_spawnp` in miri.
/// use tokio::process::Command;
///
/// async fn run_ls() -> std::process::ExitStatus {
@@ -860,8 +859,98 @@ impl Command {
/// On Unix platforms this method will fail with `std::io::ErrorKind::WouldBlock`
/// if the system process limit is reached (which includes other applications
/// running on the system).
#[inline]
pub fn spawn(&mut self) -> io::Result<Child> {
imp::spawn_child(&mut self.std).map(|spawned_child| Child {
// On two lines to circumvent a mutable borrow check failure.
let child = self.std.spawn()?;
self.build_child(child)
}
/// Executes the command as a child process with a custom spawning function,
/// returning a handle to it.
///
/// This is identical to [`Self::spawn`] in every aspect except the spawn:
/// here, it is customizable through the `with` parameter instead of
/// defaulting to the usual spawn. In fact, [`Self::spawn`] is just
/// [`Self::spawn_with`] with [`StdCommand::spawn`].
///
/// This is useful mostly under Windows for now, since the platform exposes
/// special APIs to configure child processes when spawning them with various
/// attributes that customize the exact behavior of the spawn operation.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// # async fn test() { // allow using await
/// use std::process::Stdio;
///
/// let output = tokio::process::Command::new("ls")
/// .stdin(Stdio::null())
/// .stdout(Stdio::piped())
/// .stderr(Stdio::piped())
/// .spawn_with(std::process::Command::spawn)
/// .unwrap()
/// .wait_with_output()
/// .await
/// .unwrap();
/// # }
/// ```
///
/// Actually customizing the spawn under Windows:
///
/// ```ignore
/// #![feature(windows_process_extensions_raw_attribute)]
/// # #[cfg(windows)] // Windows-only nightly APIs are used here.
/// # async fn test() { // Allow using await.
/// use std::os::windows::process::{CommandExt, ProcThreadAttributeList};
/// use std::process::Stdio;
/// use tokio::process::Command;
///
/// let parent = Command::new("cmd").spawn().unwrap();
/// let parent_process_handle = parent.raw_handle();
///
/// const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: usize = 0x00020000;
/// let attribute_list = ProcThreadAttributeList::build()
/// .attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_process_handle)
/// .finish()
/// .unwrap();
///
/// let _output = Command::new("ls")
/// .stdin(Stdio::null())
/// .stdout(Stdio::piped())
/// .stderr(Stdio::piped())
/// .spawn_with(|cmd| cmd.spawn_with_attributes(&attribute_list))
/// .unwrap()
/// .wait_with_output()
/// .await
/// .unwrap();
/// # }
/// ```
#[cfg(tokio_unstable)]
#[cfg_attr(docsrs, doc(cfg(tokio_unstable)))]
#[inline]
pub fn spawn_with(
&mut self,
with: impl Fn(&mut StdCommand) -> io::Result<StdChild>,
) -> io::Result<Child> {
// On two lines to circumvent a mutable borrow check failure.
let child = with(&mut self.std)?;
self.build_child(child)
}
/// Small indirection for the spawn implementations.
///
/// This is introduced for [`Self::spawn`] and [`Self::spawn_with`] to use:
/// [`Self::spawn`] cannot depend directly on on [`Self::spawn_with`] since
/// it is behind `tokio_unstable`. It also serves as a way to reduce
/// monomorphization bloat by taking in an already-spawned child process
/// instead of a command and custom spawn function.
fn build_child(&self, child: StdChild) -> io::Result<Child> {
let spawned_child = imp::build_child(child)?;
Ok(Child {
child: FusedChild::Child(ChildDropGuard {
inner: spawned_child.child,
kill_on_drop: self.kill_on_drop,
@@ -981,6 +1070,26 @@ impl Command {
async { child?.wait_with_output().await }
}
/// Returns the boolean value that was previously set by [`Command::kill_on_drop`].
///
/// Note that if you have not previously called [`Command::kill_on_drop`], the
/// default value of `false` will be returned here.
///
/// # Examples
///
/// ```
/// use tokio::process::Command;
///
/// let mut cmd = Command::new("echo");
/// assert!(!cmd.get_kill_on_drop());
///
/// cmd.kill_on_drop(true);
/// assert!(cmd.get_kill_on_drop());
/// ```
pub fn get_kill_on_drop(&self) -> bool {
self.kill_on_drop
}
}
impl From<StdCommand> for Command {
@@ -1028,7 +1137,7 @@ where
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
ready!(crate::trace::trace_leaf(cx));
// Keep track of task budget
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
let coop = ready!(crate::task::coop::poll_proceed(cx));
let ret = Pin::new(&mut self.inner).poll(cx);
@@ -1138,16 +1247,20 @@ impl Child {
pub fn start_kill(&mut self) -> io::Result<()> {
match &mut self.child {
FusedChild::Child(child) => child.kill(),
FusedChild::Done(_) => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid argument: can't kill an exited process",
)),
FusedChild::Done(_) => Ok(()),
}
}
/// Forces the child to exit.
///
/// This is equivalent to sending a `SIGKILL` on unix platforms.
/// This is equivalent to sending a `SIGKILL` on unix platforms
/// followed by [`wait`](Child::wait).
///
/// Note: std version of [`Child::kill`](std::process::Child::kill) does not `wait`.
/// For an equivalent of `Child::kill` in the standard library,
/// use [`start_kill`](Child::start_kill).
///
/// # Examples
///
/// If the child has to be killed remotely, it is possible to do it using
/// a combination of the select! macro and a `oneshot` channel. In the following
@@ -1170,6 +1283,46 @@ impl Child {
/// }
/// }
/// ```
///
/// You can also interact with the child's standard I/O. For example, you can
/// read its stdout while waiting for it to exit.
///
/// ```no_run
/// # use std::process::Stdio;
/// #
/// # use tokio::io::AsyncReadExt;
/// # use tokio::process::Command;
/// # use tokio::sync::oneshot::channel;
///
/// #[tokio::main]
/// async fn main() {
/// let (_tx, rx) = channel::<()>();
///
/// let mut child = Command::new("echo")
/// .arg("Hello World!")
/// .stdout(Stdio::piped())
/// .spawn()
/// .unwrap();
///
/// let mut stdout = child.stdout.take().expect("stdout is not captured");
///
/// let read_stdout = tokio::spawn(async move {
/// let mut buff = Vec::new();
/// let _ = stdout.read_to_end(&mut buff).await;
///
/// buff
/// });
///
/// tokio::select! {
/// _ = child.wait() => {}
/// _ = rx => { child.kill().await.expect("kill failed") },
/// }
///
/// let buff = read_stdout.await.unwrap();
///
/// assert_eq!(buff, b"Hello World!\n");
/// }
/// ```
pub async fn kill(&mut self) -> io::Result<()> {
self.start_kill()?;
self.wait().await?;
@@ -1193,7 +1346,6 @@ impl Child {
/// This function is cancel safe.
///
/// ```
/// # if cfg!(miri) { return } // No `pidfd_spawnp` in miri.
/// # #[cfg(not(unix))]fn main(){}
/// # #[cfg(unix)]
/// use tokio::io::AsyncWriteExt;
@@ -1205,6 +1357,7 @@ impl Child {
/// # #[cfg(unix)]
/// #[tokio::main]
/// async fn main() {
/// # if cfg!(miri) { return; } // No `pidfd_spawnp` in miri.
/// let mut child = Command::new("cat")
/// .stdin(Stdio::piped())
/// .spawn()
@@ -1323,7 +1476,7 @@ impl Child {
/// The standard input stream for spawned children.
///
/// This type implements the `AsyncWrite` trait to pass data to the stdin handle of
/// This type implements the `AsyncWrite` trait to pass data to the stdin
/// handle of a child process asynchronously.
#[derive(Debug)]
pub struct ChildStdin {
+4 -5
View File
@@ -66,11 +66,11 @@ impl Kill for StdChild {
cfg_not_has_const_mutex_new! {
fn get_orphan_queue() -> &'static OrphanQueueImpl<StdChild> {
use crate::util::once_cell::OnceCell;
use std::sync::OnceLock;
static ORPHAN_QUEUE: OnceCell<OrphanQueueImpl<StdChild>> = OnceCell::new();
static ORPHAN_QUEUE: OnceLock<OrphanQueueImpl<StdChild>> = OnceLock::new();
ORPHAN_QUEUE.get(OrphanQueueImpl::new)
ORPHAN_QUEUE.get_or_init(OrphanQueueImpl::new)
}
}
@@ -115,8 +115,7 @@ impl fmt::Debug for Child {
}
}
pub(crate) fn spawn_child(cmd: &mut std::process::Command) -> io::Result<SpawnedChild> {
let mut child = cmd.spawn()?;
pub(crate) fn build_child(mut child: StdChild) -> io::Result<SpawnedChild> {
let stdin = child.stdin.take().map(stdio).transpose()?;
let stdout = child.stdout.take().map(stdio).transpose()?;
let stderr = child.stderr.take().map(stdio).transpose()?;
+11 -15
View File
@@ -19,7 +19,7 @@ use std::{
pin::Pin,
process::ExitStatus,
sync::atomic::{AtomicBool, Ordering::Relaxed},
task::{Context, Poll},
task::{ready, Context, Poll},
};
#[derive(Debug)]
@@ -117,21 +117,17 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = Pin::into_inner(self);
match this.pidfd.registration().poll_read_ready(cx) {
Poll::Ready(Ok(evt)) => {
if let Some(exit_code) = this.inner.try_wait()? {
return Poll::Ready(Ok(exit_code));
}
this.pidfd.registration().clear_readiness(evt);
match ready!(this.pidfd.poll_read_ready(cx)) {
Err(err) if is_rt_shutdown_err(&err) => {
this.pidfd.reregister(Interest::READABLE)?;
ready!(this.pidfd.poll_read_ready(cx))?
}
Poll::Ready(Err(err)) if is_rt_shutdown_err(&err) => {}
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
Poll::Pending => return Poll::Pending,
};
this.pidfd.reregister(Interest::READABLE)?;
cx.waker().wake_by_ref();
Poll::Pending
res => res?,
}
Poll::Ready(Ok(this
.inner
.try_wait()?
.expect("pidfd is ready to read, the process should have exited")))
}
}
+2 -3
View File
@@ -27,7 +27,7 @@ use std::io;
use std::os::windows::prelude::{AsRawHandle, IntoRawHandle, OwnedHandle, RawHandle};
use std::pin::Pin;
use std::process::Stdio;
use std::process::{Child as StdChild, Command as StdCommand, ExitStatus};
use std::process::{Child as StdChild, ExitStatus};
use std::sync::Arc;
use std::task::{Context, Poll};
@@ -66,8 +66,7 @@ struct Waiting {
unsafe impl Sync for Waiting {}
unsafe impl Send for Waiting {}
pub(crate) fn spawn_child(cmd: &mut StdCommand) -> io::Result<SpawnedChild> {
let mut child = cmd.spawn()?;
pub(crate) fn build_child(mut child: StdChild) -> io::Result<SpawnedChild> {
let stdin = child.stdin.take().map(stdio).transpose()?;
let stdout = child.stdout.take().map(stdio).transpose()?;
let stderr = child.stderr.take().map(stdio).transpose()?;
+1 -1
View File
@@ -128,7 +128,7 @@ pub(crate) struct Task {
#[derive(PartialEq, Eq)]
pub(crate) enum Mandatory {
#[cfg_attr(not(fs), allow(dead_code))]
#[cfg_attr(not(feature = "fs"), allow(dead_code))]
Mandatory,
NonMandatory,
}
-4
View File
@@ -26,8 +26,6 @@ impl BlockingSchedule {
}
#[cfg(feature = "rt-multi-thread")]
scheduler::Handle::MultiThread(_) => {}
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
scheduler::Handle::MultiThreadAlt(_) => {}
}
}
BlockingSchedule {
@@ -51,8 +49,6 @@ impl task::Schedule for BlockingSchedule {
}
#[cfg(feature = "rt-multi-thread")]
scheduler::Handle::MultiThread(_) => {}
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
scheduler::Handle::MultiThreadAlt(_) => {}
}
}
None
+1 -1
View File
@@ -37,7 +37,7 @@ where
// currently goes through Task::poll(), and so is subject to budgeting. That isn't really
// what we want; a blocking task may itself want to run tasks (it might be a Worker!), so
// we want it to start without any budgeting.
crate::runtime::coop::stop();
crate::task::coop::stop();
Poll::Ready(func())
}
+120 -92
View File
@@ -88,6 +88,14 @@ pub struct Builder {
/// To run before each task is spawned.
pub(super) before_spawn: Option<TaskCallback>,
/// To run before each poll
#[cfg(tokio_unstable)]
pub(super) before_poll: Option<TaskCallback>,
/// To run after each poll
#[cfg(tokio_unstable)]
pub(super) after_poll: Option<TaskCallback>,
/// To run after each task is terminated.
pub(super) after_termination: Option<TaskCallback>,
@@ -106,8 +114,6 @@ pub struct Builder {
/// How many ticks before yielding to the driver for timer and I/O events?
pub(super) event_interval: u32,
pub(super) local_queue_capacity: usize,
/// When true, the multi-threade scheduler LIFO slot should not be used.
///
/// This option should only be exposed as unstable.
@@ -214,8 +220,6 @@ pub(crate) enum Kind {
CurrentThread,
#[cfg(feature = "rt-multi-thread")]
MultiThread,
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
MultiThreadAlt,
}
impl Builder {
@@ -247,26 +251,6 @@ impl Builder {
Builder::new(Kind::MultiThread, 61)
}
cfg_unstable! {
/// Returns a new builder with the alternate multi thread scheduler
/// selected.
///
/// The alternate multi threaded scheduler is an in-progress
/// candidate to replace the existing multi threaded scheduler. It
/// currently does not scale as well to 16+ processors.
///
/// This runtime flavor is currently **not considered production
/// ready**.
///
/// Configuration methods can be chained on the return value.
#[cfg(feature = "rt-multi-thread")]
#[cfg_attr(docsrs, doc(cfg(feature = "rt-multi-thread")))]
pub fn new_multi_thread_alt() -> Builder {
// The number `61` is fairly arbitrary. I believe this value was copied from golang.
Builder::new(Kind::MultiThreadAlt, 61)
}
}
/// Returns a new runtime builder initialized with default configuration
/// values.
///
@@ -306,6 +290,11 @@ impl Builder {
before_spawn: None,
after_termination: None,
#[cfg(tokio_unstable)]
before_poll: None,
#[cfg(tokio_unstable)]
after_poll: None,
keep_alive: None,
// Defaults for these values depend on the scheduler kind, so we get them
@@ -313,12 +302,6 @@ impl Builder {
global_queue_interval: None,
event_interval,
#[cfg(not(loom))]
local_queue_capacity: 256,
#[cfg(loom)]
local_queue_capacity: 4,
seed_generator: RngSeedGenerator::new(RngSeed::new()),
#[cfg(tokio_unstable)]
@@ -432,6 +415,15 @@ impl Builder {
///
/// The default value is 512.
///
/// # Queue Behavior
///
/// When a blocking task is submitted, it will be inserted into a queue. If available, one of
/// the idle threads will be notified to run the task. Otherwise, if the threshold set by this
/// method has not been reached, a new thread will be spawned. If no idle thread is available
/// and no more threads are allowed to be spawned, the task will remain in the queue until one
/// of the busy threads pick it up. Note that since the queue does not apply any backpressure,
/// it could potentially grow unbounded.
///
/// # Panics
///
/// This will panic if `val` is not larger than `0`.
@@ -743,6 +735,92 @@ impl Builder {
self
}
/// Executes function `f` just before a task is polled
///
/// `f` is called within the Tokio context, so functions like
/// [`tokio::spawn`](crate::spawn) can be called, and may result in this callback being
/// invoked immediately.
///
/// **Note**: This is an [unstable API][unstable]. The public API of this type
/// may break in 1.x releases. See [the documentation on unstable
/// features][unstable] for details.
///
/// [unstable]: crate#unstable-features
///
/// # Examples
///
/// ```
/// # use std::sync::{atomic::AtomicUsize, Arc};
/// # use tokio::task::yield_now;
/// # pub fn main() {
/// let poll_start_counter = Arc::new(AtomicUsize::new(0));
/// let poll_start = poll_start_counter.clone();
/// let rt = tokio::runtime::Builder::new_multi_thread()
/// .enable_all()
/// .on_before_task_poll(move |meta| {
/// println!("task {} is about to be polled", meta.id())
/// })
/// .build()
/// .unwrap();
/// let task = rt.spawn(async {
/// yield_now().await;
/// });
/// let _ = rt.block_on(task);
///
/// # }
/// ```
#[cfg(tokio_unstable)]
pub fn on_before_task_poll<F>(&mut self, f: F) -> &mut Self
where
F: Fn(&TaskMeta<'_>) + Send + Sync + 'static,
{
self.before_poll = Some(std::sync::Arc::new(f));
self
}
/// Executes function `f` just after a task is polled
///
/// `f` is called within the Tokio context, so functions like
/// [`tokio::spawn`](crate::spawn) can be called, and may result in this callback being
/// invoked immediately.
///
/// **Note**: This is an [unstable API][unstable]. The public API of this type
/// may break in 1.x releases. See [the documentation on unstable
/// features][unstable] for details.
///
/// [unstable]: crate#unstable-features
///
/// # Examples
///
/// ```
/// # use std::sync::{atomic::AtomicUsize, Arc};
/// # use tokio::task::yield_now;
/// # pub fn main() {
/// let poll_stop_counter = Arc::new(AtomicUsize::new(0));
/// let poll_stop = poll_stop_counter.clone();
/// let rt = tokio::runtime::Builder::new_multi_thread()
/// .enable_all()
/// .on_after_task_poll(move |meta| {
/// println!("task {} completed polling", meta.id());
/// })
/// .build()
/// .unwrap();
/// let task = rt.spawn(async {
/// yield_now().await;
/// });
/// let _ = rt.block_on(task);
///
/// # }
/// ```
#[cfg(tokio_unstable)]
pub fn on_after_task_poll<F>(&mut self, f: F) -> &mut Self
where
F: Fn(&TaskMeta<'_>) + Send + Sync + 'static,
{
self.after_poll = Some(std::sync::Arc::new(f));
self
}
/// Executes function `f` just after a task is terminated.
///
/// `f` is called within the Tokio context, so functions like
@@ -812,8 +890,6 @@ impl Builder {
Kind::CurrentThread => self.build_current_thread_runtime(),
#[cfg(feature = "rt-multi-thread")]
Kind::MultiThread => self.build_threaded_runtime(),
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
Kind::MultiThreadAlt => self.build_alt_threaded_runtime(),
}
}
@@ -854,8 +930,6 @@ impl Builder {
Kind::CurrentThread => true,
#[cfg(feature = "rt-multi-thread")]
Kind::MultiThread => false,
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
Kind::MultiThreadAlt => false,
},
enable_io: self.enable_io,
enable_time: self.enable_time,
@@ -1343,14 +1417,6 @@ impl Builder {
}
}
cfg_loom! {
pub(crate) fn local_queue_capacity(&mut self, value: usize) -> &mut Self {
assert!(value.is_power_of_two());
self.local_queue_capacity = value;
self
}
}
fn build_current_thread_runtime(&mut self) -> io::Result<Runtime> {
use crate::runtime::runtime::Scheduler;
@@ -1410,10 +1476,13 @@ impl Builder {
before_park: self.before_park.clone(),
after_unpark: self.after_unpark.clone(),
before_spawn: self.before_spawn.clone(),
#[cfg(tokio_unstable)]
before_poll: self.before_poll.clone(),
#[cfg(tokio_unstable)]
after_poll: self.after_poll.clone(),
after_termination: self.after_termination.clone(),
global_queue_interval: self.global_queue_interval,
event_interval: self.event_interval,
local_queue_capacity: self.local_queue_capacity,
#[cfg(tokio_unstable)]
unhandled_panic: self.unhandled_panic.clone(),
disable_lifo_slot: self.disable_lifo_slot,
@@ -1537,13 +1606,13 @@ cfg_rt_multi_thread! {
use crate::runtime::{Config, runtime::Scheduler};
use crate::runtime::scheduler::{self, MultiThread};
let core_threads = self.worker_threads.unwrap_or_else(num_cpus);
let worker_threads = self.worker_threads.unwrap_or_else(num_cpus);
let (driver, driver_handle) = driver::Driver::new(self.get_cfg(core_threads))?;
let (driver, driver_handle) = driver::Driver::new(self.get_cfg(worker_threads))?;
// Create the blocking pool
let blocking_pool =
blocking::create_blocking_pool(self, self.max_blocking_threads + core_threads);
blocking::create_blocking_pool(self, self.max_blocking_threads + worker_threads);
let blocking_spawner = blocking_pool.spawner().clone();
// Generate a rng seed for this runtime.
@@ -1551,7 +1620,7 @@ cfg_rt_multi_thread! {
let seed_generator_2 = self.seed_generator.next_generator();
let (scheduler, handle, launch) = MultiThread::new(
core_threads,
worker_threads,
driver,
driver_handle,
blocking_spawner,
@@ -1560,10 +1629,13 @@ cfg_rt_multi_thread! {
before_park: self.before_park.clone(),
after_unpark: self.after_unpark.clone(),
before_spawn: self.before_spawn.clone(),
#[cfg(tokio_unstable)]
before_poll: self.before_poll.clone(),
#[cfg(tokio_unstable)]
after_poll: self.after_poll.clone(),
after_termination: self.after_termination.clone(),
global_queue_interval: self.global_queue_interval,
event_interval: self.event_interval,
local_queue_capacity: self.local_queue_capacity,
#[cfg(tokio_unstable)]
unhandled_panic: self.unhandled_panic.clone(),
disable_lifo_slot: self.disable_lifo_slot,
@@ -1580,50 +1652,6 @@ cfg_rt_multi_thread! {
Ok(Runtime::from_parts(Scheduler::MultiThread(scheduler), handle, blocking_pool))
}
cfg_unstable! {
fn build_alt_threaded_runtime(&mut self) -> io::Result<Runtime> {
use crate::loom::sys::num_cpus;
use crate::runtime::{Config, runtime::Scheduler};
use crate::runtime::scheduler::MultiThreadAlt;
let core_threads = self.worker_threads.unwrap_or_else(num_cpus);
let (driver, driver_handle) = driver::Driver::new(self.get_cfg(core_threads))?;
// Create the blocking pool
let blocking_pool =
blocking::create_blocking_pool(self, self.max_blocking_threads + core_threads);
let blocking_spawner = blocking_pool.spawner().clone();
// Generate a rng seed for this runtime.
let seed_generator_1 = self.seed_generator.next_generator();
let seed_generator_2 = self.seed_generator.next_generator();
let (scheduler, handle) = MultiThreadAlt::new(
core_threads,
driver,
driver_handle,
blocking_spawner,
seed_generator_2,
Config {
before_park: self.before_park.clone(),
after_unpark: self.after_unpark.clone(),
before_spawn: self.before_spawn.clone(),
after_termination: self.after_termination.clone(),
global_queue_interval: self.global_queue_interval,
event_interval: self.event_interval,
local_queue_capacity: self.local_queue_capacity,
#[cfg(tokio_unstable)]
unhandled_panic: self.unhandled_panic.clone(),
disable_lifo_slot: self.disable_lifo_slot,
seed_generator: seed_generator_1,
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
},
);
Ok(Runtime::from_parts(Scheduler::MultiThreadAlt(scheduler), handle, blocking_pool))
}
}
}
}
+8 -3
View File
@@ -12,9 +12,6 @@ pub(crate) struct Config {
/// How many ticks before yielding to the driver for timer and I/O events?
pub(crate) event_interval: u32,
/// How big to make each worker's local queue
pub(crate) local_queue_capacity: usize,
/// Callback for a worker parking itself
pub(crate) before_park: Option<Callback>,
@@ -27,6 +24,14 @@ pub(crate) struct Config {
/// To run after each task is terminated.
pub(crate) after_termination: Option<TaskCallback>,
/// To run before each poll
#[cfg(tokio_unstable)]
pub(crate) before_poll: Option<TaskCallback>,
/// To run after each poll
#[cfg(tokio_unstable)]
pub(crate) after_poll: Option<TaskCallback>,
/// The multi-threaded scheduler includes a per-worker LIFO slot used to
/// store the last scheduled task. This can improve certain usage patterns,
/// especially message passing between tasks. However, this LIFO slot is not
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::loom::thread::AccessError;
use crate::runtime::coop;
use crate::task::coop;
use std::cell::Cell;
@@ -135,7 +135,7 @@ pub(crate) fn thread_rng_n(n: u32) -> u32 {
})
}
pub(super) fn budget<R>(f: impl FnOnce(&Cell<coop::Budget>) -> R) -> Result<R, AccessError> {
pub(crate) fn budget<R>(f: impl FnOnce(&Cell<coop::Budget>) -> R) -> Result<R, AccessError> {
CONTEXT.try_with(|ctx| f(&ctx.budget))
}
+1 -1
View File
@@ -87,7 +87,7 @@ impl BlockingRegionGuard {
let when = Instant::now() + timeout;
loop {
if let Ready(v) = crate::runtime::coop::budget(|| f.as_mut().poll(&mut cx)) {
if let Ready(v) = crate::task::coop::budget(|| f.as_mut().poll(&mut cx)) {
return Ok(v);
}
-18
View File
@@ -63,10 +63,6 @@ impl Driver {
))
}
pub(crate) fn is_enabled(&self) -> bool {
self.inner.is_enabled()
}
pub(crate) fn park(&mut self, handle: &Handle) {
self.inner.park(handle);
}
@@ -163,13 +159,6 @@ cfg_io_driver! {
}
impl IoStack {
pub(crate) fn is_enabled(&self) -> bool {
match self {
IoStack::Enabled(..) => true,
IoStack::Disabled(..) => false,
}
}
pub(crate) fn park(&mut self, handle: &Handle) {
match self {
IoStack::Enabled(v) => v.park(handle),
@@ -320,13 +309,6 @@ cfg_time! {
}
impl TimeDriver {
pub(crate) fn is_enabled(&self) -> bool {
match self {
TimeDriver::Enabled { .. } => true,
TimeDriver::Disabled(inner) => inner.is_enabled(),
}
}
pub(crate) fn park(&mut self, handle: &Handle) {
match self {
TimeDriver::Enabled { driver, .. } => driver.park(handle),
+69 -1
View File
@@ -3,7 +3,9 @@
//! See [`Handle::dump`][crate::runtime::Handle::dump].
use crate::task::Id;
use std::{fmt, path::Path};
use std::{fmt, future::Future, path::Path};
pub use crate::runtime::task::trace::Root;
/// A snapshot of a runtime's state.
///
@@ -214,6 +216,72 @@ impl Trace {
})
.collect()
}
/// Runs the function `f` in tracing mode, and returns its result along with the resulting [`Trace`].
///
/// This is normally called with `f` being the poll function of a future, and will give you a backtrace
/// that tells you what that one future is doing.
///
/// Use [`Handle::dump`] instead if you want to know what *all the tasks* in your program are doing.
/// Also see [`Handle::dump`] for more documentation about dumps, but unlike [`Handle::dump`], this function
/// should not be much slower than calling `f` directly.
///
/// Due to the way tracing is implemented, Tokio leaf futures will usually, instead of doing their
/// actual work, do the equivalent of a `yield_now` (returning a `Poll::Pending` and scheduling the
/// current context for execution), which means forward progress will probably not happen unless
/// you eventually call your future outside of `capture`.
///
/// [`Handle::dump`]: crate::runtime::Handle::dump
///
/// Example usage:
/// ```
/// use std::future::Future;
/// use std::task::Poll;
/// use tokio::runtime::dump::Trace;
///
/// # async fn test_fn() {
/// // some future
/// let mut test_future = std::pin::pin!(async move { tokio::task::yield_now().await; 0 });
///
/// // trace it once, see what it's doing
/// let (trace, res) = Trace::root(std::future::poll_fn(|cx| {
/// let (res, trace) = Trace::capture(|| test_future.as_mut().poll(cx));
/// Poll::Ready((trace, res))
/// })).await;
///
/// // await it to let it finish, outside of a `capture`
/// let output = match res {
/// Poll::Ready(output) => output,
/// Poll::Pending => test_future.await,
/// };
///
/// println!("{trace}");
/// # }
/// ```
///
/// ### Nested calls
///
/// Nested calls to `capture` might return partial traces, but will not do any other undesirable behavior (for
/// example, they will not panic).
pub fn capture<F, R>(f: F) -> (R, Trace)
where
F: FnOnce() -> R,
{
let (res, trace) = super::task::trace::Trace::capture(f);
(res, Trace { inner: trace })
}
/// Create a root for stack traces captured using [`Trace::capture`]. Stack frames above
/// the root will not be captured.
///
/// Nesting multiple [`Root`] futures is fine. Captures will stop at the first root. Not having
/// a [`Root`] is fine as well, but there is no guarantee on where the capture will stop.
pub fn root<F>(f: F) -> Root<F>
where
F: Future,
{
crate::runtime::task::trace::Trace::root(f)
}
}
impl Dump {
+7 -6
View File
@@ -399,8 +399,6 @@ impl Handle {
scheduler::Handle::CurrentThread(_) => RuntimeFlavor::CurrentThread,
#[cfg(feature = "rt-multi-thread")]
scheduler::Handle::MultiThread(_) => RuntimeFlavor::MultiThread,
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
scheduler::Handle::MultiThreadAlt(_) => RuntimeFlavor::MultiThreadAlt,
}
}
@@ -429,8 +427,6 @@ impl Handle {
scheduler::Handle::CurrentThread(handle) => handle.owned_id(),
#[cfg(feature = "rt-multi-thread")]
scheduler::Handle::MultiThread(handle) => handle.owned_id(),
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
scheduler::Handle::MultiThreadAlt(handle) => handle.owned_id(),
};
owned_id.into()
}
@@ -443,10 +439,17 @@ impl Handle {
}
}
impl std::panic::UnwindSafe for Handle {}
impl std::panic::RefUnwindSafe for Handle {}
cfg_taskdump! {
impl Handle {
/// Captures a snapshot of the runtime's state.
///
/// If you only want to capture a snapshot of a single future's state, you can use
/// [`Trace::capture`][crate::runtime::dump::Trace].
///
/// This functionality is experimental, and comes with a number of
/// requirements and limitations.
///
@@ -575,8 +578,6 @@ cfg_taskdump! {
handle.dump().await
}).await
},
#[cfg(all(tokio_unstable, feature = "rt-multi-thread", not(target_os = "wasi")))]
scheduler::Handle::MultiThreadAlt(_) => panic!("task dump not implemented for this runtime flavor"),
}
}
+2 -2
View File
@@ -148,7 +148,7 @@ impl Registration {
) -> Poll<io::Result<ReadyEvent>> {
ready!(crate::trace::trace_leaf(cx));
// Keep track of task budget
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
let coop = ready!(crate::task::coop::poll_proceed(cx));
let ev = ready!(self.shared.poll_readiness(cx, direction));
if ev.is_shutdown {
@@ -219,7 +219,7 @@ impl Registration {
loop {
let event = self.readiness(interest).await?;
let coop = std::future::poll_fn(crate::runtime::coop::poll_proceed).await;
let coop = std::future::poll_fn(crate::task::coop::poll_proceed).await;
match f() {
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+4 -4
View File
@@ -7,6 +7,9 @@ use std::ptr::NonNull;
use std::sync::atomic::Ordering::{Acquire, Release};
use std::sync::Arc;
// Kind of arbitrary, but buffering 16 `ScheduledIo`s doesn't seem like much
const NOTIFY_AFTER: usize = 16;
pub(super) struct RegistrationSet {
num_pending_release: AtomicUsize,
}
@@ -35,7 +38,7 @@ impl RegistrationSet {
let synced = Synced {
is_shutdown: false,
registrations: LinkedList::new(),
pending_release: Vec::with_capacity(16),
pending_release: Vec::with_capacity(NOTIFY_AFTER),
};
(set, synced)
@@ -69,9 +72,6 @@ impl RegistrationSet {
// Returns `true` if the caller should unblock the I/O driver to purge
// registrations pending release.
pub(super) fn deregister(&self, synced: &mut Synced, registration: &Arc<ScheduledIo>) -> bool {
// Kind of arbitrary, but buffering 16 `ScheduledIo`s doesn't seem like much
const NOTIFY_AFTER: usize = 16;
synced.pending_release.push(registration.clone());
let len = synced.pending_release.len();
+209 -103
View File
@@ -1,125 +1,188 @@
use crate::runtime::metrics::{HistogramBatch, WorkerMetrics};
use crate::runtime::metrics::WorkerMetrics;
cfg_unstable_metrics! {
use crate::runtime::metrics::HistogramBatch;
}
use std::sync::atomic::Ordering::Relaxed;
use std::time::{Duration, Instant};
pub(crate) struct MetricsBatch {
/// Number of times the worker parked.
park_count: u64,
/// Number of times the worker parked and unparked.
park_unpark_count: u64,
/// Number of times the worker woke w/o doing work.
noop_count: u64,
/// Number of tasks stolen.
steal_count: u64,
/// Number of times tasks where stolen.
steal_operations: u64,
/// Number of tasks that were polled by the worker.
poll_count: u64,
/// Number of tasks polled when the worker entered park. This is used to
/// track the noop count.
poll_count_on_last_park: u64,
/// Number of tasks that were scheduled locally on this worker.
local_schedule_count: u64,
/// Number of tasks moved to the global queue to make space in the local
/// queue
overflow_count: u64,
/// The total busy duration in nanoseconds.
busy_duration_total: u64,
/// Instant at which work last resumed (continued after park).
processing_scheduled_tasks_started_at: Instant,
#[cfg(tokio_unstable)]
/// Number of times the worker parked.
park_count: u64,
#[cfg(tokio_unstable)]
/// Number of times the worker parked and unparked.
park_unpark_count: u64,
#[cfg(tokio_unstable)]
/// Number of times the worker woke w/o doing work.
noop_count: u64,
#[cfg(tokio_unstable)]
/// Number of tasks stolen.
steal_count: u64,
#[cfg(tokio_unstable)]
/// Number of times tasks where stolen.
steal_operations: u64,
#[cfg(tokio_unstable)]
/// Number of tasks that were polled by the worker.
poll_count: u64,
#[cfg(tokio_unstable)]
/// Number of tasks polled when the worker entered park. This is used to
/// track the noop count.
poll_count_on_last_park: u64,
#[cfg(tokio_unstable)]
/// Number of tasks that were scheduled locally on this worker.
local_schedule_count: u64,
#[cfg(tokio_unstable)]
/// Number of tasks moved to the global queue to make space in the local
/// queue
overflow_count: u64,
#[cfg(tokio_unstable)]
/// If `Some`, tracks poll times in nanoseconds
poll_timer: Option<PollTimer>,
}
struct PollTimer {
/// Histogram of poll counts within each band.
poll_counts: HistogramBatch,
cfg_unstable_metrics! {
struct PollTimer {
/// Histogram of poll counts within each band.
poll_counts: HistogramBatch,
/// Instant when the most recent task started polling.
poll_started_at: Instant,
/// Instant when the most recent task started polling.
poll_started_at: Instant,
}
}
impl MetricsBatch {
pub(crate) fn new(worker_metrics: &WorkerMetrics) -> MetricsBatch {
let now = Instant::now();
Self::new_unstable(worker_metrics, now)
}
MetricsBatch {
park_count: 0,
park_unpark_count: 0,
noop_count: 0,
steal_count: 0,
steal_operations: 0,
poll_count: 0,
poll_count_on_last_park: 0,
local_schedule_count: 0,
overflow_count: 0,
busy_duration_total: 0,
processing_scheduled_tasks_started_at: now,
poll_timer: worker_metrics
.poll_count_histogram
.as_ref()
.map(|worker_poll_counts| PollTimer {
poll_counts: HistogramBatch::from_histogram(worker_poll_counts),
poll_started_at: now,
}),
cfg_metrics_variant! {
stable: {
#[inline(always)]
fn new_unstable(_worker_metrics: &WorkerMetrics, now: Instant) -> MetricsBatch {
MetricsBatch {
busy_duration_total: 0,
processing_scheduled_tasks_started_at: now,
}
}
},
unstable: {
#[inline(always)]
fn new_unstable(worker_metrics: &WorkerMetrics, now: Instant) -> MetricsBatch {
MetricsBatch {
park_count: 0,
park_unpark_count: 0,
noop_count: 0,
steal_count: 0,
steal_operations: 0,
poll_count: 0,
poll_count_on_last_park: 0,
local_schedule_count: 0,
overflow_count: 0,
busy_duration_total: 0,
processing_scheduled_tasks_started_at: now,
poll_timer: worker_metrics.poll_count_histogram.as_ref().map(
|worker_poll_counts| PollTimer {
poll_counts: HistogramBatch::from_histogram(worker_poll_counts),
poll_started_at: now,
},
),
}
}
}
}
pub(crate) fn submit(&mut self, worker: &WorkerMetrics, mean_poll_time: u64) {
worker.mean_poll_time.store(mean_poll_time, Relaxed);
worker.park_count.store(self.park_count, Relaxed);
worker
.park_unpark_count
.store(self.park_unpark_count, Relaxed);
worker.noop_count.store(self.noop_count, Relaxed);
worker.steal_count.store(self.steal_count, Relaxed);
worker
.steal_operations
.store(self.steal_operations, Relaxed);
worker.poll_count.store(self.poll_count, Relaxed);
worker
.busy_duration_total
.store(self.busy_duration_total, Relaxed);
worker
.local_schedule_count
.store(self.local_schedule_count, Relaxed);
worker.overflow_count.store(self.overflow_count, Relaxed);
self.submit_unstable(worker, mean_poll_time);
}
if let Some(poll_timer) = &self.poll_timer {
let dst = worker.poll_count_histogram.as_ref().unwrap();
poll_timer.poll_counts.submit(dst);
cfg_metrics_variant! {
stable: {
#[inline(always)]
fn submit_unstable(&mut self, _worker: &WorkerMetrics, _mean_poll_time: u64) {}
},
unstable: {
#[inline(always)]
fn submit_unstable(&mut self, worker: &WorkerMetrics, mean_poll_time: u64) {
worker.mean_poll_time.store(mean_poll_time, Relaxed);
worker.park_count.store(self.park_count, Relaxed);
worker
.park_unpark_count
.store(self.park_unpark_count, Relaxed);
worker.noop_count.store(self.noop_count, Relaxed);
worker.steal_count.store(self.steal_count, Relaxed);
worker
.steal_operations
.store(self.steal_operations, Relaxed);
worker.poll_count.store(self.poll_count, Relaxed);
worker
.local_schedule_count
.store(self.local_schedule_count, Relaxed);
worker.overflow_count.store(self.overflow_count, Relaxed);
if let Some(poll_timer) = &self.poll_timer {
let dst = worker.poll_count_histogram.as_ref().unwrap();
poll_timer.poll_counts.submit(dst);
}
}
}
}
/// The worker is about to park.
pub(crate) fn about_to_park(&mut self) {
self.park_count += 1;
self.park_unpark_count += 1;
cfg_metrics_variant! {
stable: {
/// The worker is about to park.
pub(crate) fn about_to_park(&mut self) {}
},
unstable: {
/// The worker is about to park.
pub(crate) fn about_to_park(&mut self) {
{
self.park_count += 1;
self.park_unpark_count += 1;
if self.poll_count_on_last_park == self.poll_count {
self.noop_count += 1;
} else {
self.poll_count_on_last_park = self.poll_count;
if self.poll_count_on_last_park == self.poll_count {
self.noop_count += 1;
} else {
self.poll_count_on_last_park = self.poll_count;
}
}
}
}
}
/// The worker was unparked.
pub(crate) fn unparked(&mut self) {
self.park_unpark_count += 1;
cfg_metrics_variant! {
stable: {
/// The worker was unparked.
pub(crate) fn unparked(&mut self) {}
},
unstable: {
/// The worker was unparked.
pub(crate) fn unparked(&mut self) {
self.park_unpark_count += 1;
}
}
}
/// Start processing a batch of tasks
@@ -133,40 +196,83 @@ impl MetricsBatch {
self.busy_duration_total += duration_as_u64(busy_duration);
}
/// Start polling an individual task
pub(crate) fn start_poll(&mut self) {
self.poll_count += 1;
if let Some(poll_timer) = &mut self.poll_timer {
poll_timer.poll_started_at = Instant::now();
cfg_metrics_variant! {
stable: {
/// Start polling an individual task
pub(crate) fn start_poll(&mut self) {}
},
unstable: {
/// Start polling an individual task
pub(crate) fn start_poll(&mut self) {
self.poll_count += 1;
if let Some(poll_timer) = &mut self.poll_timer {
poll_timer.poll_started_at = Instant::now();
}
}
}
}
/// Stop polling an individual task
pub(crate) fn end_poll(&mut self) {
if let Some(poll_timer) = &mut self.poll_timer {
let elapsed = duration_as_u64(poll_timer.poll_started_at.elapsed());
poll_timer.poll_counts.measure(elapsed, 1);
cfg_metrics_variant! {
stable: {
/// Stop polling an individual task
pub(crate) fn end_poll(&mut self) {}
},
unstable: {
/// Stop polling an individual task
pub(crate) fn end_poll(&mut self) {
if let Some(poll_timer) = &mut self.poll_timer {
let elapsed = duration_as_u64(poll_timer.poll_started_at.elapsed());
poll_timer.poll_counts.measure(elapsed, 1);
}
}
}
}
pub(crate) fn inc_local_schedule_count(&mut self) {
self.local_schedule_count += 1;
cfg_metrics_variant! {
stable: {
pub(crate) fn inc_local_schedule_count(&mut self) {}
},
unstable: {
pub(crate) fn inc_local_schedule_count(&mut self) {
self.local_schedule_count += 1;
}
}
}
}
cfg_rt_multi_thread! {
impl MetricsBatch {
pub(crate) fn incr_steal_count(&mut self, by: u16) {
self.steal_count += by as u64;
cfg_metrics_variant! {
stable: {
pub(crate) fn incr_steal_count(&mut self, _by: u16) {}
},
unstable: {
pub(crate) fn incr_steal_count(&mut self, by: u16) {
self.steal_count += by as u64;
}
}
}
pub(crate) fn incr_steal_operations(&mut self) {
self.steal_operations += 1;
cfg_metrics_variant! {
stable: {
pub(crate) fn incr_steal_operations(&mut self) {}
},
unstable: {
pub(crate) fn incr_steal_operations(&mut self) {
self.steal_operations += 1;
}
}
}
pub(crate) fn incr_overflow_count(&mut self) {
self.overflow_count += 1;
cfg_metrics_variant! {
stable: {
pub(crate) fn incr_overflow_count(&mut self) {}
},
unstable: {
pub(crate) fn incr_overflow_count(&mut self) {
self.overflow_count += 1;
}
}
}
}
}
@@ -174,8 +174,8 @@ impl LogHistogramBuilder {
/// less than this will be truncated.
///
/// # Panics
/// - `max_error` < 0
/// - `max_error` > 1
/// - `max_error` <= 0
/// - `max_error` >= 1
pub fn max_error(mut self, max_error: f64) -> Self {
assert!(max_error > 0.0, "max_error must be greater than 0");
assert!(max_error < 1.0, "max_error must be less than 1");
-44
View File
@@ -1,13 +1,7 @@
//! This file contains mocks of the types in src/runtime/metrics
use std::thread::ThreadId;
pub(crate) struct SchedulerMetrics {}
pub(crate) struct WorkerMetrics {}
pub(crate) struct MetricsBatch {}
#[derive(Clone, Default)]
pub(crate) struct HistogramBuilder {}
@@ -19,41 +13,3 @@ impl SchedulerMetrics {
/// Increment the number of tasks scheduled externally
pub(crate) fn inc_remote_schedule_count(&self) {}
}
impl WorkerMetrics {
pub(crate) fn new() -> Self {
Self {}
}
pub(crate) fn from_config(config: &crate::runtime::Config) -> Self {
// Prevent the dead-code warning from being triggered
let _ = &config.metrics_poll_count_histogram;
Self::new()
}
pub(crate) fn set_queue_depth(&self, _len: usize) {}
pub(crate) fn set_thread_id(&self, _thread_id: ThreadId) {}
}
impl MetricsBatch {
pub(crate) fn new(_: &WorkerMetrics) -> Self {
Self {}
}
pub(crate) fn submit(&mut self, _to: &WorkerMetrics, _mean_poll_time: u64) {}
pub(crate) fn about_to_park(&mut self) {}
pub(crate) fn unparked(&mut self) {}
pub(crate) fn inc_local_schedule_count(&mut self) {}
pub(crate) fn start_processing_scheduled_tasks(&mut self) {}
pub(crate) fn end_processing_scheduled_tasks(&mut self) {}
pub(crate) fn start_poll(&mut self) {}
pub(crate) fn end_poll(&mut self) {}
}
cfg_rt_multi_thread! {
impl MetricsBatch {
pub(crate) fn incr_steal_count(&mut self, _by: u16) {}
pub(crate) fn incr_steal_operations(&mut self) {}
pub(crate) fn incr_overflow_count(&mut self) {}
}
}

Some files were not shown because too many files have changed in this diff Show More