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