Compare commits

...
Author SHA1 Message Date
Noah Kennedy 3fb29e3c92 rt: expose io driver fd
This change exposes the IO driver fd, used by our epoll, kqueue, uring, etc instance.

This is exposed so that users can watch it from another epoll/kqueue/io_uring/poll setup, and for no other reason. It is not valid to do anything other than watch this for readiness.
2024-08-28 10:41:29 +01:00
mox692 365269adaf Merge 'tokio-1.39.x' into master (#6788) 2024-08-17 19:35:28 +09:00
Motoyuki Kimura 3d439ab711 chore: prepare Tokio v1.39.3 (#6782) 2024-08-17 10:26:43 +02:00
Motoyuki Kimura b2ea40bb54 net: add handling for abstract socket name (#6772) 2024-08-16 23:50:34 +09:00
Rafael Bachmann 5ea3c63d7e sync: document mpsc channel allocation behavior (#6773) 2024-08-16 12:31:03 +02:00
Eliza Weisman 56f3f40c15 tests: handle ECONNREFUSED in uds_stream::epollhup (#6778)
## Motivation

Currently, the test `uds_stream::epollhup` expects that a
`UdsStream::connect` future to a Unix socket which is closed by the
accept side to always fail with `io::ErrorKind::ConnectionReset`. On
illumos, and potentially other systems, it instead fails with
`io::ErrorKind::ConnectionRefused`.

This was discovered whilst adding an illumos CI job in PR #6769. See:
https://github.com/tokio-rs/tokio/pull/6769#issuecomment-2284753794

## Solution

This commit changes the test to accept either `ConenctionReset` or
`ConnectionRefused`. This way, we are more tolerant of different
operating systems which may decide to return slightly different errnos
here. Both ECONNREFUSED and ECONNRESET seem reasonable to expect in this
situation, although arguably, ECONNREFUSED is actually more correct: the
acceptor did not accept the connection at all, which seems like
"refusing" it to me...
2024-08-16 10:55:32 +02:00
Eliza Weisman 2d697fc92b tests: handle spurious EWOULDBLOCK in io_async_fd (#6776)
* tests: handle spurious EWOULDBLOCK in io_async_fd

## Motivation

The `io_async_fd.rs` tests contain a `drain()` function, which
currently performs synchronous reads from a UDS socket until it returns
`io::ErrorKind::WouldBlock` (i.e., errno `EWOULDBLOCK`/`EAGAIN`). The
*intent* behind this function is to ensure that all data has been
drained from the UDS socket's buffer...which is what it appears to
do...on Linux. On other systems, it appears that an `EWOULDBLOCK` or
`EAGAIN` may be returned before enough data has been read from the UDS
socket to result in the other end being notified that the socket is now
writable. In particular, this appears to be the case on illumos, where
the tests using this function hang forever (see [this comment][1] on PR
#6769).

To my knowledge, this behavior is still POSIX-compliant --- the
reader will still be notified that the socket is readable, and if it
were actually doing non-blocking IO, it would continue reading upon
receipt of that notification. So, relying on `EWOULDBLOCK` to indicate
that the socket has been sufficiently drained appears to rely on
Linux/FreeBSD behavior that isn't necessarily portable to other Unices.

## Solution

This commit changes the `drain()` function to take an argument for the
number of bytes *written* to the socket previously, and continue looping
until it has read that many bytes, regardless of whether `EWOULDBLOCK`
is returned. This should ensure that the socket is drained on all
POSIX-compliant systems, and indeed, the `io_async_fd::reset_writable`
and `io_async_fd::poll_fns` tests no longer hang forever on illumos.

I think making this change is an appropriate solution to the
test failure here, as the `drain()` function is part of the test, rather
than the code in Tokio *being* tested, and (as I mentioned above) the
use of blocking reads on a non-blocking socket without a mechanism to
continue reading when the socket becomes readable again is not really
something a real life program seems likely to do. Ensuring that all the
written bytes have been read by passing in a byte count seems more
faithful to what the test is actually *trying* to do here, anyway.

Thanks to @jclulow for debugging what was going on here!

This change was cherry-picked from commit
f18d6ed7d4 from PR #6769, so that the fix
can be merged separately.

[1]: https://github.com/tokio-rs/tokio/pull/6769#issuecomment-2284753794

Signed-off-by: Eliza Weisman <[email protected]>
2024-08-15 15:44:36 +00:00
Rafael Bachmann 39c3c19bbd macros: improve documentation for select! (#6774) 2024-08-14 15:49:28 +02:00
Eliza Weisman 694577fa85 Add config file to enable Buildomat CI for illumos (#6768)
## Motivation

As described in #6763, Tokio compiles for the [illumos] operating
system, but we don't presently have automated tests on illumos. We would
like to add illumos CI jobs for Tokio using [Buildomat], a CI system
which supports illumos. Buildomat CI jobs for Tokio will run on
infrastructure contributed by Oxide Computer Company.

In order for Buildomat to watch for commits to the repo, we must first
add a configuration file in `.github/buildomat/config.toml` with the
`enable = true` key. This config file must be present on the repo's main
branch for Buildomat to enable builds for the repo. See [here] for
details.

## Solution

This branch adds a `.github/buildomat` directory containing a config
file and a README summarizing what the configs in that directory are
for, as well as documenting how to get help diagnosing illumos CI
failures.


This branch does *not* add scripts for actually running CI jobs on
Buildomat. Since the config file must be present on the repo's main
branch before Buildomat runs CI jobs for the repo, I'd like to merge the
config file separately from the actual build scripts. This way, I can
actually have the build jobs run on the PR that adds them, making it
easier to ensure everything is working correctly before merging.

Closes #6766, which is obsoleted by this branch.

[illumos]: https://www.illumos.org/
[Buildomat]: https://github.com/oxidecomputer/
[here]:
    https://github.com/oxidecomputer/buildomat/blob/main/README.md#per-repository-configuration
2024-08-12 16:22:21 +00:00
Vrtgs 17819062e2 tokio: update code according to new MSRV (#6764) 2024-08-11 11:55:22 +02:00
Sainath Singineedi 6ad1912353 task: add #[must_use] to JoinHandle::abort_handle (#6762) 2024-08-09 20:57:31 +00:00
Caleb Leinz (he/him) a491b16a89 sync: add {TrySendError,SendTimeoutError}::into_inner (#6755) 2024-08-08 00:06:37 +02:00
Austin Bonander 0ecf5f0f03 task: include panic msg when printing JoinError (#6753) 2024-08-07 16:54:02 +02:00
Adam Cigánek 1e798d26ed time: wake DelayQueue when removing last item (#6752) 2024-08-06 16:22:31 +02:00
Alice Ryhl ab53bf0c47 runtime: prevent niche-optimization to avoid triggering miri (#6744) 2024-08-03 12:32:50 +02:00
Motoyuki Kimura 338e13b04b task: use NonZeroU64 for task::Id (#6733) 2024-08-01 14:45:35 +00:00
Motoyuki Kimura 1077b0b29d io: use vectored io for write_all_buf when possible (#6724) 2024-07-29 19:39:44 +02:00
Hayden Stainsby 0cbf1a5ada time,sync: make Sleep and BatchSemaphore instrumentation explicit roots (#6727)
When instrumenting resources in Tokio, a span is created for each
resource. Previously, all resources inherited the currently active span
as their parent (tracing default). However, this would keep that parent
span alive until the resource (and its span) were dropped. This is often
not correct, as a resource may be created in a task and then sent
elsewhere, while the originating task ends.

This artificial extension of the parent span's lifetime would make it
look like that task was still alive (but idle) in any system reading the
tracing instrumentation in Tokio, for example Tokio Console as reported
in tokio-rs/console#345.

In #6107, most of the existing resource spans were updated to
make them explicit roots, so they have no contextual parent. However,
2. were missed:
- `Sleep`
- `BatchSemaphore`

This change alters the resource spans for those 2 resources to also make
them explicit roots.
2024-07-29 12:06:13 +02:00
Hayden Stainsby 04c2718508 docs: reiterate that [build] doesn't go in Cargo.toml (#6728)
To enable unstable features in Tokio, passing `--cfg tokio_unstable` to
the compiler is necessary. We document how to do this in a variety of
ways in the main Tokio (lib.rs) documentation.

One way is to add a `[build]` section to the file `.cargo/config.toml`.
Even though this filename is stated in the documentation, it is quite
common that first time users (including this author, some time ago) put
it in their `Cargo.toml` file instead.

This change adds a "warning" section to the documentation to reiterate
the point that this section doesn't go in the cargo manifest
(`Cargo.toml`).
2024-07-29 11:23:29 +02:00
Niklas Fiekas ebda4c3d3f process: stabilize Command::process_group (#6731) 2024-07-27 23:08:40 +02:00
Alice Ryhl f602eae499 chore: prepare Tokio v1.39.2 (#6730) 2024-07-27 12:36:48 +02:00
Alice Ryhl 438def7957 macros: allow temporary lifetime extension in select (#6722) 2024-07-26 17:36:28 +01:00
Motoyuki Kimura ee8d4d1b05 chore: fix ci failures (#6725) 2024-07-25 20:30:23 +02:00
Dirkjan Ochtman 3297052763 ci: test Quinn in CI (#6719) 2024-07-25 11:37:46 +02:00
Alice Ryhl f8fe0ffb23 chore: prepare Tokio v1.39.1 (#6716) 2024-07-23 18:28:04 +02:00
Alice Ryhl 47210a8e6e time: revert "avoid traversing entries in the time wheel twice" (#6715)
This reverts commit 8480a180e6.
2024-07-23 16:09:28 +00:00
Alice Ryhl 29545d9037 runtime: ignore many_oneshot_futures test for alt scheduler (#6712) 2024-07-23 16:35:20 +02:00
Alice Ryhl 48e35c11d9 chore: release Tokio v1.39.0 (#6711) 2024-07-23 15:30:11 +02:00
Alice Ryhl dd1d37167d macros: accept IntoFuture args for macros (#6710) 2024-07-23 12:52:25 +00:00
Alice Ryhl 6a1a7b1591 chore: prepare tokio-macros v2.4.0 (#6707) 2024-07-23 14:45:06 +02:00
Kenny Kerr 51b03f0334 deps: update to windows-sys v0.52 (#6154) 2024-07-23 14:43:23 +02:00
Thomas de Zeeuw 754a1fb03c deps: update to Mio v1 (#6635) 2024-07-23 14:26:07 +02:00
Sebastian Urban 90b23a9584 metrics: add worker thread id (#6695) 2024-07-23 12:41:33 +02:00
Sebastian Urban b69f16aa21 metrics: add worker_park_unpark_count (#6696) 2024-07-23 09:15:54 +02:00
Alex Butler 6e845b794d time: support IntoFuture with timeout (#6666) 2024-07-22 23:29:18 +02:00
Tim Vilgot Mikael Fredenberg feb742c58e chore: replace num_cpus with available_parallelism (#6709) 2024-07-22 23:15:23 +02:00
Alice Ryhl 15cd5146d4 chore: increase MSRV to 1.70 (#6645) 2024-07-22 18:01:27 +00:00
Alice Ryhl 56f4bc6543 chore: make 1.38 an LTS (#6706) 2024-07-21 18:28:12 +02:00
Motoyuki Kimura 3ad5b6df1a runtime: add cfg for loom specific code (#6694) 2024-07-21 17:26:36 +02:00
Russell Cohen 1be8a8e691 metrics: stabilize num_alive_tasks (#6619) 2024-07-18 22:08:29 +02:00
Motoyuki Kimura da17c61464 task: add size check for user-supplied future (#6692) 2024-07-18 11:54:37 +00:00
Ikko Eltociear Ashimine f71bded943 sync: fix typo in name of test (#6693) 2024-07-18 11:05:16 +02:00
Motoyuki Kimura fc058b9561 io: update tokio::io::stdout documentation (#6674) 2024-07-16 19:34:09 +02:00
wathenjiang c0280624f3 Merge 'tokio-1.38.1' into 'master' (#6689) 2024-07-17 00:02:27 +08:00
Evan Rittenhouse 1e286689ff runtime: fix yield_calls_park_before_scheduling_again test (#6679) 2024-07-16 17:29:19 +02:00
Weijia Jiang 14b9f71157 chore: release Tokio v1.38.1 (#6688) 2024-07-16 17:16:29 +02:00
Weijia Jiang 24344dfe4b time: fix race condition leading to lost timers (#6683) 2024-07-16 13:25:59 +00:00
Paolo Barbolini 15925efe43 macros: fix doc format issue (#6686) 2024-07-16 12:13:41 +02:00
Matthew Leach 4825c444eb test: fix tests when '-' is absent from kernel version (#6681)
On my machine, any test that calls `is_pidfd_available` fails as my
kernel string does not contain a '-'; the code expects there to be one.

Fix the test so that is works regardless on whether the kernel string
contains a '-'.
2024-07-14 13:38:23 +09:00
sharpened-nacho c8f3539bc1 stream: make stream adapters public (#6658) 2024-07-02 20:49:34 +00:00
Alice Ryhl b2e4c5fc9e io: hardcode platform list in short-read optimization (#6668) 2024-07-02 20:25:49 +00:00
teor 4d0d89fb70 task: document behavior of JoinSet::try_join_next when all tasks are running (#6671) 2024-07-02 17:54:59 +02:00
二手掉包工程师 fe7285d3d1 sync: add {Receiver,UnboundedReceiver}::{sender_strong_count,sender_weak_count} (#6661) 2024-07-02 23:41:15 +08:00
Michael MaciasandWeijia Jiang dff4ecd0e7 io: implement AsyncSeek for Empty (#6663)
* io: implement `AsyncSeek` for `Empty`

* io: add more tests for seeking `Empty`

This adds more tests seeking with other kinds of `SeekFrom`. It follows
the same structure as used in
rust-lang/rust@f1cd17961c.

See <https://github.com/tokio-rs/tokio/pull/6663#discussion_r1659491016>.

* io: add inline attribute to `AsyncSeek` implementations

This follows the style of the other trait implementations of `Empty`.

See <https://github.com/tokio-rs/tokio/pull/6663#discussion_r1658835942>.

---------

Co-authored-by: Weijia Jiang <[email protected]>
2024-07-01 09:50:49 +08:00
Alice Ryhl 68d0e3cb5f metrics: rename num_active_tasks to num_alive_tasks (#6667) 2024-06-30 15:02:29 +02:00
Tobias Nießen 65d0e08d39 runtime: fix typo in unhandled_panic (#6660) 2024-06-28 01:10:14 +09:00
Hai-Hsin 06582776a5 codec: fix length_delimited docs examples (#6638) 2024-06-23 13:52:51 +09:00
Eric Seppanen ed4ddf443d io: fix trait bounds on impl Sink for StreamReader (#6647)
This impl had a bound on `StreamReader<S, E>`; this is incorrect
because:
- The second generic parameter to `StreamReader` is not an error type;
  it's a buffer type.
- The `Stream` error type in `StreamReader` should not need to be the
  same as the `Sink` error type.

This "passthrough" `Sink` impl was effectively unusable because it
required the `Sink` error type be the same as the `StreamReader` buffer
type.

Resolve this by allowing the `StreamReader` buffer to be anything in
this impl.
2024-06-21 15:00:33 +02:00
Alice Ryhl 9a75d6f7f7 metrics: use MetricAtomic* for task counters (#6624) 2024-06-17 08:33:08 +00:00
Uwe Klotz 3bf4f93854 sync: add watch::Sender::same_channel (#6637) 2024-06-15 21:11:35 +02:00
FabijanC 39cf6bba00 macros: typo fix in join.rs and try_join.rs (#6641) 2024-06-15 21:10:47 +02:00
Weijia Jiang 8480a180e6 time: avoid traversing entries in the time wheel twice (#6584) 2024-06-14 11:03:47 +02:00
Timo 53ea44bfb9 sync: add CancellationToken::run_until_cancelled (#6618) 2024-06-13 10:58:45 +02:00
Weijia Jiang a865ca139a rt: relaxed trait bounds for LinkedList::into_guarded (#6630) 2024-06-13 08:50:28 +02:00
Niki C 479f736935 io: improve panic message of ReadBuf::put_slice() (#6629) 2024-06-13 01:09:59 +09:00
Marek Kuskowski 17555d71d9 sync: implement Default for watch::Sender (#6626) 2024-06-10 10:44:45 +02:00
Conrad Ludgate 341b5daa6e metrics: add spawned_tasks_count, rename active_tasks_count (#6114) 2024-06-09 12:25:54 +02:00
Rob Ede 53b586c5b9 task: stabilize consume_budget (#6622) 2024-06-08 22:17:06 +02:00
Hai-Hsin 833ee027d0 macros: allow unhandled_panic behavior for #[tokio::main] and #[tokio::test] (#6593) 2024-06-07 20:48:56 +09:00
Aaron Schweiger 126ce89bb4 task: implement Clone for AbortHandle (#6621) 2024-06-07 09:17:25 +02:00
Russell Cohen 8e15c234c6 metrics: add MetricAtomicUsize for usized-metrics (#6598) 2024-06-06 10:08:46 +02:00
John-John Tedro 16fccafb41 docs: fix docsrs builds with the fs feature enabled (#6585) 2024-06-05 08:20:27 +00:00
Armillus 3f397ccded io: read during write in copy_bidirectional and copy (#6532) 2024-06-05 00:29:28 +02:00
Emil Loer 49609d073f test: make Spawn forward size_hint (#6607) 2024-06-04 23:42:42 +02:00
Alan Somers a91d43823c ci: update FreeBSD CI environment (#6616)
Use the newly released FreeBSD 14.1.
2024-06-04 23:37:13 +02:00
Timo 8fca6f6dad process: add Command::as_std_mut (#6608) 2024-06-04 13:34:22 +02:00
Weijia Jiang 75c953bd63 time: fix big time panic issue (#6612) 2024-06-04 09:45:35 +02:00
121 changed files with 3522 additions and 1399 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
freebsd_instance:
image_family: freebsd-14-0
image_family: freebsd-14-1
env:
RUST_STABLE: stable
RUST_NIGHTLY: nightly-2024-05-05
+20
View File
@@ -0,0 +1,20 @@
# Buildomat illumos CI
This directory contains CI configurations for the [illumos] operating system.
Tokio's illumos CI jobs are run using [Buildomat], a CI system developed by
Oxide Computer, which supports illumos. See [the Buildomat README] for more
details.
## illumos-Specific CI Failures
If your pull request's CI build fails on illumos, and you aren't able to easily
reproduce the failure on other operating systems, don't worry! The
[tokio-rs/illumos] team is responsible for maintaining Tokio's illumos support,
and can be called on to assist contributors with illumos-specific issues. Please
feel free to tag @tokio-rs/illumos to ask for help resolving build failures on
illumos
[illumos]: https://www.illumos.org/
[Buildomat]: https://github.com/oxidecomputer/buildomat
[the Buildomat README]: https://github.com/oxidecomputer/buildomat
[tokio-rs/illumos]: https://github.com/orgs/tokio-rs/teams/illumos
+8
View File
@@ -0,0 +1,8 @@
# Repository-level Buildomat configuration.
# See: https://github.com/oxidecomputer/buildomat#per-repository-configuration
# Enable buildomat. This one should be self-explanatory.
enable = true
# Allow CI runs for PRs from users outside the `tokio-rs` organization. Our
# buildomat jobs don't touch any secrets/keys, so this should be fine.
org_only = false
+52 -1
View File
@@ -26,7 +26,7 @@ env:
# - tokio-util/Cargo.toml
# - tokio-test/Cargo.toml
# - tokio-stream/Cargo.toml
rust_min: '1.63'
rust_min: '1.70'
defaults:
run:
@@ -65,6 +65,7 @@ jobs:
- loom-compile
- check-readme
- test-hyper
- test-quinn
- x86_64-fortanix-unknown-sgx
- check-redox
- wasm32-unknown-unknown
@@ -859,6 +860,56 @@ jobs:
run: cargo test --features full
working-directory: hyper
test-quinn:
name: Test Quinn
needs: basics
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- windows-latest
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_stable }}
- name: Clone Quinn
run: git clone https://github.com/quinn-rs/quinn.git
- name: Checkout the latest release because HEAD maybe contains breakage.
run: |
set -x
tag=$(git describe --abbrev=0 --tags)
git checkout "${tag}"
working-directory: quinn
- name: Patch Quinn to use tokio from this repository
run: |
set -x
echo '[patch.crates-io]' >>Cargo.toml
echo 'tokio = { path = "../tokio" }' >>Cargo.toml
git diff
working-directory: quinn
- uses: Swatinem/rust-cache@v2
with:
# The cargo workspaces and target directory configuration.
# These entries are separated by newlines and have the form
# `$workspace -> $target`. The `$target` part is treated as a directory
# relative to the `$workspace` and defaults to "target" if not explicitly given.
# default: ". -> target"
workspaces: "./quinn"
- name: Test Quinn
working-directory: quinn
env:
RUSTFLAGS: ""
run: cargo test
x86_64-fortanix-unknown-sgx:
name: build tokio for x86_64-fortanix-unknown-sgx
needs: basics
+5 -3
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.38.0", features = ["full"] }
tokio = { version = "1.39.3", features = ["full"] }
```
Then, on your main.rs:
@@ -186,12 +186,13 @@ When updating this, also update:
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
least** 6 months. When increasing the MSRV, the new Rust version must have been
released at least six months ago. The current MSRV is 1.63.
released at least six months ago. The current MSRV is 1.70.
Note that the MSRV is not increased automatically, and only as part of a minor
release. The MSRV history for past minor releases can be found below:
* 1.30 to now - Rust 1.63
* 1.39 to now - Rust 1.70
* 1.30 to 1.38 - Rust 1.63
* 1.27 to 1.29 - Rust 1.56
* 1.17 to 1.26 - Rust 1.49
* 1.15 to 1.16 - Rust 1.46
@@ -217,6 +218,7 @@ releases are:
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
* `1.38.x` - LTS release until July 2025. (MSRV 1.63)
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
-1
View File
@@ -12,7 +12,6 @@ tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
criterion = "0.5.1"
rand = "0.8"
rand_chacha = "0.3"
num_cpus = "1.16.0"
[dev-dependencies]
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
+1 -1
View File
@@ -25,7 +25,7 @@ once_cell = "1.5.2"
rand = "0.8.3"
[target.'cfg(windows)'.dev-dependencies.windows-sys]
version = "0.48"
version = "0.52"
[[example]]
name = "chat"
+2
View File
@@ -1,3 +1,5 @@
#![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.
@@ -41,6 +41,9 @@ async fn test_crate_not_path_int() {}
#[tokio::test(crate = "456")]
async fn test_crate_not_path_invalid() {}
#[tokio::test(flavor = "multi_thread", unhandled_panic = "shutdown_runtime")]
async fn test_multi_thread_with_unhandled_panic() {}
#[tokio::test]
#[test]
async fn test_has_second_test_attr() {}
@@ -1,115 +1,121 @@
error: the `async` keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:6:1
--> tests/fail/macros_invalid_input.rs:6:1
|
6 | fn main_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:8:15
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
--> tests/fail/macros_invalid_input.rs:8:15
|
8 | #[tokio::main(foo)]
| ^^^
error: Must have specified ident
--> $DIR/macros_invalid_input.rs:11:15
--> tests/fail/macros_invalid_input.rs:11:15
|
11 | #[tokio::main(threadpool::bar)]
| ^^^^^^^^^^^^^^^
error: the `async` keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:15:1
--> tests/fail/macros_invalid_input.rs:15:1
|
15 | fn test_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:17:15
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
--> tests/fail/macros_invalid_input.rs:17:15
|
17 | #[tokio::test(foo)]
| ^^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:20:15
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`
--> tests/fail/macros_invalid_input.rs:20:15
|
20 | #[tokio::test(foo = 123)]
| ^^^^^^^^^
error: Failed to parse value of `flavor` as string.
--> $DIR/macros_invalid_input.rs:23:24
--> tests/fail/macros_invalid_input.rs:23:24
|
23 | #[tokio::test(flavor = 123)]
| ^^^
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
--> $DIR/macros_invalid_input.rs:26:24
--> tests/fail/macros_invalid_input.rs:26:24
|
26 | #[tokio::test(flavor = "foo")]
| ^^^^^
error: The `start_paused` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
--> $DIR/macros_invalid_input.rs:29:55
--> tests/fail/macros_invalid_input.rs:29:55
|
29 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
| ^^^^^
error: Failed to parse value of `worker_threads` as integer.
--> $DIR/macros_invalid_input.rs:32:57
--> tests/fail/macros_invalid_input.rs:32:57
|
32 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
| ^^^^^
error: The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[tokio::test(flavor = "multi_thread")]`
--> $DIR/macros_invalid_input.rs:35:59
--> tests/fail/macros_invalid_input.rs:35:59
|
35 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
| ^
error: Failed to parse value of `crate` as path.
--> $DIR/macros_invalid_input.rs:38:23
--> tests/fail/macros_invalid_input.rs:38:23
|
38 | #[tokio::test(crate = 456)]
| ^^^
error: Failed to parse value of `crate` as path: "456"
--> $DIR/macros_invalid_input.rs:41:23
--> tests/fail/macros_invalid_input.rs:41:23
|
41 | #[tokio::test(crate = "456")]
| ^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> $DIR/macros_invalid_input.rs:45:1
error: The `unhandled_panic` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
--> tests/fail/macros_invalid_input.rs:44:58
|
45 | #[test]
44 | #[tokio::test(flavor = "multi_thread", unhandled_panic = "shutdown_runtime")]
| ^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> tests/fail/macros_invalid_input.rs:48:1
|
48 | #[test]
| ^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> $DIR/macros_invalid_input.rs:49:1
--> tests/fail/macros_invalid_input.rs:52:1
|
49 | #[::core::prelude::v1::test]
52 | #[::core::prelude::v1::test]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> $DIR/macros_invalid_input.rs:53:1
--> tests/fail/macros_invalid_input.rs:56:1
|
53 | #[core::prelude::rust_2015::test]
56 | #[core::prelude::rust_2015::test]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> $DIR/macros_invalid_input.rs:57:1
--> tests/fail/macros_invalid_input.rs:60:1
|
57 | #[::std::prelude::rust_2018::test]
60 | #[::std::prelude::rust_2018::test]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> $DIR/macros_invalid_input.rs:61:1
--> tests/fail/macros_invalid_input.rs:64:1
|
61 | #[std::prelude::rust_2021::test]
64 | #[std::prelude::rust_2021::test]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> $DIR/macros_invalid_input.rs:64:1
--> tests/fail/macros_invalid_input.rs:67:1
|
64 | #[tokio::test]
67 | #[tokio::test]
| ^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `tokio::test` (in Nightly builds, run with -Z macro-backtrace for more info)
+8
View File
@@ -1,3 +1,11 @@
# 2.4.0 (July 22nd, 2024)
- msrv: increase MSRV to 1.70 ([#6645])
- macros: allow `unhandled_panic` behavior for `#[tokio::main]` and `#[tokio::test]` ([#6593])
[#6593]: https://github.com/tokio-rs/tokio/pull/6593
[#6645]: https://github.com/tokio-rs/tokio/pull/6645
# 2.3.0 (May 30th, 2024)
- macros: make `#[tokio::test]` append `#[test]` at the end of the attribute list ([#6497])
+2 -2
View File
@@ -4,9 +4,9 @@ name = "tokio-macros"
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-macros-1.x.y" git tag.
version = "2.3.0"
version = "2.4.0"
edition = "2021"
rust-version = "1.63"
rust-version = "1.70"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
+72 -3
View File
@@ -25,11 +25,37 @@ impl RuntimeFlavor {
}
}
#[derive(Clone, Copy, PartialEq)]
enum UnhandledPanic {
Ignore,
ShutdownRuntime,
}
impl UnhandledPanic {
fn from_str(s: &str) -> Result<UnhandledPanic, String> {
match s {
"ignore" => Ok(UnhandledPanic::Ignore),
"shutdown_runtime" => Ok(UnhandledPanic::ShutdownRuntime),
_ => Err(format!("No such unhandled panic behavior `{}`. The unhandled panic behaviors are `ignore` and `shutdown_runtime`.", s)),
}
}
fn into_tokens(self, crate_path: &TokenStream) -> TokenStream {
match self {
UnhandledPanic::Ignore => quote! { #crate_path::runtime::UnhandledPanic::Ignore },
UnhandledPanic::ShutdownRuntime => {
quote! { #crate_path::runtime::UnhandledPanic::ShutdownRuntime }
}
}
}
}
struct FinalConfig {
flavor: RuntimeFlavor,
worker_threads: Option<usize>,
start_paused: Option<bool>,
crate_name: Option<Path>,
unhandled_panic: Option<UnhandledPanic>,
}
/// Config used in case of the attribute not being able to build a valid config
@@ -38,6 +64,7 @@ const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
worker_threads: None,
start_paused: None,
crate_name: None,
unhandled_panic: None,
};
struct Configuration {
@@ -48,6 +75,7 @@ struct Configuration {
start_paused: Option<(bool, Span)>,
is_test: bool,
crate_name: Option<Path>,
unhandled_panic: Option<(UnhandledPanic, Span)>,
}
impl Configuration {
@@ -63,6 +91,7 @@ impl Configuration {
start_paused: None,
is_test,
crate_name: None,
unhandled_panic: None,
}
}
@@ -117,6 +146,25 @@ impl Configuration {
Ok(())
}
fn set_unhandled_panic(
&mut self,
unhandled_panic: syn::Lit,
span: Span,
) -> Result<(), syn::Error> {
if self.unhandled_panic.is_some() {
return Err(syn::Error::new(
span,
"`unhandled_panic` set multiple times.",
));
}
let unhandled_panic = parse_string(unhandled_panic, span, "unhandled_panic")?;
let unhandled_panic =
UnhandledPanic::from_str(&unhandled_panic).map_err(|err| syn::Error::new(span, err))?;
self.unhandled_panic = Some((unhandled_panic, span));
Ok(())
}
fn macro_name(&self) -> &'static str {
if self.is_test {
"tokio::test"
@@ -163,11 +211,24 @@ impl Configuration {
(_, None) => None,
};
let unhandled_panic = match (flavor, self.unhandled_panic) {
(F::Threaded, Some((_, unhandled_panic_span))) => {
let msg = format!(
"The `unhandled_panic` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
self.macro_name(),
);
return Err(syn::Error::new(unhandled_panic_span, msg));
}
(F::CurrentThread, Some((unhandled_panic, _))) => Some(unhandled_panic),
(_, None) => None,
};
Ok(FinalConfig {
crate_name: self.crate_name.clone(),
flavor,
worker_threads,
start_paused,
unhandled_panic,
})
}
}
@@ -275,9 +336,13 @@ fn build_config(
"crate" => {
config.set_crate_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
}
"unhandled_panic" => {
config
.set_unhandled_panic(lit.clone(), syn::spanned::Spanned::span(lit))?;
}
name => {
let msg = format!(
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`",
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
name,
);
return Err(syn::Error::new_spanned(namevalue, msg));
@@ -303,11 +368,11 @@ fn build_config(
macro_name
)
}
"flavor" | "worker_threads" | "start_paused" => {
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
format!("The `{}` attribute requires an argument.", name)
}
name => {
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`", name)
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.", name)
}
};
return Err(syn::Error::new_spanned(path, msg));
@@ -359,6 +424,10 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
if let Some(v) = config.start_paused {
rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) };
}
if let Some(v) = config.unhandled_panic {
let unhandled_panic = v.into_tokens(&crate_path);
rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) };
}
let generated_attrs = if is_test {
quote! {
+98 -1
View File
@@ -202,6 +202,54 @@ use proc_macro::TokenStream;
/// })
/// }
/// ```
///
/// ### Configure unhandled panic behavior
///
/// Available options are `shutdown_runtime` and `ignore`. For more details, see
/// [`Builder::unhandled_panic`].
///
/// 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() {
/// let _ = tokio::spawn(async {
/// panic!("This panic will shutdown the runtime.");
/// }).await;
/// }
/// # #[cfg(not(tokio_unstable))]
/// # fn main() { }
/// ```
///
/// Equivalent code not using `#[tokio::main]`
///
/// ```no_run
/// # #![allow(unknown_lints, unexpected_cfgs)]
/// #[cfg(tokio_unstable)]
/// fn main() {
/// tokio::runtime::Builder::new_current_thread()
/// .enable_all()
/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
/// .build()
/// .unwrap()
/// .block_on(async {
/// let _ = tokio::spawn(async {
/// panic!("This panic will shutdown the runtime.");
/// }).await;
/// })
/// }
/// # #[cfg(not(tokio_unstable))]
/// # fn main() { }
/// ```
///
/// **Note**: This option depends on Tokio's [unstable API][unstable]. See [the
/// documentation on unstable features][unstable] for details on how to enable
/// Tokio's unstable features.
///
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
/// [unstable]: ../tokio/index.html#unstable-features
#[proc_macro_attribute]
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
entry::main(args.into(), item.into(), true).into()
@@ -364,7 +412,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// ### Set number of worker threads
///
/// ```no_run
/// #[tokio::test(flavor ="multi_thread", worker_threads = 2)]
/// #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
/// async fn my_test() {
/// assert!(true);
/// }
@@ -423,6 +471,55 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// println!("Hello world");
/// }
/// ```
///
/// ### Configure unhandled panic behavior
///
/// Available options are `shutdown_runtime` and `ignore`. For more details, see
/// [`Builder::unhandled_panic`].
///
/// 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() {
/// let _ = tokio::spawn(async {
/// panic!("This panic will shutdown the runtime.");
/// }).await;
/// }
/// # #[cfg(not(tokio_unstable))]
/// # fn main() { }
/// ```
///
/// Equivalent code not using `#[tokio::test]`
///
/// ```no_run
/// # #![allow(unknown_lints, unexpected_cfgs)]
/// #[cfg(tokio_unstable)]
/// #[test]
/// fn my_test() {
/// tokio::runtime::Builder::new_current_thread()
/// .enable_all()
/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
/// .build()
/// .unwrap()
/// .block_on(async {
/// let _ = tokio::spawn(async {
/// panic!("This panic will shutdown the runtime.");
/// }).await;
/// })
/// }
/// # #[cfg(not(tokio_unstable))]
/// # fn main() { }
/// ```
///
/// **Note**: This option depends on Tokio's [unstable API][unstable]. See [the
/// documentation on unstable features][unstable] for details on how to enable
/// Tokio's unstable features.
///
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
/// [unstable]: ../tokio/index.html#unstable-features
#[proc_macro_attribute]
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
entry::test(args.into(), item.into(), true).into()
+1 -1
View File
@@ -6,7 +6,7 @@ name = "tokio-stream"
# - Create "tokio-stream-0.1.x" git tag.
version = "0.1.15"
edition = "2021"
rust-version = "1.63"
rust-version = "1.70"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
+15 -1
View File
@@ -81,8 +81,22 @@ pub mod wrappers;
mod stream_ext;
pub use stream_ext::{collect::FromStream, StreamExt};
/// Adapters for [`Stream`]s created by methods in [`StreamExt`].
pub mod adapters {
pub use crate::stream_ext::{
Chain, Filter, FilterMap, Fuse, Map, MapWhile, Merge, Peekable, Skip, SkipWhile, Take,
TakeWhile, Then,
};
cfg_time! {
pub use crate::stream_ext::{ChunksTimeout, Timeout, TimeoutRepeating};
}
}
cfg_time! {
pub use stream_ext::timeout::{Elapsed, Timeout};
#[deprecated = "Import those symbols from adapters instead"]
#[doc(hidden)]
pub use stream_ext::timeout::Timeout;
pub use stream_ext::timeout::Elapsed;
}
mod empty;
+16 -16
View File
@@ -8,66 +8,66 @@ mod any;
use any::AnyFuture;
mod chain;
use chain::Chain;
pub use chain::Chain;
pub(crate) mod collect;
use collect::{Collect, FromStream};
mod filter;
use filter::Filter;
pub use filter::Filter;
mod filter_map;
use filter_map::FilterMap;
pub use filter_map::FilterMap;
mod fold;
use fold::FoldFuture;
mod fuse;
use fuse::Fuse;
pub use fuse::Fuse;
mod map;
use map::Map;
pub use map::Map;
mod map_while;
use map_while::MapWhile;
pub use map_while::MapWhile;
mod merge;
use merge::Merge;
pub use merge::Merge;
mod next;
use next::Next;
mod skip;
use skip::Skip;
pub use skip::Skip;
mod skip_while;
use skip_while::SkipWhile;
pub use skip_while::SkipWhile;
mod take;
use take::Take;
pub use take::Take;
mod take_while;
use take_while::TakeWhile;
pub use take_while::TakeWhile;
mod then;
use then::Then;
pub use then::Then;
mod try_next;
use try_next::TryNext;
mod peekable;
use peekable::Peekable;
pub use peekable::Peekable;
cfg_time! {
pub(crate) mod timeout;
pub(crate) mod timeout_repeating;
use timeout::Timeout;
use timeout_repeating::TimeoutRepeating;
pub use timeout::Timeout;
pub use timeout_repeating::TimeoutRepeating;
use tokio::time::{Duration, Interval};
mod throttle;
use throttle::{throttle, Throttle};
mod chunks_timeout;
use chunks_timeout::ChunksTimeout;
pub use chunks_timeout::ChunksTimeout;
}
/// An extension trait for the [`Stream`] trait that provides a variety of
+10 -1
View File
@@ -6,13 +6,14 @@ mod support {
}
use support::mpsc;
use tokio_stream::adapters::Chain;
#[tokio::test]
async fn basic_usage() {
let one = stream::iter(vec![1, 2, 3]);
let two = stream::iter(vec![4, 5, 6]);
let mut stream = one.chain(two);
let mut stream = visibility_test(one, two);
assert_eq!(stream.size_hint(), (6, Some(6)));
assert_eq!(stream.next().await, Some(1));
@@ -39,6 +40,14 @@ async fn basic_usage() {
assert_eq!(stream.next().await, None);
}
fn visibility_test<I, S1, S2>(s1: S1, s2: S2) -> Chain<S1, S2>
where
S1: Stream<Item = I>,
S2: Stream<Item = I>,
{
s1.chain(s2)
}
#[tokio::test]
async fn pending_first() {
let (tx1, rx1) = mpsc::unbounded_channel_stream();
+1 -1
View File
@@ -6,7 +6,7 @@ name = "tokio-test"
# - Create "tokio-test-0.4.x" git tag.
version = "0.4.4"
edition = "2021"
rust-version = "1.63"
rust-version = "1.70"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
+4
View File
@@ -148,6 +148,10 @@ impl<T: Stream> Stream for Spawn<T> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.future.as_mut().poll_next(cx)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.future.size_hint()
}
}
impl MockTask {
+25
View File
@@ -0,0 +1,25 @@
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio_stream::Stream;
use tokio_test::task;
/// A [`Stream`] that has a stub size hint.
struct SizedStream;
impl Stream for SizedStream {
type Item = ();
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Poll::Pending
}
fn size_hint(&self) -> (usize, Option<usize>) {
(100, Some(200))
}
}
#[test]
fn test_spawn_stream_size_hint() {
let spawn = task::spawn(SizedStream);
assert_eq!(spawn.size_hint(), (100, Some(200)));
}
+1 -1
View File
@@ -6,7 +6,7 @@ name = "tokio-util"
# - Create "tokio-util-0.7.x" git tag.
version = "0.7.11"
edition = "2021"
rust-version = "1.63"
rust-version = "1.70"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
+90 -83
View File
@@ -75,55 +75,23 @@
//!
//! ## Example 1
//!
//! The following will parse a `u16` length field at offset 0, including the
//! frame head in the yielded `BytesMut`.
//!
//! ```
//! # use tokio::io::AsyncRead;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_type::<u16>()
//! .length_adjustment(0) // default value
//! .num_skip(0) // Do not strip frame header
//! .new_read(io);
//! # }
//! # pub fn main() {}
//! ```
//!
//! The following frame will be decoded as such:
//!
//! ```text
//! INPUT DECODED
//! +-- len ---+--- Payload ---+ +-- len ---+--- Payload ---+
//! | \x00\x0B | Hello world | --> | \x00\x0B | Hello world |
//! +----------+---------------+ +----------+---------------+
//! ```
//!
//! The value of the length field is 11 (`\x0B`) which represents the length
//! of the payload, `hello world`. By default, [`FramedRead`] assumes that
//! the length field represents the number of bytes that **follows** the
//! length field. Thus, the entire frame has a length of 13: 2 bytes for the
//! frame head + 11 bytes for the payload.
//!
//! ## Example 2
//!
//! The following will parse a `u16` length field at offset 0, omitting the
//! frame head in the yielded `BytesMut`.
//!
//! ```
//! # use tokio::io::AsyncRead;
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! # #[tokio::main]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x0BHello world";
//! let mut reader = LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_type::<u16>()
//! .length_adjustment(0) // default value
//! // `num_skip` is not needed, the default is to skip
//! .new_read(io);
//! # let res = reader.next().await.unwrap().unwrap().to_vec();
//! # assert_eq!(res, b"Hello world");
//! # }
//! # pub fn main() {}
//! ```
//!
//! The following frame will be decoded as such:
@@ -135,27 +103,32 @@
//! +----------+---------------+ +---------------+
//! ```
//!
//! This is similar to the first example, the only difference is that the
//! frame head is **not** included in the yielded `BytesMut` value.
//! The value of the length field is 11 (`\x0B`) which represents the length
//! of the payload, `hello world`. By default, [`FramedRead`] assumes that
//! the length field represents the number of bytes that **follows** the
//! length field. Thus, the entire frame has a length of 13: 2 bytes for the
//! frame head + 11 bytes for the payload.
//!
//! ## Example 3
//! ## Example 2
//!
//! The following will parse a `u16` length field at offset 0, including the
//! frame head in the yielded `BytesMut`. In this case, the length field
//! **includes** the frame head length.
//! frame head in the yielded `BytesMut`.
//!
//! ```
//! # use tokio::io::AsyncRead;
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! # #[tokio::main]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x0BHello world";
//! let mut reader = LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_type::<u16>()
//! .length_adjustment(-2) // size of head
//! .num_skip(0)
//! .length_adjustment(2) // Add head size to length
//! .num_skip(0) // Do NOT skip the head
//! .new_read(io);
//! # let res = reader.next().await.unwrap().unwrap().to_vec();
//! # assert_eq!(res, b"\x00\x0BHello world");
//! # }
//! # pub fn main() {}
//! ```
//!
//! The following frame will be decoded as such:
@@ -163,10 +136,46 @@
//! ```text
//! INPUT DECODED
//! +-- len ---+--- Payload ---+ +-- len ---+--- Payload ---+
//! | \x00\x0D | Hello world | --> | \x00\x0D | Hello world |
//! | \x00\x0B | Hello world | --> | \x00\x0B | Hello world |
//! +----------+---------------+ +----------+---------------+
//! ```
//!
//! This is similar to the first example, the only difference is that the
//! frame head is **included** in the yielded `BytesMut` value. To achieve
//! this, we need to add the header size to the length with `length_adjustment`,
//! and set `num_skip` to `0` to prevent skipping the head.
//!
//! ## Example 3
//!
//! The following will parse a `u16` length field at offset 0, omitting the
//! frame head in the yielded `BytesMut`. In this case, the length field
//! **includes** the frame head length.
//!
//! ```
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # #[tokio::main]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x0DHello world";
//! let mut reader = LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_type::<u16>()
//! .length_adjustment(-2) // size of head
//! .new_read(io);
//! # let res = reader.next().await.unwrap().unwrap().to_vec();
//! # assert_eq!(res, b"Hello world");
//! # }
//! ```
//!
//! The following frame will be decoded as such:
//!
//! ```text
//! INPUT DECODED
//! +-- len ---+--- Payload ---+ +--- Payload ---+
//! | \x00\x0D | Hello world | --> | Hello world |
//! +----------+---------------+ +---------------+
//! ```
//!
//! In most cases, the length field represents the length of the payload
//! only, as shown in the previous examples. However, in some protocols the
//! length field represents the length of the whole frame, including the
@@ -179,17 +188,20 @@
//! frame head, including the frame head in the yielded `BytesMut`.
//!
//! ```
//! # use tokio::io::AsyncRead;
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! # #[tokio::main]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x00\x0B\xCA\xFEHello world";
//! let mut reader = LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_length(3)
//! .length_adjustment(2) // remaining head
//! .length_adjustment(3 + 2) // len field and remaining head
//! .num_skip(0)
//! .new_read(io);
//! # let res = reader.next().await.unwrap().unwrap().to_vec();
//! # assert_eq!(res, b"\x00\x00\x0B\xCA\xFEHello world");
//! # }
//! # pub fn main() {}
//! ```
//!
//! The following frame will be decoded as such:
@@ -223,17 +235,20 @@
//! included.
//!
//! ```
//! # use tokio::io::AsyncRead;
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! # #[tokio::main]
//! # async fn main() {
//! # let io: &[u8] = b"\xCA\x00\x0B\xFEHello world";
//! let mut reader = LengthDelimitedCodec::builder()
//! .length_field_offset(1) // length of hdr1
//! .length_field_type::<u16>()
//! .length_adjustment(1) // length of hdr2
//! .num_skip(3) // length of hdr1 + LEN
//! .new_read(io);
//! # let res = reader.next().await.unwrap().unwrap().to_vec();
//! # assert_eq!(res, b"\xFEHello world");
//! # }
//! # pub fn main() {}
//! ```
//!
//! The following frame will be decoded as such:
@@ -269,15 +284,19 @@
//! length.
//!
//! ```
//! # use tokio::io::AsyncRead;
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! # #[tokio::main]
//! # async fn main() {
//! # let io: &[u8] = b"\xCA\x00\x0F\xFEHello world";
//! let mut reader = LengthDelimitedCodec::builder()
//! .length_field_offset(1) // length of hdr1
//! .length_field_type::<u16>()
//! .length_adjustment(-3) // length of hdr1 + LEN, negative
//! .num_skip(3)
//! .new_read(io);
//! # let res = reader.next().await.unwrap().unwrap().to_vec();
//! # assert_eq!(res, b"\xFEHello world");
//! # }
//! ```
//!
@@ -308,17 +327,20 @@
//! frame head, excluding the 4th byte from the yielded `BytesMut`.
//!
//! ```
//! # use tokio::io::AsyncRead;
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! # #[tokio::main]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x00\x0B\xFFHello world";
//! let mut reader = LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_length(3)
//! .length_adjustment(0) // default value
//! .num_skip(4) // skip the first 4 bytes
//! .new_read(io);
//! # let res = reader.next().await.unwrap().unwrap().to_vec();
//! # assert_eq!(res, b"Hello world");
//! # }
//! # pub fn main() {}
//! ```
//!
//! The following frame will be decoded as such:
@@ -1027,28 +1049,13 @@ impl Builder {
}
fn adjust_max_frame_len(&mut self) {
// This function is basically `std::u64::saturating_add_signed`. Since it
// requires MSRV 1.66, its implementation is copied here.
//
// TODO: use the method from std when MSRV becomes >= 1.66
fn saturating_add_signed(num: u64, rhs: i64) -> u64 {
let (res, overflow) = num.overflowing_add(rhs as u64);
if overflow == (rhs < 0) {
res
} else if overflow {
u64::MAX
} else {
0
}
}
// Calculate the maximum number that can be represented using `length_field_len` bytes.
let max_number = match 1u64.checked_shl((8 * self.length_field_len) as u32) {
Some(shl) => shl - 1,
None => u64::MAX,
};
let max_allowed_len = saturating_add_signed(max_number, self.length_adjustment as i64);
let max_allowed_len = max_number.saturating_add_signed(self.length_adjustment as i64);
if self.max_frame_len as u64 > max_allowed_len {
self.max_frame_len = usize::try_from(max_allowed_len).unwrap_or(usize::MAX);
+1 -1
View File
@@ -326,7 +326,7 @@ impl<S, B> StreamReader<S, B> {
}
}
impl<S: Sink<T, Error = E>, E, T> Sink<T> for StreamReader<S, E> {
impl<S: Sink<T, Error = E>, B, E, T> Sink<T> for StreamReader<S, B> {
type Error = E;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().inner.poll_ready(cx)
+46
View File
@@ -241,6 +241,52 @@ impl CancellationToken {
pub fn drop_guard(self) -> DropGuard {
DropGuard { inner: Some(self) }
}
/// Runs a future to completion and returns its result wrapped inside of an `Option`
/// unless the `CancellationToken` is cancelled. In that case the function returns
/// `None` and the future gets dropped.
///
/// # Cancel safety
///
/// This method is only cancel safe if `fut` is cancel safe.
pub async fn run_until_cancelled<F>(&self, fut: F) -> Option<F::Output>
where
F: Future,
{
pin_project! {
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// is cancelled or a given Future gets resolved. It is biased towards the
/// Future completion.
#[must_use = "futures do nothing unless polled"]
struct RunUntilCancelledFuture<'a, F: Future> {
#[pin]
cancellation: WaitForCancellationFuture<'a>,
#[pin]
future: F,
}
}
impl<'a, F: Future> Future for RunUntilCancelledFuture<'a, F> {
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Poll::Ready(res) = this.future.poll(cx) {
Poll::Ready(Some(res))
} else if this.cancellation.poll(cx).is_ready() {
Poll::Ready(None)
} else {
Poll::Pending
}
}
}
RunUntilCancelledFuture {
cancellation: self.cancelled(),
future: fut,
}
.await
}
}
// ===== impl WaitForCancellationFuture =====
+6
View File
@@ -766,6 +766,12 @@ impl<T> DelayQueue<T> {
}
}
if self.slab.is_empty() {
if let Some(waker) = self.waker.take() {
waker.wake();
}
}
Expired {
key: Key::new(key.index),
data: data.inner,
@@ -1,6 +1,7 @@
#![warn(rust_2018_idioms)]
use tokio::pin;
use tokio::sync::oneshot;
use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
use core::future::Future;
@@ -445,3 +446,50 @@ fn derives_send_sync() {
assert_send::<WaitForCancellationFuture<'static>>();
assert_sync::<WaitForCancellationFuture<'static>>();
}
#[test]
fn run_until_cancelled_test() {
let (waker, _) = new_count_waker();
{
let token = CancellationToken::new();
let fut = token.run_until_cancelled(std::future::pending::<()>());
pin!(fut);
assert_eq!(
Poll::Pending,
fut.as_mut().poll(&mut Context::from_waker(&waker))
);
token.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 fut = token.run_until_cancelled(async move {
rx.await.unwrap();
42
});
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))
);
}
}
+13
View File
@@ -880,6 +880,19 @@ async fn peek() {
assert!(queue.peek().is_none());
}
#[tokio::test(start_paused = true)]
async fn wake_after_remove_last() {
let mut queue = task::spawn(DelayQueue::new());
let key = queue.insert("foo", ms(1000));
assert_pending!(poll!(queue));
queue.remove(&key);
assert!(queue.is_woken());
assert!(assert_ready!(poll!(queue)).is_none());
}
fn ms(n: u64) -> Duration {
Duration::from_millis(n)
}
+113
View File
@@ -1,3 +1,116 @@
# 1.39.3 (August 17th, 2024)
This release fixes a regression where the unix socket api stopped accepting
the abstract socket namespace. ([#6772])
[#6772]: https://github.com/tokio-rs/tokio/pull/6772
# 1.39.2 (July 27th, 2024)
This release fixes a regression where the `select!` macro stopped accepting
expressions that make use of temporary lifetime extension. ([#6722])
[#6722]: https://github.com/tokio-rs/tokio/pull/6722
# 1.39.1 (July 23rd, 2024)
This release reverts "time: avoid traversing entries in the time wheel twice"
because it contains a bug. ([#6715])
[#6715]: https://github.com/tokio-rs/tokio/pull/6715
# 1.39.0 (July 23rd, 2024)
Yanked. Please use 1.39.1 instead.
- This release bumps the MSRV to 1.70. ([#6645])
- This release upgrades to mio v1. ([#6635])
- This release upgrades to windows-sys v0.52 ([#6154])
### Added
- io: implement `AsyncSeek` for `Empty` ([#6663])
- metrics: stabilize `num_alive_tasks` ([#6619], [#6667])
- process: add `Command::as_std_mut` ([#6608])
- sync: add `watch::Sender::same_channel` ([#6637])
- sync: add `{Receiver,UnboundedReceiver}::{sender_strong_count,sender_weak_count}` ([#6661])
- sync: implement `Default` for `watch::Sender` ([#6626])
- task: implement `Clone` for `AbortHandle` ([#6621])
- task: stabilize `consume_budget` ([#6622])
### Changed
- io: improve panic message of `ReadBuf::put_slice()` ([#6629])
- io: read during write in `copy_bidirectional` and `copy` ([#6532])
- runtime: replace `num_cpus` with `available_parallelism` ([#6709])
- task: avoid stack overflow when passing large future to `block_on` ([#6692])
- time: avoid traversing entries in the time wheel twice ([#6584])
- time: support `IntoFuture` with `timeout` ([#6666])
- macros: support `IntoFuture` with `join!` and `select!` ([#6710])
### Fixed
- docs: fix docsrs builds with the fs feature enabled ([#6585])
- io: only use short-read optimization on known-to-be-compatible platforms ([#6668])
- time: fix overflow panic when using large durations with `Interval` ([#6612])
### Added (unstable)
- macros: allow `unhandled_panic` behavior for `#[tokio::main]` and `#[tokio::test]` ([#6593])
- metrics: add `spawned_tasks_count` ([#6114])
- metrics: add `worker_park_unpark_count` ([#6696])
- metrics: add worker thread id ([#6695])
### Documented
- io: update `tokio::io::stdout` documentation ([#6674])
- macros: typo fix in `join.rs` and `try_join.rs` ([#6641])
- runtime: fix typo in `unhandled_panic` ([#6660])
- task: document behavior of `JoinSet::try_join_next` when all tasks are running ([#6671])
[#6114]: https://github.com/tokio-rs/tokio/pull/6114
[#6154]: https://github.com/tokio-rs/tokio/pull/6154
[#6532]: https://github.com/tokio-rs/tokio/pull/6532
[#6584]: https://github.com/tokio-rs/tokio/pull/6584
[#6585]: https://github.com/tokio-rs/tokio/pull/6585
[#6593]: https://github.com/tokio-rs/tokio/pull/6593
[#6608]: https://github.com/tokio-rs/tokio/pull/6608
[#6612]: https://github.com/tokio-rs/tokio/pull/6612
[#6619]: https://github.com/tokio-rs/tokio/pull/6619
[#6621]: https://github.com/tokio-rs/tokio/pull/6621
[#6622]: https://github.com/tokio-rs/tokio/pull/6622
[#6626]: https://github.com/tokio-rs/tokio/pull/6626
[#6629]: https://github.com/tokio-rs/tokio/pull/6629
[#6635]: https://github.com/tokio-rs/tokio/pull/6635
[#6637]: https://github.com/tokio-rs/tokio/pull/6637
[#6641]: https://github.com/tokio-rs/tokio/pull/6641
[#6645]: https://github.com/tokio-rs/tokio/pull/6645
[#6660]: https://github.com/tokio-rs/tokio/pull/6660
[#6661]: https://github.com/tokio-rs/tokio/pull/6661
[#6663]: https://github.com/tokio-rs/tokio/pull/6663
[#6666]: https://github.com/tokio-rs/tokio/pull/6666
[#6667]: https://github.com/tokio-rs/tokio/pull/6667
[#6668]: https://github.com/tokio-rs/tokio/pull/6668
[#6671]: https://github.com/tokio-rs/tokio/pull/6671
[#6674]: https://github.com/tokio-rs/tokio/pull/6674
[#6692]: https://github.com/tokio-rs/tokio/pull/6692
[#6695]: https://github.com/tokio-rs/tokio/pull/6695
[#6696]: https://github.com/tokio-rs/tokio/pull/6696
[#6709]: https://github.com/tokio-rs/tokio/pull/6709
[#6710]: https://github.com/tokio-rs/tokio/pull/6710
# 1.38.1 (July 16th, 2024)
This release fixes the bug identified as ([#6682]), which caused timers not
to fire when they should.
### Fixed
- time: update `wake_up` while holding all the locks of sharded time wheels ([#6683])
[#6682]: https://github.com/tokio-rs/tokio/pull/6682
[#6683]: https://github.com/tokio-rs/tokio/pull/6683
# 1.38.0 (May 30th, 2024)
This release marks the beginning of stabilization for runtime metrics. It
+7 -11
View File
@@ -6,9 +6,9 @@ name = "tokio"
# - README.md
# - Update CHANGELOG.md.
# - Create "v1.x.y" git tag.
version = "1.38.0"
version = "1.39.3"
edition = "2021"
rust-version = "1.63"
rust-version = "1.70"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
readme = "README.md"
@@ -71,10 +71,7 @@ process = [
]
# Includes basic task execution capabilities
rt = []
rt-multi-thread = [
"num_cpus",
"rt",
]
rt-multi-thread = ["rt"]
signal = [
"libc",
"mio/os-poll",
@@ -89,14 +86,13 @@ test-util = ["rt", "sync", "time"]
time = []
[dependencies]
tokio-macros = { version = "~2.3.0", path = "../tokio-macros", optional = true }
tokio-macros = { version = "~2.4.0", path = "../tokio-macros", optional = true }
pin-project-lite = "0.2.11"
# Everything else is optional...
bytes = { version = "1.0.0", optional = true }
mio = { version = "0.8.9", optional = true, default-features = false }
num_cpus = { version = "1.8.0", optional = true }
mio = { version = "1.0.1", optional = true, default-features = false }
parking_lot = { version = "0.12.0", optional = true }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
@@ -121,11 +117,11 @@ libc = { version = "0.2.149" }
nix = { version = "0.29.0", default-features = false, features = ["aio", "fs", "socket"] }
[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.48"
version = "0.52"
optional = true
[target.'cfg(windows)'.dev-dependencies.windows-sys]
version = "0.48"
version = "0.52"
features = [
"Win32_Foundation",
"Win32_Security_Authorization",
+5 -3
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.38.0", features = ["full"] }
tokio = { version = "1.39.3", features = ["full"] }
```
Then, on your main.rs:
@@ -186,12 +186,13 @@ When updating this, also update:
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
least** 6 months. When increasing the MSRV, the new Rust version must have been
released at least six months ago. The current MSRV is 1.63.
released at least six months ago. The current MSRV is 1.70.
Note that the MSRV is not increased automatically, and only as part of a minor
release. The MSRV history for past minor releases can be found below:
* 1.30 to now - Rust 1.63
* 1.39 to now - Rust 1.70
* 1.30 to 1.38 - Rust 1.63
* 1.27 to 1.29 - Rust 1.56
* 1.17 to 1.26 - Rust 1.49
* 1.15 to 1.16 - Rust 1.46
@@ -217,6 +218,7 @@ releases are:
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
* `1.38.x` - LTS release until July 2025. (MSRV 1.63)
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
+1 -1
View File
@@ -43,5 +43,5 @@ impl mio::event::Source for NotDefinedHere {
}
}
#[cfg(feature = "net")]
#[cfg(any(feature = "net", feature = "fs"))]
pub mod os;
+1 -7
View File
@@ -24,11 +24,5 @@ use std::path::Path;
/// ```
pub async fn try_exists(path: impl AsRef<Path>) -> io::Result<bool> {
let path = path.as_ref().to_owned();
// std's Path::try_exists is not available for current Rust min supported version.
// Current implementation is based on its internal implementation instead.
match asyncify(move || std::fs::metadata(path)).await {
Ok(_) => Ok(true),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(error),
}
asyncify(move || path.try_exists()).await
}
+57 -3
View File
@@ -1,7 +1,7 @@
//! Definition of the [`MaybeDone`] combinator.
use pin_project_lite::pin_project;
use std::future::Future;
use std::future::{Future, IntoFuture};
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -10,6 +10,7 @@ pin_project! {
#[derive(Debug)]
#[project = MaybeDoneProj]
#[project_replace = MaybeDoneProjReplace]
#[repr(C)] // https://github.com/rust-lang/miri/issues/3780
pub enum MaybeDone<Fut: Future> {
/// A not-yet-completed future.
Future { #[pin] future: Fut },
@@ -22,8 +23,10 @@ pin_project! {
}
/// Wraps a future into a `MaybeDone`.
pub fn maybe_done<Fut: Future>(future: Fut) -> MaybeDone<Fut> {
MaybeDone::Future { future }
pub fn maybe_done<F: IntoFuture>(future: F) -> MaybeDone<F::IntoFuture> {
MaybeDone::Future {
future: future.into_future(),
}
}
impl<Fut: Future> MaybeDone<Fut> {
@@ -67,3 +70,54 @@ impl<Fut: Future> Future for MaybeDone<Fut> {
Poll::Ready(())
}
}
// Test for https://github.com/tokio-rs/tokio/issues/6729
#[cfg(test)]
mod miri_tests {
use super::maybe_done;
use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll, Wake},
};
struct ThingAdder<'a> {
thing: &'a mut String,
}
impl Future for ThingAdder<'_> {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe {
*self.get_unchecked_mut().thing += ", world";
}
Poll::Pending
}
}
#[test]
fn maybe_done_miri() {
let mut thing = "hello".to_owned();
// The async block is necessary to trigger the miri failure.
#[allow(clippy::redundant_async_block)]
let fut = async move { ThingAdder { thing: &mut thing }.await };
let mut fut = maybe_done(fut);
let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
let waker = Arc::new(DummyWaker).into();
let mut ctx = Context::from_waker(&waker);
assert_eq!(fut.as_mut().poll(&mut ctx), Poll::Pending);
assert_eq!(fut.as_mut().poll(&mut ctx), Poll::Pending);
}
struct DummyWaker;
impl Wake for DummyWaker {
fn wake(self: Arc<Self>) {}
}
}
+32 -5
View File
@@ -179,15 +179,42 @@ feature! {
let evt = ready!(self.registration.poll_read_ready(cx))?;
let b = &mut *(buf.unfilled_mut() as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]);
// used only when the cfgs below apply
#[allow(unused_variables)]
let len = b.len();
match self.io.as_ref().unwrap().read(b) {
Ok(n) => {
// if we read a partially full buffer, this is sufficient on unix to show
// that the socket buffer has been drained. Unfortunately this assumption
// fails for level-triggered selectors (like on Windows or poll even for
// UNIX): https://github.com/tokio-rs/tokio/issues/5866
if n > 0 && (!cfg!(windows) && !cfg!(mio_unsupported_force_poll_poll) && n < len) {
// When mio is using the epoll or kqueue selector, reading a partially full
// buffer is sufficient to show that the socket buffer has been drained.
//
// This optimization does not work for level-triggered selectors such as
// windows or when poll is used.
//
// Read more:
// https://github.com/tokio-rs/tokio/issues/5866
#[cfg(all(
not(mio_unsupported_force_poll_poll),
any(
// epoll
target_os = "android",
target_os = "illumos",
target_os = "linux",
target_os = "redox",
// kqueue
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "tvos",
target_os = "visionos",
target_os = "watchos",
)
))]
if 0 < n && n < len {
self.registration.clear_readiness(evt);
}
+3 -1
View File
@@ -248,7 +248,9 @@ impl<'a> ReadBuf<'a> {
pub fn put_slice(&mut self, buf: &[u8]) {
assert!(
self.remaining() >= buf.len(),
"buf.len() must fit in remaining()"
"buf.len() must fit in remaining(); buf.len() = {}, remaining() = {}",
buf.len(),
self.remaining()
);
let amt = buf.len();
+50
View File
@@ -33,6 +33,31 @@ cfg_io_std! {
/// Ok(())
/// }
/// ```
///
/// The following is an example of using `stdio` with loop.
///
/// ```
/// use tokio::io::{self, AsyncWriteExt};
///
/// #[tokio::main]
/// async fn main() {
/// let messages = vec!["hello", " world\n"];
///
/// // When you use `stdio` in a loop, it is recommended to create
/// // a single `stdio` instance outside the loop and call a write
/// // operation against that instance on each loop.
/// //
/// // Repeatedly creating `stdout` instances inside the loop and
/// // writing to that handle could result in mangled output since
/// // each write operation is handled by a different blocking thread.
/// let mut stdout = io::stdout();
///
/// for message in &messages {
/// stdout.write_all(message.as_bytes()).await.unwrap();
/// stdout.flush().await.unwrap();
/// }
/// }
/// ```
#[derive(Debug)]
pub struct Stdout {
std: SplitByUtf8BoundaryIfWindows<Blocking<std::io::Stdout>>,
@@ -64,6 +89,31 @@ cfg_io_std! {
/// Ok(())
/// }
/// ```
///
/// The following is an example of using `stdio` with loop.
///
/// ```
/// use tokio::io::{self, AsyncWriteExt};
///
/// #[tokio::main]
/// async fn main() {
/// let messages = vec!["hello", " world\n"];
///
/// // When you use `stdio` in a loop, it is recommended to create
/// // a single `stdio` instance outside the loop and call a write
/// // operation against that instance on each loop.
/// //
/// // Repeatedly creating `stdout` instances inside the loop and
/// // writing to that handle could result in mangled output since
/// // each write operation is handled by a different blocking thread.
/// let mut stdout = io::stdout();
///
/// for message in &messages {
/// stdout.write_all(message.as_bytes()).await.unwrap();
/// stdout.flush().await.unwrap();
/// }
/// }
/// ```
pub fn stdout() -> Stdout {
let std = io::stdout();
Stdout {
+30 -25
View File
@@ -96,12 +96,9 @@ impl CopyBuffer {
// Keep track of task budget
let coop = ready!(crate::runtime::coop::poll_proceed(cx));
loop {
// If our buffer is empty, then we need to read some data to
// continue.
if self.pos == self.cap && !self.read_done {
self.pos = 0;
self.cap = 0;
// 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.
if self.cap < self.buf.len() && !self.read_done {
match self.poll_fill_buf(cx, reader.as_mut()) {
Poll::Ready(Ok(())) => {
#[cfg(any(
@@ -131,25 +128,29 @@ impl CopyBuffer {
return Poll::Ready(Err(err));
}
Poll::Pending => {
// Try flushing when the reader has no progress to avoid deadlock
// when the reader depends on buffered writer.
if self.need_flush {
ready!(writer.as_mut().poll_flush(cx))?;
#[cfg(any(
feature = "fs",
feature = "io-std",
feature = "net",
feature = "process",
feature = "rt",
feature = "signal",
feature = "sync",
feature = "time",
))]
coop.made_progress();
self.need_flush = false;
}
// Ignore pending reads when our buffer is not empty, because
// we can try to write data immediately.
if self.pos == self.cap {
// Try flushing when the reader has no progress to avoid deadlock
// when the reader depends on buffered writer.
if self.need_flush {
ready!(writer.as_mut().poll_flush(cx))?;
#[cfg(any(
feature = "fs",
feature = "io-std",
feature = "net",
feature = "process",
feature = "rt",
feature = "signal",
feature = "sync",
feature = "time",
))]
coop.made_progress();
self.need_flush = false;
}
return Poll::Pending;
return Poll::Pending;
}
}
}
}
@@ -188,9 +189,13 @@ impl CopyBuffer {
"writer returned length larger than input slice"
);
// All data has been written, the buffer can be considered empty again
self.pos = 0;
self.cap = 0;
// If we've written all the data and we've seen EOF, flush out the
// data and finish the transfer.
if self.pos == self.cap && self.read_done {
if self.read_done {
ready!(writer.as_mut().poll_flush(cx))?;
#[cfg(any(
feature = "fs",
+16 -2
View File
@@ -1,8 +1,8 @@
use crate::io::util::poll_proceed_and_make_progress;
use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
use crate::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
use std::fmt;
use std::io;
use std::io::{self, SeekFrom};
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -133,6 +133,20 @@ impl AsyncWrite for Empty {
}
}
impl AsyncSeek for Empty {
#[inline]
fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> {
Ok(())
}
#[inline]
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
ready!(crate::trace::trace_leaf(cx));
ready!(poll_proceed_and_make_progress(cx));
Poll::Ready(Ok(0))
}
}
impl fmt::Debug for Empty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("Empty { .. }")
+1 -2
View File
@@ -6,13 +6,12 @@ use std::future::Future;
use std::io;
use std::io::ErrorKind::UnexpectedEof;
use std::marker::PhantomPinned;
use std::mem::size_of;
use std::pin::Pin;
use std::task::{Context, Poll};
macro_rules! reader {
($name:ident, $ty:ty, $reader:ident) => {
reader!($name, $ty, $reader, size_of::<$ty>());
reader!($name, $ty, $reader, std::mem::size_of::<$ty>());
};
($name:ident, $ty:ty, $reader:ident, $bytes:expr) => {
pin_project! {
+10 -2
View File
@@ -3,7 +3,7 @@ use crate::io::AsyncWrite;
use bytes::Buf;
use pin_project_lite::pin_project;
use std::future::Future;
use std::io;
use std::io::{self, IoSlice};
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -42,9 +42,17 @@ where
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
const MAX_VECTOR_ELEMENTS: usize = 64;
let me = self.project();
while me.buf.has_remaining() {
let n = ready!(Pin::new(&mut *me.writer).poll_write(cx, me.buf.chunk())?);
let n = if me.writer.is_write_vectored() {
let mut slices = [IoSlice::new(&[]); MAX_VECTOR_ELEMENTS];
let cnt = me.buf.chunks_vectored(&mut slices);
ready!(Pin::new(&mut *me.writer).poll_write_vectored(cx, &slices[..cnt]))?
} else {
ready!(Pin::new(&mut *me.writer).poll_write(cx, me.buf.chunk())?)
};
me.buf.advance(n);
if n == 0 {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
+1 -2
View File
@@ -5,13 +5,12 @@ use pin_project_lite::pin_project;
use std::future::Future;
use std::io;
use std::marker::PhantomPinned;
use std::mem::size_of;
use std::pin::Pin;
use std::task::{Context, Poll};
macro_rules! writer {
($name:ident, $ty:ty, $writer:ident) => {
writer!($name, $ty, $writer, size_of::<$ty>());
writer!($name, $ty, $writer, std::mem::size_of::<$ty>());
};
($name:ident, $ty:ty, $writer:ident, $bytes:expr) => {
pin_project! {
+7 -1
View File
@@ -367,6 +367,12 @@
//! rustflags = ["--cfg", "tokio_unstable"]
//! ```
//!
//! <div class="warning">
//! The <code>[build]</code> section does <strong>not</strong> go in a
//! <code>Cargo.toml</code> file. Instead it must be placed in the Cargo config
//! file <code>.cargo/config.toml</code>.
//! </div>
//!
//! Alternatively, you can specify it with an environment variable:
//!
//! ```sh
@@ -628,7 +634,7 @@ pub mod stream {}
#[cfg(docsrs)]
pub mod doc;
#[cfg(feature = "net")]
#[cfg(any(feature = "net", feature = "fs"))]
#[cfg(docsrs)]
#[allow(unused)]
pub(crate) use self::doc::os;
+5 -1
View File
@@ -84,6 +84,8 @@ pub(crate) mod sync {
pub(crate) mod sys {
#[cfg(feature = "rt-multi-thread")]
pub(crate) fn num_cpus() -> usize {
use std::num::NonZeroUsize;
const ENV_WORKER_THREADS: &str = "TOKIO_WORKER_THREADS";
match std::env::var(ENV_WORKER_THREADS) {
@@ -97,7 +99,9 @@ pub(crate) mod sys {
assert!(n > 0, "\"{}\" cannot be set to 0", ENV_WORKER_THREADS);
n
}
Err(std::env::VarError::NotPresent) => usize::max(1, num_cpus::get()),
Err(std::env::VarError::NotPresent) => {
std::thread::available_parallelism().map_or(1, NonZeroUsize::get)
}
Err(std::env::VarError::NotUnicode(e)) => {
panic!(
"\"{}\" must be valid unicode, error: {:?}",
+1 -1
View File
@@ -16,7 +16,7 @@
///
/// # Notes
///
/// The supplied futures are stored inline and does not require allocating a
/// The supplied futures are stored inline and do not require allocating a
/// `Vec`.
///
/// ### Runtime characteristics
+217 -8
View File
@@ -39,13 +39,13 @@ macro_rules! doc {
/// 2. Aggregate the `<async expression>`s from each branch, including the
/// disabled ones. If the branch is disabled, `<async expression>` is still
/// evaluated, but the resulting future is not polled.
/// 3. Concurrently await on the results for all remaining `<async expression>`s.
/// 4. Once an `<async expression>` returns a value, attempt to apply the value
/// to the provided `<pattern>`, if the pattern matches, evaluate `<handler>`
/// and return. If the pattern **does not** match, disable the current branch
/// and for the remainder of the current call to `select!`. Continue from step 3.
/// 5. If **all** branches are disabled, evaluate the `else` expression. If no
/// else branch is provided, panic.
/// 3. If **all** branches are disabled: go to step 6.
/// 4. Concurrently await on the results for all remaining `<async expression>`s.
/// 5. Once an `<async expression>` returns a value, attempt to apply the value to the
/// provided `<pattern>`. If the pattern matches, evaluate the `<handler>` and return.
/// If the pattern **does not** match, disable the current branch for the remainder of
/// the current call to `select!`. Continue from step 3.
/// 6. Evaluate the `else` expression. If no else expression is provided, panic.
///
/// # Runtime characteristics
///
@@ -489,13 +489,22 @@ doc! {macro_rules! select {
// Create a scope to separate polling from handling the output. This
// adds borrow checker flexibility when using the macro.
let mut output = {
// Store each future directly first (that is, without wrapping the future in a call to
// `IntoFuture::into_future`). This allows the `$fut` expression to make use of
// temporary lifetime extension.
//
// https://doc.rust-lang.org/1.58.1/reference/destructors.html#temporary-lifetime-extension
let futures_init = ($( $fut, )+);
// Safety: Nothing must be moved out of `futures`. This is to
// satisfy the requirement of `Pin::new_unchecked` called below.
//
// We can't use the `pin!` macro for this because `futures` is a
// tuple and the standard library provides no way to pin-project to
// the fields of a tuple.
let mut futures = ( $( $fut , )+ );
let mut futures = ($( $crate::macros::support::IntoFuture::into_future(
$crate::count_field!( futures_init.$($skip)* )
),)+);
// This assignment makes sure that the `poll_fn` closure only has a
// reference to the futures, instead of taking ownership of them.
@@ -854,6 +863,206 @@ macro_rules! count {
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! count_field {
($var:ident. ) => {
$var.0
};
($var:ident. _) => {
$var.1
};
($var:ident. _ _) => {
$var.2
};
($var:ident. _ _ _) => {
$var.3
};
($var:ident. _ _ _ _) => {
$var.4
};
($var:ident. _ _ _ _ _) => {
$var.5
};
($var:ident. _ _ _ _ _ _) => {
$var.6
};
($var:ident. _ _ _ _ _ _ _) => {
$var.7
};
($var:ident. _ _ _ _ _ _ _ _) => {
$var.8
};
($var:ident. _ _ _ _ _ _ _ _ _) => {
$var.9
};
($var:ident. _ _ _ _ _ _ _ _ _ _) => {
$var.10
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _) => {
$var.11
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.12
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.13
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.14
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.15
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.16
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.17
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.18
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.19
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.20
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.21
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.22
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.23
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.24
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.25
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.26
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.27
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.28
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.29
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.30
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.31
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.32
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.33
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.34
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.35
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.36
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.37
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.38
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.39
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.40
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.41
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.42
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.43
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.44
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.45
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.46
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.47
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.48
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.49
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.50
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.51
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.52
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.53
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.54
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.55
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.56
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.57
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.58
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.59
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.60
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.61
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.62
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.63
};
($var:ident. _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => {
$var.64
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! select_variant {
+1 -1
View File
@@ -8,6 +8,6 @@ cfg_macros! {
}
}
pub use std::future::Future;
pub use std::future::{Future, IntoFuture};
pub use std::pin::Pin;
pub use std::task::Poll;
+1 -1
View File
@@ -14,7 +14,7 @@
///
/// # Notes
///
/// The supplied futures are stored inline and does not require allocating a
/// The supplied futures are stored inline and do not require allocating a
/// `Vec`.
///
/// ### Runtime characteristics
+21 -2
View File
@@ -3,8 +3,14 @@ use crate::net::unix::{SocketAddr, UnixStream};
use std::fmt;
use std::io;
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
use std::os::unix::net;
use std::os::unix::net::{self, SocketAddr as StdSocketAddr};
use std::path::Path;
use std::task::{Context, Poll};
@@ -70,7 +76,20 @@ impl UnixListener {
where
P: AsRef<Path>,
{
let listener = mio::net::UnixListener::bind(path)?;
// For now, we handle abstract socket paths on linux here.
#[cfg(any(target_os = "linux", target_os = "android"))]
let addr = {
let os_str_bytes = path.as_ref().as_os_str().as_bytes();
if os_str_bytes.starts_with(b"\0") {
StdSocketAddr::from_abstract_name(os_str_bytes)?
} else {
StdSocketAddr::from_pathname(path)?
}
};
#[cfg(not(any(target_os = "linux", target_os = "android")))]
let addr = StdSocketAddr::from_pathname(path)?;
let listener = mio::net::UnixListener::bind_addr(&addr)?;
let io = PollEvented::new(listener)?;
Ok(UnixListener { io })
}
+1 -1
View File
@@ -2,7 +2,7 @@ use std::fmt;
use std::path::Path;
/// An address associated with a Tokio Unix socket.
pub struct SocketAddr(pub(super) mio::net::SocketAddr);
pub struct SocketAddr(pub(super) std::os::unix::net::SocketAddr);
impl SocketAddr {
/// Returns `true` if the address is unnamed.
+21 -2
View File
@@ -8,8 +8,14 @@ use crate::net::unix::SocketAddr;
use std::fmt;
use std::io::{self, Read, Write};
use std::net::Shutdown;
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
use std::os::unix::net;
use std::os::unix::net::{self, SocketAddr as StdSocketAddr};
use std::path::Path;
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -66,7 +72,20 @@ impl UnixStream {
where
P: AsRef<Path>,
{
let stream = mio::net::UnixStream::connect(path)?;
// On linux, abstract socket paths need to be considered.
#[cfg(any(target_os = "linux", target_os = "android"))]
let addr = {
let os_str_bytes = path.as_ref().as_os_str().as_bytes();
if os_str_bytes.starts_with(b"\0") {
StdSocketAddr::from_abstract_name(os_str_bytes)?
} else {
StdSocketAddr::from_pathname(path)?
}
};
#[cfg(not(any(target_os = "linux", target_os = "android")))]
let addr = StdSocketAddr::from_pathname(path)?;
let stream = mio::net::UnixStream::connect_addr(&addr)?;
let stream = UnixStream::new(stream)?;
poll_fn(|cx| stream.io.registration().poll_write_ready(cx)).await?;
+24 -13
View File
@@ -325,6 +325,12 @@ impl Command {
&self.std
}
/// Cheaply convert to a `&mut std::process::Command` for places where the type from the
/// standard library is expected.
pub fn as_std_mut(&mut self) -> &mut StdCommand {
&mut self.std
}
/// Adds an argument to pass to the program.
///
/// Only one argument can be passed per use. So instead of:
@@ -745,29 +751,34 @@ impl Command {
///
/// Process groups determine which processes receive signals.
///
/// **Note**: This is an [unstable API][unstable] but will be stabilised once
/// tokio's `MSRV` is sufficiently new. See [the documentation on
/// unstable features][unstable] for details about using unstable features.
/// # Examples
///
/// If you want similar behavior without using this unstable feature you can
/// create a [`std::process::Command`] and convert that into a
/// [`tokio::process::Command`] using the `From` trait.
/// Pressing Ctrl-C in a terminal will send `SIGINT` to all processes
/// in the current foreground process group. By spawning the `sleep`
/// subprocess in a new process group, it will not receive `SIGINT`
/// from the terminal.
///
/// [unstable]: crate#unstable-features
/// [`tokio::process::Command`]: crate::process::Command
/// The parent process could install a [signal handler] and manage the
/// process on its own terms.
///
/// A process group ID of 0 will use the process ID as the PGID.
///
/// ```no_run
/// # async fn test() { // allow using await
/// use tokio::process::Command;
///
/// let output = Command::new("ls")
/// .process_group(0)
/// .output().await.unwrap();
/// let output = Command::new("sleep")
/// .arg("10")
/// .process_group(0)
/// .output()
/// .await
/// .unwrap();
/// # }
/// ```
///
/// [signal handler]: crate::signal
#[cfg(unix)]
#[cfg(tokio_unstable)]
#[cfg_attr(docsrs, doc(cfg(all(unix, tokio_unstable))))]
#[cfg_attr(docsrs, doc(cfg(unix)))]
pub fn process_group(&mut self, pgroup: i32) -> &mut Command {
self.std.process_group(pgroup);
self
+6 -1
View File
@@ -245,7 +245,12 @@ mod test {
assert!(status.success());
let stdout = String::from_utf8_lossy(&stdout);
let mut kernel_version_iter = stdout.split_once('-').unwrap().0.split('.');
let mut kernel_version_iter = match stdout.split_once('-') {
Some((version, _)) => version,
_ => &stdout,
}
.split('.');
let major: u32 = kernel_version_iter.next().unwrap().parse().unwrap();
let minor: u32 = kernel_version_iter.next().unwrap().parse().unwrap();
+23 -18
View File
@@ -6,12 +6,13 @@ use crate::runtime::blocking::schedule::BlockingSchedule;
use crate::runtime::blocking::{shutdown, BlockingTask};
use crate::runtime::builder::ThreadNameFn;
use crate::runtime::task::{self, JoinHandle};
use crate::runtime::{Builder, Callback, Handle};
use crate::runtime::{Builder, Callback, Handle, BOX_FUTURE_THRESHOLD};
use crate::util::metric_atomics::MetricAtomicUsize;
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::io;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::Ordering;
use std::time::Duration;
pub(crate) struct BlockingPool {
@@ -26,9 +27,9 @@ pub(crate) struct Spawner {
#[derive(Default)]
pub(crate) struct SpawnerMetrics {
num_threads: AtomicUsize,
num_idle_threads: AtomicUsize,
queue_depth: AtomicUsize,
num_threads: MetricAtomicUsize,
num_idle_threads: MetricAtomicUsize,
queue_depth: MetricAtomicUsize,
}
impl SpawnerMetrics {
@@ -47,27 +48,27 @@ impl SpawnerMetrics {
}
fn inc_num_threads(&self) {
self.num_threads.fetch_add(1, Ordering::Relaxed);
self.num_threads.increment();
}
fn dec_num_threads(&self) {
self.num_threads.fetch_sub(1, Ordering::Relaxed);
self.num_threads.decrement();
}
fn inc_num_idle_threads(&self) {
self.num_idle_threads.fetch_add(1, Ordering::Relaxed);
self.num_idle_threads.increment();
}
fn dec_num_idle_threads(&self) -> usize {
self.num_idle_threads.fetch_sub(1, Ordering::Relaxed)
self.num_idle_threads.decrement()
}
fn inc_queue_depth(&self) {
self.queue_depth.fetch_add(1, Ordering::Relaxed);
self.queue_depth.increment();
}
fn dec_queue_depth(&self) {
self.queue_depth.fetch_sub(1, Ordering::Relaxed);
self.queue_depth.decrement();
}
}
@@ -263,8 +264,12 @@ impl BlockingPool {
// Loom requires that execution be deterministic, so sort by thread ID before joining.
// (HashMaps use a randomly-seeded hash function, so the order is nondeterministic)
let mut workers: Vec<(usize, thread::JoinHandle<()>)> = workers.into_iter().collect();
workers.sort_by_key(|(id, _)| *id);
#[cfg(loom)]
let workers: Vec<(usize, thread::JoinHandle<()>)> = {
let mut workers: Vec<_> = workers.into_iter().collect();
workers.sort_by_key(|(id, _)| *id);
workers
};
for (_id, handle) in workers {
let _ = handle.join();
@@ -295,7 +300,7 @@ impl Spawner {
R: Send + 'static,
{
let (join_handle, spawn_result) =
if cfg!(debug_assertions) && std::mem::size_of::<F>() > 2048 {
if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
self.spawn_blocking_inner(Box::new(func), Mandatory::NonMandatory, None, rt)
} else {
self.spawn_blocking_inner(func, Mandatory::NonMandatory, None, rt)
@@ -322,7 +327,7 @@ impl Spawner {
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
let (join_handle, spawn_result) = if cfg!(debug_assertions) && std::mem::size_of::<F>() > 2048 {
let (join_handle, spawn_result) = if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
self.spawn_blocking_inner(
Box::new(func),
Mandatory::Mandatory,
@@ -456,7 +461,7 @@ impl Spawner {
shutdown_tx: shutdown::Sender,
rt: &Handle,
id: usize,
) -> std::io::Result<thread::JoinHandle<()>> {
) -> io::Result<thread::JoinHandle<()>> {
let mut builder = thread::Builder::new().name((self.inner.thread_name)());
if let Some(stack_size) = self.inner.stack_size {
@@ -492,8 +497,8 @@ cfg_unstable_metrics! {
// Tells whether the error when spawning a thread is temporary.
#[inline]
fn is_temporary_os_thread_error(error: &std::io::Error) -> bool {
matches!(error.kind(), std::io::ErrorKind::WouldBlock)
fn is_temporary_os_thread_error(error: &io::Error) -> bool {
matches!(error.kind(), io::ErrorKind::WouldBlock)
}
impl Inner {
+1 -1
View File
@@ -816,7 +816,7 @@ impl Builder {
///
/// By default, an unhandled panic (i.e. a panic not caught by
/// [`std::panic::catch_unwind`]) has no impact on the runtime's
/// execution. The panic is error value is forwarded to the task's
/// execution. The panic's error value is forwarded to the task's
/// [`JoinHandle`] and all other spawned tasks continue running.
///
/// The `unhandled_panic` option enables configuring this behavior.
+15 -1
View File
@@ -16,6 +16,7 @@ pub struct Handle {
}
use crate::runtime::task::JoinHandle;
use crate::runtime::BOX_FUTURE_THRESHOLD;
use crate::util::error::{CONTEXT_MISSING_ERROR, THREAD_LOCAL_DESTROYED_ERROR};
use std::future::Future;
@@ -188,7 +189,11 @@ impl Handle {
F: Future + Send + 'static,
F::Output: Send + 'static,
{
self.spawn_named(future, None)
if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
self.spawn_named(Box::pin(future), None)
} else {
self.spawn_named(future, None)
}
}
/// Runs the provided function on an executor dedicated to blocking
@@ -291,6 +296,15 @@ impl Handle {
/// [`tokio::time`]: crate::time
#[track_caller]
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
self.block_on_inner(Box::pin(future))
} else {
self.block_on_inner(future)
}
}
#[track_caller]
fn block_on_inner<F: Future>(&self, future: F) -> F::Output {
#[cfg(all(
tokio_unstable,
tokio_taskdump,
+6
View File
@@ -13,6 +13,7 @@ use crate::runtime::io::{IoDriverMetrics, RegistrationSet, ScheduledIo};
use mio::event::Source;
use std::fmt;
use std::io;
use std::os::fd::AsRawFd;
use std::sync::Arc;
use std::time::Duration;
@@ -209,6 +210,11 @@ impl Handle {
self.waker.wake().expect("failed to wake I/O driver");
}
#[cfg(unix)]
pub(crate) fn get_raw_poll_fd(&self) -> std::os::fd::RawFd {
self.registry.as_raw_fd()
}
/// Registers an I/O resource with the reactor for a given `mio::Ready` state.
///
/// The registration token is returned.
+13
View File
@@ -7,6 +7,9 @@ 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,
@@ -54,6 +57,7 @@ impl MetricsBatch {
MetricsBatch {
park_count: 0,
park_unpark_count: 0,
noop_count: 0,
steal_count: 0,
steal_operations: 0,
@@ -76,6 +80,9 @@ impl MetricsBatch {
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
@@ -101,6 +108,7 @@ impl MetricsBatch {
/// 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;
@@ -109,6 +117,11 @@ impl MetricsBatch {
}
}
/// The worker was unparked.
pub(crate) fn unparked(&mut self) {
self.park_unpark_count += 1;
}
/// Start processing a batch of tasks
pub(crate) fn start_processing_scheduled_tasks(&mut self) {
self.processing_scheduled_tasks_started_at = Instant::now();
+4
View File
@@ -1,5 +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 {}
@@ -30,6 +32,7 @@ impl WorkerMetrics {
}
pub(crate) fn set_queue_depth(&self, _len: usize) {}
pub(crate) fn set_thread_id(&self, _thread_id: ThreadId) {}
}
impl MetricsBatch {
@@ -39,6 +42,7 @@ impl MetricsBatch {
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) {}
+407 -275
View File
@@ -2,6 +2,7 @@ use crate::runtime::Handle;
cfg_unstable_metrics! {
use std::ops::Range;
use std::thread::ThreadId;
cfg_64bit_metrics! {
use std::sync::atomic::Ordering::Relaxed;
}
@@ -47,6 +48,28 @@ impl RuntimeMetrics {
self.handle.inner.num_workers()
}
/// Returns the current number of alive tasks in the runtime.
///
/// This counter increases when a task is spawned and decreases when a
/// task exits.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.num_alive_tasks();
/// println!("Runtime has {} alive tasks", n);
/// }
/// ```
pub fn num_alive_tasks(&self) -> usize {
self.handle.inner.num_alive_tasks()
}
cfg_unstable_metrics! {
/// Returns the number of additional threads spawned by the runtime.
@@ -75,23 +98,10 @@ impl RuntimeMetrics {
self.handle.inner.num_blocking_threads()
}
/// Returns the number of active tasks in the runtime.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.active_tasks_count();
/// println!("Runtime has {} active tasks", n);
/// }
/// ```
#[deprecated = "Renamed to num_alive_tasks"]
/// Renamed to [`RuntimeMetrics::num_alive_tasks`]
pub fn active_tasks_count(&self) -> usize {
self.handle.inner.active_tasks_count()
self.num_alive_tasks()
}
/// Returns the number of idle threads, which have spawned by the runtime
@@ -118,272 +128,394 @@ impl RuntimeMetrics {
self.handle.inner.num_idle_blocking_threads()
}
/// Returns the thread id of the given worker thread.
///
/// The returned value is `None` if the worker thread has not yet finished
/// starting up.
///
/// If additional information about the thread, such as its native id, are
/// required, those can be collected in [`on_thread_start`] and correlated
/// using the thread id.
///
/// [`on_thread_start`]: crate::runtime::Builder::on_thread_start
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let id = metrics.worker_thread_id(0);
/// println!("worker 0 has id {:?}", id);
/// }
/// ```
pub fn worker_thread_id(&self, worker: usize) -> Option<ThreadId> {
self.handle
.inner
.worker_metrics(worker)
.thread_id()
}
cfg_64bit_metrics! {
/// Returns the number of tasks scheduled from **outside** of the runtime.
///
/// The remote schedule count starts at zero when the runtime is created and
/// increases by one each time a task is woken from **outside** of the
/// runtime. This usually means that a task is spawned or notified from a
/// non-runtime thread and must be queued using the Runtime's injection
/// queue, which tends to be slower.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.remote_schedule_count();
/// println!("{} tasks were scheduled from outside the runtime", n);
/// }
/// ```
pub fn remote_schedule_count(&self) -> u64 {
self.handle
.inner
.scheduler_metrics()
.remote_schedule_count
.load(Relaxed)
}
/// Returns the number of tasks spawned in this runtime since it was created.
///
/// This count starts at zero when the runtime is created and increases by one each time a task is spawned.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.spawned_tasks_count();
/// println!("Runtime has had {} tasks spawned", n);
/// }
/// ```
pub fn spawned_tasks_count(&self) -> u64 {
self.handle.inner.spawned_tasks_count()
}
/// Returns the number of times that tasks have been forced to yield back to the scheduler
/// after exhausting their task budgets.
///
/// This count starts at zero when the runtime is created and increases by one each time a task yields due to exhausting its budget.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
pub fn budget_forced_yield_count(&self) -> u64 {
self.handle
.inner
.scheduler_metrics()
.budget_forced_yield_count
.load(Relaxed)
}
/// Returns the number of tasks scheduled from **outside** of the runtime.
///
/// The remote schedule count starts at zero when the runtime is created and
/// increases by one each time a task is woken from **outside** of the
/// runtime. This usually means that a task is spawned or notified from a
/// non-runtime thread and must be queued using the Runtime's injection
/// queue, which tends to be slower.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.remote_schedule_count();
/// println!("{} tasks were scheduled from outside the runtime", n);
/// }
/// ```
pub fn remote_schedule_count(&self) -> u64 {
self.handle
.inner
.scheduler_metrics()
.remote_schedule_count
.load(Relaxed)
}
/// Returns the total number of times the given worker thread has parked.
///
/// The worker park count starts at zero when the runtime is created and
/// increases by one each time the worker parks the thread waiting for new
/// inbound events to process. This usually means the worker has processed
/// all pending work and is currently idle.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_park_count(0);
/// println!("worker 0 parked {} times", n);
/// }
/// ```
pub fn worker_park_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.park_count
.load(Relaxed)
}
/// Returns the number of times that tasks have been forced to yield back to the scheduler
/// after exhausting their task budgets.
///
/// This count starts at zero when the runtime is created and increases by one each time a task yields due to exhausting its budget.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
pub fn budget_forced_yield_count(&self) -> u64 {
self.handle
.inner
.scheduler_metrics()
.budget_forced_yield_count
.load(Relaxed)
}
/// Returns the number of times the given worker thread unparked but
/// performed no work before parking again.
///
/// The worker no-op count starts at zero when the runtime is created and
/// increases by one each time the worker unparks the thread but finds no
/// new work and goes back to sleep. This indicates a false-positive wake up.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_noop_count(0);
/// println!("worker 0 had {} no-op unparks", n);
/// }
/// ```
pub fn worker_noop_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.noop_count
.load(Relaxed)
}
/// Returns the total number of times the given worker thread has parked.
///
/// The worker park count starts at zero when the runtime is created and
/// increases by one each time the worker parks the thread waiting for new
/// inbound events to process. This usually means the worker has processed
/// all pending work and is currently idle.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_park_count(0);
/// println!("worker 0 parked {} times", n);
/// }
/// ```
pub fn worker_park_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.park_count
.load(Relaxed)
}
/// Returns the number of tasks the given worker thread stole from
/// another worker thread.
///
/// This metric only applies to the **multi-threaded** runtime and will
/// always return `0` when using the current thread runtime.
///
/// The worker steal count starts at zero when the runtime is created and
/// increases by `N` each time the worker has processed its scheduled queue
/// and successfully steals `N` more pending tasks from another worker.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_steal_count(0);
/// println!("worker 0 has stolen {} tasks", n);
/// }
/// ```
pub fn worker_steal_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.steal_count
.load(Relaxed)
}
/// Returns the total number of times the given worker thread has parked
/// and unparked.
///
/// The worker park/unpark count starts at zero when the runtime is created
/// and increases by one each time the worker parks the thread waiting for
/// new inbound events to process. This usually means the worker has processed
/// all pending work and is currently idle. When new work becomes available,
/// the worker is unparked and the park/unpark count is again increased by one.
///
/// An odd count means that the worker is currently parked.
/// An even count means that the worker is currently active.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
/// let n = metrics.worker_park_unpark_count(0);
///
/// println!("worker 0 parked and unparked {} times", n);
///
/// if n % 2 == 0 {
/// println!("worker 0 is active");
/// } else {
/// println!("worker 0 is parked");
/// }
/// }
/// ```
pub fn worker_park_unpark_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.park_unpark_count
.load(Relaxed)
}
/// Returns the number of times the given worker thread stole tasks from
/// another worker thread.
///
/// This metric only applies to the **multi-threaded** runtime and will
/// always return `0` when using the current thread runtime.
///
/// The worker steal count starts at zero when the runtime is created and
/// increases by one each time the worker has processed its scheduled queue
/// and successfully steals more pending tasks from another worker.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_steal_operations(0);
/// println!("worker 0 has stolen tasks {} times", n);
/// }
/// ```
pub fn worker_steal_operations(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.steal_operations
.load(Relaxed)
}
/// Returns the number of tasks the given worker thread has polled.
///
/// The worker poll count starts at zero when the runtime is created and
/// increases by one each time the worker polls a scheduled task.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_poll_count(0);
/// println!("worker 0 has polled {} tasks", n);
/// }
/// ```
pub fn worker_poll_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.poll_count
.load(Relaxed)
}
/// Returns the number of times the given worker thread unparked but
/// performed no work before parking again.
///
/// The worker no-op count starts at zero when the runtime is created and
/// increases by one each time the worker unparks the thread but finds no
/// new work and goes back to sleep. This indicates a false-positive wake up.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_noop_count(0);
/// println!("worker 0 had {} no-op unparks", n);
/// }
/// ```
pub fn worker_noop_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.noop_count
.load(Relaxed)
}
/// Returns the number of tasks the given worker thread stole from
/// another worker thread.
///
/// This metric only applies to the **multi-threaded** runtime and will
/// always return `0` when using the current thread runtime.
///
/// The worker steal count starts at zero when the runtime is created and
/// increases by `N` each time the worker has processed its scheduled queue
/// and successfully steals `N` more pending tasks from another worker.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_steal_count(0);
/// println!("worker 0 has stolen {} tasks", n);
/// }
/// ```
pub fn worker_steal_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.steal_count
.load(Relaxed)
}
/// Returns the number of times the given worker thread stole tasks from
/// another worker thread.
///
/// This metric only applies to the **multi-threaded** runtime and will
/// always return `0` when using the current thread runtime.
///
/// The worker steal count starts at zero when the runtime is created and
/// increases by one each time the worker has processed its scheduled queue
/// and successfully steals more pending tasks from another worker.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_steal_operations(0);
/// println!("worker 0 has stolen tasks {} times", n);
/// }
/// ```
pub fn worker_steal_operations(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.steal_operations
.load(Relaxed)
}
/// Returns the number of tasks the given worker thread has polled.
///
/// The worker poll count starts at zero when the runtime is created and
/// increases by one each time the worker polls a scheduled task.
///
/// The counter is monotonically increasing. It is never decremented or
/// reset to zero.
///
/// # Arguments
///
/// `worker` is the index of the worker being queried. The given value must
/// be between 0 and `num_workers()`. The index uniquely identifies a single
/// worker and will continue to identify the worker throughout the lifetime
/// of the runtime instance.
///
/// # Panics
///
/// The method panics when `worker` represents an invalid worker, i.e. is
/// greater than or equal to `num_workers()`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main]
/// async fn main() {
/// let metrics = Handle::current().metrics();
///
/// let n = metrics.worker_poll_count(0);
/// println!("worker 0 has polled {} tasks", n);
/// }
/// ```
pub fn worker_poll_count(&self, worker: usize) -> u64 {
self.handle
.inner
.worker_metrics(worker)
.poll_count
.load(Relaxed)
}
/// Returns the amount of time the given worker thread has been busy.
///
+20 -19
View File
@@ -1,10 +1,9 @@
use crate::runtime::metrics::Histogram;
use crate::runtime::Config;
use crate::util::metric_atomics::MetricAtomicU64;
// This is NOT the Loom atomic. To avoid an unnecessary state explosion in loom,
// all metrics use regular atomics.
use std::sync::atomic::AtomicUsize;
use crate::util::metric_atomics::{MetricAtomicU64, MetricAtomicUsize};
use std::sync::atomic::Ordering::Relaxed;
use std::sync::Mutex;
use std::thread::ThreadId;
/// Retrieve runtime worker metrics.
///
@@ -13,12 +12,15 @@ use std::sync::atomic::Ordering::Relaxed;
/// features][unstable] for details.
///
/// [unstable]: crate#unstable-features
#[derive(Debug)]
#[derive(Debug, Default)]
#[repr(align(128))]
pub(crate) struct WorkerMetrics {
/// Number of times the worker parked.
pub(crate) park_count: MetricAtomicU64,
/// Number of times the worker parked and unparked.
pub(crate) park_unpark_count: MetricAtomicU64,
/// Number of times the worker woke then parked again without doing work.
pub(crate) noop_count: MetricAtomicU64,
@@ -45,10 +47,13 @@ pub(crate) struct WorkerMetrics {
/// Number of tasks currently in the local queue. Used only by the
/// current-thread scheduler.
pub(crate) queue_depth: AtomicUsize,
pub(crate) queue_depth: MetricAtomicUsize,
/// If `Some`, tracks the number of polls by duration range.
pub(super) poll_count_histogram: Option<Histogram>,
/// Thread id of worker thread.
thread_id: Mutex<Option<ThreadId>>,
}
impl WorkerMetrics {
@@ -62,19 +67,7 @@ impl WorkerMetrics {
}
pub(crate) fn new() -> WorkerMetrics {
WorkerMetrics {
park_count: MetricAtomicU64::new(0),
noop_count: MetricAtomicU64::new(0),
steal_count: MetricAtomicU64::new(0),
steal_operations: MetricAtomicU64::new(0),
poll_count: MetricAtomicU64::new(0),
mean_poll_time: MetricAtomicU64::new(0),
overflow_count: MetricAtomicU64::new(0),
busy_duration_total: MetricAtomicU64::new(0),
local_schedule_count: MetricAtomicU64::new(0),
queue_depth: AtomicUsize::new(0),
poll_count_histogram: None,
}
WorkerMetrics::default()
}
pub(crate) fn queue_depth(&self) -> usize {
@@ -84,4 +77,12 @@ impl WorkerMetrics {
pub(crate) fn set_queue_depth(&self, len: usize) {
self.queue_depth.store(len, Relaxed);
}
pub(crate) fn thread_id(&self) -> Option<ThreadId> {
*self.thread_id.lock().unwrap()
}
pub(crate) fn set_thread_id(&self, thread_id: ThreadId) {
*self.thread_id.lock().unwrap() = Some(thread_id);
}
}
+4
View File
@@ -385,6 +385,10 @@ cfg_rt! {
mod runtime;
pub use runtime::{Runtime, RuntimeFlavor};
/// Boundary value to prevent stack overflow caused by a large-sized
/// Future being placed in the stack.
pub(crate) const BOX_FUTURE_THRESHOLD: usize = 2048;
mod thread_id;
pub(crate) use thread_id::ThreadId;
+1 -1
View File
@@ -35,7 +35,7 @@ tokio_thread_local! {
// Bit of a hack, but it is only for loom
#[cfg(loom)]
tokio_thread_local! {
static CURRENT_THREAD_PARK_COUNT: AtomicUsize = AtomicUsize::new(0);
pub(crate) static CURRENT_THREAD_PARK_COUNT: AtomicUsize = AtomicUsize::new(0);
}
// ==== impl ParkThread ====
+26 -1
View File
@@ -1,3 +1,4 @@
use super::BOX_FUTURE_THRESHOLD;
use crate::runtime::blocking::BlockingPool;
use crate::runtime::scheduler::CurrentThread;
use crate::runtime::{context, EnterGuard, Handle};
@@ -240,7 +241,11 @@ impl Runtime {
F: Future + Send + 'static,
F::Output: Send + 'static,
{
self.handle.spawn(future)
if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
self.handle.spawn_named(Box::pin(future), None)
} else {
self.handle.spawn_named(future, None)
}
}
/// Runs the provided function on an executor dedicated to blocking operations.
@@ -324,6 +329,15 @@ impl Runtime {
/// [handle]: fn@Handle::block_on
#[track_caller]
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
self.block_on_inner(Box::pin(future))
} else {
self.block_on_inner(future)
}
}
#[track_caller]
fn block_on_inner<F: Future>(&self, future: F) -> F::Output {
#[cfg(all(
tokio_unstable,
tokio_taskdump,
@@ -461,6 +475,17 @@ impl Runtime {
pub fn metrics(&self) -> crate::runtime::RuntimeMetrics {
self.handle.metrics()
}
/// Gets a raw fd for the IO driver which can be polled.
///
/// Do NOT use this for anything other than watching it with epoll/kqueue/poll.
///
/// # Safety
/// Don't use this after the runtime goes away.
#[cfg(unix)]
pub unsafe fn get_raw_poll_fd(&self) -> Option<std::os::fd::RawFd> {
Some(self.handle.inner.driver().io.as_ref()?.get_raw_poll_fd())
}
}
#[allow(clippy::single_match)] // there are comments in the error branch, so we don't want if-let
@@ -11,12 +11,12 @@ use crate::util::{waker_ref, RngSeedGenerator, Wake, WakerRef};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use std::future::Future;
use std::sync::atomic::Ordering::{AcqRel, Release};
use std::task::Poll::{Pending, Ready};
use std::task::Waker;
use std::time::Duration;
use std::{fmt, thread};
/// Executes tasks on the current thread
pub(crate) struct CurrentThread {
@@ -123,6 +123,7 @@ impl CurrentThread {
config: Config,
) -> (CurrentThread, Arc<Handle>) {
let worker_metrics = WorkerMetrics::from_config(&config);
worker_metrics.set_thread_id(thread::current().id());
// Get the configured global queue interval, or use the default.
let global_queue_interval = config
@@ -172,6 +173,10 @@ impl CurrentThread {
// available or the future is complete.
loop {
if let Some(core) = self.take_core(handle) {
handle
.shared
.worker_metrics
.set_thread_id(thread::current().id());
return core.block_on(future);
} else {
let notified = self.notify.notified();
@@ -368,6 +373,9 @@ impl Context {
});
core = c;
core.metrics.unparked();
core.submit_metrics(handle);
}
if let Some(f) = &handle.shared.config.after_unpark {
@@ -500,6 +508,10 @@ impl Handle {
pub(crate) fn reset_woken(&self) -> bool {
self.shared.woken.swap(false, AcqRel)
}
pub(crate) fn num_alive_tasks(&self) -> usize {
self.shared.owned.num_alive_tasks()
}
}
cfg_unstable_metrics! {
@@ -533,8 +545,10 @@ cfg_unstable_metrics! {
self.blocking_spawner.queue_depth()
}
pub(crate) fn active_tasks_count(&self) -> usize {
self.shared.owned.active_tasks_count()
cfg_64bit_metrics! {
pub(crate) fn spawned_tasks_count(&self) -> u64 {
self.shared.owned.spawned_tasks_count()
}
}
}
}
+10 -4
View File
@@ -173,12 +173,22 @@ cfg_rt! {
Handle::MultiThreadAlt(handle) => handle.num_workers(),
}
}
pub(crate) fn num_alive_tasks(&self) -> usize {
match_flavor!(self, Handle(handle) => handle.num_alive_tasks())
}
}
cfg_unstable_metrics! {
use crate::runtime::{SchedulerMetrics, WorkerMetrics};
impl Handle {
cfg_64bit_metrics! {
pub(crate) fn spawned_tasks_count(&self) -> u64 {
match_flavor!(self, Handle(handle) => handle.spawned_tasks_count())
}
}
pub(crate) fn num_blocking_threads(&self) -> usize {
match_flavor!(self, Handle(handle) => handle.num_blocking_threads())
}
@@ -187,10 +197,6 @@ cfg_rt! {
match_flavor!(self, Handle(handle) => handle.num_idle_blocking_threads())
}
pub(crate) fn active_tasks_count(&self) -> usize {
match_flavor!(self, Handle(handle) => handle.active_tasks_count())
}
pub(crate) fn scheduler_metrics(&self) -> &SchedulerMetrics {
match_flavor!(self, Handle(handle) => handle.scheduler_metrics())
}
@@ -9,7 +9,17 @@ impl Handle {
self.shared.worker_metrics.len()
}
pub(crate) fn num_alive_tasks(&self) -> usize {
self.shared.owned.num_alive_tasks()
}
cfg_unstable_metrics! {
cfg_64bit_metrics! {
pub(crate) fn spawned_tasks_count(&self) -> u64 {
self.shared.owned.spawned_tasks_count()
}
}
pub(crate) fn num_blocking_threads(&self) -> usize {
// workers are currently spawned using spawn_blocking
self.blocking_spawner
@@ -21,10 +31,6 @@ impl Handle {
self.blocking_spawner.num_idle_threads()
}
pub(crate) fn active_tasks_count(&self) -> usize {
self.shared.owned.active_tasks_count()
}
pub(crate) fn scheduler_metrics(&self) -> &SchedulerMetrics {
&self.shared.scheduler_metrics
}
@@ -10,6 +10,9 @@ use crate::util::TryLock;
use std::sync::atomic::Ordering::SeqCst;
use std::time::Duration;
#[cfg(loom)]
use crate::runtime::park::CURRENT_THREAD_PARK_COUNT;
pub(crate) struct Parker {
inner: Arc<Inner>,
}
@@ -73,6 +76,13 @@ impl Parker {
if let Some(mut driver) = self.inner.shared.driver.try_lock() {
driver.park_timeout(handle, duration);
} else {
// https://github.com/tokio-rs/tokio/issues/6536
// Hacky, but it's just for loom tests. The counter gets incremented during
// `park_timeout`, but we still have to increment the counter if we can't acquire the
// lock.
#[cfg(loom)]
CURRENT_THREAD_PARK_COUNT.with(|count| count.fetch_add(1, SeqCst));
}
}
@@ -74,6 +74,10 @@ impl Stats {
self.batch.about_to_park();
}
pub(crate) fn unparked(&mut self) {
self.batch.unparked();
}
pub(crate) fn inc_local_schedule_count(&mut self) {
self.batch.inc_local_schedule_count();
}
@@ -72,6 +72,7 @@ use crate::util::rand::{FastRand, RngSeedGenerator};
use std::cell::RefCell;
use std::task::Waker;
use std::thread;
use std::time::Duration;
cfg_unstable_metrics! {
@@ -334,6 +335,12 @@ where
if let Some(cx) = maybe_cx {
if self.take_core {
let core = cx.worker.core.take();
if core.is_some() {
cx.worker.handle.shared.worker_metrics[cx.worker.index]
.set_thread_id(thread::current().id());
}
let mut cx_core = cx.core.borrow_mut();
assert!(cx_core.is_none());
*cx_core = core;
@@ -482,6 +489,8 @@ fn run(worker: Arc<Worker>) {
None => return,
};
worker.handle.shared.worker_metrics[worker.index].set_thread_id(thread::current().id());
let handle = scheduler::Handle::MultiThread(worker.handle.clone());
crate::runtime::context::enter_runtime(&handle, true, |_| {
@@ -699,8 +708,13 @@ impl Context {
if core.transition_to_parked(&self.worker) {
while !core.is_shutdown && !core.is_traced {
core.stats.about_to_park();
core.stats
.submit(&self.worker.handle.shared.worker_metrics[self.worker.index]);
core = self.park_timeout(core, None);
core.stats.unparked();
// Run regularly scheduled maintenance
core.maintenance(&self.worker);
@@ -1000,7 +1014,7 @@ impl Core {
.tuned_global_queue_interval(&worker.handle.shared.config);
// Smooth out jitter
if abs_diff(self.global_queue_interval, next) > 2 {
if u32::abs_diff(self.global_queue_interval, next) > 2 {
self.global_queue_interval = next;
}
}
@@ -1235,12 +1249,3 @@ fn with_current<R>(f: impl FnOnce(Option<&Context>) -> R) -> R {
_ => f(None),
})
}
// `u32::abs_diff` is not available on Tokio's MSRV.
fn abs_diff(a: u32, b: u32) -> u32 {
if a > b {
a - b
} else {
b - a
}
}
@@ -18,8 +18,14 @@ impl Handle {
self.blocking_spawner.num_idle_threads()
}
pub(crate) fn active_tasks_count(&self) -> usize {
self.shared.owned.active_tasks_count()
pub(crate) fn num_alive_tasks(&self) -> usize {
self.shared.owned.num_alive_tasks()
}
cfg_64bit_metrics! {
pub(crate) fn spawned_tasks_count(&self) -> u64 {
self.shared.owned.spawned_tasks_count()
}
}
pub(crate) fn scheduler_metrics(&self) -> &SchedulerMetrics {
@@ -100,6 +100,10 @@ impl Stats {
self.batch.about_to_park();
}
pub(crate) fn unparked(&mut self) {
self.batch.unparked();
}
pub(crate) fn inc_local_schedule_count(&mut self) {
self.batch.inc_local_schedule_count();
}
@@ -70,9 +70,9 @@ use crate::util::atomic_cell::AtomicCell;
use crate::util::rand::{FastRand, RngSeedGenerator};
use std::cell::{Cell, RefCell};
use std::cmp;
use std::task::Waker;
use std::time::Duration;
use std::{cmp, thread};
cfg_unstable_metrics! {
mod metrics;
@@ -569,6 +569,7 @@ impl Worker {
}
};
cx.shared().worker_metrics[core.index].set_thread_id(thread::current().id());
core.stats.start_processing_scheduled_tasks(&mut self.stats);
if let Some(task) = maybe_task {
@@ -658,6 +659,9 @@ impl Worker {
let n = cmp::max(core.run_queue.remaining_slots() / 2, 1);
let maybe_task = self.next_remote_task_batch_synced(cx, &mut synced, &mut core, n);
core.stats.unparked();
self.flush_metrics(cx, &mut core);
Ok((maybe_task, core))
}
@@ -1288,7 +1292,7 @@ impl Worker {
let next = core.stats.tuned_global_queue_interval(&cx.shared().config);
// Smooth out jitter
if abs_diff(self.global_queue_interval, next) > 2 {
if u32::abs_diff(self.global_queue_interval, next) > 2 {
self.global_queue_interval = next;
}
}
@@ -1588,12 +1592,3 @@ fn with_current<R>(f: impl FnOnce(Option<&Context>) -> R) -> R {
_ => f(None),
})
}
// `u32::abs_diff` is not available on Tokio's MSRV.
fn abs_diff(a: u32, b: u32) -> u32 {
if a > b {
a - b
} else {
b - a
}
}
+8
View File
@@ -102,3 +102,11 @@ impl Drop for AbortHandle {
self.raw.drop_abort_handle();
}
}
impl Clone for AbortHandle {
/// Returns a cloned `AbortHandle` that can be used to remotely abort this task.
fn clone(&self) -> Self {
self.raw.ref_inc();
Self::new(self.raw)
}
}
+2 -3
View File
@@ -196,6 +196,7 @@ generate_addr_of_methods! {
}
/// Either the future or the output.
#[repr(C)] // https://github.com/rust-lang/miri/issues/3780
pub(super) enum Stage<T: Future> {
Running(T),
Finished(super::Result<T::Output>),
@@ -488,7 +489,5 @@ impl Trailer {
#[test]
#[cfg(not(loom))]
fn header_lte_cache_line() {
use std::mem::size_of;
assert!(size_of::<Header>() <= 8 * size_of::<*const ()>());
assert!(std::mem::size_of::<Header>() <= 8 * std::mem::size_of::<*const ()>());
}
+35 -2
View File
@@ -140,7 +140,18 @@ impl fmt::Display for JoinError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.repr {
Repr::Cancelled => write!(fmt, "task {} was cancelled", self.id),
Repr::Panic(_) => write!(fmt, "task {} panicked", self.id),
Repr::Panic(p) => match panic_payload_as_str(p) {
Some(panic_str) => {
write!(
fmt,
"task {} panicked with message {:?}",
self.id, panic_str
)
}
None => {
write!(fmt, "task {} panicked", self.id)
}
},
}
}
}
@@ -149,7 +160,12 @@ impl fmt::Debug for JoinError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.repr {
Repr::Cancelled => write!(fmt, "JoinError::Cancelled({:?})", self.id),
Repr::Panic(_) => write!(fmt, "JoinError::Panic({:?}, ...)", self.id),
Repr::Panic(p) => match panic_payload_as_str(p) {
Some(panic_str) => {
write!(fmt, "JoinError::Panic({:?}, {:?}, ...)", self.id, panic_str)
}
None => write!(fmt, "JoinError::Panic({:?}, ...)", self.id),
},
}
}
}
@@ -167,3 +183,20 @@ impl From<JoinError> for io::Error {
)
}
}
fn panic_payload_as_str(payload: &SyncWrapper<Box<dyn Any + Send>>) -> Option<&str> {
// Panic payloads are almost always `String` (if invoked with formatting arguments)
// or `&'static str` (if invoked with a string literal).
//
// Non-string panic payloads have niche use-cases,
// so we don't really need to worry about those.
if let Some(s) = payload.downcast_ref_sync::<String>() {
return Some(s);
}
if let Some(s) = payload.downcast_ref_sync::<&'static str>() {
return Some(s);
}
None
}
+12 -11
View File
@@ -1,6 +1,6 @@
use crate::runtime::context;
use std::fmt;
use std::{fmt, num::NonZeroU64};
/// An opaque ID that uniquely identifies a task relative to all other currently
/// running tasks.
@@ -24,7 +24,7 @@ use std::fmt;
#[cfg_attr(docsrs, doc(cfg(all(feature = "rt", tokio_unstable))))]
#[cfg_attr(not(tokio_unstable), allow(unreachable_pub))]
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub struct Id(pub(crate) u64);
pub struct Id(pub(crate) NonZeroU64);
/// Returns the [`Id`] of the currently running task.
///
@@ -78,21 +78,22 @@ impl Id {
use crate::loom::sync::atomic::StaticAtomicU64;
#[cfg(all(test, loom))]
{
crate::loom::lazy_static! {
static ref NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(1);
}
Self(NEXT_ID.fetch_add(1, Relaxed))
crate::loom::lazy_static! {
static ref NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(1);
}
#[cfg(not(all(test, loom)))]
{
static NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(1);
Self(NEXT_ID.fetch_add(1, Relaxed))
static NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(1);
loop {
let id = NEXT_ID.fetch_add(1, Relaxed);
if let Some(id) = NonZeroU64::new(id) {
return Self(id);
}
}
}
pub(crate) fn as_u64(&self) -> u64 {
self.0
self.0.get()
}
}
+1
View File
@@ -296,6 +296,7 @@ impl<T> JoinHandle<T> {
/// # }
/// ```
/// [cancelled]: method@super::error::JoinError::is_cancelled
#[must_use = "abort handles do nothing unless `.abort` is called"]
pub fn abort_handle(&self) -> super::AbortHandle {
self.raw.ref_inc();
super::AbortHandle::new(self.raw)
+7 -1
View File
@@ -166,10 +166,16 @@ impl<S: 'static> OwnedTasks<S> {
self.list.shard_size()
}
pub(crate) fn active_tasks_count(&self) -> usize {
pub(crate) fn num_alive_tasks(&self) -> usize {
self.list.len()
}
cfg_64bit_metrics! {
pub(crate) fn spawned_tasks_count(&self) -> u64 {
self.list.added()
}
}
pub(crate) fn remove(&self, task: &Task<S>) -> Option<Task<S>> {
// If the task's owner ID is `None` then it is not part of any list and
// doesn't need removing.
+1 -1
View File
@@ -532,6 +532,6 @@ unsafe impl<S> sharded_list::ShardedListItem for Task<S> {
unsafe fn get_shard_id(target: NonNull<Self::Target>) -> usize {
// SAFETY: The caller guarantees that `target` points at a valid task.
let task_id = unsafe { Header::get_id(target) };
task_id.0 as usize
task_id.0.get() as usize
}
}
@@ -3,7 +3,6 @@ use crate::runtime::tests::loom_oneshot as oneshot;
use crate::runtime::{self, Runtime};
#[test]
#[ignore]
fn yield_calls_park_before_scheduling_again() {
// Don't need to check all permutations
let mut loom = loom::model::Builder::default();
+1
View File
@@ -273,6 +273,7 @@ fn stress2() {
}
}
#[allow(dead_code)]
struct Runtime;
impl Schedule for Runtime {
+117
View File
@@ -119,6 +119,29 @@ fn drop_abort_handle2() {
handle.assert_dropped();
}
#[test]
fn drop_abort_handle_clone() {
let (ad, handle) = AssertDrop::new();
let (notified, join) = unowned(
async {
drop(ad);
unreachable!()
},
NoopSchedule,
Id::next(),
);
let abort = join.abort_handle();
let abort_clone = abort.clone();
drop(join);
handle.assert_not_dropped();
drop(notified);
handle.assert_not_dropped();
drop(abort);
handle.assert_not_dropped();
drop(abort_clone);
handle.assert_dropped();
}
// Shutting down through Notified works
#[test]
fn create_shutdown1() {
@@ -200,6 +223,100 @@ fn shutdown_immediately() {
})
}
// Test for https://github.com/tokio-rs/tokio/issues/6729
#[test]
fn spawn_niche_in_task() {
use crate::future::poll_fn;
use std::task::{Context, Poll, Waker};
with(|rt| {
let state = Arc::new(Mutex::new(State::new()));
let mut subscriber = Subscriber::new(Arc::clone(&state), 1);
rt.spawn(async move {
subscriber.wait().await;
subscriber.wait().await;
});
rt.spawn(async move {
state.lock().unwrap().set_version(2);
state.lock().unwrap().set_version(0);
});
rt.tick_max(10);
assert!(rt.is_empty());
rt.shutdown();
});
pub(crate) struct Subscriber {
state: Arc<Mutex<State>>,
observed_version: u64,
waker_key: Option<usize>,
}
impl Subscriber {
pub(crate) fn new(state: Arc<Mutex<State>>, version: u64) -> Self {
Self {
state,
observed_version: version,
waker_key: None,
}
}
pub(crate) async fn wait(&mut self) {
poll_fn(|cx| {
self.state
.lock()
.unwrap()
.poll_update(&mut self.observed_version, &mut self.waker_key, cx)
.map(|_| ())
})
.await;
}
}
struct State {
version: u64,
wakers: Vec<Waker>,
}
impl State {
pub(crate) fn new() -> Self {
Self {
version: 1,
wakers: Vec::new(),
}
}
pub(crate) fn poll_update(
&mut self,
observed_version: &mut u64,
waker_key: &mut Option<usize>,
cx: &Context<'_>,
) -> Poll<Option<()>> {
if self.version == 0 {
*waker_key = None;
Poll::Ready(None)
} else if *observed_version < self.version {
*waker_key = None;
*observed_version = self.version;
Poll::Ready(Some(()))
} else {
self.wakers.push(cx.waker().clone());
*waker_key = Some(self.wakers.len());
Poll::Pending
}
}
pub(crate) fn set_version(&mut self, version: u64) {
self.version = version;
for waker in self.wakers.drain(..) {
waker.wake();
}
}
}
}
#[test]
fn spawn_during_shutdown() {
static DID_SPAWN: AtomicBool = AtomicBool::new(false);
+10 -5
View File
@@ -190,11 +190,13 @@ impl Driver {
assert!(!handle.is_shutdown());
// Finds out the min expiration time to park.
let expiration_time = (0..rt_handle.time().inner.get_shard_size())
.filter_map(|id| {
let lock = rt_handle.time().inner.lock_sharded_wheel(id);
lock.next_expiration_time()
})
let locks = (0..rt_handle.time().inner.get_shard_size())
.map(|id| rt_handle.time().inner.lock_sharded_wheel(id))
.collect::<Vec<_>>();
let expiration_time = locks
.iter()
.filter_map(|lock| lock.next_expiration_time())
.min();
rt_handle
@@ -203,6 +205,9 @@ impl Driver {
.next_wake
.store(next_wake_time(expiration_time));
// Safety: After updating the `next_wake`, we drop all the locks.
drop(locks);
match expiration_time {
Some(when) => {
let now = handle.time_source.now(rt_handle.clock());
+11 -3
View File
@@ -22,9 +22,11 @@ impl TimeSource {
pub(crate) fn instant_to_tick(&self, t: Instant) -> u64 {
// round up
let dur: Duration = t.saturating_duration_since(self.start_time);
let ms = dur.as_millis();
ms.try_into().unwrap_or(MAX_SAFE_MILLIS_DURATION)
let ms = dur
.as_millis()
.try_into()
.unwrap_or(MAX_SAFE_MILLIS_DURATION);
ms.min(MAX_SAFE_MILLIS_DURATION)
}
pub(crate) fn tick_to_duration(&self, t: u64) -> Duration {
@@ -34,4 +36,10 @@ impl TimeSource {
pub(crate) fn now(&self, clock: &Clock) -> u64 {
self.instant_to_tick(clock.now())
}
#[cfg(test)]
#[allow(dead_code)]
pub(super) fn start_time(&self) -> Instant {
self.start_time
}
}
+14
View File
@@ -267,3 +267,17 @@ fn poll_process_levels_targeted() {
handle.process_at_time(0, 192);
handle.process_at_time(0, 192);
}
#[test]
#[cfg(not(loom))]
fn instant_to_tick_max() {
use crate::runtime::time::entry::MAX_SAFE_MILLIS_DURATION;
let rt = rt(true);
let handle = rt.handle().inner.driver().time();
let start_time = handle.time_source.start_time();
let long_future = start_time + std::time::Duration::from_millis(MAX_SAFE_MILLIS_DURATION + 1);
assert!(handle.time_source.instant_to_tick(long_future) <= MAX_SAFE_MILLIS_DURATION);
}
+1
View File
@@ -147,6 +147,7 @@ impl Semaphore {
#[cfg(all(tokio_unstable, feature = "tracing"))]
let resource_span = {
let resource_span = tracing::trace_span!(
parent: None,
"runtime.resource",
concrete_type = "Semaphore",
kind = "Sync",
+10
View File
@@ -711,6 +711,16 @@ impl<T> Receiver<T> {
) -> Poll<usize> {
self.chan.recv_many(cx, buffer, limit)
}
/// Returns the number of [`Sender`] handles.
pub fn sender_strong_count(&self) -> usize {
self.chan.sender_strong_count()
}
/// Returns the number of [`WeakSender`] handles.
pub fn sender_weak_count(&self) -> usize {
self.chan.sender_weak_count()
}
}
impl<T> fmt::Debug for Receiver<T> {
+8
View File
@@ -469,6 +469,14 @@ impl<T, S: Semaphore> Rx<T, S> {
pub(super) fn semaphore(&self) -> &S {
&self.inner.semaphore
}
pub(super) fn sender_strong_count(&self) -> usize {
self.inner.tx_count.load(Acquire)
}
pub(super) fn sender_weak_count(&self) -> usize {
self.inner.tx_weak_count.load(Relaxed)
}
}
impl<T, S: Semaphore> Drop for Rx<T, S> {
+20
View File
@@ -36,6 +36,16 @@ pub enum TrySendError<T> {
Closed(T),
}
impl<T> TrySendError<T> {
/// Consume the `TrySendError`, returning the unsent value.
pub fn into_inner(self) -> T {
match self {
TrySendError::Full(val) => val,
TrySendError::Closed(val) => val,
}
}
}
impl<T> fmt::Debug for TrySendError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
@@ -123,6 +133,16 @@ cfg_time! {
Closed(T),
}
impl<T> SendTimeoutError<T> {
/// Consume the `SendTimeoutError`, returning the unsent value.
pub fn into_inner(self) -> T {
match self {
SendTimeoutError::Timeout(val) => val,
SendTimeoutError::Closed(val) => val,
}
}
}
impl<T> fmt::Debug for SendTimeoutError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
+16
View File
@@ -78,6 +78,22 @@
//! within a Tokio runtime, however it is still not tied to one specific Tokio
//! runtime, and the sender may be moved from one Tokio runtime to another.
//!
//! # Allocation behavior
//!
//! <div class="warning">The implementation details described in this section may change in future
//! Tokio releases.</div>
//!
//! The mpsc channel stores elements in blocks. Blocks are organized in a linked list. Sending
//! pushes new elements onto the block at the front of the list, and receiving pops them off the
//! one at the back. A block can hold 32 messages on a 64-bit target and 16 messages on a 32-bit
//! target. This number is independent of channel and message size. Each block also stores 4
//! pointer-sized values for bookkeeping (so on a 64-bit machine, each message has 1 byte of
//! overhead).
//!
//! When all values in a block have been received, it becomes empty. It will then be freed, unless
//! the channel's first block (where newly-sent elements are being stored) has no next block. In
//! that case, the empty block is reused as the next block.
//!
//! [`Sender`]: crate::sync::mpsc::Sender
//! [`Receiver`]: crate::sync::mpsc::Receiver
//! [bounded-send]: crate::sync::mpsc::Sender::send()
+11 -1
View File
@@ -348,7 +348,7 @@ impl<T> UnboundedReceiver<T> {
/// assert!(!rx.is_closed());
///
/// rx.close();
///
///
/// assert!(rx.is_closed());
/// }
/// ```
@@ -498,6 +498,16 @@ impl<T> UnboundedReceiver<T> {
) -> Poll<usize> {
self.chan.recv_many(cx, buffer, limit)
}
/// Returns the number of [`UnboundedSender`] handles.
pub fn sender_strong_count(&self) -> usize {
self.chan.sender_strong_count()
}
/// Returns the number of [`WeakUnboundedSender`] handles.
pub fn sender_weak_count(&self) -> usize {
self.chan.sender_weak_count()
}
}
impl<T> UnboundedSender<T> {
+22
View File
@@ -156,6 +156,12 @@ impl<T> Clone for Sender<T> {
}
}
impl<T: Default> Default for Sender<T> {
fn default() -> Self {
Self::new(T::default())
}
}
/// Returns a reference to the inner value.
///
/// Outstanding borrows hold a read lock on the inner value. This means that
@@ -1318,6 +1324,22 @@ impl<T> Sender<T> {
pub fn receiver_count(&self) -> usize {
self.shared.ref_count_rx.load(Relaxed)
}
/// Returns `true` if senders belong to the same channel.
///
/// # Examples
///
/// ```
/// let (tx, rx) = tokio::sync::watch::channel(true);
/// let tx2 = tx.clone();
/// assert!(tx.same_channel(&tx2));
///
/// let (tx3, rx3) = tokio::sync::watch::channel(true);
/// assert!(!tx3.same_channel(&tx2));
/// ```
pub fn same_channel(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.shared, &other.shared)
}
}
impl<T> Drop for Sender<T> {
+38 -10
View File
@@ -1,6 +1,6 @@
#![allow(unreachable_pub)]
use crate::{
runtime::Handle,
runtime::{Handle, BOX_FUTURE_THRESHOLD},
task::{JoinHandle, LocalSet},
};
use std::{future::Future, io};
@@ -88,7 +88,13 @@ impl<'a> Builder<'a> {
Fut: Future + Send + 'static,
Fut::Output: Send + 'static,
{
Ok(super::spawn::spawn_inner(future, self.name))
Ok(
if cfg!(debug_assertions) && std::mem::size_of::<Fut>() > BOX_FUTURE_THRESHOLD {
super::spawn::spawn_inner(Box::pin(future), self.name)
} else {
super::spawn::spawn_inner(future, self.name)
},
)
}
/// Spawn a task with this builder's settings on the provided [runtime
@@ -104,7 +110,13 @@ impl<'a> Builder<'a> {
Fut: Future + Send + 'static,
Fut::Output: Send + 'static,
{
Ok(handle.spawn_named(future, self.name))
Ok(
if cfg!(debug_assertions) && std::mem::size_of::<Fut>() > BOX_FUTURE_THRESHOLD {
handle.spawn_named(Box::pin(future), self.name)
} else {
handle.spawn_named(future, self.name)
},
)
}
/// Spawns `!Send` a task on the current [`LocalSet`] with this builder's
@@ -127,7 +139,13 @@ impl<'a> Builder<'a> {
Fut: Future + 'static,
Fut::Output: 'static,
{
Ok(super::local::spawn_local_inner(future, self.name))
Ok(
if cfg!(debug_assertions) && std::mem::size_of::<Fut>() > BOX_FUTURE_THRESHOLD {
super::local::spawn_local_inner(Box::pin(future), self.name)
} else {
super::local::spawn_local_inner(future, self.name)
},
)
}
/// Spawns `!Send` a task on the provided [`LocalSet`] with this builder's
@@ -188,12 +206,22 @@ impl<'a> Builder<'a> {
Output: Send + 'static,
{
use crate::runtime::Mandatory;
let (join_handle, spawn_result) = handle.inner.blocking_spawner().spawn_blocking_inner(
function,
Mandatory::NonMandatory,
self.name,
handle,
);
let (join_handle, spawn_result) =
if cfg!(debug_assertions) && std::mem::size_of::<Function>() > BOX_FUTURE_THRESHOLD {
handle.inner.blocking_spawner().spawn_blocking_inner(
Box::new(function),
Mandatory::NonMandatory,
self.name,
handle,
)
} else {
handle.inner.blocking_spawner().spawn_blocking_inner(
function,
Mandatory::NonMandatory,
self.name,
handle,
)
};
spawn_result?;
Ok(join_handle)
+1 -6
View File
@@ -8,10 +8,6 @@ use std::task::Poll;
/// computations that do not use Tokio resources like sockets or semaphores,
/// without redundantly yielding to the runtime each time.
///
/// **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.
///
/// # Examples
///
/// Make sure that a function which returns a sum of (potentially lots of)
@@ -27,8 +23,7 @@ use std::task::Poll;
/// sum
/// }
/// ```
/// [unstable]: crate#unstable-features
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
pub async fn consume_budget() {
let mut status = Poll::Pending;
+2 -2
View File
@@ -308,7 +308,7 @@ impl<T: 'static> JoinSet<T> {
/// Tries to join one of the tasks in the set that has completed and return its output.
///
/// Returns `None` if the set is empty.
/// Returns `None` if there are no completed tasks, or if the set is empty.
pub fn try_join_next(&mut self) -> Option<Result<T, JoinError>> {
// Loop over all notified `JoinHandle`s to find one that's ready, or until none are left.
loop {
@@ -331,7 +331,7 @@ impl<T: 'static> JoinSet<T> {
/// Tries to join one of the tasks in the set that has completed and return its output,
/// along with the [task ID] of the completed task.
///
/// Returns `None` if the set is empty.
/// Returns `None` if there are no completed tasks, or if the set is empty.
///
/// When this method returns an error, then the id of the task that failed can be accessed
/// using the [`JoinError::id`] method.
+19 -2
View File
@@ -4,7 +4,7 @@ use crate::loom::sync::{Arc, Mutex};
#[cfg(tokio_unstable)]
use crate::runtime;
use crate::runtime::task::{self, JoinHandle, LocalOwnedTasks, Task};
use crate::runtime::{context, ThreadId};
use crate::runtime::{context, ThreadId, BOX_FUTURE_THRESHOLD};
use crate::sync::AtomicWaker;
use crate::util::RcCell;
@@ -367,7 +367,11 @@ cfg_rt! {
F: Future + 'static,
F::Output: 'static,
{
spawn_local_inner(future, None)
if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
spawn_local_inner(Box::pin(future), None)
} else {
spawn_local_inner(future, None)
}
}
@@ -641,6 +645,19 @@ impl LocalSet {
future: F,
name: Option<&str>,
) -> JoinHandle<F::Output>
where
F: Future + 'static,
F::Output: 'static,
{
if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
self.spawn_named_inner(Box::pin(future), name)
} else {
self.spawn_named_inner(future, name)
}
}
#[track_caller]
fn spawn_named_inner<F>(&self, future: F, name: Option<&str>) -> JoinHandle<F::Output>
where
F: Future + 'static,
F::Output: 'static,
+2 -4
View File
@@ -337,10 +337,8 @@ cfg_rt! {
mod yield_now;
pub use yield_now::yield_now;
cfg_unstable! {
mod consume_budget;
pub use consume_budget::consume_budget;
}
mod consume_budget;
pub use consume_budget::consume_budget;
mod local;
pub use local::{spawn_local, LocalSet, LocalEnterGuard};
+3 -2
View File
@@ -1,10 +1,11 @@
use crate::runtime::BOX_FUTURE_THRESHOLD;
use crate::task::JoinHandle;
use std::future::Future;
cfg_rt! {
/// Spawns a new asynchronous task, returning a
/// [`JoinHandle`](super::JoinHandle) for it.
/// [`JoinHandle`](JoinHandle) for it.
///
/// The provided future will start running in the background immediately
/// when `spawn` is called, even if you don't await the returned
@@ -168,7 +169,7 @@ cfg_rt! {
{
// preventing stack overflows on debug mode, by quickly sending the
// task to the heap.
if cfg!(debug_assertions) && std::mem::size_of::<F>() > 2048 {
if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD {
spawn_inner(Box::pin(future), None)
} else {
spawn_inner(future, None)
+1
View File
@@ -267,6 +267,7 @@ impl Sleep {
let location = location.expect("should have location if tracing");
let resource_span = tracing::trace_span!(
parent: None,
"runtime.resource",
concrete_type = "Sleep",
kind = "timer",

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