mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
77
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fb29e3c92 | ||
|
|
365269adaf | ||
|
|
3d439ab711 | ||
|
|
b2ea40bb54 | ||
|
|
5ea3c63d7e | ||
|
|
56f3f40c15 | ||
|
|
2d697fc92b | ||
|
|
39c3c19bbd | ||
|
|
694577fa85 | ||
|
|
17819062e2 | ||
|
|
6ad1912353 | ||
|
|
a491b16a89 | ||
|
|
0ecf5f0f03 | ||
|
|
1e798d26ed | ||
|
|
ab53bf0c47 | ||
|
|
338e13b04b | ||
|
|
1077b0b29d | ||
|
|
0cbf1a5ada | ||
|
|
04c2718508 | ||
|
|
ebda4c3d3f | ||
|
|
f602eae499 | ||
|
|
438def7957 | ||
|
|
ee8d4d1b05 | ||
|
|
3297052763 | ||
|
|
f8fe0ffb23 | ||
|
|
47210a8e6e | ||
|
|
29545d9037 | ||
|
|
48e35c11d9 | ||
|
|
dd1d37167d | ||
|
|
6a1a7b1591 | ||
|
|
51b03f0334 | ||
|
|
754a1fb03c | ||
|
|
90b23a9584 | ||
|
|
b69f16aa21 | ||
|
|
6e845b794d | ||
|
|
feb742c58e | ||
|
|
15cd5146d4 | ||
|
|
56f4bc6543 | ||
|
|
3ad5b6df1a | ||
|
|
1be8a8e691 | ||
|
|
da17c61464 | ||
|
|
f71bded943 | ||
|
|
fc058b9561 | ||
|
|
c0280624f3 | ||
|
|
1e286689ff | ||
|
|
14b9f71157 | ||
|
|
24344dfe4b | ||
|
|
15925efe43 | ||
|
|
4825c444eb | ||
|
|
c8f3539bc1 | ||
|
|
b2e4c5fc9e | ||
|
|
4d0d89fb70 | ||
|
|
fe7285d3d1 | ||
|
|
dff4ecd0e7 | ||
|
|
68d0e3cb5f | ||
|
|
65d0e08d39 | ||
|
|
06582776a5 | ||
|
|
ed4ddf443d | ||
|
|
9a75d6f7f7 | ||
|
|
3bf4f93854 | ||
|
|
39cf6bba00 | ||
|
|
8480a180e6 | ||
|
|
53ea44bfb9 | ||
|
|
a865ca139a | ||
|
|
479f736935 | ||
|
|
17555d71d9 | ||
|
|
341b5daa6e | ||
|
|
53b586c5b9 | ||
|
|
833ee027d0 | ||
|
|
126ce89bb4 | ||
|
|
8e15c234c6 | ||
|
|
16fccafb41 | ||
|
|
3f397ccded | ||
|
|
49609d073f | ||
|
|
a91d43823c | ||
|
|
8fca6f6dad | ||
|
|
75c953bd63 |
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 =====
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -43,5 +43,5 @@ impl mio::event::Source for NotDefinedHere {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "net")]
|
||||
#[cfg(any(feature = "net", feature = "fs"))]
|
||||
pub mod os;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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 { .. }")
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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: {:?}",
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 ====
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ()>());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -273,6 +273,7 @@ fn stress2() {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct Runtime;
|
||||
|
||||
impl Schedule for Runtime {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user