mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
85
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17984a5e1a | ||
|
|
970d4b754d | ||
|
|
67b171ac77 | ||
|
|
f7a626ff03 | ||
|
|
d801664c89 | ||
|
|
58acb56a17 | ||
|
|
d22c549d97 | ||
|
|
bc48a6fa8d | ||
|
|
70410836ae | ||
|
|
881b510a07 | ||
|
|
6871084629 | ||
|
|
1b8ebfcffb | ||
|
|
654a3d5acf | ||
|
|
a08ad926b1 | ||
|
|
723934242b | ||
|
|
944f769cd5 | ||
|
|
f3ad6cffd9 | ||
|
|
f1e41a4ad4 | ||
|
|
f9335b8186 | ||
|
|
1134cbb168 | ||
|
|
339c78a680 | ||
|
|
c00861210b | ||
|
|
0f296d2089 | ||
|
|
d420d528f9 | ||
|
|
0457690d01 | ||
|
|
4557451257 | ||
|
|
2bd43765d9 | ||
|
|
f306bd02c3 | ||
|
|
6b010ac80f | ||
|
|
8cd3383913 | ||
|
|
d6ed00c292 | ||
|
|
5d29136a83 | ||
|
|
52b29b33bb | ||
|
|
eaba9712e8 | ||
|
|
310adf7ca6 | ||
|
|
0700d6a7cd | ||
|
|
ca89c5b2ec | ||
|
|
453c720709 | ||
|
|
e76c06ba38 | ||
|
|
02aacf5110 | ||
|
|
707fb4d0df | ||
|
|
b161633b5f | ||
|
|
f5b8cf9dac | ||
|
|
aa36807c02 | ||
|
|
74c7a87985 | ||
|
|
ccb37c4f39 | ||
|
|
9ab4ca68ac | ||
|
|
60a0ca58fa | ||
|
|
938c7eb023 | ||
|
|
bfa9ea8d9b | ||
|
|
9bc782acfc | ||
|
|
3f6165d82e | ||
|
|
ad7f988da3 | ||
|
|
9d51b76d01 | ||
|
|
804511822b | ||
|
|
e6553c4ee3 | ||
|
|
98bb3be094 | ||
|
|
d247e7f5df | ||
|
|
65e7715909 | ||
|
|
61042b4d90 | ||
|
|
1c428cc558 | ||
|
|
61f095fdc1 | ||
|
|
65027b60bc | ||
|
|
b046c0dcbb | ||
|
|
737dff40cb | ||
|
|
a6be73eecb | ||
|
|
9fafe783d3 | ||
|
|
fb3ae0a254 | ||
|
|
aad1892ab5 | ||
|
|
8ea303e027 | ||
|
|
84ed35ef70 | ||
|
|
95fb599664 | ||
|
|
8b312ee571 | ||
|
|
fd7d0ad5e5 | ||
|
|
37bb47c4a2 | ||
|
|
cb1e10b745 | ||
|
|
fb3028f3a2 | ||
|
|
b45f5831cf | ||
|
|
0fe24fcffa | ||
|
|
d1dae25cd2 | ||
|
|
59c9364689 | ||
|
|
3b79be624d | ||
|
|
8955ed5f85 | ||
|
|
bc26934e3b | ||
|
|
3d64a06600 |
+1
-1
@@ -4,7 +4,7 @@ freebsd_instance:
|
||||
image_family: freebsd-13-1
|
||||
env:
|
||||
RUST_STABLE: stable
|
||||
RUST_NIGHTLY: nightly-2022-10-25
|
||||
RUST_NIGHTLY: nightly-2023-10-21
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
|
||||
@@ -15,7 +15,7 @@ env:
|
||||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2023-05-18
|
||||
rust_nightly: nightly-2023-10-21
|
||||
rust_clippy: 1.65.0
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
@@ -411,9 +411,7 @@ jobs:
|
||||
target:
|
||||
- powerpc-unknown-linux-gnu
|
||||
- powerpc64-unknown-linux-gnu
|
||||
- mips-unknown-linux-gnu
|
||||
- arm-linux-androideabi
|
||||
- mipsel-unknown-linux-musl
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
|
||||
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
tokio = { version = "1.33.0", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -132,6 +132,8 @@ project.
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`axum`]: A web application framework that focuses on ergonomics and modularity.
|
||||
|
||||
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
|
||||
|
||||
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
|
||||
@@ -142,21 +144,18 @@ several other libraries, including:
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers `tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
* [`loom`]: A testing tool for concurrent Rust code
|
||||
* [`loom`]: A testing tool for concurrent Rust code.
|
||||
|
||||
[`axum`]: https://github.com/tokio-rs/axum
|
||||
[`warp`]: https://github.com/seanmonstar/warp
|
||||
[`hyper`]: https://github.com/hyperium/hyper
|
||||
[`tonic`]: https://github.com/hyperium/tonic
|
||||
[`tower`]: https://github.com/tower-rs/tower
|
||||
[`loom`]: https://github.com/tokio-rs/loom
|
||||
[`rdbc`]: https://github.com/tokio-rs/rdbc
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
@@ -216,18 +215,18 @@ 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.20.x` - LTS release until September 2023. (MSRV 1.49)
|
||||
* `1.25.x` - LTS release until March 2024. (MSRV 1.49)
|
||||
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
use an LTS release.
|
||||
|
||||
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.18.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.25.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.18", features = [...] }
|
||||
tokio = { version = "~1.25", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -235,6 +234,7 @@ tokio = { version = "~1.18", features = [...] }
|
||||
* `1.8.x` - LTS release until February 2022.
|
||||
* `1.14.x` - LTS release until June 2022.
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ test-util = ["tokio/test-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
criterion = "0.5.1"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
|
||||
|
||||
+51
-39
@@ -1,4 +1,4 @@
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
@@ -174,65 +174,77 @@ fn rt() -> tokio::runtime::Runtime {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn copy_mem_to_mem(b: &mut Bencher) {
|
||||
fn copy_mem_to_mem(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
c.bench_function("copy_mem_to_mem", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_mem_to_slow_hdd(b: &mut Bencher) {
|
||||
fn copy_mem_to_slow_hdd(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
c.bench_function("copy_mem_to_slow_hdd", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = repeat(0).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_chunk_to_mem(b: &mut Bencher) {
|
||||
fn copy_chunk_to_mem(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
c.bench_function("copy_chunk_to_mem", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source =
|
||||
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = Vec::new();
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn copy_chunk_to_slow_hdd(b: &mut Bencher) {
|
||||
fn copy_chunk_to_slow_hdd(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
c.bench_function("copy_chunk_to_slow_hdd", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut source =
|
||||
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
|
||||
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
|
||||
copy(&mut source, &mut dest).await.unwrap();
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
copy_bench,
|
||||
copy_mem_to_mem,
|
||||
copy_mem_to_slow_hdd,
|
||||
copy_chunk_to_mem,
|
||||
copy_chunk_to_slow_hdd,
|
||||
);
|
||||
benchmark_main!(copy_bench);
|
||||
criterion_main!(copy_bench);
|
||||
|
||||
+66
-57
@@ -6,7 +6,7 @@ use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead /*FramedWrite*/};
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use std::fs::File as StdFile;
|
||||
use std::io::Read as StdRead;
|
||||
@@ -23,81 +23,90 @@ const BLOCK_COUNT: usize = 1_000;
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
const DEV_ZERO: &str = "/dev/zero";
|
||||
|
||||
fn async_read_codec(b: &mut Bencher) {
|
||||
fn async_read_codec(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut input_stream = FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
|
||||
c.bench_function("async_read_codec", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut input_stream =
|
||||
FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let _bytes = input_stream.next().await.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_buf(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let count = file.read(&mut buffer).await.unwrap();
|
||||
if count == 0 {
|
||||
break;
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let _bytes = input_stream.next().await.unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
rt.block_on(task());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_std_file(b: &mut Bencher) {
|
||||
fn async_read_buf(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
let task = || async {
|
||||
let mut file = tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
|
||||
c.bench_function("async_read_buf", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file = File::open(DEV_ZERO).await.unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let count = file.read(&mut buffer).await.unwrap();
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_std_file(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
c.bench_function("async_read_std_file", |b| {
|
||||
b.iter(|| {
|
||||
let task = || async {
|
||||
let mut file =
|
||||
tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
let mut file_ref = file.as_mut();
|
||||
|
||||
tokio::task::block_in_place(move || {
|
||||
file_ref.read_exact(&mut buffer).unwrap();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn sync_read(c: &mut Criterion) {
|
||||
c.bench_function("sync_read", |b| {
|
||||
b.iter(|| {
|
||||
let mut file = StdFile::open(DEV_ZERO).unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
let mut file_ref = file.as_mut();
|
||||
|
||||
tokio::task::block_in_place(move || {
|
||||
file_ref.read_exact(&mut buffer).unwrap();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(task());
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
file.read_exact(&mut buffer).unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn sync_read(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let mut file = StdFile::open(DEV_ZERO).unwrap();
|
||||
let mut buffer = [0u8; BUFFER_SIZE];
|
||||
|
||||
for _i in 0..BLOCK_COUNT {
|
||||
file.read_exact(&mut buffer).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
file,
|
||||
async_read_std_file,
|
||||
async_read_buf,
|
||||
async_read_codec,
|
||||
sync_read
|
||||
);
|
||||
|
||||
benchmark_main!(file);
|
||||
criterion_main!(file);
|
||||
|
||||
@@ -4,46 +4,50 @@
|
||||
|
||||
use tokio::runtime::{self, Runtime};
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
fn spawn_many_local(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(tokio::spawn(async move {}));
|
||||
}
|
||||
c.bench_function("spawn_many_local", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(tokio::spawn(async move {}));
|
||||
}
|
||||
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
fn spawn_many_remote_idle(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_idle", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_busy(b: &mut Bencher) {
|
||||
fn spawn_many_remote_busy(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
@@ -56,16 +60,18 @@ fn spawn_many_remote_busy(b: &mut Bencher) {
|
||||
iter()
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_busy", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,11 +79,11 @@ fn rt() -> Runtime {
|
||||
runtime::Builder::new_current_thread().build().unwrap()
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
spawn_many_remote_busy
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
criterion_main!(scheduler);
|
||||
|
||||
+117
-104
@@ -5,63 +5,68 @@
|
||||
use tokio::runtime::{self, Runtime};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
const NUM_WORKERS: usize = 4;
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const STALL_DUR: Duration = Duration::from_micros(10);
|
||||
|
||||
fn spawn_many_local(b: &mut Bencher) {
|
||||
fn spawn_many_local(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(|| {
|
||||
rem.store(NUM_SPAWN, Relaxed);
|
||||
c.bench_function("spawn_many_local", |b| {
|
||||
b.iter(|| {
|
||||
rem.store(NUM_SPAWN, Relaxed);
|
||||
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
tokio::spawn(async move {
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_many_remote_idle(b: &mut Bencher) {
|
||||
fn spawn_many_remote_idle(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_idle", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and yield. Yielding is a
|
||||
// lower notification priority than spawning / regular notification.
|
||||
fn spawn_many_remote_busy1(b: &mut Bencher) {
|
||||
fn spawn_many_remote_busy1(c: &mut Criterion) {
|
||||
let rt = rt();
|
||||
let rt_handle = rt.handle();
|
||||
let mut handles = Vec::with_capacity(NUM_SPAWN);
|
||||
@@ -78,16 +83,18 @@ fn spawn_many_remote_busy1(b: &mut Bencher) {
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_busy1", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
@@ -95,7 +102,7 @@ fn spawn_many_remote_busy1(b: &mut Bencher) {
|
||||
|
||||
// The runtime is busy with tasks that consume CPU time and spawn new high-CPU
|
||||
// tasks. Spawning goes via a higher notification priority than yielding.
|
||||
fn spawn_many_remote_busy2(b: &mut Bencher) {
|
||||
fn spawn_many_remote_busy2(c: &mut Criterion) {
|
||||
const NUM_SPAWN: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
@@ -119,49 +126,52 @@ fn spawn_many_remote_busy2(b: &mut Bencher) {
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
c.bench_function("spawn_many_remote_busy2", |b| {
|
||||
b.iter(|| {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
handles.push(rt_handle.spawn(async {}));
|
||||
}
|
||||
});
|
||||
|
||||
rt.block_on(async {
|
||||
for handle in handles.drain(..) {
|
||||
handle.await.unwrap();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
flag.store(false, Relaxed);
|
||||
}
|
||||
|
||||
fn yield_many(b: &mut Bencher) {
|
||||
fn yield_many(c: &mut Criterion) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS: usize = 200;
|
||||
|
||||
let rt = rt();
|
||||
c.bench_function("yield_many", |b| {
|
||||
let rt = rt();
|
||||
let (tx, rx) = mpsc::sync_channel(TASKS);
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(TASKS);
|
||||
b.iter(move || {
|
||||
for _ in 0..TASKS {
|
||||
let tx = tx.clone();
|
||||
|
||||
b.iter(move || {
|
||||
for _ in 0..TASKS {
|
||||
let tx = tx.clone();
|
||||
rt.spawn(async move {
|
||||
for _ in 0..NUM_YIELD {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
rt.spawn(async move {
|
||||
for _ in 0..NUM_YIELD {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..TASKS {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
for _ in 0..TASKS {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn ping_pong(b: &mut Bencher) {
|
||||
fn ping_pong(c: &mut Criterion) {
|
||||
const NUM_PINGS: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
@@ -169,46 +179,46 @@ fn ping_pong(b: &mut Bencher) {
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(|| {
|
||||
let done_tx = done_tx.clone();
|
||||
let rem = rem.clone();
|
||||
rem.store(NUM_PINGS, Relaxed);
|
||||
c.bench_function("ping_pong", |b| {
|
||||
b.iter(|| {
|
||||
let done_tx = done_tx.clone();
|
||||
let rem = rem.clone();
|
||||
rem.store(NUM_PINGS, Relaxed);
|
||||
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..NUM_PINGS {
|
||||
let rem = rem.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..NUM_PINGS {
|
||||
let rem = rem.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
rx1.await.unwrap();
|
||||
tx2.send(()).unwrap();
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
rx1.await.unwrap();
|
||||
tx2.send(()).unwrap();
|
||||
});
|
||||
|
||||
tx1.send(()).unwrap();
|
||||
rx2.await.unwrap();
|
||||
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
done_tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tx1.send(()).unwrap();
|
||||
rx2.await.unwrap();
|
||||
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
done_tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn chained_spawn(b: &mut Bencher) {
|
||||
fn chained_spawn(c: &mut Criterion) {
|
||||
const ITER: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
|
||||
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
done_tx.send(()).unwrap();
|
||||
@@ -219,18 +229,21 @@ fn chained_spawn(b: &mut Bencher) {
|
||||
}
|
||||
}
|
||||
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
c.bench_function("chained_spawn", |b| {
|
||||
let rt = rt();
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
|
||||
b.iter(move || {
|
||||
let done_tx = done_tx.clone();
|
||||
b.iter(move || {
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
iter(done_tx, ITER);
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
iter(done_tx, ITER);
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -249,7 +262,7 @@ fn stall() {
|
||||
}
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
scheduler,
|
||||
spawn_many_local,
|
||||
spawn_many_remote_idle,
|
||||
@@ -260,4 +273,4 @@ benchmark_group!(
|
||||
chained_spawn,
|
||||
);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
criterion_main!(scheduler);
|
||||
|
||||
+17
-15
@@ -1,7 +1,7 @@
|
||||
//! Benchmark the delay in propagating OS signals to any listeners.
|
||||
#![cfg(unix)]
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -41,7 +41,7 @@ pub fn send_signal(signal: libc::c_int) {
|
||||
}
|
||||
}
|
||||
|
||||
fn many_signals(bench: &mut Bencher) {
|
||||
fn many_signals(c: &mut Criterion) {
|
||||
let num_signals = 10;
|
||||
let (tx, mut rx) = mpsc::channel(num_signals);
|
||||
|
||||
@@ -75,21 +75,23 @@ fn many_signals(bench: &mut Bencher) {
|
||||
// tasks have been polled at least once
|
||||
rt.block_on(Spinner::new());
|
||||
|
||||
bench.iter(|| {
|
||||
rt.block_on(async {
|
||||
send_signal(libc::SIGCHLD);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
c.bench_function("many_signals", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
send_signal(libc::SIGCHLD);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
|
||||
send_signal(libc::SIGIO);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
});
|
||||
send_signal(libc::SIGIO);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(signal_group, many_signals,);
|
||||
criterion_group!(signal_group, many_signals);
|
||||
|
||||
benchmark_main!(signal_group);
|
||||
criterion_main!(signal_group);
|
||||
|
||||
+47
-40
@@ -2,10 +2,7 @@
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bencher;
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
async fn work() -> usize {
|
||||
let val = 1 + 1;
|
||||
@@ -13,67 +10,77 @@ async fn work() -> usize {
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn(bench: &mut Bencher) {
|
||||
fn basic_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
|
||||
c.bench_function("basic_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn basic_scheduler_spawn10(bench: &mut Bencher) {
|
||||
fn basic_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
|
||||
c.bench_function("basic_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn(bench: &mut Bencher) {
|
||||
fn threaded_scheduler_spawn(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
c.bench_function("threaded_scheduler_spawn", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let h = tokio::spawn(work());
|
||||
assert_eq!(h.await.unwrap(), 2);
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_spawn10(bench: &mut Bencher) {
|
||||
fn threaded_scheduler_spawn10(c: &mut Criterion) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
bench.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
c.bench_function("threaded_scheduler_spawn10", |b| {
|
||||
b.iter(|| {
|
||||
runtime.block_on(async {
|
||||
let mut handles = Vec::with_capacity(10);
|
||||
for _ in 0..10 {
|
||||
handles.push(tokio::spawn(work()));
|
||||
}
|
||||
for handle in handles {
|
||||
assert_eq!(handle.await.unwrap(), 2);
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
criterion_group!(
|
||||
spawn,
|
||||
basic_scheduler_spawn,
|
||||
basic_scheduler_spawn10,
|
||||
@@ -81,4 +88,4 @@ bencher::benchmark_group!(
|
||||
threaded_scheduler_spawn10,
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(spawn);
|
||||
criterion_main!(spawn);
|
||||
|
||||
+282
-130
@@ -1,8 +1,23 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
type Medium = [usize; 64];
|
||||
type Large = [Medium; 64];
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Medium([usize; 64]);
|
||||
impl Default for Medium {
|
||||
fn default() -> Self {
|
||||
Medium([0; 64])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Large([Medium; 64]);
|
||||
impl Default for Large {
|
||||
fn default() -> Self {
|
||||
Large([Medium::default(); 64])
|
||||
}
|
||||
}
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
@@ -11,169 +26,306 @@ fn rt() -> tokio::runtime::Runtime {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create_1_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(1));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_000_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100_000));
|
||||
});
|
||||
}
|
||||
|
||||
fn send_medium(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
|
||||
let _ = rt.block_on(tx.send([0; 64]));
|
||||
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn send_large(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
let _ = rt.block_on(tx.send([[0; 64]; 64]));
|
||||
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
fn create_medium<const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
g.bench_function(SIZE.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(SIZE));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded_full(b: &mut Bencher) {
|
||||
fn send_data<T: Default, const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>, prefix: &str) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
g.bench_function(format!("{}_{}", prefix, SIZE), |b| {
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<T>(SIZE);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
let _ = rt.block_on(tx.send(T::default()));
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded(b: &mut Bencher) {
|
||||
fn contention_bounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
g.bench_function("bounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded(b: &mut Bencher) {
|
||||
fn contention_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
g.bench_function("bounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(b: &mut Bencher) {
|
||||
fn contention_bounded_full(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
g.bench_function("bounded_full", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
create,
|
||||
create_1_medium,
|
||||
create_100_medium,
|
||||
create_100_000_medium
|
||||
);
|
||||
fn contention_bounded_full_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
bencher::benchmark_group!(send, send_medium, send_large);
|
||||
g.bench_function("bounded_full_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
bencher::benchmark_group!(
|
||||
contention,
|
||||
contention_bounded,
|
||||
contention_bounded_full,
|
||||
contention_unbounded,
|
||||
uncontented_bounded,
|
||||
uncontented_unbounded
|
||||
);
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_main!(create, send, contention);
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("bounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
|
||||
g.bench_function("unbounded_recv_many", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
let mut buffer = Vec::<usize>::with_capacity(5_000);
|
||||
let mut total = 0;
|
||||
while total < 1_000 * 5 {
|
||||
total += rx.recv_many(&mut buffer, 5_000).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_create_medium(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("create_medium");
|
||||
create_medium::<1>(&mut group);
|
||||
create_medium::<100>(&mut group);
|
||||
create_medium::<100_000>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_send(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("send");
|
||||
send_data::<Medium, 1000>(&mut group, "medium");
|
||||
send_data::<Large, 1000>(&mut group, "large");
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
contention_bounded(&mut group);
|
||||
contention_bounded_recv_many(&mut group);
|
||||
contention_bounded_full(&mut group);
|
||||
contention_bounded_full_recv_many(&mut group);
|
||||
contention_unbounded(&mut group);
|
||||
contention_unbounded_recv_many(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
uncontented_bounded(&mut group);
|
||||
uncontented_bounded_recv_many(&mut group);
|
||||
uncontented_unbounded(&mut group);
|
||||
uncontented_unbounded_recv_many(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(create, bench_create_medium);
|
||||
criterion_group!(send, bench_send);
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(create, send, contention, uncontented);
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
sync::{mpsc, oneshot},
|
||||
};
|
||||
|
||||
fn request_reply_current_thread(b: &mut Bencher) {
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
fn request_reply_current_thread(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(b, rt);
|
||||
request_reply(c, rt);
|
||||
}
|
||||
|
||||
fn request_reply_multi_threaded(b: &mut Bencher) {
|
||||
fn request_reply_multi_threaded(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
request_reply(b, rt);
|
||||
request_reply(c, rt);
|
||||
}
|
||||
|
||||
fn request_reply(b: &mut Bencher, rt: Runtime) {
|
||||
fn request_reply(b: &mut Criterion, rt: Runtime) {
|
||||
let tx = rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<oneshot::Sender<()>>(10);
|
||||
tokio::spawn(async move {
|
||||
@@ -32,22 +33,24 @@ fn request_reply(b: &mut Bencher, rt: Runtime) {
|
||||
tx
|
||||
});
|
||||
|
||||
b.iter(|| {
|
||||
let task_tx = tx.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..1_000 {
|
||||
let (o_tx, o_rx) = oneshot::channel();
|
||||
task_tx.send(o_tx).await.unwrap();
|
||||
let _ = o_rx.await;
|
||||
}
|
||||
b.bench_function("request_reply", |b| {
|
||||
b.iter(|| {
|
||||
let task_tx = tx.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..1_000 {
|
||||
let (o_tx, o_rx) = oneshot::channel();
|
||||
task_tx.send(o_tx).await.unwrap();
|
||||
let _ = o_rx.await;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(
|
||||
criterion_group!(
|
||||
sync_mpsc_oneshot_group,
|
||||
request_reply_current_thread,
|
||||
request_reply_multi_threaded,
|
||||
);
|
||||
|
||||
benchmark_main!(sync_mpsc_oneshot_group);
|
||||
criterion_main!(sync_mpsc_oneshot_group);
|
||||
|
||||
+47
-33
@@ -1,9 +1,11 @@
|
||||
use bencher::Bencher;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
@@ -11,7 +13,7 @@ fn rt() -> tokio::runtime::Runtime {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn notify_waiters<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
fn notify_waiters<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
@@ -29,18 +31,20 @@ fn notify_waiters<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_waiters();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
g.bench_function(N_WAITERS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_waiters();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn notify_one<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
fn notify_one<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let notify = Arc::new(Notify::new());
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
@@ -58,33 +62,43 @@ fn notify_one<const N_WAITERS: usize>(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
const N_ITERS: usize = 500;
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_one();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
g.bench_function(N_WAITERS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
counter.store(0, Ordering::Relaxed);
|
||||
loop {
|
||||
notify.notify_one();
|
||||
if counter.load(Ordering::Relaxed) >= N_ITERS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
fn bench_notify_one(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("notify_one");
|
||||
notify_one::<10>(&mut group);
|
||||
notify_one::<50>(&mut group);
|
||||
notify_one::<100>(&mut group);
|
||||
notify_one::<200>(&mut group);
|
||||
notify_one::<500>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_notify_waiters(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("notify_waiters");
|
||||
notify_waiters::<10>(&mut group);
|
||||
notify_waiters::<50>(&mut group);
|
||||
notify_waiters::<100>(&mut group);
|
||||
notify_waiters::<200>(&mut group);
|
||||
notify_waiters::<500>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
notify_waiters_simple,
|
||||
notify_waiters::<10>,
|
||||
notify_waiters::<50>,
|
||||
notify_waiters::<100>,
|
||||
notify_waiters::<200>,
|
||||
notify_waiters::<500>
|
||||
bench_notify_one,
|
||||
bench_notify_waiters
|
||||
);
|
||||
|
||||
bencher::benchmark_group!(
|
||||
notify_one_simple,
|
||||
notify_one::<10>,
|
||||
notify_one::<50>,
|
||||
notify_one::<100>,
|
||||
notify_one::<200>,
|
||||
notify_one::<500>
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(notify_waiters_simple, notify_one_simple);
|
||||
criterion_main!(notify_waiters_simple);
|
||||
|
||||
+91
-70
@@ -1,26 +1,30 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use std::sync::Arc;
|
||||
use tokio::{sync::RwLock, task};
|
||||
|
||||
fn read_uncontended(b: &mut Bencher) {
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn read_uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let read = lock.read().await;
|
||||
let _read = black_box(read);
|
||||
}
|
||||
g.bench_function("read", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let read = lock.read().await;
|
||||
let _read = black_box(read);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
fn read_concurrent_uncontended_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
@@ -32,23 +36,25 @@ fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
g.bench_function("read_concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
fn read_concurrent_uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
@@ -59,22 +65,24 @@ fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone())
|
||||
};
|
||||
g.bench_function("read_concurrent", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone())
|
||||
};
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
fn read_concurrent_contended_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
@@ -86,24 +94,26 @@ fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
let j = tokio::try_join! {
|
||||
async move { drop(write); Ok(()) },
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
};
|
||||
j.unwrap();
|
||||
g.bench_function("read_concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
let j = tokio::try_join! {
|
||||
async move { drop(write); Ok(()) },
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_contended(b: &mut Bencher) {
|
||||
fn read_concurrent_contended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
@@ -114,29 +124,40 @@ fn read_concurrent_contended(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
tokio::join! {
|
||||
async move { drop(write) },
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
};
|
||||
g.bench_function("read_concurrent", |b| {
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
tokio::join! {
|
||||
async move { drop(write) },
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
};
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
sync_rwlock,
|
||||
read_uncontended,
|
||||
read_concurrent_uncontended,
|
||||
read_concurrent_uncontended_multi,
|
||||
read_concurrent_contended,
|
||||
read_concurrent_contended_multi
|
||||
);
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
read_concurrent_contended(&mut group);
|
||||
read_concurrent_contended_multi(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
bencher::benchmark_main!(sync_rwlock);
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
read_uncontended(&mut group);
|
||||
read_concurrent_uncontended(&mut group);
|
||||
read_concurrent_uncontended_multi(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(contention, uncontented);
|
||||
|
||||
+106
-84
@@ -1,21 +1,36 @@
|
||||
use bencher::Bencher;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::{sync::Semaphore, task};
|
||||
|
||||
fn uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn single_rt() -> Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn multi_rt() -> Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn uncontended(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let permit = s.acquire().await;
|
||||
drop(permit);
|
||||
}
|
||||
g.bench_function("multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let permit = s.acquire().await;
|
||||
drop(permit);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -25,101 +40,108 @@ async fn task(s: Arc<Semaphore>) {
|
||||
drop(permit);
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
fn uncontended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
g.bench_function("concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
fn uncontended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = single_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
g.bench_function("concurrent_single", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
fn contended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = multi_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(5));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
g.bench_function("concurrent_multi", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
fn contended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = single_rt();
|
||||
|
||||
let s = Arc::new(Semaphore::new(5));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
g.bench_function("concurrent_single", |b| {
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
sync_semaphore,
|
||||
uncontended,
|
||||
uncontended_concurrent_multi,
|
||||
uncontended_concurrent_single,
|
||||
contended_concurrent_multi,
|
||||
contended_concurrent_single
|
||||
);
|
||||
fn bench_contention(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention");
|
||||
contended_concurrent_multi(&mut group);
|
||||
contended_concurrent_single(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
bencher::benchmark_main!(sync_semaphore);
|
||||
fn bench_uncontented(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("uncontented");
|
||||
uncontended(&mut group);
|
||||
uncontended_concurrent_multi(&mut group);
|
||||
uncontended_concurrent_single(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(contention, bench_contention);
|
||||
criterion_group!(uncontented, bench_uncontented);
|
||||
|
||||
criterion_main!(contention, uncontented);
|
||||
|
||||
+40
-19
@@ -1,9 +1,11 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use rand::prelude::*;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{watch, Notify};
|
||||
|
||||
use criterion::measurement::WallTime;
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
@@ -24,16 +26,15 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
|
||||
.fold(0, u32::wrapping_add)
|
||||
}
|
||||
|
||||
fn contention_resubscribe(b: &mut Bencher) {
|
||||
const NTASK: u64 = 1000;
|
||||
|
||||
fn contention_resubscribe<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
|
||||
let rt = rt();
|
||||
let (snd, rcv) = watch::channel(0i32);
|
||||
let (snd, _) = watch::channel(0i32);
|
||||
let snd = Arc::new(snd);
|
||||
let wg = Arc::new((AtomicU64::new(0), Notify::new()));
|
||||
for n in 0..NTASK {
|
||||
let mut rcv = rcv.clone();
|
||||
for n in 0..N_TASKS {
|
||||
let mut rcv = snd.subscribe();
|
||||
let wg = wg.clone();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n);
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
|
||||
rt.spawn(async move {
|
||||
while rcv.changed().await.is_ok() {
|
||||
let _ = *rcv.borrow(); // contend on rwlock
|
||||
@@ -46,19 +47,39 @@ fn contention_resubscribe(b: &mut Bencher) {
|
||||
});
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
for _ in 0..100 {
|
||||
assert_eq!(wg.0.fetch_add(NTASK, Ordering::Relaxed), 0);
|
||||
let _ = snd.send(black_box(42));
|
||||
while wg.0.load(Ordering::Acquire) > 0 {
|
||||
wg.1.notified().await;
|
||||
const N_ITERS: usize = 100;
|
||||
g.bench_function(N_TASKS.to_string(), |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on({
|
||||
let snd = snd.clone();
|
||||
let wg = wg.clone();
|
||||
async move {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..N_ITERS {
|
||||
assert_eq!(wg.0.fetch_add(N_TASKS as u64, Ordering::Relaxed), 0);
|
||||
let _ = snd.send(black_box(42));
|
||||
while wg.0.load(Ordering::Acquire) > 0 {
|
||||
wg.1.notified().await;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(contention, contention_resubscribe);
|
||||
fn bench_contention_resubscribe(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("contention_resubscribe");
|
||||
contention_resubscribe::<10>(&mut group);
|
||||
contention_resubscribe::<100>(&mut group);
|
||||
contention_resubscribe::<500>(&mut group);
|
||||
contention_resubscribe::<1000>(&mut group);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
bencher::benchmark_main!(contention);
|
||||
criterion_group!(contention, bench_contention_resubscribe);
|
||||
|
||||
criterion_main!(contention);
|
||||
|
||||
+10
-11
@@ -2,24 +2,23 @@
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bencher;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
|
||||
fn time_now_current_thread(bench: &mut Bencher) {
|
||||
fn time_now_current_thread(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
bench.iter(|| {
|
||||
rt.block_on(async {
|
||||
black_box(tokio::time::Instant::now());
|
||||
c.bench_function("time_now_current_thread", |b| {
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
black_box(tokio::time::Instant::now());
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(time_now, time_now_current_thread,);
|
||||
criterion_group!(time_now, time_now_current_thread);
|
||||
|
||||
bencher::benchmark_main!(time_now);
|
||||
criterion_main!(time_now);
|
||||
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Publish a new release of a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-v, --verbose Use verbose Cargo output
|
||||
-d, --dry-run Perform a dry run (do not publish or tag the release)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
DRY_RUN=""
|
||||
VERBOSE=""
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
verify() {
|
||||
status "Verifying" "if $CRATE v$VERSION can be released"
|
||||
ACTUAL=$(cargo pkgid | sed -n 's/.*#\(.*\)/\1/p')
|
||||
|
||||
if [ "$ACTUAL" != "$VERSION" ]; then
|
||||
err "expected to release version $VERSION, but Cargo.toml contained $ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git tag -l | grep -Fxq "$TAG" ; then
|
||||
err "git tag \`$TAG\` already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATH_DEPS=$(grep -F "path = \"" Cargo.toml | sed -e 's/^/ /')
|
||||
if [ -n "$PATH_DEPS" ]; then
|
||||
err "crate \`$CRATE\` contained path dependencies:\n$PATH_DEPS"
|
||||
echo "path dependencies must be removed prior to release"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
release() {
|
||||
status "Releasing" "$CRATE v$VERSION"
|
||||
cargo package $VERBOSE
|
||||
cargo publish $VERBOSE $DRY_RUN
|
||||
|
||||
status "Tagging" "$TAG"
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
echo "# git tag $TAG && git push --tags"
|
||||
else
|
||||
git tag "$TAG" && git push --tags
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE="--verbose"
|
||||
set +x
|
||||
shift
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
(cd "$CRATE" && verify && release )
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Update links to docs.rs in a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-d, --dry-run Perform a dry run (do not modify any file)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
c1grep() { grep "$@" || test $? = 1; }
|
||||
|
||||
update_versions_in_doc() {
|
||||
# Print what is being/would be done
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
local MSG="Would change:"
|
||||
else
|
||||
local MSG="Updating:"
|
||||
fi
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed --quiet \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|gp" \
|
||||
| sed -e "s/^/$MSG /"
|
||||
|
||||
# Apply changes if not in dry run
|
||||
if [ -z "$DRY_RUN" ]; then
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed -i \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|g"
|
||||
fi
|
||||
}
|
||||
|
||||
update() {
|
||||
update_versions_in_doc
|
||||
}
|
||||
|
||||
show_outdated() {
|
||||
OUTDATED=$(git grep -rn "docs.rs/$CRATE/" \
|
||||
| c1grep -v "$VERSION" \
|
||||
| sed -e 's/^/ - /')
|
||||
if [[ -n "$OUTDATED" ]]; then
|
||||
echo "Found the following links to docs.rs with an outdated version:"
|
||||
echo "$OUTDATED"
|
||||
echo
|
||||
else
|
||||
echo "Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
# Does not cd in order to update everywhere
|
||||
show_outdated && update
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+2
-1
@@ -8,8 +8,9 @@
|
||||
RUSTDOCFLAGS="""
|
||||
--cfg docsrs \
|
||||
--cfg tokio_unstable \
|
||||
--cfg tokio_taskdump \
|
||||
"""
|
||||
RUSTFLAGS="--cfg tokio_unstable --cfg docsrs"
|
||||
RUSTFLAGS="--cfg tokio_unstable --cfg tokio_taskdump --cfg docsrs"
|
||||
|
||||
[[redirects]]
|
||||
from = "/"
|
||||
|
||||
@@ -22,7 +22,7 @@ proc-macro = true
|
||||
[features]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.7"
|
||||
proc-macro2 = "1.0.60"
|
||||
quote = "1"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
|
||||
+10
-10
@@ -126,22 +126,22 @@ impl Configuration {
|
||||
}
|
||||
|
||||
fn build(&self) -> Result<FinalConfig, syn::Error> {
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
use RuntimeFlavor::*;
|
||||
use RuntimeFlavor as F;
|
||||
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
let worker_threads = match (flavor, self.worker_threads) {
|
||||
(CurrentThread, Some((_, worker_threads_span))) => {
|
||||
(F::CurrentThread, Some((_, worker_threads_span))) => {
|
||||
let msg = format!(
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(worker_threads_span, msg));
|
||||
}
|
||||
(CurrentThread, None) => None,
|
||||
(Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
(F::CurrentThread, None) => None,
|
||||
(F::Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
worker_threads.map(|(val, _span)| val)
|
||||
}
|
||||
(Threaded, _) => {
|
||||
(F::Threaded, _) => {
|
||||
let msg = if self.flavor.is_none() {
|
||||
"The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled."
|
||||
} else {
|
||||
@@ -152,14 +152,14 @@ impl Configuration {
|
||||
};
|
||||
|
||||
let start_paused = match (flavor, self.start_paused) {
|
||||
(Threaded, Some((_, start_paused_span))) => {
|
||||
(F::Threaded, Some((_, start_paused_span))) => {
|
||||
let msg = format!(
|
||||
"The `start_paused` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(start_paused_span, msg));
|
||||
}
|
||||
(CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(F::CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
@@ -402,7 +402,7 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
|
||||
quote! {
|
||||
let body = async #body;
|
||||
#crate_path::pin!(body);
|
||||
let body: ::std::pin::Pin<&mut dyn ::std::future::Future<Output = #output_type>> = body;
|
||||
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
@@ -586,6 +586,6 @@ impl ToTokens for Body<'_> {
|
||||
for stmt in self.stmts {
|
||||
stmt.to_tokens(tokens);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,27 +73,27 @@ fn clean_pattern(pat: &mut syn::Pat) {
|
||||
}
|
||||
}
|
||||
syn::Pat::Or(or) => {
|
||||
for case in or.cases.iter_mut() {
|
||||
for case in &mut or.cases {
|
||||
clean_pattern(case);
|
||||
}
|
||||
}
|
||||
syn::Pat::Slice(slice) => {
|
||||
for elem in slice.elems.iter_mut() {
|
||||
for elem in &mut slice.elems {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::Struct(struct_pat) => {
|
||||
for field in struct_pat.fields.iter_mut() {
|
||||
for field in &mut struct_pat.fields {
|
||||
clean_pattern(&mut field.pat);
|
||||
}
|
||||
}
|
||||
syn::Pat::Tuple(tuple) => {
|
||||
for elem in tuple.elems.iter_mut() {
|
||||
for elem in &mut tuple.elems {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
syn::Pat::TupleStruct(tuple) => {
|
||||
for elem in tuple.elems.iter_mut() {
|
||||
for elem in &mut tuple.elems {
|
||||
clean_pattern(elem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl<I> Unpin for Once<I> {}
|
||||
/// ```
|
||||
pub fn once<T>(value: T) -> Once<T> {
|
||||
Once {
|
||||
iter: crate::iter(Some(value).into_iter()),
|
||||
iter: crate::iter(Some(value)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -848,8 +848,7 @@ pub trait StreamExt: Stream {
|
||||
///
|
||||
/// `collect` streams all values, awaiting as needed. Values are pushed into
|
||||
/// a collection. A number of different target collection types are
|
||||
/// supported, including [`Vec`](std::vec::Vec),
|
||||
/// [`String`](std::string::String), and [`Bytes`].
|
||||
/// supported, including [`Vec`], [`String`], and [`Bytes`].
|
||||
///
|
||||
/// [`Bytes`]: https://docs.rs/bytes/0.6.0/bytes/struct.Bytes.html
|
||||
///
|
||||
@@ -1005,9 +1004,8 @@ pub trait StreamExt: Stream {
|
||||
|
||||
/// Applies a per-item timeout to the passed stream.
|
||||
///
|
||||
/// `timeout_repeating()` takes an [`Interval`](tokio::time::Interval) that
|
||||
/// controls the time each element of the stream has to complete before
|
||||
/// timing out.
|
||||
/// `timeout_repeating()` takes an [`Interval`] that controls the time each
|
||||
/// element of the stream has to complete before timing out.
|
||||
///
|
||||
/// If the wrapped stream yields a value before the deadline is reached, the
|
||||
/// value is returned. Otherwise, an error is returned. The caller may decide
|
||||
|
||||
@@ -26,7 +26,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from a [`Stream`](crate::Stream).
|
||||
/// Convert from a [`Stream`].
|
||||
///
|
||||
/// This trait is not intended to be used directly. Instead, call
|
||||
/// [`StreamExt::collect()`](super::StreamExt::collect).
|
||||
|
||||
@@ -66,25 +66,23 @@ where
|
||||
T: Stream,
|
||||
U: Stream<Item = T::Item>,
|
||||
{
|
||||
use Poll::*;
|
||||
|
||||
let mut done = true;
|
||||
|
||||
match first.poll_next(cx) {
|
||||
Ready(Some(val)) => return Ready(Some(val)),
|
||||
Ready(None) => {}
|
||||
Pending => done = false,
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some(val)),
|
||||
Poll::Ready(None) => {}
|
||||
Poll::Pending => done = false,
|
||||
}
|
||||
|
||||
match second.poll_next(cx) {
|
||||
Ready(Some(val)) => return Ready(Some(val)),
|
||||
Ready(None) => {}
|
||||
Pending => done = false,
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some(val)),
|
||||
Poll::Ready(None) => {}
|
||||
Poll::Pending => done = false,
|
||||
}
|
||||
|
||||
if done {
|
||||
Ready(None)
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Pending
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,8 +518,6 @@ where
|
||||
{
|
||||
/// Polls the next value, includes the vec entry index
|
||||
fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<Option<(usize, V::Item)>> {
|
||||
use Poll::*;
|
||||
|
||||
let start = self::rand::thread_rng_n(self.entries.len() as u32) as usize;
|
||||
let mut idx = start;
|
||||
|
||||
@@ -527,8 +525,8 @@ where
|
||||
let (_, stream) = &mut self.entries[idx];
|
||||
|
||||
match Pin::new(stream).poll_next(cx) {
|
||||
Ready(Some(val)) => return Ready(Some((idx, val))),
|
||||
Ready(None) => {
|
||||
Poll::Ready(Some(val)) => return Poll::Ready(Some((idx, val))),
|
||||
Poll::Ready(None) => {
|
||||
// Remove the entry
|
||||
self.entries.swap_remove(idx);
|
||||
|
||||
@@ -542,7 +540,7 @@ where
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
Pending => {
|
||||
Poll::Pending => {
|
||||
idx = idx.wrapping_add(1) % self.entries.len();
|
||||
}
|
||||
}
|
||||
@@ -550,9 +548,9 @@ where
|
||||
|
||||
// If the map is empty, then the stream is complete.
|
||||
if self.entries.is_empty() {
|
||||
Ready(None)
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Pending
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ impl<T> ReceiverStream<T> {
|
||||
///
|
||||
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close()
|
||||
self.inner.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ impl<T> UnboundedReceiverStream<T> {
|
||||
/// This prevents any further messages from being sent on the channel while
|
||||
/// still enabling the receiver to drain messages that are buffered.
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close()
|
||||
self.inner.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
# 0.4.3 (August 23, 2023)
|
||||
|
||||
- deps: fix minimum required version of `async-stream` ([#5347])
|
||||
- deps: fix minimum required version of `tokio-stream` ([#4376])
|
||||
- docs: improve `tokio_test::task` docs ([#5132])
|
||||
- io: fetch actions from mock handle before write ([#5814])
|
||||
- io: fix wait operation on mock ([#5554])
|
||||
|
||||
[#4376]: https://github.com/tokio-rs/tokio/pull/4376
|
||||
[#5132]: https://github.com/tokio-rs/tokio/pull/5132
|
||||
[#5347]: https://github.com/tokio-rs/tokio/pull/5347
|
||||
[#5554]: https://github.com/tokio-rs/tokio/pull/5554
|
||||
[#5814]: https://github.com/tokio-rs/tokio/pull/5814
|
||||
|
||||
# 0.4.2 (May 14, 2021)
|
||||
|
||||
- test: add `assert_elapsed!` macro ([#3728])
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-test"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -14,7 +14,7 @@ homepage = "https://tokio.rs"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
categories = ["asynchronous", "testing"]
|
||||
categories = ["asynchronous", "development-tools::testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
|
||||
@@ -74,7 +74,7 @@ struct Inner {
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Return a new, empty `Builder.
|
||||
/// Return a new, empty `Builder`.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
@@ -478,7 +478,7 @@ impl Drop for Mock {
|
||||
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."),
|
||||
_ => (),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
+12
-12
@@ -22,17 +22,17 @@
|
||||
#[macro_export]
|
||||
macro_rules! assert_ready {
|
||||
($e:expr) => {{
|
||||
use core::task::Poll::*;
|
||||
use core::task::Poll;
|
||||
match $e {
|
||||
Ready(v) => v,
|
||||
Pending => panic!("pending"),
|
||||
Poll::Ready(v) => v,
|
||||
Poll::Pending => panic!("pending"),
|
||||
}
|
||||
}};
|
||||
($e:expr, $($msg:tt)+) => {{
|
||||
use core::task::Poll::*;
|
||||
use core::task::Poll;
|
||||
match $e {
|
||||
Ready(v) => v,
|
||||
Pending => {
|
||||
Poll::Ready(v) => v,
|
||||
Poll::Pending => {
|
||||
panic!("pending; {}", format_args!($($msg)+))
|
||||
}
|
||||
}
|
||||
@@ -127,17 +127,17 @@ macro_rules! assert_ready_err {
|
||||
#[macro_export]
|
||||
macro_rules! assert_pending {
|
||||
($e:expr) => {{
|
||||
use core::task::Poll::*;
|
||||
use core::task::Poll;
|
||||
match $e {
|
||||
Pending => {}
|
||||
Ready(v) => panic!("ready; value = {:?}", v),
|
||||
Poll::Pending => {}
|
||||
Poll::Ready(v) => panic!("ready; value = {:?}", v),
|
||||
}
|
||||
}};
|
||||
($e:expr, $($msg:tt)+) => {{
|
||||
use core::task::Poll::*;
|
||||
use core::task::Poll;
|
||||
match $e {
|
||||
Pending => {}
|
||||
Ready(v) => {
|
||||
Poll::Pending => {}
|
||||
Poll::Ready(v) => {
|
||||
panic!("ready; value = {:?}; {}", v, format_args!($($msg)+))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ impl<T: Future> Spawn<T> {
|
||||
}
|
||||
|
||||
impl<T: Stream> Spawn<T> {
|
||||
/// If `T` is a [`Stream`] then poll_next it. This will handle pinning and the context
|
||||
/// If `T` is a [`Stream`] then `poll_next` it. This will handle pinning and the context
|
||||
/// type for the stream.
|
||||
pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
|
||||
let stream = self.future.as_mut();
|
||||
|
||||
@@ -1,3 +1,31 @@
|
||||
# 0.7.9 (September 20th, 2023)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add passthrough `AsyncRead`/`AsyncWrite` to `InspectWriter`/`InspectReader` ([#5739])
|
||||
- task: add spawn blocking methods to `JoinMap` ([#5797])
|
||||
- io: pass through traits for `StreamReader` and `SinkWriter` ([#5941])
|
||||
- io: add `SyncIoBridge::into_inner` ([#5971])
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: handle possibly dangling reference safely ([#5812])
|
||||
- util: fix broken intra-doc link ([#5849])
|
||||
- compat: fix clippy warnings ([#5891])
|
||||
|
||||
### Documented
|
||||
|
||||
- codec: Specify the line ending of `LinesCodec` ([#5982])
|
||||
|
||||
[#5739]: https://github.com/tokio-rs/tokio/pull/5739
|
||||
[#5797]: https://github.com/tokio-rs/tokio/pull/5797
|
||||
[#5941]: https://github.com/tokio-rs/tokio/pull/5941
|
||||
[#5971]: https://github.com/tokio-rs/tokio/pull/5971
|
||||
[#5812]: https://github.com/tokio-rs/tokio/pull/5812
|
||||
[#5849]: https://github.com/tokio-rs/tokio/pull/5849
|
||||
[#5891]: https://github.com/tokio-rs/tokio/pull/5891
|
||||
[#5982]: https://github.com/tokio-rs/tokio/pull/5982
|
||||
|
||||
# 0.7.8 (April 25th, 2023)
|
||||
|
||||
This release bumps the MSRV of tokio-util to 1.56.
|
||||
|
||||
@@ -4,7 +4,7 @@ name = "tokio-util"
|
||||
# - Remove path dependencies
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.7.x" git tag.
|
||||
version = "0.7.8"
|
||||
version = "0.7.9"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -45,7 +45,7 @@ slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
|
||||
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
[target.'cfg(tokio_unstable)'.dependencies]
|
||||
hashbrown = { version = "0.12.0", optional = true }
|
||||
hashbrown = { version = "0.14.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
@@ -6,6 +6,8 @@ use std::{cmp, fmt, io, str, usize};
|
||||
|
||||
/// A simple [`Decoder`] and [`Encoder`] implementation that splits up data into lines.
|
||||
///
|
||||
/// This uses the `\n` character as the line ending on all platforms.
|
||||
///
|
||||
/// [`Decoder`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
|
||||
@@ -116,7 +116,7 @@ where
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
delegate_call!(self.consume(amt))
|
||||
delegate_call!(self.consume(amt));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use bytes::Bytes;
|
||||
use futures_core::stream::Stream;
|
||||
use futures_sink::Sink;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
@@ -66,3 +67,10 @@ where
|
||||
self.project().inner.poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for CopyToBytes<S> {
|
||||
type Item = S::Item;
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.project().inner.poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
|
||||
use futures_core::stream::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
pin_project! {
|
||||
/// Convert a [`Sink`] of byte chunks into an [`AsyncWrite`].
|
||||
@@ -115,3 +116,20 @@ where
|
||||
self.project().inner.poll_close(cx).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Stream> Stream for SinkWriter<S> {
|
||||
type Item = S::Item;
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.project().inner.poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsyncRead> AsyncRead for SinkWriter<S> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
self.project().inner.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use bytes::Buf;
|
||||
use futures_core::stream::Stream;
|
||||
use futures_sink::Sink;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -165,7 +166,7 @@ where
|
||||
B: Buf,
|
||||
E: Into<std::io::Error>,
|
||||
{
|
||||
/// Convert a stream of byte chunks into an [`AsyncRead`](tokio::io::AsyncRead).
|
||||
/// 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
|
||||
@@ -324,3 +325,22 @@ impl<S, B> StreamReader<S, B> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Sink<T, Error = E>, E, T> Sink<T> for StreamReader<S, E> {
|
||||
type Error = E;
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
|
||||
self.project().inner.start_send(item)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::io::{BufRead, Read, Write};
|
||||
use std::io::{BufRead, Read, Seek, Write};
|
||||
use tokio::io::{
|
||||
AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt,
|
||||
AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite,
|
||||
AsyncWriteExt,
|
||||
};
|
||||
|
||||
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
|
||||
@@ -79,6 +80,13 @@ impl<T: AsyncWrite + Unpin> Write for SyncIoBridge<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncSeek + Unpin> Seek for SyncIoBridge<T> {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
let src = &mut self.src;
|
||||
self.rt.block_on(AsyncSeekExt::seek(src, pos))
|
||||
}
|
||||
}
|
||||
|
||||
// Because https://doc.rust-lang.org/std/io/trait.Write.html#method.is_write_vectored is at the time
|
||||
// of this writing still unstable, we expose this as part of a standalone method.
|
||||
impl<T: AsyncWrite> SyncIoBridge<T> {
|
||||
@@ -140,4 +148,9 @@ impl<T: Unpin> SyncIoBridge<T> {
|
||||
pub fn new_with_handle(src: T, rt: tokio::runtime::Handle) -> Self {
|
||||
Self { src, rt }
|
||||
}
|
||||
|
||||
/// Consume this bridge, returning the underlying stream.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.src
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ impl Default for CancellationToken {
|
||||
}
|
||||
|
||||
impl CancellationToken {
|
||||
/// Creates a new CancellationToken in the non-cancelled state.
|
||||
/// Creates a new `CancellationToken` in the non-cancelled state.
|
||||
pub fn new() -> CancellationToken {
|
||||
CancellationToken {
|
||||
inner: Arc::new(tree_node::TreeNode::new()),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! This mod provides the logic for the inner tree structure of the CancellationToken.
|
||||
//!
|
||||
//! CancellationTokens are only light handles with references to TreeNode.
|
||||
//! All the logic is actually implemented in the TreeNode.
|
||||
//! CancellationTokens are only light handles with references to [`TreeNode`].
|
||||
//! All the logic is actually implemented in the [`TreeNode`].
|
||||
//!
|
||||
//! A TreeNode is part of the cancellation tree and may have one parent and an arbitrary number of
|
||||
//! A [`TreeNode`] is part of the cancellation tree and may have one parent and an arbitrary number of
|
||||
//! children.
|
||||
//!
|
||||
//! A TreeNode can receive the request to perform a cancellation through a CancellationToken.
|
||||
//! A [`TreeNode`] can receive the request to perform a cancellation through a CancellationToken.
|
||||
//! This cancellation request will cancel the node and all of its descendants.
|
||||
//!
|
||||
//! As soon as a node cannot get cancelled any more (because it was already cancelled or it has no
|
||||
|
||||
@@ -29,7 +29,7 @@ impl PollSemaphore {
|
||||
|
||||
/// Closes the semaphore.
|
||||
pub fn close(&self) {
|
||||
self.semaphore.close()
|
||||
self.semaphore.close();
|
||||
}
|
||||
|
||||
/// Obtain a clone of the inner semaphore.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::alloc::Layout;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::future::{self, Future};
|
||||
use std::mem::{self, ManuallyDrop};
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
@@ -61,7 +60,7 @@ impl<'a, T> ReusableBoxFuture<'a, T> {
|
||||
F: Future + Send + 'a,
|
||||
{
|
||||
// future::Pending<T> is a ZST so this never allocates.
|
||||
let boxed = mem::replace(&mut this.boxed, Box::pin(Pending(PhantomData)));
|
||||
let boxed = mem::replace(&mut this.boxed, Box::pin(future::pending()));
|
||||
reuse_pin_box(boxed, future, |boxed| this.boxed = Pin::from(boxed))
|
||||
}
|
||||
|
||||
@@ -156,16 +155,3 @@ impl<O, F: FnOnce() -> O> Drop for CallOnDrop<O, F> {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as `std::future::Pending<T>`; we can't use that type directly because on rustc
|
||||
/// versions <1.60 it didn't unconditionally implement `Send`.
|
||||
// FIXME: use `std::future::Pending<T>` once the MSRV is >=1.60
|
||||
struct Pending<T>(PhantomData<fn() -> T>);
|
||||
|
||||
impl<T> Future for Pending<T> {
|
||||
type Output = T;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::collections::hash_map::RandomState;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task::{AbortHandle, Id, JoinError, JoinSet, LocalSet};
|
||||
|
||||
@@ -626,6 +627,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator visiting all keys in this `JoinMap` in arbitrary order.
|
||||
///
|
||||
/// If a task has completed, but its output hasn't yet been consumed by a
|
||||
/// call to [`join_next`], this method will still return its key.
|
||||
///
|
||||
/// [`join_next`]: fn@Self::join_next
|
||||
pub fn keys(&self) -> JoinMapKeys<'_, K, V> {
|
||||
JoinMapKeys {
|
||||
iter: self.tasks_by_key.keys(),
|
||||
_value: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this `JoinMap` contains a task for the provided key.
|
||||
///
|
||||
/// If the task has completed, but its output hasn't yet been consumed by a
|
||||
@@ -859,3 +873,32 @@ impl<K: PartialEq> PartialEq for Key<K> {
|
||||
}
|
||||
|
||||
impl<K: Eq> Eq for Key<K> {}
|
||||
|
||||
/// An iterator over the keys of a [`JoinMap`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JoinMapKeys<'a, K, V> {
|
||||
iter: hashbrown::hash_map::Keys<'a, Key<K>, AbortHandle>,
|
||||
/// To make it easier to change JoinMap in the future, keep V as a generic
|
||||
/// parameter.
|
||||
_value: PhantomData<&'a V>,
|
||||
}
|
||||
|
||||
impl<'a, K, V> Iterator for JoinMapKeys<'a, K, V> {
|
||||
type Item = &'a K;
|
||||
|
||||
fn next(&mut self) -> Option<&'a K> {
|
||||
self.iter.next().map(|key| &key.key)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> ExactSizeIterator for JoinMapKeys<'a, K, V> {
|
||||
fn len(&self) -> usize {
|
||||
self.iter.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> std::iter::FusedIterator for JoinMapKeys<'a, K, V> {}
|
||||
|
||||
@@ -9,4 +9,7 @@ pub use spawn_pinned::LocalPoolHandle;
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt"))))]
|
||||
pub use join_map::JoinMap;
|
||||
pub use join_map::{JoinMap, JoinMapKeys};
|
||||
|
||||
pub mod task_tracker;
|
||||
pub use task_tracker::TaskTracker;
|
||||
|
||||
@@ -0,0 +1,719 @@
|
||||
//! Types related to the [`TaskTracker`] collection.
|
||||
//!
|
||||
//! See the documentation of [`TaskTracker`] for more information.
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::{futures::Notified, Notify};
|
||||
|
||||
#[cfg(feature = "rt")]
|
||||
use tokio::{
|
||||
runtime::Handle,
|
||||
task::{JoinHandle, LocalSet},
|
||||
};
|
||||
|
||||
/// A task tracker used for waiting until tasks exit.
|
||||
///
|
||||
/// This is usually used together with [`CancellationToken`] to implement [graceful shutdown]. The
|
||||
/// `CancellationToken` is used to signal to tasks that they should shut down, and the
|
||||
/// `TaskTracker` is used to wait for them to finish shutting down.
|
||||
///
|
||||
/// The `TaskTracker` will also keep track of a `closed` boolean. This is used to handle the case
|
||||
/// where the `TaskTracker` is empty, but we don't want to shut down yet. This means that the
|
||||
/// [`wait`] method will wait until *both* of the following happen at the same time:
|
||||
///
|
||||
/// * The `TaskTracker` must be closed using the [`close`] method.
|
||||
/// * The `TaskTracker` must be empty, that is, all tasks that it is tracking must have exited.
|
||||
///
|
||||
/// When a call to [`wait`] returns, it is guaranteed that all tracked tasks have exited and that
|
||||
/// the destructor of the future has finished running. However, there might be a short amount of
|
||||
/// time where [`JoinHandle::is_finished`] returns false.
|
||||
///
|
||||
/// # Comparison to `JoinSet`
|
||||
///
|
||||
/// The main Tokio crate has a similar collection known as [`JoinSet`]. The `JoinSet` type has a
|
||||
/// lot more features than `TaskTracker`, so `TaskTracker` should only be used when one of its
|
||||
/// unique features is required:
|
||||
///
|
||||
/// 1. When tasks exit, a `TaskTracker` will allow the task to immediately free its memory.
|
||||
/// 2. By not closing the `TaskTracker`, [`wait`] will be prevented from from returning even if
|
||||
/// the `TaskTracker` is empty.
|
||||
/// 3. A `TaskTracker` does not require mutable access to insert tasks.
|
||||
/// 4. A `TaskTracker` can be cloned to share it with many tasks.
|
||||
///
|
||||
/// The first point is the most important one. A [`JoinSet`] keeps track of the return value of
|
||||
/// every inserted task. This means that if the caller keeps inserting tasks and never calls
|
||||
/// [`join_next`], then their return values will keep building up and consuming memory, _even if_
|
||||
/// most of the tasks have already exited. This can cause the process to run out of memory. With a
|
||||
/// `TaskTracker`, this does not happen. Once tasks exit, they are immediately removed from the
|
||||
/// `TaskTracker`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// For more examples, please see the topic page on [graceful shutdown].
|
||||
///
|
||||
/// ## Spawn tasks and wait for them to exit
|
||||
///
|
||||
/// This is a simple example. For this case, [`JoinSet`] should probably be used instead.
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
///
|
||||
/// for i in 0..10 {
|
||||
/// tracker.spawn(async move {
|
||||
/// println!("Task {} is running!", i);
|
||||
/// });
|
||||
/// }
|
||||
/// // Once we spawned everything, we close the tracker.
|
||||
/// tracker.close();
|
||||
///
|
||||
/// // Wait for everything to finish.
|
||||
/// tracker.wait().await;
|
||||
///
|
||||
/// println!("This is printed after all of the tasks.");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Wait for tasks to exit
|
||||
///
|
||||
/// This example shows the intended use-case of `TaskTracker`. It is used together with
|
||||
/// [`CancellationToken`] to implement graceful shutdown.
|
||||
/// ```
|
||||
/// use tokio_util::sync::CancellationToken;
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
/// use tokio::time::{self, Duration};
|
||||
///
|
||||
/// async fn background_task(num: u64) {
|
||||
/// for i in 0..10 {
|
||||
/// time::sleep(Duration::from_millis(100*num)).await;
|
||||
/// println!("Background task {} in iteration {}.", num, i);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// # async fn _hidden() {}
|
||||
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
/// let token = CancellationToken::new();
|
||||
///
|
||||
/// for i in 0..10 {
|
||||
/// let token = token.clone();
|
||||
/// tracker.spawn(async move {
|
||||
/// // Use a `tokio::select!` to kill the background task if the token is
|
||||
/// // cancelled.
|
||||
/// tokio::select! {
|
||||
/// () = background_task(i) => {
|
||||
/// println!("Task {} exiting normally.", i);
|
||||
/// },
|
||||
/// () = token.cancelled() => {
|
||||
/// // Do some cleanup before we really exit.
|
||||
/// time::sleep(Duration::from_millis(50)).await;
|
||||
/// println!("Task {} finished cleanup.", i);
|
||||
/// },
|
||||
/// }
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// // Spawn a background task that will send the shutdown signal.
|
||||
/// {
|
||||
/// let tracker = tracker.clone();
|
||||
/// tokio::spawn(async move {
|
||||
/// // Normally you would use something like ctrl-c instead of
|
||||
/// // sleeping.
|
||||
/// time::sleep(Duration::from_secs(2)).await;
|
||||
/// tracker.close();
|
||||
/// token.cancel();
|
||||
/// });
|
||||
/// }
|
||||
///
|
||||
/// // Wait for all tasks to exit.
|
||||
/// tracker.wait().await;
|
||||
///
|
||||
/// println!("All tasks have exited now.");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`CancellationToken`]: crate::sync::CancellationToken
|
||||
/// [`JoinHandle::is_finished`]: tokio::task::JoinHandle::is_finished
|
||||
/// [`JoinSet`]: tokio::task::JoinSet
|
||||
/// [`close`]: Self::close
|
||||
/// [`join_next`]: tokio::task::JoinSet::join_next
|
||||
/// [`wait`]: Self::wait
|
||||
/// [graceful shutdown]: https://tokio.rs/tokio/topics/shutdown
|
||||
pub struct TaskTracker {
|
||||
inner: Arc<TaskTrackerInner>,
|
||||
}
|
||||
|
||||
/// Represents a task tracked by a [`TaskTracker`].
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct TaskTrackerToken {
|
||||
task_tracker: TaskTracker,
|
||||
}
|
||||
|
||||
struct TaskTrackerInner {
|
||||
/// Keeps track of the state.
|
||||
///
|
||||
/// The lowest bit is whether the task tracker is closed.
|
||||
///
|
||||
/// The rest of the bits count the number of tracked tasks.
|
||||
state: AtomicUsize,
|
||||
/// Used to notify when the last task exits.
|
||||
on_last_exit: Notify,
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A future that is tracked as a task by a [`TaskTracker`].
|
||||
///
|
||||
/// The associated [`TaskTracker`] cannot complete until this future is dropped.
|
||||
///
|
||||
/// This future is returned by [`TaskTracker::track_future`].
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct TrackedFuture<F> {
|
||||
#[pin]
|
||||
future: F,
|
||||
token: TaskTrackerToken,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// A future that completes when the [`TaskTracker`] is empty and closed.
|
||||
///
|
||||
/// This future is returned by [`TaskTracker::wait`].
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct TaskTrackerWaitFuture<'a> {
|
||||
#[pin]
|
||||
future: Notified<'a>,
|
||||
inner: Option<&'a TaskTrackerInner>,
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTrackerInner {
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
state: AtomicUsize::new(0),
|
||||
on_last_exit: Notify::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_closed_and_empty(&self) -> bool {
|
||||
// If empty and closed bit set, then we are done.
|
||||
//
|
||||
// The acquire load will synchronize with the release store of any previous call to
|
||||
// `set_closed` and `drop_task`.
|
||||
self.state.load(Ordering::Acquire) == 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_closed(&self) -> bool {
|
||||
// The AcqRel ordering makes the closed bit behave like a `Mutex<bool>` for synchronization
|
||||
// purposes. We do this because it makes the return value of `TaskTracker::{close,reopen}`
|
||||
// more meaningful for the user. Without these orderings, this assert could fail:
|
||||
// ```
|
||||
// // thread 1
|
||||
// some_other_atomic.store(true, Relaxed);
|
||||
// tracker.close();
|
||||
//
|
||||
// // thread 2
|
||||
// if tracker.reopen() {
|
||||
// assert!(some_other_atomic.load(Relaxed));
|
||||
// }
|
||||
// ```
|
||||
// However, with the AcqRel ordering, we establish a happens-before relationship from the
|
||||
// call to `close` and the later call to `reopen` that returned true.
|
||||
let state = self.state.fetch_or(1, Ordering::AcqRel);
|
||||
|
||||
// If there are no tasks, and if it was not already closed:
|
||||
if state == 0 {
|
||||
self.notify_now();
|
||||
}
|
||||
|
||||
(state & 1) == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_open(&self) -> bool {
|
||||
// See `set_closed` regarding the AcqRel ordering.
|
||||
let state = self.state.fetch_and(!1, Ordering::AcqRel);
|
||||
(state & 1) == 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_task(&self) {
|
||||
self.state.fetch_add(2, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn drop_task(&self) {
|
||||
let state = self.state.fetch_sub(2, Ordering::Release);
|
||||
|
||||
// If this was the last task and we are closed:
|
||||
if state == 3 {
|
||||
self.notify_now();
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn notify_now(&self) {
|
||||
// Insert an acquire fence. This matters for `drop_task` but doesn't matter for
|
||||
// `set_closed` since it already uses AcqRel.
|
||||
//
|
||||
// This synchronizes with the release store of any other call to `drop_task`, and with the
|
||||
// release store in the call to `set_closed`. That ensures that everything that happened
|
||||
// before those other calls to `drop_task` or `set_closed` will be visible after this load,
|
||||
// and those things will also be visible to anything woken by the call to `notify_waiters`.
|
||||
self.state.load(Ordering::Acquire);
|
||||
|
||||
self.on_last_exit.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTracker {
|
||||
/// Creates a new `TaskTracker`.
|
||||
///
|
||||
/// The `TaskTracker` will start out as open.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(TaskTrackerInner::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until this `TaskTracker` is both closed and empty.
|
||||
///
|
||||
/// If the `TaskTracker` is already closed and empty when this method is called, then it
|
||||
/// returns immediately.
|
||||
///
|
||||
/// The `wait` future is resistant against [ABA problems][aba]. That is, if the `TaskTracker`
|
||||
/// becomes both closed and empty for a short amount of time, then it is guarantee that all
|
||||
/// `wait` futures that were created before the short time interval will trigger, even if they
|
||||
/// are not polled during that short time interval.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe.
|
||||
///
|
||||
/// However, the resistance against [ABA problems][aba] is lost when using `wait` as the
|
||||
/// condition in a `tokio::select!` loop.
|
||||
///
|
||||
/// [aba]: https://en.wikipedia.org/wiki/ABA_problem
|
||||
#[inline]
|
||||
pub fn wait(&self) -> TaskTrackerWaitFuture<'_> {
|
||||
TaskTrackerWaitFuture {
|
||||
future: self.inner.on_last_exit.notified(),
|
||||
inner: if self.inner.is_closed_and_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&self.inner)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Close this `TaskTracker`.
|
||||
///
|
||||
/// This allows [`wait`] futures to complete. It does not prevent you from spawning new tasks.
|
||||
///
|
||||
/// Returns `true` if this closed the `TaskTracker`, or `false` if it was already closed.
|
||||
///
|
||||
/// [`wait`]: Self::wait
|
||||
#[inline]
|
||||
pub fn close(&self) -> bool {
|
||||
self.inner.set_closed()
|
||||
}
|
||||
|
||||
/// Reopen this `TaskTracker`.
|
||||
///
|
||||
/// This prevents [`wait`] futures from completing even if the `TaskTracker` is empty.
|
||||
///
|
||||
/// Returns `true` if this reopened the `TaskTracker`, or `false` if it was already open.
|
||||
///
|
||||
/// [`wait`]: Self::wait
|
||||
#[inline]
|
||||
pub fn reopen(&self) -> bool {
|
||||
self.inner.set_open()
|
||||
}
|
||||
|
||||
/// Returns `true` if this `TaskTracker` is [closed](Self::close).
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_closed(&self) -> bool {
|
||||
(self.inner.state.load(Ordering::Acquire) & 1) != 0
|
||||
}
|
||||
|
||||
/// Returns the number of tasks tracked by this `TaskTracker`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.state.load(Ordering::Acquire) >> 1
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no tasks in this `TaskTracker`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.state.load(Ordering::Acquire) <= 1
|
||||
}
|
||||
|
||||
/// Spawn the provided future on the current Tokio runtime, and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `tokio::spawn(tracker.track_future(task))`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn<F>(&self, task: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
tokio::task::spawn(self.track_future(task))
|
||||
}
|
||||
|
||||
/// Spawn the provided future on the provided Tokio runtime, and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `handle.spawn(tracker.track_future(task))`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_on<F>(&self, task: F, handle: &Handle) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
handle.spawn(self.track_future(task))
|
||||
}
|
||||
|
||||
/// Spawn the provided future on the current [`LocalSet`], and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `tokio::task::spawn_local(tracker.track_future(task))`.
|
||||
///
|
||||
/// [`LocalSet`]: tokio::task::LocalSet
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_local<F>(&self, task: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + 'static,
|
||||
F::Output: 'static,
|
||||
{
|
||||
tokio::task::spawn_local(self.track_future(task))
|
||||
}
|
||||
|
||||
/// Spawn the provided future on the provided [`LocalSet`], and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `local_set.spawn_local(tracker.track_future(task))`.
|
||||
///
|
||||
/// [`LocalSet`]: tokio::task::LocalSet
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_local_on<F>(&self, task: F, local_set: &LocalSet) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + 'static,
|
||||
F::Output: 'static,
|
||||
{
|
||||
local_set.spawn_local(self.track_future(task))
|
||||
}
|
||||
|
||||
/// Spawn the provided blocking task on the current Tokio runtime, and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `tokio::task::spawn_blocking(tracker.track_future(task))`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_blocking<F, T>(&self, task: F) -> JoinHandle<T>
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
F: Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let token = self.token();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let res = task();
|
||||
drop(token);
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn the provided blocking task on the provided Tokio runtime, and track it in this `TaskTracker`.
|
||||
///
|
||||
/// This is equivalent to `handle.spawn_blocking(tracker.track_future(task))`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
pub fn spawn_blocking_on<F, T>(&self, task: F, handle: &Handle) -> JoinHandle<T>
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
F: Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let token = self.token();
|
||||
handle.spawn_blocking(move || {
|
||||
let res = task();
|
||||
drop(token);
|
||||
res
|
||||
})
|
||||
}
|
||||
|
||||
/// Track the provided future.
|
||||
///
|
||||
/// The returned [`TrackedFuture`] will count as a task tracked by this collection, and will
|
||||
/// prevent calls to [`wait`] from returning until the task is dropped.
|
||||
///
|
||||
/// The task is removed from the collection when it is dropped, not when [`poll`] returns
|
||||
/// [`Poll::Ready`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Track a future spawned with [`tokio::spawn`].
|
||||
///
|
||||
/// ```
|
||||
/// # async fn my_async_fn() {}
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
///
|
||||
/// tokio::spawn(tracker.track_future(my_async_fn()));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Track a future spawned on a [`JoinSet`].
|
||||
/// ```
|
||||
/// # async fn my_async_fn() {}
|
||||
/// use tokio::task::JoinSet;
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
/// let mut join_set = JoinSet::new();
|
||||
///
|
||||
/// join_set.spawn(tracker.track_future(my_async_fn()));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`JoinSet`]: tokio::task::JoinSet
|
||||
/// [`Poll::Pending`]: std::task::Poll::Pending
|
||||
/// [`poll`]: std::future::Future::poll
|
||||
/// [`wait`]: Self::wait
|
||||
#[inline]
|
||||
pub fn track_future<F: Future>(&self, future: F) -> TrackedFuture<F> {
|
||||
TrackedFuture {
|
||||
future,
|
||||
token: self.token(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`TaskTrackerToken`] representing a task tracked by this `TaskTracker`.
|
||||
///
|
||||
/// This token is a lower-level utility than the spawn methods. Each token is considered to
|
||||
/// correspond to a task. As long as the token exists, the `TaskTracker` cannot complete.
|
||||
/// Furthermore, the count returned by the [`len`] method will include the tokens in the count.
|
||||
///
|
||||
/// Dropping the token indicates to the `TaskTracker` that the task has exited.
|
||||
///
|
||||
/// [`len`]: TaskTracker::len
|
||||
#[inline]
|
||||
pub fn token(&self) -> TaskTrackerToken {
|
||||
self.inner.add_task();
|
||||
TaskTrackerToken {
|
||||
task_tracker: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if both task trackers correspond to the same set of tasks.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// let tracker_1 = TaskTracker::new();
|
||||
/// let tracker_2 = TaskTracker::new();
|
||||
/// let tracker_1_clone = tracker_1.clone();
|
||||
///
|
||||
/// assert!(TaskTracker::ptr_eq(&tracker_1, &tracker_1_clone));
|
||||
/// assert!(!TaskTracker::ptr_eq(&tracker_1, &tracker_2));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn ptr_eq(left: &TaskTracker, right: &TaskTracker) -> bool {
|
||||
Arc::ptr_eq(&left.inner, &right.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TaskTracker {
|
||||
/// Creates a new `TaskTracker`.
|
||||
///
|
||||
/// The `TaskTracker` will start out as open.
|
||||
#[inline]
|
||||
fn default() -> TaskTracker {
|
||||
TaskTracker::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TaskTracker {
|
||||
/// Returns a new `TaskTracker` that tracks the same set of tasks.
|
||||
///
|
||||
/// Since the new `TaskTracker` shares the same set of tasks, changes to one set are visible in
|
||||
/// all other clones.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::task::TaskTracker;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// # async fn _hidden() {}
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// let tracker = TaskTracker::new();
|
||||
/// let cloned = tracker.clone();
|
||||
///
|
||||
/// // Spawns on `tracker` are visible in `cloned`.
|
||||
/// tracker.spawn(std::future::pending::<()>());
|
||||
/// assert_eq!(cloned.len(), 1);
|
||||
///
|
||||
/// // Spawns on `cloned` are visible in `tracker`.
|
||||
/// cloned.spawn(std::future::pending::<()>());
|
||||
/// assert_eq!(tracker.len(), 2);
|
||||
///
|
||||
/// // Calling `close` is visible to `cloned`.
|
||||
/// tracker.close();
|
||||
/// assert!(cloned.is_closed());
|
||||
///
|
||||
/// // Calling `reopen` is visible to `tracker`.
|
||||
/// cloned.reopen();
|
||||
/// assert!(!tracker.is_closed());
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
fn clone(&self) -> TaskTracker {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_inner(inner: &TaskTrackerInner, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let state = inner.state.load(Ordering::Acquire);
|
||||
let is_closed = (state & 1) != 0;
|
||||
let len = state >> 1;
|
||||
|
||||
f.debug_struct("TaskTracker")
|
||||
.field("len", &len)
|
||||
.field("is_closed", &is_closed)
|
||||
.field("inner", &(inner as *const TaskTrackerInner))
|
||||
.finish()
|
||||
}
|
||||
|
||||
impl fmt::Debug for TaskTracker {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
debug_inner(&self.inner, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskTrackerToken {
|
||||
/// Returns the [`TaskTracker`] that this token is associated with.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn task_tracker(&self) -> &TaskTracker {
|
||||
&self.task_tracker
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TaskTrackerToken {
|
||||
/// Returns a new `TaskTrackerToken` associated with the same [`TaskTracker`].
|
||||
///
|
||||
/// This is equivalent to `token.task_tracker().token()`.
|
||||
#[inline]
|
||||
fn clone(&self) -> TaskTrackerToken {
|
||||
self.task_tracker.token()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TaskTrackerToken {
|
||||
/// Dropping the token indicates to the [`TaskTracker`] that the task has exited.
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
self.task_tracker.inner.drop_task();
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for TrackedFuture<F> {
|
||||
type Output = F::Output;
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
|
||||
self.project().future.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: fmt::Debug> fmt::Debug for TrackedFuture<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TrackedFuture")
|
||||
.field("future", &self.future)
|
||||
.field("task_tracker", self.token.task_tracker())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Future for TaskTrackerWaitFuture<'a> {
|
||||
type Output = ();
|
||||
|
||||
#[inline]
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||
let me = self.project();
|
||||
|
||||
let inner = match me.inner.as_ref() {
|
||||
None => return Poll::Ready(()),
|
||||
Some(inner) => inner,
|
||||
};
|
||||
|
||||
let ready = inner.is_closed_and_empty() || me.future.poll(cx).is_ready();
|
||||
if ready {
|
||||
*me.inner = None;
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for TaskTrackerWaitFuture<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
struct Helper<'a>(&'a TaskTrackerInner);
|
||||
|
||||
impl fmt::Debug for Helper<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
debug_inner(self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
f.debug_struct("TaskTrackerWaitFuture")
|
||||
.field("future", &self.future)
|
||||
.field("task_tracker", &self.inner.map(Helper))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,18 @@ async fn test_async_write_to_sync() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_into_inner() -> Result<(), Box<dyn Error>> {
|
||||
let mut buf = Vec::new();
|
||||
SyncIoBridge::new(tokio::io::empty())
|
||||
.into_inner()
|
||||
.read_to_end(&mut buf)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(buf.len(), 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_shutdown() -> Result<(), Box<dyn Error>> {
|
||||
let (s1, mut s2) = tokio::io::duplex(1024);
|
||||
|
||||
@@ -12,7 +12,6 @@ use futures::{pin_mut, Sink, Stream};
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll::*;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
macro_rules! mock {
|
||||
@@ -39,10 +38,10 @@ macro_rules! assert_next_eq {
|
||||
macro_rules! assert_next_pending {
|
||||
($io:ident) => {{
|
||||
task::spawn(()).enter(|cx, _| match $io.as_mut().poll_next(cx) {
|
||||
Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Ready(Some(Err(e))) => panic!("error = {:?}", e),
|
||||
Ready(None) => panic!("done"),
|
||||
Pending => {}
|
||||
Poll::Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Poll::Ready(Some(Err(e))) => panic!("error = {:?}", e),
|
||||
Poll::Ready(None) => panic!("done"),
|
||||
Poll::Pending => {}
|
||||
});
|
||||
}};
|
||||
}
|
||||
@@ -50,10 +49,10 @@ macro_rules! assert_next_pending {
|
||||
macro_rules! assert_next_err {
|
||||
($io:ident) => {{
|
||||
task::spawn(()).enter(|cx, _| match $io.as_mut().poll_next(cx) {
|
||||
Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Ready(Some(Err(_))) => {}
|
||||
Ready(None) => panic!("done"),
|
||||
Pending => panic!("pending"),
|
||||
Poll::Ready(Some(Ok(v))) => panic!("value = {:?}", v),
|
||||
Poll::Ready(Some(Err(_))) => {}
|
||||
Poll::Ready(None) => panic!("done"),
|
||||
Poll::Pending => panic!("pending"),
|
||||
});
|
||||
}};
|
||||
}
|
||||
@@ -186,11 +185,11 @@ fn read_single_frame_multi_packet_wait() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x09abc"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"defghi"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
@@ -208,15 +207,15 @@ fn read_multi_frame_multi_packet_wait() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x09abc"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"defghi"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x00\x00\x0312"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"3\x00\x00\x00\x0bhello world"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
@@ -250,9 +249,9 @@ fn read_incomplete_head() {
|
||||
fn read_incomplete_head_multi() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
@@ -268,9 +267,9 @@ fn read_incomplete_payload() {
|
||||
let io = FramedRead::new(
|
||||
mock! {
|
||||
data(b"\x00\x00\x00\x09ab"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"cd"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
},
|
||||
LengthDelimitedCodec::new(),
|
||||
);
|
||||
@@ -310,7 +309,7 @@ fn read_update_max_frame_len_at_rest() {
|
||||
fn read_update_max_frame_len_in_flight() {
|
||||
let io = length_delimited::Builder::new().new_read(mock! {
|
||||
data(b"\x00\x00\x00\x09abcd"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"efghi"),
|
||||
data(b"\x00\x00\x00\x09abcdefghi"),
|
||||
});
|
||||
@@ -533,9 +532,9 @@ fn write_single_multi_frame_multi_packet() {
|
||||
fn write_single_frame_would_block() {
|
||||
let io = FramedWrite::new(
|
||||
mock! {
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x00"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"\x00\x09"),
|
||||
data(b"abcdefghi"),
|
||||
flush(),
|
||||
@@ -640,7 +639,7 @@ fn write_update_max_frame_len_in_flight() {
|
||||
let io = length_delimited::Builder::new().new_write(mock! {
|
||||
data(b"\x00\x00\x00\x06"),
|
||||
data(b"ab"),
|
||||
Pending,
|
||||
Poll::Pending,
|
||||
data(b"cdef"),
|
||||
flush(),
|
||||
});
|
||||
@@ -701,8 +700,6 @@ enum Op {
|
||||
Flush,
|
||||
}
|
||||
|
||||
use self::Op::*;
|
||||
|
||||
impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
@@ -710,15 +707,15 @@ impl AsyncRead for Mock {
|
||||
dst: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Data(data)))) => {
|
||||
Some(Poll::Ready(Ok(Op::Data(data)))) => {
|
||||
debug_assert!(dst.remaining() >= data.len());
|
||||
dst.put_slice(&data);
|
||||
Ready(Ok(()))
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(())),
|
||||
Some(Poll::Ready(Ok(_))) => panic!(),
|
||||
Some(Poll::Ready(Err(e))) => Poll::Ready(Err(e)),
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,31 +727,31 @@ impl AsyncWrite for Mock {
|
||||
src: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Data(data)))) => {
|
||||
Some(Poll::Ready(Ok(Op::Data(data)))) => {
|
||||
let len = data.len();
|
||||
assert!(src.len() >= len, "expect={:?}; actual={:?}", data, src);
|
||||
assert_eq!(&data[..], &src[..len]);
|
||||
Ready(Ok(len))
|
||||
Poll::Ready(Ok(len))
|
||||
}
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(0)),
|
||||
Some(Poll::Ready(Ok(_))) => panic!(),
|
||||
Some(Poll::Ready(Err(e))) => Poll::Ready(Err(e)),
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(Ok(0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Flush))) => Ready(Ok(())),
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(())),
|
||||
Some(Poll::Ready(Ok(Op::Flush))) => Poll::Ready(Ok(())),
|
||||
Some(Poll::Ready(Ok(_))) => panic!(),
|
||||
Some(Poll::Ready(Err(e))) => Poll::Ready(Err(e)),
|
||||
Some(Poll::Pending) => Poll::Pending,
|
||||
None => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Ready(Ok(()))
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,9 +768,9 @@ impl From<Vec<u8>> for Op {
|
||||
}
|
||||
|
||||
fn data(bytes: &[u8]) -> Poll<io::Result<Op>> {
|
||||
Ready(Ok(bytes.into()))
|
||||
Poll::Ready(Ok(bytes.into()))
|
||||
}
|
||||
|
||||
fn flush() -> Poll<io::Result<Op>> {
|
||||
Ready(Ok(Flush))
|
||||
Poll::Ready(Ok(Op::Flush))
|
||||
}
|
||||
|
||||
@@ -109,6 +109,30 @@ async fn alternating() {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_keys() {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let mut map = JoinMap::new();
|
||||
|
||||
assert_eq!(map.len(), 0);
|
||||
map.spawn(1, async {});
|
||||
assert_eq!(map.len(), 1);
|
||||
map.spawn(2, async {});
|
||||
assert_eq!(map.len(), 2);
|
||||
|
||||
let keys = map.keys().collect::<HashSet<&u32>>();
|
||||
assert!(keys.contains(&1));
|
||||
assert!(keys.contains(&2));
|
||||
|
||||
let _ = map.join_next().await.unwrap();
|
||||
let _ = map.join_next().await.unwrap();
|
||||
|
||||
assert_eq!(map.len(), 0);
|
||||
let keys = map.keys().collect::<HashSet<&u32>>();
|
||||
assert!(keys.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn abort_by_key() {
|
||||
let mut map = JoinMap::new();
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio_test::{assert_pending, assert_ready, task};
|
||||
use tokio_util::task::TaskTracker;
|
||||
|
||||
#[test]
|
||||
fn open_close() {
|
||||
let tracker = TaskTracker::new();
|
||||
assert!(!tracker.is_closed());
|
||||
assert!(tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 0);
|
||||
|
||||
tracker.close();
|
||||
assert!(tracker.is_closed());
|
||||
assert!(tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 0);
|
||||
|
||||
tracker.reopen();
|
||||
assert!(!tracker.is_closed());
|
||||
tracker.reopen();
|
||||
assert!(!tracker.is_closed());
|
||||
|
||||
assert!(tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 0);
|
||||
|
||||
tracker.close();
|
||||
assert!(tracker.is_closed());
|
||||
tracker.close();
|
||||
assert!(tracker.is_closed());
|
||||
|
||||
assert!(tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_len() {
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
let mut tokens = Vec::new();
|
||||
for i in 0..10 {
|
||||
assert_eq!(tracker.len(), i);
|
||||
tokens.push(tracker.token());
|
||||
}
|
||||
|
||||
assert!(!tracker.is_empty());
|
||||
assert_eq!(tracker.len(), 10);
|
||||
|
||||
for (i, token) in tokens.into_iter().enumerate() {
|
||||
drop(token);
|
||||
assert_eq!(tracker.len(), 9 - i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_immediately() {
|
||||
let tracker = TaskTracker::new();
|
||||
tracker.close();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_immediately_on_reopen() {
|
||||
let tracker = TaskTracker::new();
|
||||
tracker.close();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
tracker.reopen();
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_on_close() {
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
|
||||
assert_pending!(wait.poll());
|
||||
tracker.close();
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_on_close_reopen() {
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
|
||||
assert_pending!(wait.poll());
|
||||
tracker.close();
|
||||
tracker.reopen();
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_on_last_task() {
|
||||
let tracker = TaskTracker::new();
|
||||
tracker.close();
|
||||
let token = tracker.token();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
assert_pending!(wait.poll());
|
||||
drop(token);
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_on_last_task_respawn() {
|
||||
let tracker = TaskTracker::new();
|
||||
tracker.close();
|
||||
let token = tracker.token();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
assert_pending!(wait.poll());
|
||||
drop(token);
|
||||
let token2 = tracker.token();
|
||||
assert_ready!(wait.poll());
|
||||
drop(token2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_notify_on_respawn_if_open() {
|
||||
let tracker = TaskTracker::new();
|
||||
let token = tracker.token();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
assert_pending!(wait.poll());
|
||||
drop(token);
|
||||
let token2 = tracker.token();
|
||||
assert_pending!(wait.poll());
|
||||
drop(token2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_during_exit() {
|
||||
const ITERS: usize = 5;
|
||||
|
||||
for close_spot in 0..=ITERS {
|
||||
let tracker = TaskTracker::new();
|
||||
let tokens: Vec<_> = (0..ITERS).map(|_| tracker.token()).collect();
|
||||
|
||||
let mut wait = task::spawn(tracker.wait());
|
||||
|
||||
for (i, token) in tokens.into_iter().enumerate() {
|
||||
assert_pending!(wait.poll());
|
||||
if i == close_spot {
|
||||
tracker.close();
|
||||
assert_pending!(wait.poll());
|
||||
}
|
||||
drop(token);
|
||||
}
|
||||
|
||||
if close_spot == ITERS {
|
||||
assert_pending!(wait.poll());
|
||||
tracker.close();
|
||||
}
|
||||
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_many() {
|
||||
let tracker = TaskTracker::new();
|
||||
|
||||
let mut waits: Vec<_> = (0..10).map(|_| task::spawn(tracker.wait())).collect();
|
||||
|
||||
for wait in &mut waits {
|
||||
assert_pending!(wait.poll());
|
||||
}
|
||||
|
||||
tracker.close();
|
||||
|
||||
for wait in &mut waits {
|
||||
assert_ready!(wait.poll());
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,10 @@ use futures::sink::SinkExt;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg_attr(any(target_os = "macos", target_os = "ios"), allow(unused_assignments))]
|
||||
#[cfg_attr(
|
||||
any(target_os = "macos", target_os = "ios", target_os = "tvos"),
|
||||
allow(unused_assignments)
|
||||
)]
|
||||
#[tokio::test]
|
||||
async fn send_framed_byte_codec() -> std::io::Result<()> {
|
||||
let mut a_soc = UdpSocket::bind("127.0.0.1:0").await?;
|
||||
@@ -41,7 +44,7 @@ async fn send_framed_byte_codec() -> std::io::Result<()> {
|
||||
b_soc = b.into_inner();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
||||
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos")))]
|
||||
// test sending & receiving an empty message
|
||||
{
|
||||
let mut a = UdpFramed::new(a_soc, ByteCodec);
|
||||
|
||||
+104
-1
@@ -1,8 +1,91 @@
|
||||
[comment]: <> (Include tokio-macros changes in next release.)
|
||||
|
||||
# 1.33.0 (October 9, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: mark `Interest::add` with `#[must_use]` ([#6037])
|
||||
- runtime: fix cache line size for RISC-V ([#5994])
|
||||
- sync: prevent lock poisoning in `watch::Receiver::wait_for` ([#6021])
|
||||
- task: fix `spawn_local` source location ([#5984])
|
||||
|
||||
### Changed
|
||||
|
||||
- sync: use Acquire/Release orderings instead of SeqCst in `watch` ([#6018])
|
||||
|
||||
### Added
|
||||
|
||||
- fs: add vectored writes to `tokio::fs::File` ([#5958])
|
||||
- io: add `Interest::remove` method ([#5906])
|
||||
- io: add vectored writes to `DuplexStream` ([#5985])
|
||||
- net: add Apple tvOS support ([#6045])
|
||||
- sync: add `?Sized` bound to `{MutexGuard,OwnedMutexGuard}::map` ([#5997])
|
||||
- sync: add `watch::Receiver::mark_unseen` ([#5962], [#6014], [#6017])
|
||||
- sync: add `watch::Sender::new` ([#5998])
|
||||
- sync: add const fn `OnceCell::from_value` ([#5903])
|
||||
|
||||
### Removed
|
||||
|
||||
- remove unused `stats` feature ([#5952])
|
||||
|
||||
### Documented
|
||||
|
||||
- add missing backticks in code examples ([#5938], [#6056])
|
||||
- fix typos ([#5988], [#6030])
|
||||
- process: document that `Child::wait` is cancel safe ([#5977])
|
||||
- sync: add examples for `Semaphore` ([#5939], [#5956], [#5978], [#6031], [#6032], [#6050])
|
||||
- sync: document that `broadcast` capacity is a lower bound ([#6042])
|
||||
- sync: document that `const_new` is not instrumented ([#6002])
|
||||
- sync: improve cancel-safety documentation for `mpsc::Sender::send` ([#5947])
|
||||
- sync: improve docs for `watch` channel ([#5954])
|
||||
- taskdump: render taskdump documentation on docs.rs ([#5972])
|
||||
|
||||
### Unstable
|
||||
|
||||
- taskdump: fix potential deadlock ([#6036])
|
||||
|
||||
[#5903]: https://github.com/tokio-rs/tokio/pull/5903
|
||||
[#5906]: https://github.com/tokio-rs/tokio/pull/5906
|
||||
[#5938]: https://github.com/tokio-rs/tokio/pull/5938
|
||||
[#5939]: https://github.com/tokio-rs/tokio/pull/5939
|
||||
[#5947]: https://github.com/tokio-rs/tokio/pull/5947
|
||||
[#5952]: https://github.com/tokio-rs/tokio/pull/5952
|
||||
[#5954]: https://github.com/tokio-rs/tokio/pull/5954
|
||||
[#5956]: https://github.com/tokio-rs/tokio/pull/5956
|
||||
[#5958]: https://github.com/tokio-rs/tokio/pull/5958
|
||||
[#5960]: https://github.com/tokio-rs/tokio/pull/5960
|
||||
[#5962]: https://github.com/tokio-rs/tokio/pull/5962
|
||||
[#5971]: https://github.com/tokio-rs/tokio/pull/5971
|
||||
[#5972]: https://github.com/tokio-rs/tokio/pull/5972
|
||||
[#5977]: https://github.com/tokio-rs/tokio/pull/5977
|
||||
[#5978]: https://github.com/tokio-rs/tokio/pull/5978
|
||||
[#5984]: https://github.com/tokio-rs/tokio/pull/5984
|
||||
[#5985]: https://github.com/tokio-rs/tokio/pull/5985
|
||||
[#5988]: https://github.com/tokio-rs/tokio/pull/5988
|
||||
[#5994]: https://github.com/tokio-rs/tokio/pull/5994
|
||||
[#5997]: https://github.com/tokio-rs/tokio/pull/5997
|
||||
[#5998]: https://github.com/tokio-rs/tokio/pull/5998
|
||||
[#6002]: https://github.com/tokio-rs/tokio/pull/6002
|
||||
[#6014]: https://github.com/tokio-rs/tokio/pull/6014
|
||||
[#6017]: https://github.com/tokio-rs/tokio/pull/6017
|
||||
[#6018]: https://github.com/tokio-rs/tokio/pull/6018
|
||||
[#6021]: https://github.com/tokio-rs/tokio/pull/6021
|
||||
[#6030]: https://github.com/tokio-rs/tokio/pull/6030
|
||||
[#6031]: https://github.com/tokio-rs/tokio/pull/6031
|
||||
[#6032]: https://github.com/tokio-rs/tokio/pull/6032
|
||||
[#6036]: https://github.com/tokio-rs/tokio/pull/6036
|
||||
[#6037]: https://github.com/tokio-rs/tokio/pull/6037
|
||||
[#6042]: https://github.com/tokio-rs/tokio/pull/6042
|
||||
[#6045]: https://github.com/tokio-rs/tokio/pull/6045
|
||||
[#6050]: https://github.com/tokio-rs/tokio/pull/6050
|
||||
[#6056]: https://github.com/tokio-rs/tokio/pull/6056
|
||||
[#6058]: https://github.com/tokio-rs/tokio/pull/6058
|
||||
|
||||
# 1.32.0 (August 16, 2023)
|
||||
|
||||
### Fixed
|
||||
|
||||
- sync: fix potential quadradic behavior in `broadcast::Receiver` ([#5925])
|
||||
- sync: fix potential quadratic behavior in `broadcast::Receiver` ([#5925])
|
||||
|
||||
### Added
|
||||
|
||||
@@ -360,6 +443,16 @@ This release bumps the MSRV of Tokio to 1.56. ([#5559])
|
||||
[#5513]: https://github.com/tokio-rs/tokio/pull/5513
|
||||
[#5517]: https://github.com/tokio-rs/tokio/pull/5517
|
||||
|
||||
# 1.25.2 (September 22, 2023)
|
||||
|
||||
Forward ports 1.20.6 changes.
|
||||
|
||||
### Changed
|
||||
|
||||
- io: use `memchr` from `libc` ([#5960])
|
||||
|
||||
[#5960]: https://github.com/tokio-rs/tokio/pull/5960
|
||||
|
||||
# 1.25.1 (May 28, 2023)
|
||||
|
||||
Forward ports 1.18.6 changes.
|
||||
@@ -706,6 +799,16 @@ wasm32-wasi target is given unstable support for the `net` feature.
|
||||
[#4956]: https://github.com/tokio-rs/tokio/pull/4956
|
||||
[#4959]: https://github.com/tokio-rs/tokio/pull/4959
|
||||
|
||||
# 1.20.6 (September 22, 2023)
|
||||
|
||||
This is a backport of a change from 1.27.0.
|
||||
|
||||
### Changed
|
||||
|
||||
- io: use `memchr` from `libc` ([#5960])
|
||||
|
||||
[#5960]: https://github.com/tokio-rs/tokio/pull/5960
|
||||
|
||||
# 1.20.5 (May 28, 2023)
|
||||
|
||||
Forward ports 1.18.6 changes.
|
||||
|
||||
+5
-10
@@ -6,7 +6,7 @@ name = "tokio"
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.x.y" git tag.
|
||||
version = "1.32.0"
|
||||
version = "1.33.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
@@ -88,11 +88,6 @@ sync = []
|
||||
test-util = ["rt", "sync", "time"]
|
||||
time = []
|
||||
|
||||
# Technically, removing this is a breaking change even though it only ever did
|
||||
# anything with the unstable flag on. It is probably safe to get rid of it after
|
||||
# a few releases.
|
||||
stats = []
|
||||
|
||||
[dependencies]
|
||||
tokio-macros = { version = "~2.1.0", path = "../tokio-macros", optional = true }
|
||||
|
||||
@@ -162,10 +157,10 @@ loom = { version = "0.7", features = ["futures", "checkpoint"] }
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# enable unstable features in the documentation
|
||||
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"]
|
||||
# it's necessary to _also_ pass `--cfg tokio_unstable` to rustc, or else
|
||||
# dependencies will not be enabled, and the docs build will fail.
|
||||
rustc-args = ["--cfg", "tokio_unstable"]
|
||||
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable", "--cfg", "tokio_taskdump"]
|
||||
# it's necessary to _also_ pass `--cfg tokio_unstable` and `--cfg tokio_taskdump`
|
||||
# to rustc, or else dependencies will not be enabled, and the docs build will fail.
|
||||
rustc-args = ["--cfg", "tokio_unstable", "--cfg", "tokio_taskdump"]
|
||||
|
||||
[package.metadata.playground]
|
||||
features = ["full", "test-util"]
|
||||
|
||||
+10
-10
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
tokio = { version = "1.33.0", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -132,6 +132,8 @@ project.
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`axum`]: A web application framework that focuses on ergonomics and modularity.
|
||||
|
||||
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
|
||||
|
||||
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
|
||||
@@ -142,21 +144,18 @@ several other libraries, including:
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers `tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
* [`loom`]: A testing tool for concurrent Rust code
|
||||
* [`loom`]: A testing tool for concurrent Rust code.
|
||||
|
||||
[`axum`]: https://github.com/tokio-rs/axum
|
||||
[`warp`]: https://github.com/seanmonstar/warp
|
||||
[`hyper`]: https://github.com/hyperium/hyper
|
||||
[`tonic`]: https://github.com/hyperium/tonic
|
||||
[`tower`]: https://github.com/tower-rs/tower
|
||||
[`loom`]: https://github.com/tokio-rs/loom
|
||||
[`rdbc`]: https://github.com/tokio-rs/rdbc
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
@@ -216,18 +215,18 @@ 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.20.x` - LTS release until September 2023. (MSRV 1.49)
|
||||
* `1.25.x` - LTS release until March 2024. (MSRV 1.49)
|
||||
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
|
||||
|
||||
Each LTS release will continue to receive backported fixes for at least a year.
|
||||
If you wish to use a fixed minor release in your project, we recommend that you
|
||||
use an LTS release.
|
||||
|
||||
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.18.x` patch release, you
|
||||
example, to specify that you wish to use the newest `1.25.x` patch release, you
|
||||
can use the following dependency specification:
|
||||
```text
|
||||
tokio = { version = "~1.18", features = [...] }
|
||||
tokio = { version = "~1.25", features = [...] }
|
||||
```
|
||||
|
||||
### Previous LTS releases
|
||||
@@ -235,6 +234,7 @@ tokio = { version = "~1.18", features = [...] }
|
||||
* `1.8.x` - LTS release until February 2022.
|
||||
* `1.14.x` - LTS release until June 2022.
|
||||
* `1.18.x` - LTS release until June 2023.
|
||||
* `1.20.x` - LTS release until September 2023.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ use std::path::{Path, PathBuf};
|
||||
/// Returns the canonical, absolute form of a path with all intermediate
|
||||
/// components normalized and symbolic links resolved.
|
||||
///
|
||||
/// This is an async version of [`std::fs::canonicalize`][std]
|
||||
///
|
||||
/// [std]: std::fs::canonicalize
|
||||
/// This is an async version of [`std::fs::canonicalize`].
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::path::Path;
|
||||
/// of the original file to the destination file.
|
||||
/// This function will overwrite the contents of to.
|
||||
///
|
||||
/// This is the async equivalent of [`std::fs::copy`][std].
|
||||
///
|
||||
/// [std]: fn@std::fs::copy
|
||||
/// This is the async equivalent of [`std::fs::copy`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::path::Path;
|
||||
|
||||
/// Creates a new, empty directory at the provided path.
|
||||
///
|
||||
/// This is an async version of [`std::fs::create_dir`][std]
|
||||
///
|
||||
/// [std]: std::fs::create_dir
|
||||
/// This is an async version of [`std::fs::create_dir`].
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
|
||||
@@ -6,9 +6,7 @@ use std::path::Path;
|
||||
/// Recursively creates a directory and all of its parent components if they
|
||||
/// are missing.
|
||||
///
|
||||
/// This is an async version of [`std::fs::create_dir_all`][std]
|
||||
///
|
||||
/// [std]: std::fs::create_dir_all
|
||||
/// This is an async version of [`std::fs::create_dir_all`].
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
|
||||
@@ -7,8 +7,6 @@ use std::path::Path;
|
||||
///
|
||||
/// This is a specialized version of [`std::fs::DirBuilder`] for usage on
|
||||
/// the Tokio runtime.
|
||||
///
|
||||
/// [std::fs::DirBuilder]: std::fs::DirBuilder
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DirBuilder {
|
||||
/// Indicates whether to create parent directories if they are missing.
|
||||
@@ -23,9 +21,7 @@ impl DirBuilder {
|
||||
/// Creates a new set of options with default mode/security settings for all
|
||||
/// platforms and also non-recursive.
|
||||
///
|
||||
/// This is an async version of [`std::fs::DirBuilder::new`][std]
|
||||
///
|
||||
/// [std]: std::fs::DirBuilder::new
|
||||
/// This is an async version of [`std::fs::DirBuilder::new`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -35,7 +31,7 @@ impl DirBuilder {
|
||||
/// let builder = DirBuilder::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
DirBuilder::default()
|
||||
}
|
||||
|
||||
/// Indicates whether to create directories recursively (including all parent directories).
|
||||
@@ -43,9 +39,7 @@ impl DirBuilder {
|
||||
///
|
||||
/// This option defaults to `false`.
|
||||
///
|
||||
/// This is an async version of [`std::fs::DirBuilder::recursive`][std]
|
||||
///
|
||||
/// [std]: std::fs::DirBuilder::recursive
|
||||
/// This is an async version of [`std::fs::DirBuilder::recursive`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -65,9 +59,7 @@ impl DirBuilder {
|
||||
/// It is considered an error if the directory already exists unless
|
||||
/// recursive mode is enabled.
|
||||
///
|
||||
/// This is an async version of [`std::fs::DirBuilder::create`][std]
|
||||
///
|
||||
/// [std]: std::fs::DirBuilder::create
|
||||
/// This is an async version of [`std::fs::DirBuilder::create`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
|
||||
+119
-54
@@ -2,7 +2,6 @@
|
||||
//!
|
||||
//! [`File`]: File
|
||||
|
||||
use self::State::*;
|
||||
use crate::fs::{asyncify, OpenOptions};
|
||||
use crate::io::blocking::Buf;
|
||||
use crate::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
||||
@@ -17,7 +16,6 @@ use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use std::task::Poll::*;
|
||||
|
||||
#[cfg(test)]
|
||||
use super::mocks::JoinHandle;
|
||||
@@ -34,7 +32,7 @@ use std::fs::File as StdFile;
|
||||
|
||||
/// A reference to an open file on the filesystem.
|
||||
///
|
||||
/// This is a specialized version of [`std::fs::File`][std] for usage from the
|
||||
/// This is a specialized version of [`std::fs::File`] for usage from the
|
||||
/// Tokio runtime.
|
||||
///
|
||||
/// An instance of a `File` can be read and/or written depending on what options
|
||||
@@ -52,7 +50,6 @@ use std::fs::File as StdFile;
|
||||
/// Reading and writing to a `File` is usually done using the convenience
|
||||
/// methods found on the [`AsyncReadExt`] and [`AsyncWriteExt`] traits.
|
||||
///
|
||||
/// [std]: struct@std::fs::File
|
||||
/// [`AsyncSeek`]: trait@crate::io::AsyncSeek
|
||||
/// [`flush`]: fn@crate::io::AsyncWriteExt::flush
|
||||
/// [`sync_all`]: fn@crate::fs::File::sync_all
|
||||
@@ -128,7 +125,7 @@ impl File {
|
||||
///
|
||||
/// This function will return an error if called from outside of the Tokio
|
||||
/// runtime or if path does not already exist. Other errors may also be
|
||||
/// returned according to OpenOptions::open.
|
||||
/// returned according to `OpenOptions::open`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -226,10 +223,7 @@ impl File {
|
||||
OpenOptions::new()
|
||||
}
|
||||
|
||||
/// Converts a [`std::fs::File`][std] to a [`tokio::fs::File`][file].
|
||||
///
|
||||
/// [std]: std::fs::File
|
||||
/// [file]: File
|
||||
/// Converts a [`std::fs::File`] to a [`tokio::fs::File`](File).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -351,7 +345,7 @@ impl File {
|
||||
inner.complete_inflight().await;
|
||||
|
||||
let mut buf = match inner.state {
|
||||
Idle(ref mut buf_cell) => buf_cell.take().unwrap(),
|
||||
State::Idle(ref mut buf_cell) => buf_cell.take().unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@@ -363,24 +357,24 @@ impl File {
|
||||
|
||||
let std = self.std.clone();
|
||||
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
inner.state = State::Busy(spawn_blocking(move || {
|
||||
let res = if let Some(seek) = seek {
|
||||
(&*std).seek(seek).and_then(|_| std.set_len(size))
|
||||
} else {
|
||||
std.set_len(size)
|
||||
}
|
||||
.map(|_| 0); // the value is discarded later
|
||||
.map(|()| 0); // the value is discarded later
|
||||
|
||||
// Return the result as a seek
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
|
||||
let (op, buf) = match inner.state {
|
||||
Idle(_) => unreachable!(),
|
||||
Busy(ref mut rx) => rx.await?,
|
||||
State::Idle(_) => unreachable!(),
|
||||
State::Busy(ref mut rx) => rx.await?,
|
||||
};
|
||||
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Seek(res) => res.map(|pos| {
|
||||
@@ -432,13 +426,11 @@ impl File {
|
||||
Ok(File::from_std(std_file))
|
||||
}
|
||||
|
||||
/// Destructures `File` into a [`std::fs::File`][std]. This function is
|
||||
/// Destructures `File` into a [`std::fs::File`]. This function is
|
||||
/// async to allow any in-flight operations to complete.
|
||||
///
|
||||
/// Use `File::try_into_std` to attempt conversion immediately.
|
||||
///
|
||||
/// [std]: std::fs::File
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -455,9 +447,7 @@ impl File {
|
||||
Arc::try_unwrap(self.std).expect("Arc::try_unwrap failed")
|
||||
}
|
||||
|
||||
/// Tries to immediately destructure `File` into a [`std::fs::File`][std].
|
||||
///
|
||||
/// [std]: std::fs::File
|
||||
/// Tries to immediately destructure `File` into a [`std::fs::File`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -532,51 +522,51 @@ impl AsyncRead for File {
|
||||
|
||||
loop {
|
||||
match inner.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
if !buf.is_empty() {
|
||||
buf.copy_to(dst);
|
||||
*buf_cell = Some(buf);
|
||||
return Ready(Ok(()));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
buf.ensure_capacity_for(dst);
|
||||
let std = me.std.clone();
|
||||
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
inner.state = State::Busy(spawn_blocking(move || {
|
||||
let res = buf.read_from(&mut &*std);
|
||||
(Operation::Read(res), buf)
|
||||
}));
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, mut buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
|
||||
match op {
|
||||
Operation::Read(Ok(_)) => {
|
||||
buf.copy_to(dst);
|
||||
inner.state = Idle(Some(buf));
|
||||
return Ready(Ok(()));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
Operation::Read(Err(e)) => {
|
||||
assert!(buf.is_empty());
|
||||
|
||||
inner.state = Idle(Some(buf));
|
||||
return Ready(Err(e));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
Operation::Write(Ok(_)) => {
|
||||
Operation::Write(Ok(())) => {
|
||||
assert!(buf.is_empty());
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
continue;
|
||||
}
|
||||
Operation::Write(Err(e)) => {
|
||||
assert!(inner.last_write_err.is_none());
|
||||
inner.last_write_err = Some(e.kind());
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
}
|
||||
Operation::Seek(result) => {
|
||||
assert!(buf.is_empty());
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
if let Ok(pos) = result {
|
||||
inner.pos = pos;
|
||||
}
|
||||
@@ -595,11 +585,11 @@ impl AsyncSeek for File {
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
match inner.state {
|
||||
Busy(_) => Err(io::Error::new(
|
||||
State::Busy(_) => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"other file operation is pending, call poll_complete before start_seek",
|
||||
)),
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
// Factor in any unread data from the buf
|
||||
@@ -613,7 +603,7 @@ impl AsyncSeek for File {
|
||||
|
||||
let std = me.std.clone();
|
||||
|
||||
inner.state = Busy(spawn_blocking(move || {
|
||||
inner.state = State::Busy(spawn_blocking(move || {
|
||||
let res = (&*std).seek(pos);
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
@@ -628,10 +618,10 @@ impl AsyncSeek for File {
|
||||
|
||||
loop {
|
||||
match inner.state {
|
||||
Idle(_) => return Poll::Ready(Ok(inner.pos)),
|
||||
Busy(ref mut rx) => {
|
||||
State::Idle(_) => return Poll::Ready(Ok(inner.pos)),
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {}
|
||||
@@ -644,7 +634,7 @@ impl AsyncSeek for File {
|
||||
if let Ok(pos) = res {
|
||||
inner.pos = pos;
|
||||
}
|
||||
return Ready(res);
|
||||
return Poll::Ready(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -664,12 +654,12 @@ impl AsyncWrite for File {
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
if let Some(e) = inner.last_write_err.take() {
|
||||
return Ready(Err(e.into()));
|
||||
return Poll::Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
loop {
|
||||
match inner.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
let seek = if !buf.is_empty() {
|
||||
@@ -694,13 +684,13 @@ impl AsyncWrite for File {
|
||||
io::Error::new(io::ErrorKind::Other, "background task failed")
|
||||
})?;
|
||||
|
||||
inner.state = Busy(blocking_task_join_handle);
|
||||
inner.state = State::Busy(blocking_task_join_handle);
|
||||
|
||||
return Ready(Ok(n));
|
||||
return Poll::Ready(Ok(n));
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
inner.state = Idle(Some(buf));
|
||||
inner.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {
|
||||
@@ -725,6 +715,81 @@ impl AsyncWrite for File {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let me = self.get_mut();
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
if let Some(e) = inner.last_write_err.take() {
|
||||
return Poll::Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
loop {
|
||||
match inner.state {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
let seek = if !buf.is_empty() {
|
||||
Some(SeekFrom::Current(buf.discard_read()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let n = buf.copy_from_bufs(bufs);
|
||||
let std = me.std.clone();
|
||||
|
||||
let blocking_task_join_handle = spawn_mandatory_blocking(move || {
|
||||
let res = if let Some(seek) = seek {
|
||||
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
|
||||
} else {
|
||||
buf.write_to(&mut &*std)
|
||||
};
|
||||
|
||||
(Operation::Write(res), buf)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "background task failed")
|
||||
})?;
|
||||
|
||||
inner.state = State::Busy(blocking_task_join_handle);
|
||||
|
||||
return Poll::Ready(Ok(n));
|
||||
}
|
||||
State::Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
inner.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {
|
||||
// We don't care about the result here. The fact
|
||||
// that the cursor has advanced will be reflected in
|
||||
// the next iteration of the loop
|
||||
continue;
|
||||
}
|
||||
Operation::Write(res) => {
|
||||
// If the previous write was successful, continue.
|
||||
// Otherwise, error.
|
||||
res?;
|
||||
continue;
|
||||
}
|
||||
Operation::Seek(_) => {
|
||||
// Ignore the seek
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
let inner = self.inner.get_mut();
|
||||
@@ -804,7 +869,7 @@ impl Inner {
|
||||
async fn complete_inflight(&mut self) {
|
||||
use crate::future::poll_fn;
|
||||
|
||||
poll_fn(|cx| self.poll_complete_inflight(cx)).await
|
||||
poll_fn(|cx| self.poll_complete_inflight(cx)).await;
|
||||
}
|
||||
|
||||
fn poll_complete_inflight(&mut self, cx: &mut Context<'_>) -> Poll<()> {
|
||||
@@ -821,21 +886,21 @@ impl Inner {
|
||||
|
||||
fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
if let Some(e) = self.last_write_err.take() {
|
||||
return Ready(Err(e.into()));
|
||||
return Poll::Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
let (op, buf) = match self.state {
|
||||
Idle(_) => return Ready(Ok(())),
|
||||
Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx))?,
|
||||
State::Idle(_) => return Poll::Ready(Ok(())),
|
||||
State::Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx))?,
|
||||
};
|
||||
|
||||
// The buffer is not used here
|
||||
self.state = Idle(Some(buf));
|
||||
self.state = State::Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => Ready(Ok(())),
|
||||
Operation::Write(res) => Ready(res),
|
||||
Operation::Seek(_) => Ready(Ok(())),
|
||||
Operation::Read(_) => Poll::Ready(Ok(())),
|
||||
Operation::Write(res) => Poll::Ready(res),
|
||||
Operation::Seek(_) => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::path::Path;
|
||||
|
||||
/// Creates a new hard link on the filesystem.
|
||||
///
|
||||
/// This is an async version of [`std::fs::hard_link`][std]
|
||||
///
|
||||
/// [std]: std::fs::hard_link
|
||||
/// 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
|
||||
/// often require these two paths to both be located on the same filesystem.
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::path::Path;
|
||||
/// Given a path, queries the file system to get information about a file,
|
||||
/// directory, etc.
|
||||
///
|
||||
/// This is an async version of [`std::fs::metadata`][std]
|
||||
/// This is an async version of [`std::fs::metadata`].
|
||||
///
|
||||
/// This function will traverse symbolic links to query information about the
|
||||
/// destination file.
|
||||
@@ -18,7 +18,6 @@ use std::path::Path;
|
||||
/// `GetFileAttributesEx` function on Windows. Note that, this [may change in
|
||||
/// the future][changes].
|
||||
///
|
||||
/// [std]: std::fs::metadata
|
||||
/// [changes]: https://doc.rust-lang.org/std/io/index.html#platform-specific-behavior
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
@@ -124,12 +124,12 @@ impl<T> Future for JoinHandle<T> {
|
||||
type Output = Result<T, io::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
use std::task::Poll::*;
|
||||
use std::task::Poll;
|
||||
|
||||
match Pin::new(&mut self.rx).poll(cx) {
|
||||
Ready(Ok(v)) => Ready(Ok(v)),
|
||||
Ready(Err(e)) => panic!("error = {:?}", e),
|
||||
Pending => Pending,
|
||||
Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)),
|
||||
Poll::Ready(Err(e)) => panic!("error = {:?}", e),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use std::os::windows::fs::OpenOptionsExt;
|
||||
/// Generally speaking, when using `OpenOptions`, you'll first call [`new`],
|
||||
/// then chain calls to methods to set each option, then call [`open`], passing
|
||||
/// the path of the file you're trying to open. This will give you a
|
||||
/// [`io::Result`][result] with a [`File`] inside that you can further operate
|
||||
/// [`io::Result`] with a [`File`] inside that you can further operate
|
||||
/// on.
|
||||
///
|
||||
/// This is a specialized version of [`std::fs::OpenOptions`] for usage from
|
||||
@@ -36,11 +36,9 @@ use std::os::windows::fs::OpenOptionsExt;
|
||||
///
|
||||
/// [`new`]: OpenOptions::new
|
||||
/// [`open`]: OpenOptions::open
|
||||
/// [result]: std::io::Result
|
||||
/// [`File`]: File
|
||||
/// [`File::open`]: File::open
|
||||
/// [`File::create`]: File::create
|
||||
/// [`std::fs::OpenOptions`]: std::fs::OpenOptions
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -4,9 +4,7 @@ use std::{io, path::Path};
|
||||
|
||||
/// Reads the entire contents of a file into a bytes vector.
|
||||
///
|
||||
/// This is an async version of [`std::fs::read`][std]
|
||||
///
|
||||
/// [std]: std::fs::read
|
||||
/// This is an async version of [`std::fs::read`].
|
||||
///
|
||||
/// This is a convenience function for using [`File::open`] and [`read_to_end`]
|
||||
/// with fewer imports and without an intermediate variable. It pre-allocates a
|
||||
|
||||
@@ -24,7 +24,7 @@ const CHUNK_SIZE: usize = 32;
|
||||
|
||||
/// Returns a stream over the entries within a directory.
|
||||
///
|
||||
/// This is an async version of [`std::fs::read_dir`](std::fs::read_dir)
|
||||
/// This is an async version of [`std::fs::read_dir`].
|
||||
///
|
||||
/// This operation is implemented by running the equivalent blocking
|
||||
/// operation on a separate thread pool using [`spawn_blocking`].
|
||||
@@ -140,6 +140,7 @@ impl ReadDir {
|
||||
target_os = "illumos",
|
||||
target_os = "haiku",
|
||||
target_os = "vxworks",
|
||||
target_os = "aix",
|
||||
target_os = "nto",
|
||||
target_os = "vita",
|
||||
)))]
|
||||
@@ -203,6 +204,7 @@ pub struct DirEntry {
|
||||
target_os = "illumos",
|
||||
target_os = "haiku",
|
||||
target_os = "vxworks",
|
||||
target_os = "aix",
|
||||
target_os = "nto",
|
||||
target_os = "vita",
|
||||
)))]
|
||||
@@ -336,6 +338,7 @@ impl DirEntry {
|
||||
target_os = "illumos",
|
||||
target_os = "haiku",
|
||||
target_os = "vxworks",
|
||||
target_os = "aix",
|
||||
target_os = "nto",
|
||||
target_os = "vita",
|
||||
)))]
|
||||
|
||||
@@ -5,9 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
/// Reads a symbolic link, returning the file that the link points to.
|
||||
///
|
||||
/// This is an async version of [`std::fs::read_link`][std]
|
||||
///
|
||||
/// [std]: std::fs::read_link
|
||||
/// This is an async version of [`std::fs::read_link`].
|
||||
pub async fn read_link(path: impl AsRef<Path>) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref().to_owned();
|
||||
asyncify(move || std::fs::read_link(path)).await
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::Path;
|
||||
|
||||
/// Removes an existing, empty directory.
|
||||
///
|
||||
/// This is an async version of [`std::fs::remove_dir`](std::fs::remove_dir)
|
||||
/// This is an async version of [`std::fs::remove_dir`].
|
||||
pub async fn remove_dir(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
asyncify(move || std::fs::remove_dir(path)).await
|
||||
|
||||
@@ -9,9 +9,7 @@ use std::path::Path;
|
||||
/// depending on platform, other open file descriptors may prevent immediate
|
||||
/// removal).
|
||||
///
|
||||
/// This is an async version of [`std::fs::remove_file`][std]
|
||||
///
|
||||
/// [std]: std::fs::remove_file
|
||||
/// This is an async version of [`std::fs::remove_file`].
|
||||
pub async fn remove_file(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
asyncify(move || std::fs::remove_file(path)).await
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::path::Path;
|
||||
///
|
||||
/// This will not work if the new name is on a different mount point.
|
||||
///
|
||||
/// This is an async version of [`std::fs::rename`](std::fs::rename)
|
||||
/// This is an async version of [`std::fs::rename`].
|
||||
pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
|
||||
let from = from.as_ref().to_owned();
|
||||
let to = to.as_ref().to_owned();
|
||||
|
||||
@@ -7,9 +7,7 @@ use std::path::Path;
|
||||
///
|
||||
/// The `dst` path will be a symbolic link pointing to the `src` path.
|
||||
///
|
||||
/// This is an async version of [`std::os::unix::fs::symlink`][std]
|
||||
///
|
||||
/// [std]: std::os::unix::fs::symlink
|
||||
/// 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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Definition of the MaybeDone combinator.
|
||||
//! Definition of the [`MaybeDone`] combinator.
|
||||
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
|
||||
@@ -20,6 +20,7 @@ cfg_sync! {
|
||||
|
||||
cfg_trace! {
|
||||
mod trace;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use trace::InstrumentedFuture as Future;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ where
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
self.get_mut().as_mut().consume(amt)
|
||||
self.get_mut().as_mut().consume(amt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,6 @@ impl<T: AsRef<[u8]> + Unpin> AsyncBufRead for io::Cursor<T> {
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
io::BufRead::consume(self.get_mut(), amt)
|
||||
io::BufRead::consume(self.get_mut(), amt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@ use std::{task::Context, task::Poll};
|
||||
/// `kqueue`, etc), such as a network socket or pipe, and the file descriptor
|
||||
/// must have the nonblocking mode set to true.
|
||||
///
|
||||
/// Creating an AsyncFd registers the file descriptor with the current tokio
|
||||
/// Creating an [`AsyncFd`] registers the file descriptor with the current tokio
|
||||
/// Reactor, allowing you to directly await the file descriptor being readable
|
||||
/// or writable. Once registered, the file descriptor remains registered until
|
||||
/// the AsyncFd is dropped.
|
||||
/// the [`AsyncFd`] is dropped.
|
||||
///
|
||||
/// The AsyncFd takes ownership of an arbitrary object to represent the IO
|
||||
/// The [`AsyncFd`] takes ownership of an arbitrary object to represent the IO
|
||||
/// object. It is intended that this object will handle closing the file
|
||||
/// descriptor when it is dropped, avoiding resource leaks and ensuring that the
|
||||
/// AsyncFd can clean up the registration before closing the file descriptor.
|
||||
/// [`AsyncFd`] can clean up the registration before closing the file descriptor.
|
||||
/// The [`AsyncFd::into_inner`] function can be used to extract the inner object
|
||||
/// to retake control from the tokio IO reactor.
|
||||
///
|
||||
@@ -204,7 +204,7 @@ pub struct AsyncFdReadyMutGuard<'a, T: AsRawFd> {
|
||||
}
|
||||
|
||||
impl<T: AsRawFd> AsyncFd<T> {
|
||||
/// Creates an AsyncFd backed by (and taking ownership of) an object
|
||||
/// Creates an [`AsyncFd`] backed by (and taking ownership of) an object
|
||||
/// implementing [`AsRawFd`]. The backing file descriptor is cached at the
|
||||
/// time of creation.
|
||||
///
|
||||
@@ -226,7 +226,7 @@ impl<T: AsRawFd> AsyncFd<T> {
|
||||
Self::with_interest(inner, Interest::READABLE | Interest::WRITABLE)
|
||||
}
|
||||
|
||||
/// Creates an AsyncFd backed by (and taking ownership of) an object
|
||||
/// Creates an [`AsyncFd`] backed by (and taking ownership of) an object
|
||||
/// implementing [`AsRawFd`], with a specific [`Interest`]. The backing
|
||||
/// file descriptor is cached at the time of creation.
|
||||
///
|
||||
|
||||
+37
-23
@@ -6,11 +6,8 @@ use std::future::Future;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll::*;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use self::State::*;
|
||||
|
||||
/// `T` should not implement _both_ Read and Write.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Blocking<T> {
|
||||
@@ -58,38 +55,38 @@ where
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
match self.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
if !buf.is_empty() {
|
||||
buf.copy_to(dst);
|
||||
*buf_cell = Some(buf);
|
||||
return Ready(Ok(()));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
buf.ensure_capacity_for(dst);
|
||||
let mut inner = self.inner.take().unwrap();
|
||||
|
||||
self.state = Busy(sys::run(move || {
|
||||
self.state = State::Busy(sys::run(move || {
|
||||
let res = buf.read_from(&mut inner);
|
||||
(res, buf, inner)
|
||||
}));
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
State::Busy(ref mut rx) => {
|
||||
let (res, mut buf, inner) = ready!(Pin::new(rx).poll(cx))?;
|
||||
self.inner = Some(inner);
|
||||
|
||||
match res {
|
||||
Ok(_) => {
|
||||
buf.copy_to(dst);
|
||||
self.state = Idle(Some(buf));
|
||||
return Ready(Ok(()));
|
||||
self.state = State::Idle(Some(buf));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
Err(e) => {
|
||||
assert!(buf.is_empty());
|
||||
|
||||
self.state = Idle(Some(buf));
|
||||
return Ready(Err(e));
|
||||
self.state = State::Idle(Some(buf));
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +106,7 @@ where
|
||||
) -> Poll<io::Result<usize>> {
|
||||
loop {
|
||||
match self.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
assert!(buf.is_empty());
|
||||
@@ -117,19 +114,19 @@ where
|
||||
let n = buf.copy_from(src);
|
||||
let mut inner = self.inner.take().unwrap();
|
||||
|
||||
self.state = Busy(sys::run(move || {
|
||||
self.state = State::Busy(sys::run(move || {
|
||||
let n = buf.len();
|
||||
let res = buf.write_to(&mut inner).map(|_| n);
|
||||
let res = buf.write_to(&mut inner).map(|()| n);
|
||||
|
||||
(res, buf, inner)
|
||||
}));
|
||||
self.need_flush = true;
|
||||
|
||||
return Ready(Ok(n));
|
||||
return Poll::Ready(Ok(n));
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
State::Busy(ref mut rx) => {
|
||||
let (res, buf, inner) = ready!(Pin::new(rx).poll(cx))?;
|
||||
self.state = Idle(Some(buf));
|
||||
self.state = State::Idle(Some(buf));
|
||||
self.inner = Some(inner);
|
||||
|
||||
// If error, return
|
||||
@@ -144,24 +141,24 @@ where
|
||||
let need_flush = self.need_flush;
|
||||
match self.state {
|
||||
// The buffer is not used here
|
||||
Idle(ref mut buf_cell) => {
|
||||
State::Idle(ref mut buf_cell) => {
|
||||
if need_flush {
|
||||
let buf = buf_cell.take().unwrap();
|
||||
let mut inner = self.inner.take().unwrap();
|
||||
|
||||
self.state = Busy(sys::run(move || {
|
||||
let res = inner.flush().map(|_| 0);
|
||||
self.state = State::Busy(sys::run(move || {
|
||||
let res = inner.flush().map(|()| 0);
|
||||
(res, buf, inner)
|
||||
}));
|
||||
|
||||
self.need_flush = false;
|
||||
} else {
|
||||
return Ready(Ok(()));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
State::Busy(ref mut rx) => {
|
||||
let (res, buf, inner) = ready!(Pin::new(rx).poll(cx))?;
|
||||
self.state = Idle(Some(buf));
|
||||
self.state = State::Idle(Some(buf));
|
||||
self.inner = Some(inner);
|
||||
|
||||
// If error, return
|
||||
@@ -276,5 +273,22 @@ cfg_fs! {
|
||||
self.buf.truncate(0);
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn copy_from_bufs(&mut self, bufs: &[io::IoSlice<'_>]) -> usize {
|
||||
assert!(self.is_empty());
|
||||
|
||||
let mut rem = MAX_BUF;
|
||||
for buf in bufs {
|
||||
if rem == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
let len = buf.len().min(rem);
|
||||
self.buf.extend_from_slice(&buf[..len]);
|
||||
rem -= len;
|
||||
}
|
||||
|
||||
MAX_BUF - rem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,10 +163,45 @@ impl Interest {
|
||||
///
|
||||
/// assert!(BOTH.is_readable());
|
||||
/// assert!(BOTH.is_writable());
|
||||
#[must_use = "this returns the result of the operation, without modifying the original"]
|
||||
pub const fn add(self, other: Interest) -> Interest {
|
||||
Self(self.0 | other.0)
|
||||
}
|
||||
|
||||
/// Remove `Interest` from `self`.
|
||||
///
|
||||
/// Interests present in `other` but *not* in `self` are ignored.
|
||||
///
|
||||
/// Returns `None` if the set would be empty after removing `Interest`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::io::Interest;
|
||||
///
|
||||
/// const RW_INTEREST: Interest = Interest::READABLE.add(Interest::WRITABLE);
|
||||
///
|
||||
/// let w_interest = RW_INTEREST.remove(Interest::READABLE).unwrap();
|
||||
/// assert!(!w_interest.is_readable());
|
||||
/// assert!(w_interest.is_writable());
|
||||
///
|
||||
/// // Removing all interests from the set returns `None`.
|
||||
/// assert_eq!(w_interest.remove(Interest::WRITABLE), None);
|
||||
///
|
||||
/// // Remove all interests at once.
|
||||
/// assert_eq!(RW_INTEREST.remove(RW_INTEREST), None);
|
||||
/// ```
|
||||
#[must_use = "this returns the result of the operation, without modifying the original"]
|
||||
pub fn remove(self, other: Interest) -> Option<Interest> {
|
||||
let value = self.0 & !other.0;
|
||||
|
||||
if value != 0 {
|
||||
Some(Self(value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// This function must be crate-private to avoid exposing a `mio` dependency.
|
||||
pub(crate) fn to_mio(self) -> mio::Interest {
|
||||
fn mio_add(wrapped: &mut Option<mio::Interest>, add: mio::Interest) {
|
||||
@@ -244,7 +279,7 @@ impl ops::BitOr for Interest {
|
||||
impl ops::BitOrAssign for Interest {
|
||||
#[inline]
|
||||
fn bitor_assign(&mut self, other: Self) {
|
||||
*self = *self | other
|
||||
*self = *self | other;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::io::AsyncWrite;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
/// # Windows
|
||||
/// AsyncWrite adapter that finds last char boundary in given buffer and does not write the rest,
|
||||
/// if buffer contents seems to be utf8. Otherwise it only trims buffer down to MAX_BUF.
|
||||
/// [`AsyncWrite`] adapter that finds last char boundary in given buffer and does not write the rest,
|
||||
/// if buffer contents seems to be utf8. Otherwise it only trims buffer down to `MAX_BUF`.
|
||||
/// That's why, wrapped writer will always receive well-formed utf-8 bytes.
|
||||
/// # Other platforms
|
||||
/// Passes data to `inner` as is.
|
||||
|
||||
@@ -294,7 +294,7 @@ cfg_io_util! {
|
||||
where
|
||||
Self: Unpin,
|
||||
{
|
||||
std::pin::Pin::new(self).consume(amt)
|
||||
std::pin::Pin::new(self).consume(amt);
|
||||
}
|
||||
|
||||
/// Returns a stream over the lines of this reader.
|
||||
|
||||
@@ -192,7 +192,7 @@ impl<RW: AsyncRead + AsyncWrite> AsyncBufRead for BufStream<RW> {
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
self.project().inner.consume(amt)
|
||||
self.project().inner.consume(amt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@ impl<W: AsyncWrite + AsyncBufRead> AsyncBufRead for BufWriter<W> {
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
self.get_pin_mut().consume(amt)
|
||||
self.get_pin_mut().consume(amt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ impl CopyBuffer {
|
||||
buf.set_filled(me.cap);
|
||||
|
||||
let res = reader.poll_read(cx, &mut buf);
|
||||
if let Poll::Ready(Ok(_)) = res {
|
||||
if let Poll::Ready(Ok(())) = res {
|
||||
let filled_len = buf.filled().len();
|
||||
me.read_done = me.cap == filled_len;
|
||||
me.cap = filled_len;
|
||||
@@ -90,7 +90,7 @@ impl CopyBuffer {
|
||||
self.cap = 0;
|
||||
|
||||
match self.poll_fill_buf(cx, reader.as_mut()) {
|
||||
Poll::Ready(Ok(_)) => (),
|
||||
Poll::Ready(Ok(())) => (),
|
||||
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
||||
Poll::Pending => {
|
||||
// Try flushing when the reader has no progress to avoid deadlock
|
||||
|
||||
@@ -124,6 +124,18 @@ impl AsyncWrite for DuplexStream {
|
||||
Pin::new(&mut *self.write.lock()).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
Pin::new(&mut *self.write.lock()).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
fn poll_flush(
|
||||
mut self: Pin<&mut Self>,
|
||||
@@ -224,6 +236,37 @@ impl Pipe {
|
||||
}
|
||||
Poll::Ready(Ok(len))
|
||||
}
|
||||
|
||||
fn poll_write_vectored_internal(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
if self.is_closed {
|
||||
return Poll::Ready(Err(std::io::ErrorKind::BrokenPipe.into()));
|
||||
}
|
||||
let avail = self.max_buf_size - self.buffer.len();
|
||||
if avail == 0 {
|
||||
self.write_waker = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
let mut rem = avail;
|
||||
for buf in bufs {
|
||||
if rem == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let len = buf.len().min(rem);
|
||||
self.buffer.extend_from_slice(&buf[..len]);
|
||||
rem -= len;
|
||||
}
|
||||
|
||||
if let Some(waker) = self.read_waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
Poll::Ready(Ok(avail - rem))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Pipe {
|
||||
@@ -285,6 +328,38 @@ impl AsyncWrite for Pipe {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_coop! {
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
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 ret = self.poll_write_vectored_internal(cx, bufs);
|
||||
if ret.is_ready() {
|
||||
coop.made_progress();
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
cfg_not_coop! {
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
ready!(crate::trace::trace_leaf(cx));
|
||||
self.poll_write_vectored_internal(cx, bufs)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, _: &mut task::Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(super) fn read_to_end_internal<V: VecU8, R: AsyncRead + ?Sized>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to read from the provided AsyncRead.
|
||||
/// Tries to read from the provided [`AsyncRead`].
|
||||
///
|
||||
/// The length of the buffer is increased by the number of bytes read.
|
||||
fn poll_read_to_end<V: VecU8, R: AsyncRead + ?Sized>(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::io::{AsyncBufRead, AsyncRead, ReadBuf};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::convert::TryFrom;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{cmp, io};
|
||||
@@ -42,7 +43,7 @@ impl<R: AsyncRead> Take<R> {
|
||||
/// the amount of bytes read and the previous limit value don't matter when
|
||||
/// calling this method.
|
||||
pub fn set_limit(&mut self, limit: u64) {
|
||||
self.limit_ = limit
|
||||
self.limit_ = limit;
|
||||
}
|
||||
|
||||
/// Gets a reference to the underlying reader.
|
||||
@@ -85,7 +86,7 @@ impl<R: AsyncRead> AsyncRead for Take<R> {
|
||||
}
|
||||
|
||||
let me = self.project();
|
||||
let mut b = buf.take(*me.limit_ as usize);
|
||||
let mut b = buf.take(usize::try_from(*me.limit_).unwrap_or(usize::MAX));
|
||||
|
||||
let buf_ptr = b.filled().as_ptr();
|
||||
ready!(me.inner.poll_read(cx, &mut b))?;
|
||||
|
||||
@@ -268,7 +268,7 @@ impl TcpListener {
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(IntoRawFd::into_raw_fd)
|
||||
.map(|raw_fd| unsafe { std::net::TcpListener::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
|
||||
@@ -378,13 +378,13 @@ impl TcpSocket {
|
||||
self.inner.recv_buffer_size().map(|n| n as u32)
|
||||
}
|
||||
|
||||
/// Sets the linger duration of this socket by setting the SO_LINGER option.
|
||||
/// Sets the linger duration of this socket by setting the `SO_LINGER` option.
|
||||
///
|
||||
/// This option controls the action taken when a stream has unsent messages and the stream is
|
||||
/// closed. If SO_LINGER is set, the system shall block the process until it can transmit the
|
||||
/// closed. If `SO_LINGER` is set, the system shall block the process until it can transmit the
|
||||
/// data or until the time expires.
|
||||
///
|
||||
/// If SO_LINGER is not specified, and the socket is closed, the system handles the call in a
|
||||
/// If `SO_LINGER` is not specified, and the socket is closed, the system handles the call in a
|
||||
/// way that allows the process to continue as quickly as possible.
|
||||
pub fn set_linger(&self, dur: Option<Duration>) -> io::Result<()> {
|
||||
self.inner.set_linger(dur)
|
||||
|
||||
@@ -222,10 +222,14 @@ impl TcpStream {
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// let mut data = [0u8; 12];
|
||||
/// # if false {
|
||||
/// let listener = TcpListener::bind("127.0.0.1:34254").await?;
|
||||
/// # let handle = tokio::spawn(async {
|
||||
/// # let mut stream: TcpStream = TcpStream::connect("127.0.0.1:34254").await.unwrap();
|
||||
/// # stream.write(b"Hello world!").await.unwrap();
|
||||
/// # }
|
||||
/// # let listener = TcpListener::bind("127.0.0.1:0").await?;
|
||||
/// # let addr = listener.local_addr().unwrap();
|
||||
/// # let handle = tokio::spawn(async move {
|
||||
/// # let mut stream: TcpStream = TcpStream::connect(addr).await.unwrap();
|
||||
/// # stream.write_all(b"Hello world!").await.unwrap();
|
||||
/// # });
|
||||
/// let (tokio_tcp_stream, _) = listener.accept().await?;
|
||||
/// let mut std_tcp_stream = tokio_tcp_stream.into_std()?;
|
||||
@@ -245,7 +249,7 @@ impl TcpStream {
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(IntoRawFd::into_raw_fd)
|
||||
.map(|raw_fd| unsafe { std::net::TcpStream::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ impl UdpSocket {
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(IntoRawFd::into_raw_fd)
|
||||
.map(|raw_fd| unsafe { std::net::UdpSocket::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ impl UdpSocket {
|
||||
|
||||
for addr in addrs {
|
||||
match self.io.connect(addr) {
|
||||
Ok(_) => return Ok(()),
|
||||
Ok(()) => return Ok(()),
|
||||
Err(e) => last_err = Some(e),
|
||||
}
|
||||
}
|
||||
@@ -1506,7 +1506,7 @@ impl UdpSocket {
|
||||
/// # Notes
|
||||
///
|
||||
/// On Windows, if the data is larger than the buffer specified, the buffer
|
||||
/// is filled with the first part of the data, and peek_from returns the error
|
||||
/// is filled with the first part of the data, and `peek_from` returns the error
|
||||
/// WSAEMSGSIZE(10040). The excess data is lost.
|
||||
/// Make sure to always use a sufficiently large buffer to hold the
|
||||
/// maximum UDP packet size, which can be up to 65536 bytes in size.
|
||||
|
||||
@@ -423,7 +423,7 @@ impl UnixDatagram {
|
||||
Ok((a, b))
|
||||
}
|
||||
|
||||
/// Creates new `UnixDatagram` from a `std::os::unix::net::UnixDatagram`.
|
||||
/// Creates new [`UnixDatagram`] from a [`std::os::unix::net::UnixDatagram`].
|
||||
///
|
||||
/// This function is intended to be used to wrap a UnixDatagram from the
|
||||
/// standard library in the Tokio equivalent.
|
||||
@@ -498,7 +498,7 @@ impl UnixDatagram {
|
||||
pub fn into_std(self) -> io::Result<std::os::unix::net::UnixDatagram> {
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(IntoRawFd::into_raw_fd)
|
||||
.map(|raw_fd| unsafe { std::os::unix::net::UnixDatagram::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ impl UnixListener {
|
||||
Ok(UnixListener { io })
|
||||
}
|
||||
|
||||
/// Creates new `UnixListener` from a `std::os::unix::net::UnixListener `.
|
||||
/// Creates new [`UnixListener`] from a [`std::os::unix::net::UnixListener`].
|
||||
///
|
||||
/// This function is intended to be used to wrap a UnixListener from the
|
||||
/// standard library in the Tokio equivalent.
|
||||
@@ -137,7 +137,7 @@ impl UnixListener {
|
||||
pub fn into_std(self) -> io::Result<std::os::unix::net::UnixListener> {
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(IntoRawFd::into_raw_fd)
|
||||
.map(|raw_fd| unsafe { net::UnixListener::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,12 @@ cfg_io_util! {
|
||||
/// Generally speaking, when using `OpenOptions`, you'll first call [`new`],
|
||||
/// then chain calls to methods to set each option, then call either
|
||||
/// [`open_receiver`] or [`open_sender`], passing the path of the FIFO file you
|
||||
/// are trying to open. This will give you a [`io::Result`][result] with a pipe
|
||||
/// end inside that you can further operate on.
|
||||
/// are trying to open. This will give you a [`io::Result`] with a pipe end
|
||||
/// inside that you can further operate on.
|
||||
///
|
||||
/// [`new`]: OpenOptions::new
|
||||
/// [`open_receiver`]: OpenOptions::open_receiver
|
||||
/// [`open_sender`]: OpenOptions::open_sender
|
||||
/// [result]: std::io::Result
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -1189,19 +1188,19 @@ fn get_file_flags(file: &File) -> io::Result<libc::c_int> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for O_RDONLY or O_RDWR access mode.
|
||||
/// Checks for `O_RDONLY` or `O_RDWR` access mode.
|
||||
fn has_read_access(flags: libc::c_int) -> bool {
|
||||
let mode = flags & libc::O_ACCMODE;
|
||||
mode == libc::O_RDONLY || mode == libc::O_RDWR
|
||||
}
|
||||
|
||||
/// Checks for O_WRONLY or O_RDWR access mode.
|
||||
/// Checks for `O_WRONLY` or `O_RDWR` access mode.
|
||||
fn has_write_access(flags: libc::c_int) -> bool {
|
||||
let mode = flags & libc::O_ACCMODE;
|
||||
mode == libc::O_WRONLY || mode == libc::O_RDWR
|
||||
}
|
||||
|
||||
/// Sets file's flags with O_NONBLOCK by fcntl.
|
||||
/// Sets file's flags with `O_NONBLOCK` by fcntl.
|
||||
fn set_nonblocking(file: &mut File, current_flags: libc::c_int) -> io::Result<()> {
|
||||
let fd = file.as_raw_fd();
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ pub struct ReadHalf<'a>(&'a UnixStream);
|
||||
/// Borrowed write half of a [`UnixStream`], created by [`split`].
|
||||
///
|
||||
/// Note that in the [`AsyncWrite`] implementation of this type, [`poll_shutdown`] will
|
||||
/// shut down the UnixStream stream in the write direction.
|
||||
/// shut down the [`UnixStream`] stream in the write direction.
|
||||
///
|
||||
/// Writing to an `WriteHalf` is usually done using the convenience methods found
|
||||
/// on the [`AsyncWriteExt`] trait.
|
||||
|
||||
@@ -742,7 +742,7 @@ impl UnixStream {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates new `UnixStream` from a `std::os::unix::net::UnixStream`.
|
||||
/// Creates new [`UnixStream`] from a [`std::os::unix::net::UnixStream`].
|
||||
///
|
||||
/// This function is intended to be used to wrap a UnixStream from the
|
||||
/// standard library in the Tokio equivalent.
|
||||
@@ -828,7 +828,7 @@ impl UnixStream {
|
||||
pub fn into_std(self) -> io::Result<std::os::unix::net::UnixStream> {
|
||||
self.io
|
||||
.into_inner()
|
||||
.map(|io| io.into_raw_fd())
|
||||
.map(IntoRawFd::into_raw_fd)
|
||||
.map(|raw_fd| unsafe { std::os::unix::net::UnixStream::from_raw_fd(raw_fd) })
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ pub(crate) use self::impl_netbsd::get_peer_cred;
|
||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
|
||||
pub(crate) use self::impl_bsd::get_peer_cred;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))]
|
||||
pub(crate) use self::impl_macos::get_peer_cred;
|
||||
|
||||
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
|
||||
@@ -187,7 +187,7 @@ pub(crate) mod impl_bsd {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))]
|
||||
pub(crate) mod impl_macos {
|
||||
use crate::net::unix::{self, UnixStream};
|
||||
|
||||
|
||||
@@ -1166,6 +1166,10 @@ impl Child {
|
||||
/// If the caller wishes to explicitly control when the child's stdin
|
||||
/// handle is closed, they may `.take()` it before calling `.wait()`:
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This function is cancel safe.
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(not(unix))]fn main(){}
|
||||
/// # #[cfg(unix)]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user