mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
77
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
998a125717 | ||
|
|
cbfdc9d69e | ||
|
|
08337c5f79 | ||
|
|
87f4969fbc | ||
|
|
7601dc6d2a | ||
|
|
34c6a26c01 | ||
|
|
97e7830364 | ||
|
|
606206ecad | ||
|
|
dfe4013ff2 | ||
|
|
18779aa2e2 | ||
|
|
2c24a028f6 | ||
|
|
f55b77aadd | ||
|
|
21de476ae7 | ||
|
|
cb147a2b3f | ||
|
|
f759240254 | ||
|
|
2e44cd29df | ||
|
|
2ab1fb00a9 | ||
|
|
d16e50639a | ||
|
|
e7d74b3119 | ||
|
|
d101feac50 | ||
|
|
9d8b37d51a | ||
|
|
d4c89758fc | ||
|
|
932be12481 | ||
|
|
bdd6765016 | ||
|
|
8f6d8b25bf | ||
|
|
eb7aee980c | ||
|
|
1baea398c4 | ||
|
|
3fbcf1ba50 | ||
|
|
21264f1d33 | ||
|
|
81ee3d202a | ||
|
|
a39e6c2439 | ||
|
|
4abeca7bc5 | ||
|
|
5ed84e1cd8 | ||
|
|
44a070d1b4 | ||
|
|
ce9ca45c92 | ||
|
|
f3ed064a26 | ||
|
|
652f0ae728 | ||
|
|
ff9b0ef7ca | ||
|
|
aaa150d211 | ||
|
|
deb1f98125 | ||
|
|
3a659c47c3 | ||
|
|
580dc9594c | ||
|
|
8c395dfe61 | ||
|
|
e188e99ca3 | ||
|
|
0b93bd511d | ||
|
|
d846bf24b1 | ||
|
|
9ff7d8c352 | ||
|
|
8324317005 | ||
|
|
7207e3ca43 | ||
|
|
312321cbd3 | ||
|
|
17c7ce616c | ||
|
|
f9ce18a524 | ||
|
|
05c3cf3a0a | ||
|
|
2c5dc83019 | ||
|
|
6b9bdd5ca2 | ||
|
|
d4075a4457 | ||
|
|
177522cd43 | ||
|
|
7ac341b526 | ||
|
|
c4b6b130f3 | ||
|
|
6845a93cbf | ||
|
|
8ef39dfb22 | ||
|
|
541b0c3af2 | ||
|
|
55c5d12451 | ||
|
|
a945ce0996 | ||
|
|
14bb2f624f | ||
|
|
a08ce0d3e0 | ||
|
|
078e317c0b | ||
|
|
ce9697f2a2 | ||
|
|
73466f4b6c | ||
|
|
0ba1e3c3e1 | ||
|
|
1e2f893da2 | ||
|
|
2b9b558108 | ||
|
|
d6da67b2e6 | ||
|
|
c8a6bb0b90 | ||
|
|
9eeec039f2 | ||
|
|
39706b198c | ||
|
|
cab4a592ac |
@@ -0,0 +1 @@
|
||||
msrv = "1.45"
|
||||
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [tokio-rs]
|
||||
@@ -9,7 +9,7 @@ name: CI
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
nightly: nightly-2020-09-21
|
||||
nightly: nightly-2021-04-25
|
||||
minrust: 1.45.2
|
||||
|
||||
jobs:
|
||||
@@ -68,6 +68,13 @@ jobs:
|
||||
run: cargo hack test --each-feature
|
||||
working-directory: tests-build
|
||||
|
||||
# Build benchmarks. Run of benchmarks is done by bench.yml workflow.
|
||||
- name: build benches
|
||||
run: cargo build --benches
|
||||
working-directory: benches
|
||||
# bench.yml workflow runs benchmarks only on linux.
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
runs-on: ubuntu-latest
|
||||
@@ -231,7 +238,7 @@ jobs:
|
||||
cargo hack --remove-dev-deps --workspace
|
||||
# Update Cargo.lock to minimal version dependencies.
|
||||
cargo update -Z minimal-versions
|
||||
cargo check --all-features
|
||||
cargo hack check --all-features --ignore-private
|
||||
|
||||
fmt:
|
||||
name: fmt
|
||||
@@ -258,7 +265,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update ${{ env.minrust }} && rustup default ${{ env.minrust }}
|
||||
run: rustup update 1.52.1 && rustup default 1.52.1
|
||||
- name: Install clippy
|
||||
run: rustup component add clippy
|
||||
|
||||
@@ -277,9 +284,9 @@ jobs:
|
||||
override: true
|
||||
|
||||
- name: "doc --lib --all-features"
|
||||
run: cargo doc --lib --no-deps --all-features
|
||||
run: cargo doc --lib --no-deps --all-features --document-private-items
|
||||
env:
|
||||
RUSTDOCFLAGS: --cfg docsrs
|
||||
RUSTDOCFLAGS: --cfg docsrs -Dwarnings
|
||||
|
||||
loom:
|
||||
name: loom
|
||||
|
||||
@@ -54,9 +54,9 @@ A basic TCP echo server with Tokio.
|
||||
|
||||
Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```text
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.4.0", features = ["full"] }
|
||||
tokio = { version = "1.7.0", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
@@ -66,7 +66,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
let listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
+10
-1
@@ -5,9 +5,13 @@ publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-util = { version = "0.6.6", path = "../tokio-util", features = ["full"] }
|
||||
tokio-stream = { path = "../tokio-stream" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.42"
|
||||
|
||||
@@ -41,3 +45,8 @@ harness = false
|
||||
name = "signal"
|
||||
path = "signal.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "fs"
|
||||
path = "fs.rs"
|
||||
harness = false
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#![cfg(unix)]
|
||||
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead /*FramedWrite*/};
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
|
||||
use std::fs::File as StdFile;
|
||||
use std::io::Read as StdRead;
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
const BLOCK_COUNT: usize = 1_000;
|
||||
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
const DEV_ZERO: &'static str = "/dev/zero";
|
||||
|
||||
fn async_read_codec(b: &mut Bencher) {
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(task());
|
||||
});
|
||||
}
|
||||
|
||||
fn async_read_std_file(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(task());
|
||||
});
|
||||
}
|
||||
|
||||
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!(
|
||||
file,
|
||||
async_read_std_file,
|
||||
async_read_buf,
|
||||
async_read_codec,
|
||||
sync_read
|
||||
);
|
||||
|
||||
benchmark_main!(file);
|
||||
+14
-3
@@ -7,9 +7,9 @@ edition = "2018"
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead.
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", features = ["full", "tracing"] }
|
||||
tokio-util = { version = "0.6.3", features = ["full"] }
|
||||
tokio-stream = { version = "0.1" }
|
||||
tokio = { version = "1.0.0", path = "../tokio",features = ["full", "tracing"] }
|
||||
tokio-util = { version = "0.6.3", path = "../tokio-util",features = ["full"] }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
|
||||
@@ -22,7 +22,10 @@ serde_json = "1.0"
|
||||
httparse = "1.0"
|
||||
time = "0.1"
|
||||
once_cell = "1.5.2"
|
||||
rand = "0.8.3"
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.winapi]
|
||||
version = "0.3.8"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
@@ -76,3 +79,11 @@ path = "custom-executor.rs"
|
||||
[[example]]
|
||||
name = "custom-executor-tokio-context"
|
||||
path = "custom-executor-tokio-context.rs"
|
||||
|
||||
[[example]]
|
||||
name = "named-pipe"
|
||||
path = "named-pipe.rs"
|
||||
|
||||
[[example]]
|
||||
name = "named-pipe-multi-client"
|
||||
path = "named-pipe-multi-client.rs"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
use std::io;
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn windows_main() -> io::Result<()> {
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
|
||||
use tokio::time;
|
||||
use winapi::shared::winerror;
|
||||
|
||||
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client";
|
||||
const N: usize = 10;
|
||||
|
||||
// The first server needs to be constructed early so that clients can
|
||||
// be correctly connected. Otherwise a waiting client will error.
|
||||
//
|
||||
// Here we also make use of `first_pipe_instance`, which will ensure
|
||||
// that there are no other servers up and running already.
|
||||
let mut server = ServerOptions::new()
|
||||
.first_pipe_instance(true)
|
||||
.create(PIPE_NAME)?;
|
||||
|
||||
let server = tokio::spawn(async move {
|
||||
// Artificial workload.
|
||||
time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
for _ in 0..N {
|
||||
// Wait for client to connect.
|
||||
server.connect().await?;
|
||||
let mut inner = server;
|
||||
|
||||
// Construct the next server to be connected before sending the one
|
||||
// we already have of onto a task. This ensures that the server
|
||||
// isn't closed (after it's done in the task) before a new one is
|
||||
// available. Otherwise the client might error with
|
||||
// `io::ErrorKind::NotFound`.
|
||||
server = ServerOptions::new().create(PIPE_NAME)?;
|
||||
|
||||
let _ = tokio::spawn(async move {
|
||||
let mut buf = vec![0u8; 4];
|
||||
inner.read_exact(&mut buf).await?;
|
||||
inner.write_all(b"pong").await?;
|
||||
Ok::<_, io::Error>(())
|
||||
});
|
||||
}
|
||||
|
||||
Ok::<_, io::Error>(())
|
||||
});
|
||||
|
||||
let mut clients = Vec::new();
|
||||
|
||||
for _ in 0..N {
|
||||
clients.push(tokio::spawn(async move {
|
||||
// This showcases a generic connect loop.
|
||||
//
|
||||
// We immediately try to create a client, if it's not found or
|
||||
// the pipe is busy we use the specialized wait function on the
|
||||
// client builder.
|
||||
let mut client = loop {
|
||||
match ClientOptions::new().open(PIPE_NAME) {
|
||||
Ok(client) => break client,
|
||||
Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
time::sleep(Duration::from_millis(5)).await;
|
||||
};
|
||||
|
||||
let mut buf = [0u8; 4];
|
||||
client.write_all(b"ping").await?;
|
||||
client.read_exact(&mut buf).await?;
|
||||
Ok::<_, io::Error>(buf)
|
||||
}));
|
||||
}
|
||||
|
||||
for client in clients {
|
||||
let result = client.await?;
|
||||
assert_eq!(&result?[..], b"pong");
|
||||
}
|
||||
|
||||
server.await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_main().await?;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
println!("Named pipes are only supported on Windows!");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use std::io;
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn windows_main() -> io::Result<()> {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
|
||||
|
||||
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client";
|
||||
|
||||
let server = ServerOptions::new().create(PIPE_NAME)?;
|
||||
|
||||
let server = tokio::spawn(async move {
|
||||
// Note: we wait for a client to connect.
|
||||
server.connect().await?;
|
||||
|
||||
let mut server = BufReader::new(server);
|
||||
|
||||
let mut buf = String::new();
|
||||
server.read_line(&mut buf).await?;
|
||||
server.write_all(b"pong\n").await?;
|
||||
Ok::<_, io::Error>(buf)
|
||||
});
|
||||
|
||||
let client = tokio::spawn(async move {
|
||||
// There's no need to use a connect loop here, since we know that the
|
||||
// server is already up - `open` was called before spawning any of the
|
||||
// tasks.
|
||||
let client = ClientOptions::new().open(PIPE_NAME)?;
|
||||
|
||||
let mut client = BufReader::new(client);
|
||||
|
||||
let mut buf = String::new();
|
||||
client.write_all(b"ping\n").await?;
|
||||
client.read_line(&mut buf).await?;
|
||||
Ok::<_, io::Error>(buf)
|
||||
});
|
||||
|
||||
let (server, client) = tokio::try_join!(server, client)?;
|
||||
|
||||
assert_eq!(server?, "ping\n");
|
||||
assert_eq!(client?, "pong\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_main().await?;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
println!("Named pipes are only supported on Windows!");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#![deny(dead_code)]
|
||||
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn f() {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: function is never used: `f`
|
||||
--> $DIR/macros_dead_code.rs:6:10
|
||||
|
|
||||
6 | async fn f() {}
|
||||
| ^
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> $DIR/macros_dead_code.rs:1:9
|
||||
|
|
||||
1 | #![deny(dead_code)]
|
||||
| ^^^^^^^^^
|
||||
@@ -0,0 +1,32 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn missing_semicolon_or_return_type() {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn missing_return_type() {
|
||||
/* TODO(taiki-e): one of help messages still wrong
|
||||
help: consider using a semicolon here
|
||||
|
|
||||
16 | return Ok(());;
|
||||
|
|
||||
*/
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn extra_semicolon() -> Result<(), ()> {
|
||||
/* TODO(taiki-e): help message still wrong
|
||||
help: try using a variant of the expected enum
|
||||
|
|
||||
29 | Ok(Ok(());)
|
||||
|
|
||||
29 | Err(Ok(());)
|
||||
|
|
||||
*/
|
||||
Ok(());
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,51 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:5:5
|
||||
|
|
||||
5 | Ok(())
|
||||
| ^^^^^^ expected `()`, found enum `Result`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
help: consider using a semicolon here
|
||||
|
|
||||
5 | Ok(());
|
||||
| ^
|
||||
help: try adding a return type
|
||||
|
|
||||
4 | async fn missing_semicolon_or_return_type() -> Result<(), _> {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:16:5
|
||||
|
|
||||
16 | return Ok(());
|
||||
| ^^^^^^^^^^^^^^ expected `()`, found enum `Result`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found enum `Result<(), _>`
|
||||
help: consider using a semicolon here
|
||||
|
|
||||
16 | return Ok(());;
|
||||
| ^
|
||||
help: try adding a return type
|
||||
|
|
||||
9 | async fn missing_return_type() -> Result<(), _> {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/macros_type_mismatch.rs:29:5
|
||||
|
|
||||
20 | async fn extra_semicolon() -> Result<(), ()> {
|
||||
| -------------- expected `Result<(), ()>` because of return type
|
||||
...
|
||||
29 | Ok(());
|
||||
| ^^^^^^^ expected enum `Result`, found `()`
|
||||
|
|
||||
= note: expected enum `Result<(), ()>`
|
||||
found unit type `()`
|
||||
help: try using a variant of the expected enum
|
||||
|
|
||||
29 | Ok(Ok(());)
|
||||
|
|
||||
29 | Err(Ok(());)
|
||||
|
|
||||
@@ -8,6 +8,12 @@ fn compile_fail_full() {
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_dead_code.rs");
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_type_mismatch.rs");
|
||||
|
||||
#[cfg(all(feature = "rt", not(feature = "full")))]
|
||||
t.compile_fail("tests/fail/macros_core_no_default.rs");
|
||||
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
# 1.2.0 (May 14, 2021)
|
||||
|
||||
- macros: forward input arguments in `#[tokio::test]` ([#3691])
|
||||
- macros: improve diagnostics on type mismatch ([#3766])
|
||||
- macros: various error message improvements ([#3677])
|
||||
|
||||
[#3677]: https://github.com/tokio-rs/tokio/pull/3677
|
||||
[#3691]: https://github.com/tokio-rs/tokio/pull/3691
|
||||
[#3766]: https://github.com/tokio-rs/tokio/pull/3766
|
||||
|
||||
# 1.1.0 (February 5, 2021)
|
||||
|
||||
- add `start_paused` option to macros ([#3492])
|
||||
|
||||
@@ -6,13 +6,13 @@ name = "tokio-macros"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.0.x" git tag.
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-macros/1.1.0/tokio_macros"
|
||||
documentation = "https://docs.rs/tokio-macros/1.2.0/tokio_macros"
|
||||
description = """
|
||||
Tokio's proc macros.
|
||||
"""
|
||||
@@ -26,7 +26,7 @@ proc-macro = true
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.7"
|
||||
quote = "1"
|
||||
syn = { version = "1.0.3", features = ["full"] }
|
||||
syn = { version = "1.0.56", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum RuntimeFlavor {
|
||||
@@ -278,11 +278,29 @@ fn parse_knobs(
|
||||
|
||||
let config = config.build()?;
|
||||
|
||||
// If type mismatch occurs, the current rustc points to the last statement.
|
||||
let (last_stmt_start_span, last_stmt_end_span) = {
|
||||
let mut last_stmt = input
|
||||
.block
|
||||
.stmts
|
||||
.last()
|
||||
.map(ToTokens::into_token_stream)
|
||||
.unwrap_or_default()
|
||||
.into_iter();
|
||||
// `Span` on stable Rust has a limitation that only points to the first
|
||||
// token, not the whole tokens. We can work around this limitation by
|
||||
// using the first/last span of the tokens like
|
||||
// `syn::Error::new_spanned` does.
|
||||
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
|
||||
let end = last_stmt.last().map_or(start, |t| t.span());
|
||||
(start, end)
|
||||
};
|
||||
|
||||
let mut rt = match config.flavor {
|
||||
RuntimeFlavor::CurrentThread => quote! {
|
||||
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
},
|
||||
RuntimeFlavor::Threaded => quote! {
|
||||
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
},
|
||||
};
|
||||
@@ -302,7 +320,8 @@ fn parse_knobs(
|
||||
};
|
||||
|
||||
let body = &input.block;
|
||||
input.block = syn::parse_quote! {
|
||||
let brace_token = input.block.brace_token;
|
||||
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
|
||||
{
|
||||
#rt
|
||||
.enable_all()
|
||||
@@ -310,7 +329,9 @@ fn parse_knobs(
|
||||
.unwrap()
|
||||
.block_on(async #body)
|
||||
}
|
||||
};
|
||||
})
|
||||
.unwrap();
|
||||
input.block.brace_token = brace_token;
|
||||
|
||||
let result = quote! {
|
||||
#header
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
# 0.1.6 (May 14, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- stream: implement `Error` and `Display` for `BroadcastStreamRecvError` ([#3745])
|
||||
|
||||
### Fixed
|
||||
|
||||
- stream: avoid yielding in `AllFuture` and `AnyFuture` ([#3625])
|
||||
|
||||
[#3745]: https://github.com/tokio-rs/tokio/pull/3745
|
||||
[#3625]: https://github.com/tokio-rs/tokio/pull/3625
|
||||
|
||||
# 0.1.5 (March 20, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -6,13 +6,13 @@ name = "tokio-stream"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-stream/0.1.5/tokio_stream"
|
||||
documentation = "https://docs.rs/tokio-stream/0.1.6/tokio_stream"
|
||||
description = """
|
||||
Utilities to work with `Stream` and `tokio`.
|
||||
"""
|
||||
@@ -31,7 +31,7 @@ signal = ["tokio/signal"]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.6.3", optional = true }
|
||||
tokio-util = { version = "0.6.3", path = "../tokio-util", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
|
||||
|
||||
@@ -605,10 +605,10 @@ mod rand {
|
||||
/// Fast random number generate
|
||||
///
|
||||
/// Implement xorshift64+: 2 32-bit xorshift sequences added together.
|
||||
/// Shift triplet [17,7,16] was calculated as indicated in Marsaglia's
|
||||
/// Xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
|
||||
/// Shift triplet `[17,7,16]` was calculated as indicated in Marsaglia's
|
||||
/// Xorshift paper: <https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf>
|
||||
/// This generator passes the SmallCrush suite, part of TestU01 framework:
|
||||
/// http://simul.iro.umontreal.ca/testu01/tu01.html
|
||||
/// <http://simul.iro.umontreal.ca/testu01/tu01.html>
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FastRand {
|
||||
one: Cell<u32>,
|
||||
|
||||
@@ -27,6 +27,16 @@ pub enum BroadcastStreamRecvError {
|
||||
Lagged(u64),
|
||||
}
|
||||
|
||||
impl fmt::Display for BroadcastStreamRecvError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BroadcastStreamRecvError::Lagged(amt) => write!(f, "channel lagged by {}", amt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for BroadcastStreamRecvError {}
|
||||
|
||||
async fn make_future<T: Clone>(mut rx: Receiver<T>) -> (Result<T, RecvError>, Receiver<T>) {
|
||||
let result = rx.recv().await;
|
||||
(result, rx)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::diverging_sub_expression)]
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -89,12 +89,12 @@ fn size_overflow() {
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(usize::max_value(), Some(usize::max_value()))
|
||||
(usize::MAX, Some(usize::MAX))
|
||||
}
|
||||
}
|
||||
|
||||
let m1 = Monster;
|
||||
let m2 = Monster;
|
||||
let m = m1.chain(m2);
|
||||
assert_eq!(m.size_hint(), (usize::max_value(), None));
|
||||
assert_eq!(m.size_hint(), (usize::MAX, None));
|
||||
}
|
||||
|
||||
@@ -72,12 +72,12 @@ fn size_overflow() {
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(usize::max_value(), Some(usize::max_value()))
|
||||
(usize::MAX, Some(usize::MAX))
|
||||
}
|
||||
}
|
||||
|
||||
let m1 = Monster;
|
||||
let m2 = Monster;
|
||||
let m = m1.merge(m2);
|
||||
assert_eq!(m.size_hint(), (usize::max_value(), None));
|
||||
assert_eq!(m.size_hint(), (usize::MAX, None));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# 0.4.2 (May 14, 2021)
|
||||
|
||||
- test: add `assert_elapsed!` macro ([#3728])
|
||||
|
||||
[#3728]: https://github.com/tokio-rs/tokio/pull/3728
|
||||
|
||||
# 0.4.1 (March 10, 2021)
|
||||
|
||||
- Fix `io::Mock` to be `Send` and `Sync` ([#3594])
|
||||
|
||||
@@ -6,13 +6,13 @@ name = "tokio-test"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-test/0.4.1/tokio_test"
|
||||
documentation = "https://docs.rs/tokio-test/0.4.2/tokio_test"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
|
||||
@@ -259,3 +259,37 @@ macro_rules! assert_err {
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts that an exact duration has elapsed since since the start instant ±1ms.
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::{self, Instant};
|
||||
/// use std::time::Duration;
|
||||
/// use tokio_test::assert_elapsed;
|
||||
/// # async fn test_time_passed() {
|
||||
///
|
||||
/// let start = Instant::now();
|
||||
/// let dur = Duration::from_millis(50);
|
||||
/// time::sleep(dur).await;
|
||||
/// assert_elapsed!(start, dur);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// This 1ms buffer is required because Tokio's hashed-wheel timer has finite time resolution and
|
||||
/// will not always sleep for the exact interval.
|
||||
#[macro_export]
|
||||
macro_rules! assert_elapsed {
|
||||
($start:expr, $dur:expr) => {{
|
||||
let elapsed = $start.elapsed();
|
||||
// type ascription improves compiler error when wrong type is passed
|
||||
let lower: std::time::Duration = $dur;
|
||||
|
||||
// Handles ms rounding
|
||||
assert!(
|
||||
elapsed >= lower && elapsed <= lower + std::time::Duration::from_millis(1),
|
||||
"actual = {:?}, expected = {:?}",
|
||||
elapsed,
|
||||
lower
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
# 0.6.7 (May 14, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- udp: make `UdpFramed` take `Borrow<UdpSocket>` ([#3451])
|
||||
- compat: implement `AsRawFd`/`AsRawHandle` for `Compat<T>` ([#3765])
|
||||
|
||||
[#3451]: https://github.com/tokio-rs/tokio/pull/3451
|
||||
[#3765]: https://github.com/tokio-rs/tokio/pull/3765
|
||||
|
||||
# 0.6.6 (April 12, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -6,13 +6,13 @@ name = "tokio-util"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.6.x" git tag.
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-util/0.6.6/tokio_util"
|
||||
documentation = "https://docs.rs/tokio-util/0.6.7/tokio_util"
|
||||
description = """
|
||||
Additional utilities for working with Tokio.
|
||||
"""
|
||||
@@ -35,7 +35,7 @@ rt = ["tokio/rt"]
|
||||
__docs_rs = ["futures-util"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0.0", features = ["sync"] }
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["sync"] }
|
||||
|
||||
bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
@@ -47,9 +47,9 @@ pin-project-lite = "0.2.0"
|
||||
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", features = ["full"] }
|
||||
tokio-test = { version = "0.4.0" }
|
||||
tokio-stream = { version = "0.1" }
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.4.0", path = "../tokio-test" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
|
||||
async-stream = "0.3.0"
|
||||
futures = "0.3.0"
|
||||
|
||||
@@ -215,3 +215,17 @@ where
|
||||
tokio::io::AsyncWrite::poll_shutdown(self.project().inner, cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl<T: std::os::unix::io::AsRawFd> std::os::unix::io::AsRawFd for Compat<T> {
|
||||
fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
|
||||
self.inner.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl<T: std::os::windows::io::AsRawHandle> std::os::windows::io::AsRawHandle for Compat<T> {
|
||||
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
|
||||
self.inner.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ use std::time::Duration;
|
||||
|
||||
mod wheel;
|
||||
|
||||
#[doc(inline)]
|
||||
pub mod delay_queue;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use delay_queue::DelayQueue;
|
||||
|
||||
// ===== Internal utils =====
|
||||
|
||||
+35
-20
@@ -6,9 +6,12 @@ use tokio::{io::ReadBuf, net::UdpSocket};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
};
|
||||
use std::{io, mem::MaybeUninit};
|
||||
|
||||
/// A unified [`Stream`] and [`Sink`] interface to an underlying `UdpSocket`, using
|
||||
@@ -32,10 +35,10 @@ use std::{io, mem::MaybeUninit};
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
|
||||
#[must_use = "sinks do nothing unless polled"]
|
||||
#[cfg_attr(docsrs, doc(all(feature = "codec", feature = "udp")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "codec", feature = "udp"))))]
|
||||
#[derive(Debug)]
|
||||
pub struct UdpFramed<C> {
|
||||
socket: UdpSocket,
|
||||
pub struct UdpFramed<C, T = UdpSocket> {
|
||||
socket: T,
|
||||
codec: C,
|
||||
rd: BytesMut,
|
||||
wr: BytesMut,
|
||||
@@ -48,7 +51,13 @@ pub struct UdpFramed<C> {
|
||||
const INITIAL_RD_CAPACITY: usize = 64 * 1024;
|
||||
const INITIAL_WR_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
impl<C, T> Unpin for UdpFramed<C, T> {}
|
||||
|
||||
impl<C, T> Stream for UdpFramed<C, T>
|
||||
where
|
||||
T: Borrow<UdpSocket>,
|
||||
C: Decoder,
|
||||
{
|
||||
type Item = Result<(C::Item, SocketAddr), C::Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
@@ -79,7 +88,7 @@ impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
let buf = &mut *(pin.rd.chunk_mut() as *mut _ as *mut [MaybeUninit<u8>]);
|
||||
let mut read = ReadBuf::uninit(buf);
|
||||
let ptr = read.filled().as_ptr();
|
||||
let res = ready!(Pin::new(&mut pin.socket).poll_recv_from(cx, &mut read));
|
||||
let res = ready!(pin.socket.borrow().poll_recv_from(cx, &mut read));
|
||||
|
||||
assert_eq!(ptr, read.filled().as_ptr());
|
||||
let addr = res?;
|
||||
@@ -93,7 +102,11 @@ impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
|
||||
impl<I, C, T> Sink<(I, SocketAddr)> for UdpFramed<C, T>
|
||||
where
|
||||
T: Borrow<UdpSocket>,
|
||||
C: Encoder<I>,
|
||||
{
|
||||
type Error = C::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
@@ -125,13 +138,13 @@ impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
|
||||
}
|
||||
|
||||
let Self {
|
||||
ref mut socket,
|
||||
ref socket,
|
||||
ref mut out_addr,
|
||||
ref mut wr,
|
||||
..
|
||||
} = *self;
|
||||
|
||||
let n = ready!(socket.poll_send_to(cx, &wr, *out_addr))?;
|
||||
let n = ready!(socket.borrow().poll_send_to(cx, &wr, *out_addr))?;
|
||||
|
||||
let wrote_all = n == self.wr.len();
|
||||
self.wr.clear();
|
||||
@@ -156,11 +169,14 @@ impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> UdpFramed<C> {
|
||||
impl<C, T> UdpFramed<C, T>
|
||||
where
|
||||
T: Borrow<UdpSocket>,
|
||||
{
|
||||
/// Create a new `UdpFramed` backed by the given socket and codec.
|
||||
///
|
||||
/// See struct level documentation for more details.
|
||||
pub fn new(socket: UdpSocket, codec: C) -> UdpFramed<C> {
|
||||
pub fn new(socket: T, codec: C) -> UdpFramed<C, T> {
|
||||
Self {
|
||||
socket,
|
||||
codec,
|
||||
@@ -180,27 +196,21 @@ impl<C> UdpFramed<C> {
|
||||
/// Care should be taken to not tamper with the underlying stream of data
|
||||
/// coming in as it may corrupt the stream of frames otherwise being worked
|
||||
/// with.
|
||||
pub fn get_ref(&self) -> &UdpSocket {
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.socket
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
/// `Framed`.
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by `Framed`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Care should be taken to not tamper with the underlying stream of data
|
||||
/// coming in as it may corrupt the stream of frames otherwise being worked
|
||||
/// with.
|
||||
pub fn get_mut(&mut self) -> &mut UdpSocket {
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.socket
|
||||
}
|
||||
|
||||
/// Consumes the `Framed`, returning its underlying I/O stream.
|
||||
pub fn into_inner(self) -> UdpSocket {
|
||||
self.socket
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying codec wrapped by
|
||||
/// `Framed`.
|
||||
///
|
||||
@@ -228,4 +238,9 @@ impl<C> UdpFramed<C> {
|
||||
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.rd
|
||||
}
|
||||
|
||||
/// Consumes the `Framed`, returning its underlying I/O stream.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.socket
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#![cfg(not(feature = "full"))]
|
||||
compile_error!("run tokio-util tests with `--features full`");
|
||||
@@ -6,9 +6,9 @@ use tokio_util::sync::PollSemaphore;
|
||||
|
||||
type SemRet = Option<OwnedSemaphorePermit>;
|
||||
|
||||
fn semaphore_poll<'a>(
|
||||
sem: &'a mut PollSemaphore,
|
||||
) -> tokio_test::task::Spawn<impl Future<Output = SemRet> + 'a> {
|
||||
fn semaphore_poll(
|
||||
sem: &mut PollSemaphore,
|
||||
) -> tokio_test::task::Spawn<impl Future<Output = SemRet> + '_> {
|
||||
let fut = futures::future::poll_fn(move |cx| sem.poll_acquire(cx));
|
||||
tokio_test::task::spawn(fut)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use futures::future::try_join;
|
||||
use futures::future::FutureExt;
|
||||
use futures::sink::SinkExt;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg_attr(any(target_os = "macos", target_os = "ios"), allow(unused_assignments))]
|
||||
#[tokio::test]
|
||||
@@ -101,3 +102,31 @@ async fn send_framed_lines_codec() -> std::io::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn framed_half() -> std::io::Result<()> {
|
||||
let a_soc = Arc::new(UdpSocket::bind("127.0.0.1:0").await?);
|
||||
let b_soc = a_soc.clone();
|
||||
|
||||
let a_addr = a_soc.local_addr()?;
|
||||
let b_addr = b_soc.local_addr()?;
|
||||
|
||||
let mut a = UdpFramed::new(a_soc, ByteCodec);
|
||||
let mut b = UdpFramed::new(b_soc, LinesCodec::new());
|
||||
|
||||
let msg = b"1\r\n2\r\n3\r\n".to_vec();
|
||||
a.send((&msg, b_addr)).await?;
|
||||
|
||||
let msg = b"4\r\n5\r\n6\r\n".to_vec();
|
||||
a.send((&msg, b_addr)).await?;
|
||||
|
||||
assert_eq!(b.next().await.unwrap().unwrap(), ("1".to_string(), a_addr));
|
||||
assert_eq!(b.next().await.unwrap().unwrap(), ("2".to_string(), a_addr));
|
||||
assert_eq!(b.next().await.unwrap().unwrap(), ("3".to_string(), a_addr));
|
||||
|
||||
assert_eq!(b.next().await.unwrap().unwrap(), ("4".to_string(), a_addr));
|
||||
assert_eq!(b.next().await.unwrap().unwrap(), ("5".to_string(), a_addr));
|
||||
assert_eq!(b.next().await.unwrap().unwrap(), ("6".to_string(), a_addr));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,127 @@
|
||||
# 1.7.2 (July 6, 2021)
|
||||
|
||||
Forward ports 1.5.1 fixes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: remotely abort tasks on `JoinHandle::abort` ([#3934])
|
||||
|
||||
[#3934]: https://github.com/tokio-rs/tokio/pull/3934
|
||||
|
||||
# 1.7.1 (June 18, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: fix early task shutdown during runtime shutdown ([#3870])
|
||||
|
||||
[#3870]: https://github.com/tokio-rs/tokio/pull/3870
|
||||
|
||||
# 1.7.0 (June 15, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- net: add named pipes on windows ([#3760])
|
||||
- net: add `TcpSocket` from `std::net::TcpStream` conversion ([#3838])
|
||||
- sync: add `receiver_count` to `watch::Sender` ([#3729])
|
||||
- sync: export `sync::notify::Notified` future publicly ([#3840])
|
||||
- tracing: instrument task wakers ([#3836])
|
||||
|
||||
### Fixed
|
||||
|
||||
- macros: suppress `clippy::default_numeric_fallback` lint in generated code ([#3831])
|
||||
- runtime: immediately drop new tasks when runtime is shut down ([#3752])
|
||||
- sync: deprecate unused `mpsc::RecvError` type ([#3833])
|
||||
|
||||
### Documented
|
||||
|
||||
- io: clarify EOF condition for `AsyncReadExt::read_buf` ([#3850])
|
||||
- io: clarify limits on return values of `AsyncWrite::poll_write` ([#3820])
|
||||
- sync: add examples to Semaphore ([#3808])
|
||||
|
||||
[#3729]: https://github.com/tokio-rs/tokio/pull/3729
|
||||
[#3752]: https://github.com/tokio-rs/tokio/pull/3752
|
||||
[#3760]: https://github.com/tokio-rs/tokio/pull/3760
|
||||
[#3808]: https://github.com/tokio-rs/tokio/pull/3808
|
||||
[#3820]: https://github.com/tokio-rs/tokio/pull/3820
|
||||
[#3831]: https://github.com/tokio-rs/tokio/pull/3831
|
||||
[#3833]: https://github.com/tokio-rs/tokio/pull/3833
|
||||
[#3836]: https://github.com/tokio-rs/tokio/pull/3836
|
||||
[#3838]: https://github.com/tokio-rs/tokio/pull/3838
|
||||
[#3840]: https://github.com/tokio-rs/tokio/pull/3840
|
||||
[#3850]: https://github.com/tokio-rs/tokio/pull/3850
|
||||
|
||||
# 1.6.3 (July 6, 2021)
|
||||
|
||||
Forward ports 1.5.1 fixes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: remotely abort tasks on `JoinHandle::abort` ([#3934])
|
||||
|
||||
[#3934]: https://github.com/tokio-rs/tokio/pull/3934
|
||||
|
||||
# 1.6.2 (June 14, 2021)
|
||||
|
||||
### Fixes
|
||||
|
||||
- test: sub-ms `time:advance` regression introduced in 1.6 ([#3852])
|
||||
|
||||
[#3852]: https://github.com/tokio-rs/tokio/pull/3852
|
||||
|
||||
# 1.6.1 (May 28, 2021)
|
||||
|
||||
This release reverts [#3518] because it doesn't work on some kernels due to
|
||||
a kernel bug. ([#3803])
|
||||
|
||||
[#3518]: https://github.com/tokio-rs/tokio/issues/3518
|
||||
[#3803]: https://github.com/tokio-rs/tokio/issues/3803
|
||||
|
||||
# 1.6.0 (May 14, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- fs: try doing a non-blocking read before punting to the threadpool ([#3518])
|
||||
- io: add `write_all_buf` to `AsyncWriteExt` ([#3737])
|
||||
- io: implement `AsyncSeek` for `BufReader`, `BufWriter`, and `BufStream` ([#3491])
|
||||
- net: support non-blocking vectored I/O ([#3761])
|
||||
- sync: add `mpsc::Sender::{reserve_owned, try_reserve_owned}` ([#3704])
|
||||
- sync: add a `MutexGuard::map` method that returns a `MappedMutexGuard` ([#2472])
|
||||
- time: add getter for Interval's period ([#3705])
|
||||
|
||||
### Fixed
|
||||
|
||||
- io: wake pending writers on `DuplexStream` close ([#3756])
|
||||
- process: avoid redundant effort to reap orphan processes ([#3743])
|
||||
- signal: use `std::os::raw::c_int` instead of `libc::c_int` on public API ([#3774])
|
||||
- sync: preserve permit state in `notify_waiters` ([#3660])
|
||||
- task: update `JoinHandle` panic message ([#3727])
|
||||
- time: prevent `time::advance` from going too far ([#3712])
|
||||
|
||||
### Documented
|
||||
|
||||
- net: hide `net::unix::datagram` module from docs ([#3775])
|
||||
- process: updated example ([#3748])
|
||||
- sync: `Barrier` doc should use task, not thread ([#3780])
|
||||
- task: update documentation on `block_in_place` ([#3753])
|
||||
|
||||
[#2472]: https://github.com/tokio-rs/tokio/pull/2472
|
||||
[#3491]: https://github.com/tokio-rs/tokio/pull/3491
|
||||
[#3518]: https://github.com/tokio-rs/tokio/pull/3518
|
||||
[#3660]: https://github.com/tokio-rs/tokio/pull/3660
|
||||
[#3704]: https://github.com/tokio-rs/tokio/pull/3704
|
||||
[#3705]: https://github.com/tokio-rs/tokio/pull/3705
|
||||
[#3712]: https://github.com/tokio-rs/tokio/pull/3712
|
||||
[#3727]: https://github.com/tokio-rs/tokio/pull/3727
|
||||
[#3737]: https://github.com/tokio-rs/tokio/pull/3737
|
||||
[#3743]: https://github.com/tokio-rs/tokio/pull/3743
|
||||
[#3748]: https://github.com/tokio-rs/tokio/pull/3748
|
||||
[#3753]: https://github.com/tokio-rs/tokio/pull/3753
|
||||
[#3756]: https://github.com/tokio-rs/tokio/pull/3756
|
||||
[#3761]: https://github.com/tokio-rs/tokio/pull/3761
|
||||
[#3774]: https://github.com/tokio-rs/tokio/pull/3774
|
||||
[#3775]: https://github.com/tokio-rs/tokio/pull/3775
|
||||
[#3780]: https://github.com/tokio-rs/tokio/pull/3780
|
||||
|
||||
# 1.5.1 (July 6, 2021)
|
||||
|
||||
### Fixed
|
||||
@@ -27,6 +151,7 @@
|
||||
- rt: fix panic in `JoinHandle::abort()` when called from other threads ([#3672])
|
||||
- sync: don't panic in `oneshot::try_recv` ([#3674])
|
||||
- sync: fix notifications getting dropped on receiver drop ([#3652])
|
||||
- sync: fix `Semaphore` permit overflow calculation ([#3644])
|
||||
|
||||
### Documented
|
||||
|
||||
|
||||
+7
-2
@@ -7,12 +7,12 @@ name = "tokio"
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.0.x" git tag.
|
||||
version = "1.5.1"
|
||||
version = "1.7.2"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/tokio/1.5.0/tokio/"
|
||||
documentation = "https://docs.rs/tokio/1.7.2/tokio/"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
description = """
|
||||
@@ -54,6 +54,7 @@ net = [
|
||||
"mio/tcp",
|
||||
"mio/udp",
|
||||
"mio/uds",
|
||||
"winapi/namedpipeapi",
|
||||
]
|
||||
process = [
|
||||
"bytes",
|
||||
@@ -115,6 +116,9 @@ version = "0.3.8"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.ntapi]
|
||||
version = "0.3.6"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = { version = "0.4.0", path = "../tokio-test" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
@@ -123,6 +127,7 @@ proptest = "1"
|
||||
rand = "0.8.0"
|
||||
tempfile = "3.1.0"
|
||||
async-stream = "0.3"
|
||||
socket2 = "0.4"
|
||||
|
||||
[target.'cfg(loom)'.dev-dependencies]
|
||||
loom = { version = "0.5", features = ["futures", "checkpoint"] }
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
//! Types which are documented locally in the Tokio crate, but does not actually
|
||||
//! live here.
|
||||
//!
|
||||
//! **Note** this module is only visible on docs.rs, you cannot use it directly
|
||||
//! in your own code.
|
||||
|
||||
/// The name of a type which is not defined here.
|
||||
///
|
||||
/// This is typically used as an alias for another type, like so:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// /// See [some::other::location](https://example.com).
|
||||
/// type DEFINED_ELSEWHERE = crate::doc::NotDefinedHere;
|
||||
/// ```
|
||||
///
|
||||
/// This type is uninhabitable like the [`never` type] to ensure that no one
|
||||
/// will ever accidentally use it.
|
||||
///
|
||||
/// [`never` type]: https://doc.rust-lang.org/std/primitive.never.html
|
||||
pub enum NotDefinedHere {}
|
||||
|
||||
pub mod os;
|
||||
pub mod winapi;
|
||||
@@ -0,0 +1,26 @@
|
||||
//! See [std::os](https://doc.rust-lang.org/std/os/index.html).
|
||||
|
||||
/// Platform-specific extensions to `std` for Windows.
|
||||
///
|
||||
/// See [std::os::windows](https://doc.rust-lang.org/std/os/windows/index.html).
|
||||
pub mod windows {
|
||||
/// Windows-specific extensions to general I/O primitives.
|
||||
///
|
||||
/// See [std::os::windows::io](https://doc.rust-lang.org/std/os/windows/io/index.html).
|
||||
pub mod io {
|
||||
/// See [std::os::windows::io::RawHandle](https://doc.rust-lang.org/std/os/windows/io/type.RawHandle.html)
|
||||
pub type RawHandle = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [std::os::windows::io::AsRawHandle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html)
|
||||
pub trait AsRawHandle {
|
||||
/// See [std::os::windows::io::FromRawHandle::from_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.AsRawHandle.html#tymethod.as_raw_handle)
|
||||
fn as_raw_handle(&self) -> RawHandle;
|
||||
}
|
||||
|
||||
/// See [std::os::windows::io::FromRawHandle](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawHandle.html)
|
||||
pub trait FromRawHandle {
|
||||
/// See [std::os::windows::io::FromRawHandle::from_raw_handle](https://doc.rust-lang.org/std/os/windows/io/trait.FromRawHandle.html#tymethod.from_raw_handle)
|
||||
unsafe fn from_raw_handle(handle: RawHandle) -> Self;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//! See [winapi].
|
||||
//!
|
||||
//! [winapi]: https://docs.rs/winapi
|
||||
|
||||
/// See [winapi::shared](https://docs.rs/winapi/*/winapi/shared/index.html).
|
||||
pub mod shared {
|
||||
/// See [winapi::shared::winerror](https://docs.rs/winapi/*/winapi/shared/winerror/index.html).
|
||||
#[allow(non_camel_case_types)]
|
||||
pub mod winerror {
|
||||
/// See [winapi::shared::winerror::ERROR_ACCESS_DENIED][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_ACCESS_DENIED.html
|
||||
pub type ERROR_ACCESS_DENIED = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [winapi::shared::winerror::ERROR_PIPE_BUSY][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_PIPE_BUSY.html
|
||||
pub type ERROR_PIPE_BUSY = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [winapi::shared::winerror::ERROR_MORE_DATA][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/shared/winerror/constant.ERROR_MORE_DATA.html
|
||||
pub type ERROR_MORE_DATA = crate::doc::NotDefinedHere;
|
||||
}
|
||||
}
|
||||
|
||||
/// See [winapi::um](https://docs.rs/winapi/*/winapi/um/index.html).
|
||||
pub mod um {
|
||||
/// See [winapi::um::winbase](https://docs.rs/winapi/*/winapi/um/winbase/index.html).
|
||||
#[allow(non_camel_case_types)]
|
||||
pub mod winbase {
|
||||
/// See [winapi::um::winbase::PIPE_TYPE_MESSAGE][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_TYPE_MESSAGE.html
|
||||
pub type PIPE_TYPE_MESSAGE = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [winapi::um::winbase::PIPE_TYPE_BYTE][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_TYPE_BYTE.html
|
||||
pub type PIPE_TYPE_BYTE = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [winapi::um::winbase::PIPE_CLIENT_END][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_CLIENT_END.html
|
||||
pub type PIPE_CLIENT_END = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [winapi::um::winbase::PIPE_SERVER_END][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.PIPE_SERVER_END.html
|
||||
pub type PIPE_SERVER_END = crate::doc::NotDefinedHere;
|
||||
|
||||
/// See [winapi::um::winbase::SECURITY_IDENTIFICATION][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/um/winbase/constant.SECURITY_IDENTIFICATION.html
|
||||
pub type SECURITY_IDENTIFICATION = crate::doc::NotDefinedHere;
|
||||
}
|
||||
|
||||
/// See [winapi::um::minwinbase](https://docs.rs/winapi/*/winapi/um/minwinbase/index.html).
|
||||
#[allow(non_camel_case_types)]
|
||||
pub mod minwinbase {
|
||||
/// See [winapi::um::minwinbase::SECURITY_ATTRIBUTES][winapi]
|
||||
///
|
||||
/// [winapi]: https://docs.rs/winapi/*/winapi/um/minwinbase/constant.SECURITY_ATTRIBUTES.html
|
||||
pub type SECURITY_ATTRIBUTES = crate::doc::NotDefinedHere;
|
||||
}
|
||||
}
|
||||
@@ -22,3 +22,14 @@ cfg_sync! {
|
||||
mod block_on;
|
||||
pub(crate) use block_on::block_on;
|
||||
}
|
||||
|
||||
cfg_trace! {
|
||||
mod trace;
|
||||
pub(crate) use trace::InstrumentedFuture as Future;
|
||||
}
|
||||
|
||||
cfg_not_trace! {
|
||||
cfg_rt! {
|
||||
pub(crate) use std::future::Future;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use std::future::Future;
|
||||
|
||||
pub(crate) trait InstrumentedFuture: Future {
|
||||
fn id(&self) -> Option<tracing::Id>;
|
||||
}
|
||||
|
||||
impl<F: Future> InstrumentedFuture for tracing::instrument::Instrumented<F> {
|
||||
fn id(&self) -> Option<tracing::Id> {
|
||||
self.span().id()
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,11 @@ use std::task::{Context, Poll};
|
||||
pub trait AsyncWrite {
|
||||
/// Attempt to write bytes from `buf` into the object.
|
||||
///
|
||||
/// On success, returns `Poll::Ready(Ok(num_bytes_written))`.
|
||||
/// On success, returns `Poll::Ready(Ok(num_bytes_written))`. If successful,
|
||||
/// then it must be guaranteed that `n <= buf.len()`. A return value of `0`
|
||||
/// typically means that the underlying object is no longer able to accept
|
||||
/// bytes and will likely not be able to in the future as well, or that the
|
||||
/// buffer provided is empty.
|
||||
///
|
||||
/// If the object is not ready for writing, the method returns
|
||||
/// `Poll::Pending` and arranges for the current task (via
|
||||
|
||||
@@ -273,7 +273,7 @@ cfg_not_rt! {
|
||||
/// This function panics if there is no current reactor set, or if the `rt`
|
||||
/// feature flag is not enabled.
|
||||
pub(super) fn current() -> Self {
|
||||
panic!(crate::util::error::CONTEXT_MISSING_ERROR)
|
||||
panic!("{}", crate::util::error::CONTEXT_MISSING_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ cfg_io_driver! {
|
||||
/// that it will receive task notifications on readiness. This is the lowest
|
||||
/// level API for integrating with a reactor.
|
||||
///
|
||||
/// The association between an I/O resource is made by calling [`new`]. Once
|
||||
/// the association is established, it remains established until the
|
||||
/// The association between an I/O resource is made by calling
|
||||
/// [`new_with_interest_and_handle`].
|
||||
/// Once the association is established, it remains established until the
|
||||
/// registration instance is dropped.
|
||||
///
|
||||
/// A registration instance represents two separate readiness streams. One
|
||||
@@ -36,7 +37,7 @@ cfg_io_driver! {
|
||||
/// stream. The write readiness event stream is only for `Ready::writable()`
|
||||
/// events.
|
||||
///
|
||||
/// [`new`]: method@Self::new
|
||||
/// [`new_with_interest_and_handle`]: method@Self::new_with_interest_and_handle
|
||||
/// [`poll_read_ready`]: method@Self::poll_read_ready`
|
||||
/// [`poll_write_ready`]: method@Self::poll_write_ready`
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -10,10 +10,10 @@ cfg_io_driver! {
|
||||
/// [`std::io::Write`] traits with the reactor that drives it.
|
||||
///
|
||||
/// `PollEvented` uses [`Registration`] internally to take a type that
|
||||
/// implements [`mio::Evented`] as well as [`std::io::Read`] and or
|
||||
/// implements [`mio::event::Source`] as well as [`std::io::Read`] and or
|
||||
/// [`std::io::Write`] and associate it with a reactor that will drive it.
|
||||
///
|
||||
/// Once the [`mio::Evented`] type is wrapped by `PollEvented`, it can be
|
||||
/// Once the [`mio::event::Source`] type is wrapped by `PollEvented`, it can be
|
||||
/// used from within the future's execution model. As such, the
|
||||
/// `PollEvented` type provides [`AsyncRead`] and [`AsyncWrite`]
|
||||
/// implementations using the underlying I/O resource as well as readiness
|
||||
@@ -40,13 +40,13 @@ cfg_io_driver! {
|
||||
/// [`poll_read_ready`] again will also indicate read readiness.
|
||||
///
|
||||
/// When the operation is attempted and is unable to succeed due to the I/O
|
||||
/// resource not being ready, the caller must call [`clear_read_ready`] or
|
||||
/// [`clear_write_ready`]. This clears the readiness state until a new
|
||||
/// resource not being ready, the caller must call `clear_read_ready` or
|
||||
/// `clear_write_ready`. This clears the readiness state until a new
|
||||
/// readiness event is received.
|
||||
///
|
||||
/// This allows the caller to implement additional functions. For example,
|
||||
/// [`TcpListener`] implements poll_accept by using [`poll_read_ready`] and
|
||||
/// [`clear_read_ready`].
|
||||
/// `clear_read_ready`.
|
||||
///
|
||||
/// ## Platform-specific events
|
||||
///
|
||||
@@ -54,17 +54,11 @@ cfg_io_driver! {
|
||||
/// These events are included as part of the read readiness event stream. The
|
||||
/// write readiness event stream is only for `Ready::writable()` events.
|
||||
///
|
||||
/// [`std::io::Read`]: trait@std::io::Read
|
||||
/// [`std::io::Write`]: trait@std::io::Write
|
||||
/// [`AsyncRead`]: trait@AsyncRead
|
||||
/// [`AsyncWrite`]: trait@AsyncWrite
|
||||
/// [`mio::Evented`]: trait@mio::Evented
|
||||
/// [`Registration`]: struct@Registration
|
||||
/// [`TcpListener`]: struct@crate::net::TcpListener
|
||||
/// [`clear_read_ready`]: method@Self::clear_read_ready
|
||||
/// [`clear_write_ready`]: method@Self::clear_write_ready
|
||||
/// [`poll_read_ready`]: method@Self::poll_read_ready
|
||||
/// [`poll_write_ready`]: method@Self::poll_write_ready
|
||||
/// [`AsyncRead`]: crate::io::AsyncRead
|
||||
/// [`AsyncWrite`]: crate::io::AsyncWrite
|
||||
/// [`TcpListener`]: crate::net::TcpListener
|
||||
/// [`poll_read_ready`]: Registration::poll_read_ready
|
||||
/// [`poll_write_ready`]: Registration::poll_write_ready
|
||||
pub(crate) struct PollEvented<E: Source> {
|
||||
io: Option<E>,
|
||||
registration: Registration,
|
||||
|
||||
@@ -108,6 +108,8 @@ cfg_io_util! {
|
||||
/// This function does not provide any guarantees about whether it
|
||||
/// completes immediately or asynchronously
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// If the return value of this method is `Ok(n)`, then it must be
|
||||
/// guaranteed that `0 <= n <= buf.len()`. A nonzero `n` value indicates
|
||||
/// that the buffer `buf` has been filled in with `n` bytes of data from
|
||||
@@ -180,9 +182,14 @@ cfg_io_util! {
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// On a successful read, the number of read bytes is returned. If the
|
||||
/// supplied buffer is not empty and the function returns `Ok(0)` then
|
||||
/// the source has reached an "end-of-file" event.
|
||||
/// A nonzero `n` value indicates that the buffer `buf` has been filled
|
||||
/// in with `n` bytes of data from this source. If `n` is `0`, then it
|
||||
/// can indicate one of two scenarios:
|
||||
///
|
||||
/// 1. This reader has reached its "end of file" and will likely no longer
|
||||
/// be able to produce bytes. Note that this does not mean that the
|
||||
/// reader will *always* no longer be able to produce bytes.
|
||||
/// 2. The buffer specified had a remaining capacity of zero.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -579,7 +586,7 @@ cfg_io_util! {
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut reader = Cursor::new(vec![0x80, 0, 0, 0, 0, 0, 0, 0]);
|
||||
///
|
||||
/// assert_eq!(i64::min_value(), reader.read_i64().await?);
|
||||
/// assert_eq!(i64::MIN, reader.read_i64().await?);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
@@ -659,7 +666,7 @@ cfg_io_util! {
|
||||
/// 0, 0, 0, 0, 0, 0, 0, 0
|
||||
/// ]);
|
||||
///
|
||||
/// assert_eq!(i128::min_value(), reader.read_i128().await?);
|
||||
/// assert_eq!(i128::MIN, reader.read_i128().await?);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::io::util::flush::{flush, Flush};
|
||||
use crate::io::util::shutdown::{shutdown, Shutdown};
|
||||
use crate::io::util::write::{write, Write};
|
||||
use crate::io::util::write_all::{write_all, WriteAll};
|
||||
use crate::io::util::write_all_buf::{write_all_buf, WriteAllBuf};
|
||||
use crate::io::util::write_buf::{write_buf, WriteBuf};
|
||||
use crate::io::util::write_int::{
|
||||
WriteI128, WriteI128Le, WriteI16, WriteI16Le, WriteI32, WriteI32Le, WriteI64, WriteI64Le,
|
||||
@@ -159,7 +160,6 @@ cfg_io_util! {
|
||||
write_vectored(self, bufs)
|
||||
}
|
||||
|
||||
|
||||
/// Writes a buffer into this writer, advancing the buffer's internal
|
||||
/// cursor.
|
||||
///
|
||||
@@ -197,10 +197,11 @@ cfg_io_util! {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`] implements `Read` and [`Cursor<&[u8]>`] implements [`Buf`]:
|
||||
/// [`File`] implements [`AsyncWrite`] and [`Cursor`]`<&[u8]>` implements [`Buf`]:
|
||||
///
|
||||
/// [`File`]: crate::fs::File
|
||||
/// [`Buf`]: bytes::Buf
|
||||
/// [`Cursor`]: std::io::Cursor
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::io::{self, AsyncWriteExt};
|
||||
@@ -233,6 +234,59 @@ cfg_io_util! {
|
||||
write_buf(self, src)
|
||||
}
|
||||
|
||||
/// Attempts to write an entire buffer into this writer
|
||||
///
|
||||
/// Equivalent to:
|
||||
///
|
||||
/// ```ignore
|
||||
/// async fn write_all_buf(&mut self, buf: impl Buf) -> Result<(), io::Error> {
|
||||
/// while buf.has_remaining() {
|
||||
/// self.write_buf(&mut buf).await?;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This method will continuously call [`write`] until
|
||||
/// [`buf.has_remaining()`](bytes::Buf::has_remaining) returns false. This method will not
|
||||
/// return until the entire buffer has been successfully written or an error occurs. The
|
||||
/// first error generated will be returned.
|
||||
///
|
||||
/// The buffer is advanced after each chunk is successfully written. After failure,
|
||||
/// `src.chunk()` will return the chunk that failed to write.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`] implements [`AsyncWrite`] and [`Cursor`]`<&[u8]>` implements [`Buf`]:
|
||||
///
|
||||
/// [`File`]: crate::fs::File
|
||||
/// [`Buf`]: bytes::Buf
|
||||
/// [`Cursor`]: std::io::Cursor
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::io::{self, AsyncWriteExt};
|
||||
/// use tokio::fs::File;
|
||||
///
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut file = File::create("foo.txt").await?;
|
||||
/// let mut buffer = Cursor::new(b"data to write");
|
||||
///
|
||||
/// file.write_all_buf(&mut buffer).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`write`]: AsyncWriteExt::write
|
||||
fn write_all_buf<'a, B>(&'a mut self, src: &'a mut B) -> WriteAllBuf<'a, Self, B>
|
||||
where
|
||||
Self: Sized + Unpin,
|
||||
B: Buf,
|
||||
{
|
||||
write_all_buf(self, src)
|
||||
}
|
||||
|
||||
/// Attempts to write an entire buffer into this writer.
|
||||
///
|
||||
/// Equivalent to:
|
||||
@@ -567,8 +621,8 @@ cfg_io_util! {
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut writer = Vec::new();
|
||||
///
|
||||
/// writer.write_i64(i64::min_value()).await?;
|
||||
/// writer.write_i64(i64::max_value()).await?;
|
||||
/// writer.write_i64(i64::MIN).await?;
|
||||
/// writer.write_i64(i64::MAX).await?;
|
||||
///
|
||||
/// assert_eq!(writer, b"\x80\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff");
|
||||
/// Ok(())
|
||||
@@ -645,7 +699,7 @@ cfg_io_util! {
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut writer = Vec::new();
|
||||
///
|
||||
/// writer.write_i128(i128::min_value()).await?;
|
||||
/// writer.write_i128(i128::MIN).await?;
|
||||
///
|
||||
/// assert_eq!(writer, vec![
|
||||
/// 0x80, 0, 0, 0, 0, 0, 0, 0,
|
||||
@@ -876,8 +930,8 @@ cfg_io_util! {
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut writer = Vec::new();
|
||||
///
|
||||
/// writer.write_i64_le(i64::min_value()).await?;
|
||||
/// writer.write_i64_le(i64::max_value()).await?;
|
||||
/// writer.write_i64_le(i64::MIN).await?;
|
||||
/// writer.write_i64_le(i64::MAX).await?;
|
||||
///
|
||||
/// assert_eq!(writer, b"\x00\x00\x00\x00\x00\x00\x00\x80\xff\xff\xff\xff\xff\xff\xff\x7f");
|
||||
/// Ok(())
|
||||
@@ -954,7 +1008,7 @@ cfg_io_util! {
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut writer = Vec::new();
|
||||
///
|
||||
/// writer.write_i128_le(i128::min_value()).await?;
|
||||
/// writer.write_i128_le(i128::MIN).await?;
|
||||
///
|
||||
/// assert_eq!(writer, vec![
|
||||
/// 0, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::io::util::DEFAULT_BUF_SIZE;
|
||||
use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
|
||||
use crate::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::io::{self, SeekFrom};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{cmp, fmt};
|
||||
use std::{cmp, fmt, mem};
|
||||
|
||||
pin_project! {
|
||||
/// The `BufReader` struct adds buffering to any reader.
|
||||
@@ -30,6 +30,7 @@ pin_project! {
|
||||
pub(super) buf: Box<[u8]>,
|
||||
pub(super) pos: usize,
|
||||
pub(super) cap: usize,
|
||||
pub(super) seek_state: SeekState,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +49,7 @@ impl<R: AsyncRead> BufReader<R> {
|
||||
buf: buffer.into_boxed_slice(),
|
||||
pos: 0,
|
||||
cap: 0,
|
||||
seek_state: SeekState::Init,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +143,122 @@ impl<R: AsyncRead> AsyncBufRead for BufReader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(super) enum SeekState {
|
||||
/// start_seek has not been called.
|
||||
Init,
|
||||
/// start_seek has been called, but poll_complete has not yet been called.
|
||||
Start(SeekFrom),
|
||||
/// Waiting for completion of the first poll_complete in the `n.checked_sub(remainder).is_none()` branch.
|
||||
PendingOverflowed(i64),
|
||||
/// Waiting for completion of poll_complete.
|
||||
Pending,
|
||||
}
|
||||
|
||||
/// Seek to an offset, in bytes, in the underlying reader.
|
||||
///
|
||||
/// The position used for seeking with `SeekFrom::Current(_)` is the
|
||||
/// position the underlying reader would be at if the `BufReader` had no
|
||||
/// internal buffer.
|
||||
///
|
||||
/// Seeking always discards the internal buffer, even if the seek position
|
||||
/// would otherwise fall within it. This guarantees that calling
|
||||
/// `.into_inner()` immediately after a seek yields the underlying reader
|
||||
/// at the same position.
|
||||
///
|
||||
/// See [`AsyncSeek`] for more details.
|
||||
///
|
||||
/// Note: In the edge case where you're seeking with `SeekFrom::Current(n)`
|
||||
/// where `n` minus the internal buffer length overflows an `i64`, two
|
||||
/// seeks will be performed instead of one. If the second seek returns
|
||||
/// `Err`, the underlying reader will be left at the same position it would
|
||||
/// have if you called `seek` with `SeekFrom::Current(0)`.
|
||||
impl<R: AsyncRead + AsyncSeek> AsyncSeek for BufReader<R> {
|
||||
fn start_seek(self: Pin<&mut Self>, pos: SeekFrom) -> io::Result<()> {
|
||||
// We needs to call seek operation multiple times.
|
||||
// And we should always call both start_seek and poll_complete,
|
||||
// as start_seek alone cannot guarantee that the operation will be completed.
|
||||
// poll_complete receives a Context and returns a Poll, so it cannot be called
|
||||
// inside start_seek.
|
||||
*self.project().seek_state = SeekState::Start(pos);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
let res = match mem::replace(self.as_mut().project().seek_state, SeekState::Init) {
|
||||
SeekState::Init => {
|
||||
// 1.x AsyncSeek recommends calling poll_complete before start_seek.
|
||||
// We don't have to guarantee that the value returned by
|
||||
// poll_complete called without start_seek is correct,
|
||||
// so we'll return 0.
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
SeekState::Start(SeekFrom::Current(n)) => {
|
||||
let remainder = (self.cap - self.pos) as i64;
|
||||
// it should be safe to assume that remainder fits within an i64 as the alternative
|
||||
// means we managed to allocate 8 exbibytes and that's absurd.
|
||||
// But it's not out of the realm of possibility for some weird underlying reader to
|
||||
// support seeking by i64::MIN so we need to handle underflow when subtracting
|
||||
// remainder.
|
||||
if let Some(offset) = n.checked_sub(remainder) {
|
||||
self.as_mut()
|
||||
.get_pin_mut()
|
||||
.start_seek(SeekFrom::Current(offset))?;
|
||||
self.as_mut().get_pin_mut().poll_complete(cx)?
|
||||
} else {
|
||||
// seek backwards by our remainder, and then by the offset
|
||||
self.as_mut()
|
||||
.get_pin_mut()
|
||||
.start_seek(SeekFrom::Current(-remainder))?;
|
||||
if self.as_mut().get_pin_mut().poll_complete(cx)?.is_pending() {
|
||||
*self.as_mut().project().seek_state = SeekState::PendingOverflowed(n);
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust/pull/61157#issuecomment-495932676
|
||||
self.as_mut().discard_buffer();
|
||||
|
||||
self.as_mut()
|
||||
.get_pin_mut()
|
||||
.start_seek(SeekFrom::Current(n))?;
|
||||
self.as_mut().get_pin_mut().poll_complete(cx)?
|
||||
}
|
||||
}
|
||||
SeekState::PendingOverflowed(n) => {
|
||||
if self.as_mut().get_pin_mut().poll_complete(cx)?.is_pending() {
|
||||
*self.as_mut().project().seek_state = SeekState::PendingOverflowed(n);
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust/pull/61157#issuecomment-495932676
|
||||
self.as_mut().discard_buffer();
|
||||
|
||||
self.as_mut()
|
||||
.get_pin_mut()
|
||||
.start_seek(SeekFrom::Current(n))?;
|
||||
self.as_mut().get_pin_mut().poll_complete(cx)?
|
||||
}
|
||||
SeekState::Start(pos) => {
|
||||
// Seeking with Start/End doesn't care about our buffer length.
|
||||
self.as_mut().get_pin_mut().start_seek(pos)?;
|
||||
self.as_mut().get_pin_mut().poll_complete(cx)?
|
||||
}
|
||||
SeekState::Pending => self.as_mut().get_pin_mut().poll_complete(cx)?,
|
||||
};
|
||||
|
||||
match res {
|
||||
Poll::Ready(res) => {
|
||||
self.discard_buffer();
|
||||
Poll::Ready(Ok(res))
|
||||
}
|
||||
Poll::Pending => {
|
||||
*self.as_mut().project().seek_state = SeekState::Pending;
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncRead + AsyncWrite> AsyncWrite for BufReader<R> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::io::util::{BufReader, BufWriter};
|
||||
use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
|
||||
use crate::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::io::{self, SeekFrom};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
@@ -94,9 +94,11 @@ impl<RW> From<BufWriter<BufReader<RW>>> for BufStream<RW> {
|
||||
buf: rbuf,
|
||||
pos,
|
||||
cap,
|
||||
seek_state: rseek_state,
|
||||
},
|
||||
buf: wbuf,
|
||||
written,
|
||||
seek_state: wseek_state,
|
||||
} = b;
|
||||
|
||||
BufStream {
|
||||
@@ -105,10 +107,12 @@ impl<RW> From<BufWriter<BufReader<RW>>> for BufStream<RW> {
|
||||
inner,
|
||||
buf: wbuf,
|
||||
written,
|
||||
seek_state: wseek_state,
|
||||
},
|
||||
buf: rbuf,
|
||||
pos,
|
||||
cap,
|
||||
seek_state: rseek_state,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -142,6 +146,34 @@ impl<RW: AsyncRead + AsyncWrite> AsyncRead for BufStream<RW> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Seek to an offset, in bytes, in the underlying stream.
|
||||
///
|
||||
/// The position used for seeking with `SeekFrom::Current(_)` is the
|
||||
/// position the underlying stream would be at if the `BufStream` had no
|
||||
/// internal buffer.
|
||||
///
|
||||
/// Seeking always discards the internal buffer, even if the seek position
|
||||
/// would otherwise fall within it. This guarantees that calling
|
||||
/// `.into_inner()` immediately after a seek yields the underlying reader
|
||||
/// at the same position.
|
||||
///
|
||||
/// See [`AsyncSeek`] for more details.
|
||||
///
|
||||
/// Note: In the edge case where you're seeking with `SeekFrom::Current(n)`
|
||||
/// where `n` minus the internal buffer length overflows an `i64`, two
|
||||
/// seeks will be performed instead of one. If the second seek returns
|
||||
/// `Err`, the underlying reader will be left at the same position it would
|
||||
/// have if you called `seek` with `SeekFrom::Current(0)`.
|
||||
impl<RW: AsyncRead + AsyncWrite + AsyncSeek> AsyncSeek for BufStream<RW> {
|
||||
fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
|
||||
self.project().inner.start_seek(position)
|
||||
}
|
||||
|
||||
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
self.project().inner.poll_complete(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<RW: AsyncRead + AsyncWrite> AsyncBufRead for BufStream<RW> {
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
self.project().inner.poll_fill_buf(cx)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::io::util::DEFAULT_BUF_SIZE;
|
||||
use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
|
||||
use crate::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
use std::io::{self, SeekFrom, Write};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
@@ -34,6 +34,7 @@ pin_project! {
|
||||
pub(super) inner: W,
|
||||
pub(super) buf: Vec<u8>,
|
||||
pub(super) written: usize,
|
||||
pub(super) seek_state: SeekState,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +51,7 @@ impl<W: AsyncWrite> BufWriter<W> {
|
||||
inner,
|
||||
buf: Vec::with_capacity(cap),
|
||||
written: 0,
|
||||
seek_state: SeekState::Init,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +144,62 @@ impl<W: AsyncWrite> AsyncWrite for BufWriter<W> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(super) enum SeekState {
|
||||
/// start_seek has not been called.
|
||||
Init,
|
||||
/// start_seek has been called, but poll_complete has not yet been called.
|
||||
Start(SeekFrom),
|
||||
/// Waiting for completion of poll_complete.
|
||||
Pending,
|
||||
}
|
||||
|
||||
/// Seek to the offset, in bytes, in the underlying writer.
|
||||
///
|
||||
/// Seeking always writes out the internal buffer before seeking.
|
||||
impl<W: AsyncWrite + AsyncSeek> AsyncSeek for BufWriter<W> {
|
||||
fn start_seek(self: Pin<&mut Self>, pos: SeekFrom) -> io::Result<()> {
|
||||
// We need to flush the internal buffer before seeking.
|
||||
// It receives a `Context` and returns a `Poll`, so it cannot be called
|
||||
// inside `start_seek`.
|
||||
*self.project().seek_state = SeekState::Start(pos);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
let pos = match self.seek_state {
|
||||
SeekState::Init => {
|
||||
return self.project().inner.poll_complete(cx);
|
||||
}
|
||||
SeekState::Start(pos) => Some(pos),
|
||||
SeekState::Pending => None,
|
||||
};
|
||||
|
||||
// Flush the internal buffer before seeking.
|
||||
ready!(self.as_mut().flush_buf(cx))?;
|
||||
|
||||
let mut me = self.project();
|
||||
if let Some(pos) = pos {
|
||||
// Ensure previous seeks have finished before starting a new one
|
||||
ready!(me.inner.as_mut().poll_complete(cx))?;
|
||||
if let Err(e) = me.inner.as_mut().start_seek(pos) {
|
||||
*me.seek_state = SeekState::Init;
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
}
|
||||
match me.inner.poll_complete(cx) {
|
||||
Poll::Ready(res) => {
|
||||
*me.seek_state = SeekState::Init;
|
||||
Poll::Ready(res)
|
||||
}
|
||||
Poll::Pending => {
|
||||
*me.seek_state = SeekState::Pending;
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: AsyncWrite + AsyncRead> AsyncRead for BufWriter<W> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
|
||||
@@ -104,6 +104,7 @@ where
|
||||
/// # Return value
|
||||
///
|
||||
/// Returns a tuple of bytes copied `a` to `b` and bytes copied `b` to `a`.
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
|
||||
pub async fn copy_bidirectional<A, B>(a: &mut A, b: &mut B) -> Result<(u64, u64), std::io::Error>
|
||||
where
|
||||
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
|
||||
|
||||
@@ -128,7 +128,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(Some(mem::replace(me.buf, String::new()))))
|
||||
Poll::Ready(Ok(Some(mem::take(me.buf))))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@ use std::{
|
||||
/// that can be used as in-memory IO types. Writing to one of the pairs will
|
||||
/// allow that data to be read from the other, and vice versa.
|
||||
///
|
||||
/// # Closing a `DuplexStream`
|
||||
///
|
||||
/// If one end of the `DuplexStream` channel is dropped, any pending reads on
|
||||
/// the other side will continue to read data until the buffer is drained, then
|
||||
/// they will signal EOF by returning 0 bytes. Any writes to the other side,
|
||||
/// including pending ones (that are waiting for free space in the buffer) will
|
||||
/// return `Err(BrokenPipe)` immediately.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@@ -37,6 +45,7 @@ use std::{
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
|
||||
pub struct DuplexStream {
|
||||
read: Arc<Mutex<Pipe>>,
|
||||
write: Arc<Mutex<Pipe>>,
|
||||
@@ -72,6 +81,7 @@ struct Pipe {
|
||||
///
|
||||
/// The `max_buf_size` argument is the maximum amount of bytes that can be
|
||||
/// written to a side before the write returns `Poll::Pending`.
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
|
||||
pub fn duplex(max_buf_size: usize) -> (DuplexStream, DuplexStream) {
|
||||
let one = Arc::new(Mutex::new(Pipe::new(max_buf_size)));
|
||||
let two = Arc::new(Mutex::new(Pipe::new(max_buf_size)));
|
||||
@@ -134,7 +144,8 @@ impl AsyncWrite for DuplexStream {
|
||||
impl Drop for DuplexStream {
|
||||
fn drop(&mut self) {
|
||||
// notify the other side of the closure
|
||||
self.write.lock().close();
|
||||
self.write.lock().close_write();
|
||||
self.read.lock().close_read();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,12 +162,21 @@ impl Pipe {
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
fn close_write(&mut self) {
|
||||
self.is_closed = true;
|
||||
// needs to notify any readers that no more data will come
|
||||
if let Some(waker) = self.read_waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
fn close_read(&mut self) {
|
||||
self.is_closed = true;
|
||||
// needs to notify any writers that they have to abort
|
||||
if let Some(waker) = self.write_waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Pipe {
|
||||
@@ -217,7 +237,7 @@ impl AsyncWrite for Pipe {
|
||||
mut self: Pin<&mut Self>,
|
||||
_: &mut task::Context<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
self.close();
|
||||
self.close_write();
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ cfg_io_util! {
|
||||
mod write_vectored;
|
||||
mod write_all;
|
||||
mod write_buf;
|
||||
mod write_all_buf;
|
||||
mod write_int;
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ where
|
||||
{
|
||||
ReadLine {
|
||||
reader,
|
||||
buf: mem::replace(string, String::new()).into_bytes(),
|
||||
buf: mem::take(string).into_bytes(),
|
||||
output: string,
|
||||
read: 0,
|
||||
_pin: PhantomPinned,
|
||||
@@ -99,7 +99,7 @@ pub(super) fn read_line_internal<R: AsyncBufRead + ?Sized>(
|
||||
read: &mut usize,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let io_res = ready!(read_until_internal(reader, cx, b'\n', buf, read));
|
||||
let utf8_res = String::from_utf8(mem::replace(buf, Vec::new()));
|
||||
let utf8_res = String::from_utf8(mem::take(buf));
|
||||
|
||||
// At this point both buf and output are empty. The allocation is in utf8_res.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ pub(crate) fn read_to_string<'a, R>(
|
||||
where
|
||||
R: AsyncRead + ?Sized + Unpin,
|
||||
{
|
||||
let buf = mem::replace(string, String::new()).into_bytes();
|
||||
let buf = mem::take(string).into_bytes();
|
||||
ReadToString {
|
||||
reader,
|
||||
buf: VecWithInitialized::new(buf),
|
||||
|
||||
@@ -106,7 +106,7 @@ where
|
||||
me.buf.pop();
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(Some(mem::replace(me.buf, Vec::new()))))
|
||||
Poll::Ready(Ok(Some(mem::take(me.buf))))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use crate::io::AsyncWrite;
|
||||
|
||||
use bytes::Buf;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::marker::PhantomPinned;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
/// A future to write some of the buffer to an `AsyncWrite`.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct WriteAllBuf<'a, W, B> {
|
||||
writer: &'a mut W,
|
||||
buf: &'a mut B,
|
||||
#[pin]
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to write some bytes from the given `buf` to the writer in an
|
||||
/// asynchronous manner, returning a future.
|
||||
pub(crate) fn write_all_buf<'a, W, B>(writer: &'a mut W, buf: &'a mut B) -> WriteAllBuf<'a, W, B>
|
||||
where
|
||||
W: AsyncWrite + Unpin,
|
||||
B: Buf,
|
||||
{
|
||||
WriteAllBuf {
|
||||
writer,
|
||||
buf,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
impl<W, B> Future for WriteAllBuf<'_, W, B>
|
||||
where
|
||||
W: AsyncWrite + Unpin,
|
||||
B: Buf,
|
||||
{
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
let me = self.project();
|
||||
while me.buf.has_remaining() {
|
||||
let n = ready!(Pin::new(&mut *me.writer).poll_write(cx, me.buf.chunk())?);
|
||||
me.buf.advance(n);
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![deny(unused_must_use)]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
@@ -442,6 +443,28 @@ mod util;
|
||||
/// ```
|
||||
pub mod stream {}
|
||||
|
||||
// local re-exports of platform specific things, allowing for decent
|
||||
// documentation to be shimmed in on docs.rs
|
||||
|
||||
#[cfg(docsrs)]
|
||||
pub mod doc;
|
||||
|
||||
#[cfg(docsrs)]
|
||||
#[allow(unused)]
|
||||
pub(crate) use self::doc::os;
|
||||
|
||||
#[cfg(not(docsrs))]
|
||||
#[allow(unused)]
|
||||
pub(crate) use std::os;
|
||||
|
||||
#[cfg(docsrs)]
|
||||
#[allow(unused)]
|
||||
pub(crate) use self::doc::winapi;
|
||||
|
||||
#[cfg(all(not(docsrs), windows, feature = "net"))]
|
||||
#[allow(unused)]
|
||||
pub(crate) use ::winapi;
|
||||
|
||||
cfg_macros! {
|
||||
/// Implementation detail of the `select!` macro. This macro is **not**
|
||||
/// intended to be used as part of the public API and is permitted to
|
||||
@@ -453,15 +476,20 @@ cfg_macros! {
|
||||
#[cfg(feature = "rt-multi-thread")]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
#[doc(inline)]
|
||||
pub use tokio_macros::main;
|
||||
|
||||
#[cfg(feature = "rt-multi-thread")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
#[doc(inline)]
|
||||
pub use tokio_macros::test;
|
||||
|
||||
cfg_not_rt_multi_thread! {
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
#[doc(inline)]
|
||||
pub use tokio_macros::main_rt as main;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use tokio_macros::test_rt as test;
|
||||
}
|
||||
}
|
||||
@@ -469,7 +497,10 @@ cfg_macros! {
|
||||
// Always fail if rt is not enabled.
|
||||
cfg_not_rt! {
|
||||
#[cfg(not(test))]
|
||||
#[doc(inline)]
|
||||
pub use tokio_macros::main_fail as main;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use tokio_macros::test_fail as test;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -157,7 +157,6 @@ macro_rules! cfg_macros {
|
||||
$(
|
||||
#[cfg(feature = "macros")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
#[doc(inline)]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
@@ -183,6 +182,16 @@ macro_rules! cfg_net_unix {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_net_windows {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(all(any(docsrs, windows), feature = "net"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(windows, feature = "net"))))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_process {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
/// `select!` panics if all branches are disabled **and** there is no provided
|
||||
/// `else` branch. A branch is disabled when the provided `if` precondition
|
||||
/// returns `false` **or** when the pattern does not match the result of `<async
|
||||
/// expression>.
|
||||
/// expression>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -398,7 +398,7 @@ macro_rules! select {
|
||||
// set the appropriate bit in `disabled`.
|
||||
$(
|
||||
if !$c {
|
||||
let mask = 1 << $crate::count!( $($skip)* );
|
||||
let mask: util::Mask = 1 << $crate::count!( $($skip)* );
|
||||
disabled |= mask;
|
||||
}
|
||||
)*
|
||||
|
||||
@@ -46,3 +46,7 @@ cfg_net_unix! {
|
||||
pub use unix::listener::UnixListener;
|
||||
pub use unix::stream::UnixStream;
|
||||
}
|
||||
|
||||
cfg_net_windows! {
|
||||
pub mod windows;
|
||||
}
|
||||
|
||||
@@ -482,6 +482,48 @@ impl TcpSocket {
|
||||
let mio = self.inner.listen(backlog)?;
|
||||
TcpListener::new(mio)
|
||||
}
|
||||
|
||||
/// Converts a [`std::net::TcpStream`] into a `TcpSocket`. The provided
|
||||
/// socket must not have been connected prior to calling this function. This
|
||||
/// function is typically used together with crates such as [`socket2`] to
|
||||
/// configure socket options that are not available on `TcpSocket`.
|
||||
///
|
||||
/// [`std::net::TcpStream`]: struct@std::net::TcpStream
|
||||
/// [`socket2`]: https://docs.rs/socket2/
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::net::TcpSocket;
|
||||
/// use socket2::{Domain, Socket, Type};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// let socket2_socket = Socket::new(Domain::IPV4, Type::STREAM, None)?;
|
||||
///
|
||||
/// let socket = TcpSocket::from_std_stream(socket2_socket.into());
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn from_std_stream(std_stream: std::net::TcpStream) -> TcpSocket {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
|
||||
let raw_fd = std_stream.into_raw_fd();
|
||||
unsafe { TcpSocket::from_raw_fd(raw_fd) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||
|
||||
let raw_socket = std_stream.into_raw_socket();
|
||||
unsafe { TcpSocket::from_raw_socket(raw_socket) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TcpSocket {
|
||||
|
||||
@@ -563,6 +563,84 @@ impl TcpStream {
|
||||
.try_io(Interest::READABLE, || (&*self.io).read(buf))
|
||||
}
|
||||
|
||||
/// Try to read data from the stream into the provided buffers, returning
|
||||
/// how many bytes were read.
|
||||
///
|
||||
/// Data is copied to fill each buffer in order, with the final buffer
|
||||
/// written to possibly being only partially filled. This method behaves
|
||||
/// equivalently to a single call to [`try_read()`] with concatenated
|
||||
/// buffers.
|
||||
///
|
||||
/// Receives any pending data from the socket but does not wait for new data
|
||||
/// to arrive. On success, returns the number of bytes read. Because
|
||||
/// `try_read_vectored()` is non-blocking, the buffer does not have to be
|
||||
/// stored by the async task and can exist entirely on the stack.
|
||||
///
|
||||
/// Usually, [`readable()`] or [`ready()`] is used with this function.
|
||||
///
|
||||
/// [`try_read()`]: TcpStream::try_read()
|
||||
/// [`readable()`]: TcpStream::readable()
|
||||
/// [`ready()`]: TcpStream::ready()
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// If data is successfully read, `Ok(n)` is returned, where `n` is the
|
||||
/// number of bytes read. `Ok(0)` indicates the stream's read half is closed
|
||||
/// and will no longer yield data. If the stream is not ready to read data
|
||||
/// `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::TcpStream;
|
||||
/// use std::error::Error;
|
||||
/// use std::io::{self, IoSliceMut};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // Connect to a peer
|
||||
/// let stream = TcpStream::connect("127.0.0.1:8080").await?;
|
||||
///
|
||||
/// loop {
|
||||
/// // Wait for the socket to be readable
|
||||
/// stream.readable().await?;
|
||||
///
|
||||
/// // Creating the buffer **after** the `await` prevents it from
|
||||
/// // being stored in the async task.
|
||||
/// let mut buf_a = [0; 512];
|
||||
/// let mut buf_b = [0; 1024];
|
||||
/// let mut bufs = [
|
||||
/// IoSliceMut::new(&mut buf_a),
|
||||
/// IoSliceMut::new(&mut buf_b),
|
||||
/// ];
|
||||
///
|
||||
/// // Try to read data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match stream.try_read_vectored(&mut bufs) {
|
||||
/// Ok(0) => break,
|
||||
/// Ok(n) => {
|
||||
/// println!("read {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn try_read_vectored(&self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
use std::io::Read;
|
||||
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::READABLE, || (&*self.io).read_vectored(bufs))
|
||||
}
|
||||
|
||||
cfg_io_util! {
|
||||
/// Try to read data from the stream into the provided buffer, advancing the
|
||||
/// buffer's internal cursor, returning how many bytes were read.
|
||||
@@ -775,6 +853,68 @@ impl TcpStream {
|
||||
.try_io(Interest::WRITABLE, || (&*self.io).write(buf))
|
||||
}
|
||||
|
||||
/// Try to write several buffers to the stream, returning how many bytes
|
||||
/// were written.
|
||||
///
|
||||
/// Data is written from each buffer in order, with the final buffer read
|
||||
/// from possible being only partially consumed. This method behaves
|
||||
/// equivalently to a single call to [`try_write()`] with concatenated
|
||||
/// buffers.
|
||||
///
|
||||
/// This function is usually paired with `writable()`.
|
||||
///
|
||||
/// [`try_write()`]: TcpStream::try_write()
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// If data is successfully written, `Ok(n)` is returned, where `n` is the
|
||||
/// number of bytes written. If the stream is not ready to write data,
|
||||
/// `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::TcpStream;
|
||||
/// use std::error::Error;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // Connect to a peer
|
||||
/// let stream = TcpStream::connect("127.0.0.1:8080").await?;
|
||||
///
|
||||
/// let bufs = [io::IoSlice::new(b"hello "), io::IoSlice::new(b"world")];
|
||||
///
|
||||
/// loop {
|
||||
/// // Wait for the socket to be writable
|
||||
/// stream.writable().await?;
|
||||
///
|
||||
/// // Try to write data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match stream.try_write_vectored(&bufs) {
|
||||
/// Ok(n) => {
|
||||
/// break;
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn try_write_vectored(&self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
use std::io::Write;
|
||||
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::WRITABLE, || (&*self.io).write_vectored(bufs))
|
||||
}
|
||||
|
||||
/// Receives data on the socket from the remote address to which it is
|
||||
/// connected, without removing that data from the queue. On success,
|
||||
/// returns the number of bytes peeked.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! Unix domain socket utility types
|
||||
|
||||
// This module does not currently provide any public API, but it was
|
||||
// unintentionally defined as a public module. Hide it from the documentation
|
||||
// instead of changing it to a private module to avoid breakage.
|
||||
#[doc(hidden)]
|
||||
pub mod datagram;
|
||||
|
||||
pub(crate) mod listener;
|
||||
|
||||
@@ -271,6 +271,84 @@ impl UnixStream {
|
||||
.try_io(Interest::READABLE, || (&*self.io).read(buf))
|
||||
}
|
||||
|
||||
/// Try to read data from the stream into the provided buffers, returning
|
||||
/// how many bytes were read.
|
||||
///
|
||||
/// Data is copied to fill each buffer in order, with the final buffer
|
||||
/// written to possibly being only partially filled. This method behaves
|
||||
/// equivalently to a single call to [`try_read()`] with concatenated
|
||||
/// buffers.
|
||||
///
|
||||
/// Receives any pending data from the socket but does not wait for new data
|
||||
/// to arrive. On success, returns the number of bytes read. Because
|
||||
/// `try_read_vectored()` is non-blocking, the buffer does not have to be
|
||||
/// stored by the async task and can exist entirely on the stack.
|
||||
///
|
||||
/// Usually, [`readable()`] or [`ready()`] is used with this function.
|
||||
///
|
||||
/// [`try_read()`]: UnixStream::try_read()
|
||||
/// [`readable()`]: UnixStream::readable()
|
||||
/// [`ready()`]: UnixStream::ready()
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// If data is successfully read, `Ok(n)` is returned, where `n` is the
|
||||
/// number of bytes read. `Ok(0)` indicates the stream's read half is closed
|
||||
/// and will no longer yield data. If the stream is not ready to read data
|
||||
/// `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::UnixStream;
|
||||
/// use std::error::Error;
|
||||
/// use std::io::{self, IoSliceMut};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // Connect to a peer
|
||||
/// let dir = tempfile::tempdir().unwrap();
|
||||
/// let bind_path = dir.path().join("bind_path");
|
||||
/// let stream = UnixStream::connect(bind_path).await?;
|
||||
///
|
||||
/// loop {
|
||||
/// // Wait for the socket to be readable
|
||||
/// stream.readable().await?;
|
||||
///
|
||||
/// // Creating the buffer **after** the `await` prevents it from
|
||||
/// // being stored in the async task.
|
||||
/// let mut buf_a = [0; 512];
|
||||
/// let mut buf_b = [0; 1024];
|
||||
/// let mut bufs = [
|
||||
/// IoSliceMut::new(&mut buf_a),
|
||||
/// IoSliceMut::new(&mut buf_b),
|
||||
/// ];
|
||||
///
|
||||
/// // Try to read data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match stream.try_read_vectored(&mut bufs) {
|
||||
/// Ok(0) => break,
|
||||
/// Ok(n) => {
|
||||
/// println!("read {} bytes", n);
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn try_read_vectored(&self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::READABLE, || (&*self.io).read_vectored(bufs))
|
||||
}
|
||||
|
||||
cfg_io_util! {
|
||||
/// Try to read data from the stream into the provided buffer, advancing the
|
||||
/// buffer's internal cursor, returning how many bytes were read.
|
||||
@@ -487,6 +565,68 @@ impl UnixStream {
|
||||
.try_io(Interest::WRITABLE, || (&*self.io).write(buf))
|
||||
}
|
||||
|
||||
/// Try to write several buffers to the stream, returning how many bytes
|
||||
/// were written.
|
||||
///
|
||||
/// Data is written from each buffer in order, with the final buffer read
|
||||
/// from possible being only partially consumed. This method behaves
|
||||
/// equivalently to a single call to [`try_write()`] with concatenated
|
||||
/// buffers.
|
||||
///
|
||||
/// This function is usually paired with `writable()`.
|
||||
///
|
||||
/// [`try_write()`]: UnixStream::try_write()
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// If data is successfully written, `Ok(n)` is returned, where `n` is the
|
||||
/// number of bytes written. If the stream is not ready to write data,
|
||||
/// `Err(io::ErrorKind::WouldBlock)` is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::net::UnixStream;
|
||||
/// use std::error::Error;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // Connect to a peer
|
||||
/// let dir = tempfile::tempdir().unwrap();
|
||||
/// let bind_path = dir.path().join("bind_path");
|
||||
/// let stream = UnixStream::connect(bind_path).await?;
|
||||
///
|
||||
/// let bufs = [io::IoSlice::new(b"hello "), io::IoSlice::new(b"world")];
|
||||
///
|
||||
/// loop {
|
||||
/// // Wait for the socket to be writable
|
||||
/// stream.writable().await?;
|
||||
///
|
||||
/// // Try to write data, this may still fail with `WouldBlock`
|
||||
/// // if the readiness event is a false positive.
|
||||
/// match stream.try_write_vectored(&bufs) {
|
||||
/// Ok(n) => {
|
||||
/// break;
|
||||
/// }
|
||||
/// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
/// continue;
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// return Err(e.into());
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn try_write_vectored(&self, buf: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
||||
self.io
|
||||
.registration()
|
||||
.try_io(Interest::WRITABLE, || (&*self.io).write_vectored(buf))
|
||||
}
|
||||
|
||||
/// Creates new `UnixStream` from a `std::os::unix::net::UnixStream`.
|
||||
///
|
||||
/// This function is intended to be used to wrap a UnixStream from the
|
||||
|
||||
@@ -73,7 +73,7 @@ pub(crate) mod impl_linux {
|
||||
|
||||
// These paranoid checks should be optimized-out
|
||||
assert!(mem::size_of::<u32>() <= mem::size_of::<usize>());
|
||||
assert!(ucred_size <= u32::max_value() as usize);
|
||||
assert!(ucred_size <= u32::MAX as usize);
|
||||
|
||||
let mut ucred_size = ucred_size as socklen_t;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
//! Windows specific network types.
|
||||
|
||||
pub mod named_pipe;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -994,13 +994,22 @@ impl Child {
|
||||
/// If the caller wishes to explicitly control when the child's stdin
|
||||
/// handle is closed, they may `.take()` it before calling `.wait()`:
|
||||
///
|
||||
/// ```no_run
|
||||
/// ```
|
||||
/// # #[cfg(not(unix))]fn main(){}
|
||||
/// # #[cfg(unix)]
|
||||
/// use tokio::io::AsyncWriteExt;
|
||||
/// # #[cfg(unix)]
|
||||
/// use tokio::process::Command;
|
||||
/// # #[cfg(unix)]
|
||||
/// use std::process::Stdio;
|
||||
///
|
||||
/// # #[cfg(unix)]
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let mut child = Command::new("cat").spawn().unwrap();
|
||||
/// let mut child = Command::new("cat")
|
||||
/// .stdin(Stdio::piped())
|
||||
/// .spawn()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let mut stdin = child.stdin.take().unwrap();
|
||||
/// tokio::spawn(async move {
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
//! Process driver
|
||||
|
||||
use crate::park::Park;
|
||||
use crate::process::unix::orphan::ReapOrphanQueue;
|
||||
use crate::process::unix::GlobalOrphanQueue;
|
||||
use crate::signal::unix::driver::Driver as SignalDriver;
|
||||
use crate::signal::unix::{signal_with_handle, SignalKind};
|
||||
use crate::sync::watch;
|
||||
use crate::signal::unix::driver::{Driver as SignalDriver, Handle as SignalHandle};
|
||||
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
@@ -16,51 +13,20 @@ use std::time::Duration;
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Driver {
|
||||
park: SignalDriver,
|
||||
inner: CoreDriver<watch::Receiver<()>, GlobalOrphanQueue>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CoreDriver<S, Q> {
|
||||
sigchild: S,
|
||||
orphan_queue: Q,
|
||||
}
|
||||
|
||||
trait HasChanged {
|
||||
fn has_changed(&mut self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> HasChanged for watch::Receiver<T> {
|
||||
fn has_changed(&mut self) -> bool {
|
||||
self.try_has_changed().and_then(Result::ok).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl CoreDriver =====
|
||||
|
||||
impl<S, Q> CoreDriver<S, Q>
|
||||
where
|
||||
S: HasChanged,
|
||||
Q: ReapOrphanQueue,
|
||||
{
|
||||
fn process(&mut self) {
|
||||
if self.sigchild.has_changed() {
|
||||
self.orphan_queue.reap_orphans();
|
||||
}
|
||||
}
|
||||
signal_handle: SignalHandle,
|
||||
}
|
||||
|
||||
// ===== impl Driver =====
|
||||
|
||||
impl Driver {
|
||||
/// Creates a new signal `Driver` instance that delegates wakeups to `park`.
|
||||
pub(crate) fn new(park: SignalDriver) -> io::Result<Self> {
|
||||
let sigchild = signal_with_handle(SignalKind::child(), park.handle())?;
|
||||
let inner = CoreDriver {
|
||||
sigchild,
|
||||
orphan_queue: GlobalOrphanQueue,
|
||||
};
|
||||
pub(crate) fn new(park: SignalDriver) -> Self {
|
||||
let signal_handle = park.handle();
|
||||
|
||||
Ok(Self { park, inner })
|
||||
Self {
|
||||
park,
|
||||
signal_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +42,13 @@ impl Park for Driver {
|
||||
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
self.park.park()?;
|
||||
self.inner.process();
|
||||
GlobalOrphanQueue::reap_orphans(&self.signal_handle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
|
||||
self.park.park_timeout(duration)?;
|
||||
self.inner.process();
|
||||
GlobalOrphanQueue::reap_orphans(&self.signal_handle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -90,43 +56,3 @@ impl Park for Driver {
|
||||
self.park.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::process::unix::orphan::test::MockQueue;
|
||||
|
||||
struct MockStream {
|
||||
total_try_recv: usize,
|
||||
values: Vec<Option<()>>,
|
||||
}
|
||||
|
||||
impl MockStream {
|
||||
fn new(values: Vec<Option<()>>) -> Self {
|
||||
Self {
|
||||
total_try_recv: 0,
|
||||
values,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasChanged for MockStream {
|
||||
fn has_changed(&mut self) -> bool {
|
||||
self.total_try_recv += 1;
|
||||
self.values.remove(0).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_reap_if_no_signal() {
|
||||
let mut driver = CoreDriver {
|
||||
sigchild: MockStream::new(vec![None]),
|
||||
orphan_queue: MockQueue::<()>::new(),
|
||||
};
|
||||
|
||||
driver.process();
|
||||
|
||||
assert_eq!(1, driver.sigchild.total_try_recv);
|
||||
assert_eq!(0, driver.orphan_queue.total_reaps.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
pub(crate) mod driver;
|
||||
|
||||
pub(crate) mod orphan;
|
||||
use orphan::{OrphanQueue, OrphanQueueImpl, ReapOrphanQueue, Wait};
|
||||
use orphan::{OrphanQueue, OrphanQueueImpl, Wait};
|
||||
|
||||
mod reap;
|
||||
use reap::Reaper;
|
||||
@@ -32,6 +32,7 @@ use reap::Reaper;
|
||||
use crate::io::PollEvented;
|
||||
use crate::process::kill::Kill;
|
||||
use crate::process::SpawnedChild;
|
||||
use crate::signal::unix::driver::Handle as SignalHandle;
|
||||
use crate::signal::unix::{signal, Signal, SignalKind};
|
||||
|
||||
use mio::event::Source;
|
||||
@@ -73,9 +74,9 @@ impl fmt::Debug for GlobalOrphanQueue {
|
||||
}
|
||||
}
|
||||
|
||||
impl ReapOrphanQueue for GlobalOrphanQueue {
|
||||
fn reap_orphans(&self) {
|
||||
ORPHAN_QUEUE.reap_orphans()
|
||||
impl GlobalOrphanQueue {
|
||||
fn reap_orphans(handle: &SignalHandle) {
|
||||
ORPHAN_QUEUE.reap_orphans(handle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::loom::sync::{Mutex, MutexGuard};
|
||||
use crate::signal::unix::driver::Handle as SignalHandle;
|
||||
use crate::signal::unix::{signal_with_handle, SignalKind};
|
||||
use crate::sync::watch;
|
||||
use std::io;
|
||||
use std::process::ExitStatus;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// An interface for waiting on a process to exit.
|
||||
pub(crate) trait Wait {
|
||||
@@ -20,21 +23,8 @@ impl<T: Wait> Wait for &mut T {
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface for reaping a set of orphaned processes.
|
||||
pub(crate) trait ReapOrphanQueue {
|
||||
/// Attempts to reap every process in the queue, ignoring any errors and
|
||||
/// enqueueing any orphans which have not yet exited.
|
||||
fn reap_orphans(&self);
|
||||
}
|
||||
|
||||
impl<T: ReapOrphanQueue> ReapOrphanQueue for &T {
|
||||
fn reap_orphans(&self) {
|
||||
(**self).reap_orphans()
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface for queueing up an orphaned process so that it can be reaped.
|
||||
pub(crate) trait OrphanQueue<T>: ReapOrphanQueue {
|
||||
pub(crate) trait OrphanQueue<T> {
|
||||
/// Adds an orphan to the queue.
|
||||
fn push_orphan(&self, orphan: T);
|
||||
}
|
||||
@@ -48,50 +38,91 @@ impl<T, O: OrphanQueue<T>> OrphanQueue<T> for &O {
|
||||
/// An implementation of `OrphanQueue`.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OrphanQueueImpl<T> {
|
||||
sigchild: Mutex<Option<watch::Receiver<()>>>,
|
||||
queue: Mutex<Vec<T>>,
|
||||
}
|
||||
|
||||
impl<T> OrphanQueueImpl<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
sigchild: Mutex::new(None),
|
||||
queue: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn len(&self) -> usize {
|
||||
self.queue.lock().unwrap().len()
|
||||
self.queue.lock().len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Wait> OrphanQueue<T> for OrphanQueueImpl<T> {
|
||||
fn push_orphan(&self, orphan: T) {
|
||||
self.queue.lock().unwrap().push(orphan)
|
||||
pub(crate) fn push_orphan(&self, orphan: T)
|
||||
where
|
||||
T: Wait,
|
||||
{
|
||||
self.queue.lock().push(orphan)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Wait> ReapOrphanQueue for OrphanQueueImpl<T> {
|
||||
fn reap_orphans(&self) {
|
||||
let mut queue = self.queue.lock().unwrap();
|
||||
let queue = &mut *queue;
|
||||
/// Attempts to reap every process in the queue, ignoring any errors and
|
||||
/// enqueueing any orphans which have not yet exited.
|
||||
pub(crate) fn reap_orphans(&self, handle: &SignalHandle)
|
||||
where
|
||||
T: Wait,
|
||||
{
|
||||
// If someone else is holding the lock, they will be responsible for draining
|
||||
// the queue as necessary, so we can safely bail if that happens
|
||||
if let Some(mut sigchild_guard) = self.sigchild.try_lock() {
|
||||
match &mut *sigchild_guard {
|
||||
Some(sigchild) => {
|
||||
if sigchild.try_has_changed().and_then(Result::ok).is_some() {
|
||||
drain_orphan_queue(self.queue.lock());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let queue = self.queue.lock();
|
||||
|
||||
for i in (0..queue.len()).rev() {
|
||||
match queue[i].try_wait() {
|
||||
Ok(None) => {}
|
||||
Ok(Some(_)) | Err(_) => {
|
||||
// The stdlib handles interruption errors (EINTR) when polling a child process.
|
||||
// All other errors represent invalid inputs or pids that have already been
|
||||
// reaped, so we can drop the orphan in case an error is raised.
|
||||
queue.swap_remove(i);
|
||||
// Be lazy and only initialize the SIGCHLD listener if there
|
||||
// are any orphaned processes in the queue.
|
||||
if !queue.is_empty() {
|
||||
// An errors shouldn't really happen here, but if it does it
|
||||
// means that the signal driver isn't running, in
|
||||
// which case there isn't anything we can
|
||||
// register/initialize here, so we can try again later
|
||||
if let Ok(sigchild) = signal_with_handle(SignalKind::child(), &handle) {
|
||||
*sigchild_guard = Some(sigchild);
|
||||
drain_orphan_queue(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_orphan_queue<T>(mut queue: MutexGuard<'_, Vec<T>>)
|
||||
where
|
||||
T: Wait,
|
||||
{
|
||||
for i in (0..queue.len()).rev() {
|
||||
match queue[i].try_wait() {
|
||||
Ok(None) => {}
|
||||
Ok(Some(_)) | Err(_) => {
|
||||
// The stdlib handles interruption errors (EINTR) when polling a child process.
|
||||
// All other errors represent invalid inputs or pids that have already been
|
||||
// reaped, so we can drop the orphan in case an error is raised.
|
||||
queue.swap_remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(queue);
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(loom)))]
|
||||
pub(crate) mod test {
|
||||
use super::*;
|
||||
use crate::io::driver::Driver as IoDriver;
|
||||
use crate::signal::unix::driver::{Driver as SignalDriver, Handle as SignalHandle};
|
||||
use crate::sync::watch;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::io;
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
@@ -100,14 +131,12 @@ pub(crate) mod test {
|
||||
|
||||
pub(crate) struct MockQueue<W> {
|
||||
pub(crate) all_enqueued: RefCell<Vec<W>>,
|
||||
pub(crate) total_reaps: Cell<usize>,
|
||||
}
|
||||
|
||||
impl<W> MockQueue<W> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
all_enqueued: RefCell::new(Vec::new()),
|
||||
total_reaps: Cell::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,12 +147,6 @@ pub(crate) mod test {
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> ReapOrphanQueue for MockQueue<W> {
|
||||
fn reap_orphans(&self) {
|
||||
self.total_reaps.set(self.total_reaps.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
struct MockWait {
|
||||
total_waits: Rc<Cell<usize>>,
|
||||
num_wait_until_status: usize,
|
||||
@@ -191,27 +214,107 @@ pub(crate) mod test {
|
||||
|
||||
assert_eq!(orphanage.len(), 4);
|
||||
|
||||
orphanage.reap_orphans();
|
||||
drain_orphan_queue(orphanage.queue.lock());
|
||||
assert_eq!(orphanage.len(), 2);
|
||||
assert_eq!(first_waits.get(), 1);
|
||||
assert_eq!(second_waits.get(), 1);
|
||||
assert_eq!(third_waits.get(), 1);
|
||||
assert_eq!(fourth_waits.get(), 1);
|
||||
|
||||
orphanage.reap_orphans();
|
||||
drain_orphan_queue(orphanage.queue.lock());
|
||||
assert_eq!(orphanage.len(), 1);
|
||||
assert_eq!(first_waits.get(), 1);
|
||||
assert_eq!(second_waits.get(), 2);
|
||||
assert_eq!(third_waits.get(), 2);
|
||||
assert_eq!(fourth_waits.get(), 1);
|
||||
|
||||
orphanage.reap_orphans();
|
||||
drain_orphan_queue(orphanage.queue.lock());
|
||||
assert_eq!(orphanage.len(), 0);
|
||||
assert_eq!(first_waits.get(), 1);
|
||||
assert_eq!(second_waits.get(), 2);
|
||||
assert_eq!(third_waits.get(), 3);
|
||||
assert_eq!(fourth_waits.get(), 1);
|
||||
|
||||
orphanage.reap_orphans(); // Safe to reap when empty
|
||||
// Safe to reap when empty
|
||||
drain_orphan_queue(orphanage.queue.lock());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_reap_if_no_signal_received() {
|
||||
let (tx, rx) = watch::channel(());
|
||||
|
||||
let handle = SignalHandle::default();
|
||||
|
||||
let orphanage = OrphanQueueImpl::new();
|
||||
*orphanage.sigchild.lock() = Some(rx);
|
||||
|
||||
let orphan = MockWait::new(2);
|
||||
let waits = orphan.total_waits.clone();
|
||||
orphanage.push_orphan(orphan);
|
||||
|
||||
orphanage.reap_orphans(&handle);
|
||||
assert_eq!(waits.get(), 0);
|
||||
|
||||
orphanage.reap_orphans(&handle);
|
||||
assert_eq!(waits.get(), 0);
|
||||
|
||||
tx.send(()).unwrap();
|
||||
orphanage.reap_orphans(&handle);
|
||||
assert_eq!(waits.get(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_reap_if_signal_lock_held() {
|
||||
let handle = SignalHandle::default();
|
||||
|
||||
let orphanage = OrphanQueueImpl::new();
|
||||
let signal_guard = orphanage.sigchild.lock();
|
||||
|
||||
let orphan = MockWait::new(2);
|
||||
let waits = orphan.total_waits.clone();
|
||||
orphanage.push_orphan(orphan);
|
||||
|
||||
orphanage.reap_orphans(&handle);
|
||||
assert_eq!(waits.get(), 0);
|
||||
|
||||
drop(signal_guard);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_register_signal_if_queue_empty() {
|
||||
let signal_driver = IoDriver::new().and_then(SignalDriver::new).unwrap();
|
||||
let handle = signal_driver.handle();
|
||||
|
||||
let orphanage = OrphanQueueImpl::new();
|
||||
assert!(orphanage.sigchild.lock().is_none()); // Sanity
|
||||
|
||||
// No register when queue empty
|
||||
orphanage.reap_orphans(&handle);
|
||||
assert!(orphanage.sigchild.lock().is_none());
|
||||
|
||||
let orphan = MockWait::new(2);
|
||||
let waits = orphan.total_waits.clone();
|
||||
orphanage.push_orphan(orphan);
|
||||
|
||||
orphanage.reap_orphans(&handle);
|
||||
assert!(orphanage.sigchild.lock().is_some());
|
||||
assert_eq!(waits.get(), 1); // Eager reap when registering listener
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_nothing_if_signal_could_not_be_registered() {
|
||||
let handle = SignalHandle::default();
|
||||
|
||||
let orphanage = OrphanQueueImpl::new();
|
||||
assert!(orphanage.sigchild.lock().is_none());
|
||||
|
||||
let orphan = MockWait::new(2);
|
||||
let waits = orphan.total_waits.clone();
|
||||
orphanage.push_orphan(orphan);
|
||||
|
||||
// Signal handler has "gone away", nothing to register or reap
|
||||
orphanage.reap_orphans(&handle);
|
||||
assert!(orphanage.sigchild.lock().is_none());
|
||||
assert_eq!(waits.get(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,6 @@ mod test {
|
||||
assert!(grim.poll_unpin(&mut context).is_pending());
|
||||
assert_eq!(1, grim.signal.total_polls);
|
||||
assert_eq!(1, grim.total_waits);
|
||||
assert_eq!(0, grim.orphan_queue.total_reaps.get());
|
||||
assert!(grim.orphan_queue.all_enqueued.borrow().is_empty());
|
||||
|
||||
// Not yet exited, couldn't register interest the first time
|
||||
@@ -232,7 +231,6 @@ mod test {
|
||||
assert!(grim.poll_unpin(&mut context).is_pending());
|
||||
assert_eq!(3, grim.signal.total_polls);
|
||||
assert_eq!(3, grim.total_waits);
|
||||
assert_eq!(0, grim.orphan_queue.total_reaps.get());
|
||||
assert!(grim.orphan_queue.all_enqueued.borrow().is_empty());
|
||||
|
||||
// Exited
|
||||
@@ -245,7 +243,6 @@ mod test {
|
||||
}
|
||||
assert_eq!(4, grim.signal.total_polls);
|
||||
assert_eq!(4, grim.total_waits);
|
||||
assert_eq!(0, grim.orphan_queue.total_reaps.get());
|
||||
assert!(grim.orphan_queue.all_enqueued.borrow().is_empty());
|
||||
}
|
||||
|
||||
@@ -260,7 +257,6 @@ mod test {
|
||||
|
||||
grim.kill().unwrap();
|
||||
assert_eq!(1, grim.total_kills);
|
||||
assert_eq!(0, grim.orphan_queue.total_reaps.get());
|
||||
assert!(grim.orphan_queue.all_enqueued.borrow().is_empty());
|
||||
}
|
||||
|
||||
@@ -276,7 +272,6 @@ mod test {
|
||||
|
||||
drop(grim);
|
||||
|
||||
assert_eq!(0, queue.total_reaps.get());
|
||||
assert!(queue.all_enqueued.borrow().is_empty());
|
||||
}
|
||||
|
||||
@@ -294,7 +289,6 @@ mod test {
|
||||
let grim = Reaper::new(&mut mock, &queue, MockStream::new(vec![]));
|
||||
drop(grim);
|
||||
|
||||
assert_eq!(0, queue.total_reaps.get());
|
||||
assert_eq!(1, queue.all_enqueued.borrow().len());
|
||||
}
|
||||
|
||||
|
||||
@@ -84,13 +84,13 @@ unsafe impl Send for Entry {}
|
||||
|
||||
/// Scheduler state shared between threads.
|
||||
struct Shared {
|
||||
/// Remote run queue
|
||||
queue: Mutex<VecDeque<Entry>>,
|
||||
/// Remote run queue. None if the `Runtime` has been dropped.
|
||||
queue: Mutex<Option<VecDeque<Entry>>>,
|
||||
|
||||
/// Unpark the blocked thread
|
||||
/// Unpark the blocked thread.
|
||||
unpark: Box<dyn Unpark>,
|
||||
|
||||
// indicates whether the blocked on thread was woken
|
||||
/// Indicates whether the blocked on thread was woken.
|
||||
woken: AtomicBool,
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ impl<P: Park> BasicScheduler<P> {
|
||||
|
||||
let spawner = Spawner {
|
||||
shared: Arc::new(Shared {
|
||||
queue: Mutex::new(VecDeque::with_capacity(INITIAL_CAPACITY)),
|
||||
queue: Mutex::new(Some(VecDeque::with_capacity(INITIAL_CAPACITY))),
|
||||
unpark: unpark as Box<dyn Unpark>,
|
||||
woken: AtomicBool::new(false),
|
||||
}),
|
||||
@@ -351,18 +351,29 @@ impl<P: Park> Drop for BasicScheduler<P> {
|
||||
task.shutdown();
|
||||
}
|
||||
|
||||
// Drain remote queue
|
||||
for entry in scheduler.spawner.shared.queue.lock().drain(..) {
|
||||
match entry {
|
||||
Entry::Schedule(task) => {
|
||||
task.shutdown();
|
||||
}
|
||||
Entry::Release(..) => {
|
||||
// Do nothing, each entry in the linked list was *just*
|
||||
// dropped by the scheduler above.
|
||||
// Drain remote queue and set it to None
|
||||
let mut remote_queue = scheduler.spawner.shared.queue.lock();
|
||||
|
||||
// Using `Option::take` to replace the shared queue with `None`.
|
||||
if let Some(remote_queue) = remote_queue.take() {
|
||||
for entry in remote_queue {
|
||||
match entry {
|
||||
Entry::Schedule(task) => {
|
||||
task.shutdown();
|
||||
}
|
||||
Entry::Release(..) => {
|
||||
// Do nothing, each entry in the linked list was *just*
|
||||
// dropped by the scheduler above.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// By dropping the mutex lock after the full duration of the above loop,
|
||||
// any thread that sees the queue in the `None` state is guaranteed that
|
||||
// the runtime has fully shut down.
|
||||
//
|
||||
// The assert below is unrelated to this mutex.
|
||||
drop(remote_queue);
|
||||
|
||||
assert!(context.tasks.borrow().owned.is_empty());
|
||||
});
|
||||
@@ -381,7 +392,7 @@ impl Spawner {
|
||||
/// Spawns a future onto the thread pool
|
||||
pub(crate) fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F: crate::future::Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
let (task, handle) = task::joinable(future);
|
||||
@@ -390,7 +401,10 @@ impl Spawner {
|
||||
}
|
||||
|
||||
fn pop(&self) -> Option<Entry> {
|
||||
self.shared.queue.lock().pop_front()
|
||||
match self.shared.queue.lock().as_mut() {
|
||||
Some(queue) => queue.pop_front(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn waker_ref(&self) -> WakerRef<'_> {
|
||||
@@ -429,7 +443,19 @@ impl Schedule for Arc<Shared> {
|
||||
// safety: the task is inserted in the list in `bind`.
|
||||
unsafe { cx.tasks.borrow_mut().owned.remove(ptr) }
|
||||
} else {
|
||||
self.queue.lock().push_back(Entry::Release(ptr));
|
||||
// By sending an `Entry::Release` to the runtime, we ask the
|
||||
// runtime to remove this task from the linked list in
|
||||
// `Tasks::owned`.
|
||||
//
|
||||
// If the queue is `None`, then the task was already removed
|
||||
// from that list in the destructor of `BasicScheduler`. We do
|
||||
// not do anything in this case for the same reason that
|
||||
// `Entry::Release` messages are ignored in the remote queue
|
||||
// drain loop of `BasicScheduler`'s destructor.
|
||||
if let Some(queue) = self.queue.lock().as_mut() {
|
||||
queue.push_back(Entry::Release(ptr));
|
||||
}
|
||||
|
||||
self.unpark.unpark();
|
||||
// Returning `None` here prevents the task plumbing from being
|
||||
// freed. It is then up to the scheduler through the queue we
|
||||
@@ -445,8 +471,17 @@ impl Schedule for Arc<Shared> {
|
||||
cx.tasks.borrow_mut().queue.push_back(task);
|
||||
}
|
||||
_ => {
|
||||
self.queue.lock().push_back(Entry::Schedule(task));
|
||||
self.unpark.unpark();
|
||||
let mut guard = self.queue.lock();
|
||||
if let Some(queue) = guard.as_mut() {
|
||||
queue.push_back(Entry::Schedule(task));
|
||||
drop(guard);
|
||||
self.unpark.unpark();
|
||||
} else {
|
||||
// The runtime has shut down. We drop the new task
|
||||
// immediately.
|
||||
drop(guard);
|
||||
task.shutdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::loom::sync::{Arc, Condvar, Mutex};
|
||||
use crate::loom::thread;
|
||||
use crate::runtime::blocking::schedule::NoopSchedule;
|
||||
use crate::runtime::blocking::shutdown;
|
||||
use crate::runtime::blocking::task::BlockingTask;
|
||||
use crate::runtime::builder::ThreadNameFn;
|
||||
use crate::runtime::context;
|
||||
use crate::runtime::task::{self, JoinHandle};
|
||||
@@ -61,7 +60,7 @@ struct Shared {
|
||||
/// Prior to shutdown, we clean up JoinHandles by having each timed-out
|
||||
/// thread join on the previous timed-out thread. This is not strictly
|
||||
/// necessary but helps avoid Valgrind false positives, see
|
||||
/// https://github.com/tokio-rs/tokio/commit/646fbae76535e397ef79dbcaacb945d4c829f666
|
||||
/// <https://github.com/tokio-rs/tokio/commit/646fbae76535e397ef79dbcaacb945d4c829f666>
|
||||
/// for more information.
|
||||
last_exiting_thread: Option<thread::JoinHandle<()>>,
|
||||
/// This holds the JoinHandles for all running threads; on shutdown, the thread
|
||||
@@ -86,18 +85,6 @@ where
|
||||
rt.spawn_blocking(func)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn try_spawn_blocking<F, R>(func: F) -> Result<(), ()>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let rt = context::current().expect(CONTEXT_MISSING_ERROR);
|
||||
|
||||
let (task, _handle) = task::joinable(BlockingTask::new(func));
|
||||
rt.blocking_spawner.spawn(task, &rt)
|
||||
}
|
||||
|
||||
// ===== impl BlockingPool =====
|
||||
|
||||
impl BlockingPool {
|
||||
@@ -151,7 +138,7 @@ impl BlockingPool {
|
||||
self.spawner.inner.condvar.notify_all();
|
||||
|
||||
let last_exited_thread = std::mem::take(&mut shared.last_exiting_thread);
|
||||
let workers = std::mem::replace(&mut shared.worker_threads, HashMap::new());
|
||||
let workers = std::mem::take(&mut shared.worker_threads);
|
||||
|
||||
drop(shared);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ cfg_io_driver! {
|
||||
let io_handle = io_driver.handle();
|
||||
|
||||
let (signal_driver, signal_handle) = create_signal_driver(io_driver)?;
|
||||
let process_driver = create_process_driver(signal_driver)?;
|
||||
let process_driver = create_process_driver(signal_driver);
|
||||
|
||||
(Either::A(process_driver), Some(io_handle), signal_handle)
|
||||
} else {
|
||||
@@ -80,7 +80,7 @@ cfg_not_signal_internal! {
|
||||
cfg_process_driver! {
|
||||
type ProcessDriver = crate::process::unix::driver::Driver;
|
||||
|
||||
fn create_process_driver(signal_driver: SignalDriver) -> io::Result<ProcessDriver> {
|
||||
fn create_process_driver(signal_driver: SignalDriver) -> ProcessDriver {
|
||||
crate::process::unix::driver::Driver::new(signal_driver)
|
||||
}
|
||||
}
|
||||
@@ -89,8 +89,8 @@ cfg_not_process_driver! {
|
||||
cfg_io_driver! {
|
||||
type ProcessDriver = SignalDriver;
|
||||
|
||||
fn create_process_driver(signal_driver: SignalDriver) -> io::Result<ProcessDriver> {
|
||||
Ok(signal_driver)
|
||||
fn create_process_driver(signal_driver: SignalDriver) -> ProcessDriver {
|
||||
signal_driver
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,8 +174,11 @@ impl Handle {
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let fut = BlockingTask::new(func);
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let func = {
|
||||
let fut = {
|
||||
use tracing::Instrument;
|
||||
#[cfg(tokio_track_caller)]
|
||||
let location = std::panic::Location::caller();
|
||||
#[cfg(tokio_track_caller)]
|
||||
@@ -193,12 +196,9 @@ impl Handle {
|
||||
kind = %"blocking",
|
||||
function = %std::any::type_name::<F>(),
|
||||
);
|
||||
move || {
|
||||
let _g = span.enter();
|
||||
func()
|
||||
}
|
||||
fut.instrument(span)
|
||||
};
|
||||
let (task, handle) = task::joinable(BlockingTask::new(func));
|
||||
let (task, handle) = task::joinable(fut);
|
||||
let _ = self.blocking_spawner.spawn(task, &self);
|
||||
handle
|
||||
}
|
||||
|
||||
+24
-12
@@ -109,7 +109,10 @@ impl<T> Local<T> {
|
||||
}
|
||||
|
||||
/// Pushes a task to the back of the local queue, skipping the LIFO slot.
|
||||
pub(super) fn push_back(&mut self, mut task: task::Notified<T>, inject: &Inject<T>) {
|
||||
pub(super) fn push_back(&mut self, mut task: task::Notified<T>, inject: &Inject<T>)
|
||||
where
|
||||
T: crate::runtime::task::Schedule,
|
||||
{
|
||||
let tail = loop {
|
||||
let head = self.inner.head.load(Acquire);
|
||||
let (steal, real) = unpack(head);
|
||||
@@ -121,9 +124,14 @@ impl<T> Local<T> {
|
||||
// There is capacity for the task
|
||||
break tail;
|
||||
} else if steal != real {
|
||||
// Concurrently stealing, this will free up capacity, so
|
||||
// only push the new task onto the inject queue
|
||||
inject.push(task);
|
||||
// Concurrently stealing, this will free up capacity, so only
|
||||
// push the new task onto the inject queue
|
||||
//
|
||||
// If the task failes to be pushed on the injection queue, there
|
||||
// is nothing to be done at this point as the task cannot be a
|
||||
// newly spawned task. Shutting down this task is handled by the
|
||||
// worker shutdown process.
|
||||
let _ = inject.push(task);
|
||||
return;
|
||||
} else {
|
||||
// Push the current task and half of the queue into the
|
||||
@@ -504,16 +512,19 @@ impl<T: 'static> Inject<T> {
|
||||
}
|
||||
|
||||
/// Pushes a value into the queue.
|
||||
pub(super) fn push(&self, task: task::Notified<T>) {
|
||||
///
|
||||
/// Returns `Err(task)` if pushing fails due to the queue being shutdown.
|
||||
/// The caller is expected to call `shutdown()` on the task **if and only
|
||||
/// if** it is a newly spawned task.
|
||||
pub(super) fn push(&self, task: task::Notified<T>) -> Result<(), task::Notified<T>>
|
||||
where
|
||||
T: crate::runtime::task::Schedule,
|
||||
{
|
||||
// Acquire queue lock
|
||||
let mut p = self.pointers.lock();
|
||||
|
||||
if p.is_closed {
|
||||
// Drop the mutex to avoid a potential deadlock when
|
||||
// re-entering.
|
||||
drop(p);
|
||||
drop(task);
|
||||
return;
|
||||
return Err(task);
|
||||
}
|
||||
|
||||
// safety: only mutated with the lock held
|
||||
@@ -532,6 +543,7 @@ impl<T: 'static> Inject<T> {
|
||||
p.tail = Some(task);
|
||||
|
||||
self.len.store(len + 1, Release);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn push_batch(
|
||||
@@ -617,7 +629,7 @@ fn set_next(header: NonNull<task::Header>, val: Option<NonNull<task::Header>>) {
|
||||
/// Split the head value into the real head and the index a stealer is working
|
||||
/// on.
|
||||
fn unpack(n: u32) -> (u16, u16) {
|
||||
let real = n & u16::max_value() as u32;
|
||||
let real = n & u16::MAX as u32;
|
||||
let steal = n >> 16;
|
||||
|
||||
(steal as u16, real as u16)
|
||||
@@ -630,5 +642,5 @@ fn pack(steal: u16, real: u16) -> u32 {
|
||||
|
||||
#[test]
|
||||
fn test_local_queue_capacity() {
|
||||
assert!(LOCAL_QUEUE_CAPACITY - 1 <= u8::max_value() as usize);
|
||||
assert!(LOCAL_QUEUE_CAPACITY - 1 <= u8::MAX as usize);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
cfg_rt! {
|
||||
use crate::future::Future;
|
||||
use crate::runtime::basic_scheduler;
|
||||
use crate::task::JoinHandle;
|
||||
|
||||
use std::future::Future;
|
||||
}
|
||||
|
||||
cfg_rt_multi_thread! {
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
//! Make sure to consult the relevant safety section of each function before
|
||||
//! use.
|
||||
|
||||
use crate::future::Future;
|
||||
use crate::loom::cell::UnsafeCell;
|
||||
use crate::runtime::task::raw::{self, Vtable};
|
||||
use crate::runtime::task::state::State;
|
||||
use crate::runtime::task::{Notified, Schedule, Task};
|
||||
use crate::util::linked_list;
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
@@ -71,6 +71,10 @@ pub(crate) struct Header {
|
||||
|
||||
/// Table of function pointers for executing actions on the task.
|
||||
pub(super) vtable: &'static Vtable,
|
||||
|
||||
/// The tracing ID for this instrumented task.
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(super) id: Option<tracing::Id>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Header {}
|
||||
@@ -93,6 +97,8 @@ impl<T: Future, S: Schedule> Cell<T, S> {
|
||||
/// Allocates a new task cell, containing the header, trailer, and core
|
||||
/// structures.
|
||||
pub(super) fn new(future: T, state: State) -> Box<Cell<T, S>> {
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
let id = future.id();
|
||||
Box::new(Cell {
|
||||
header: Header {
|
||||
state,
|
||||
@@ -100,6 +106,8 @@ impl<T: Future, S: Schedule> Cell<T, S> {
|
||||
queue_next: UnsafeCell::new(None),
|
||||
stack_next: UnsafeCell::new(None),
|
||||
vtable: raw::vtable::<T, S>(),
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
id,
|
||||
},
|
||||
core: Core {
|
||||
scheduler: Scheduler {
|
||||
@@ -279,7 +287,7 @@ impl<T: Future> CoreStage<T> {
|
||||
// Safety:: the caller ensures mutal exclusion to the field.
|
||||
match mem::replace(unsafe { &mut *ptr }, Stage::Consumed) {
|
||||
Stage::Finished(output) => output,
|
||||
_ => panic!("unexpected task state"),
|
||||
_ => panic!("JoinHandle polled after completion"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::future::Future;
|
||||
use crate::runtime::task::core::{Cell, Core, CoreStage, Header, Scheduler, Trailer};
|
||||
use crate::runtime::task::state::Snapshot;
|
||||
use crate::runtime::task::waker::waker_ref;
|
||||
use crate::runtime::task::{JoinError, Notified, Schedule, Task};
|
||||
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
use std::panic;
|
||||
use std::ptr::NonNull;
|
||||
@@ -146,6 +146,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(tokio_unstable, feature = "tracing"))]
|
||||
pub(super) fn id(&self) -> Option<&tracing::Id> {
|
||||
self.header().id.as_ref()
|
||||
}
|
||||
|
||||
/// Forcibly shutdown the task
|
||||
///
|
||||
/// Attempt to transition to `Running` in order to forcibly shutdown the
|
||||
|
||||
@@ -26,9 +26,9 @@ cfg_rt_multi_thread! {
|
||||
pub(crate) use self::stack::TransferStack;
|
||||
}
|
||||
|
||||
use crate::future::Future;
|
||||
use crate::util::linked_list;
|
||||
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr::NonNull;
|
||||
use std::{fmt, mem};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::future::Future;
|
||||
use crate::runtime::task::{Cell, Harness, Header, Schedule, State};
|
||||
|
||||
use std::future::Future;
|
||||
use std::ptr::NonNull;
|
||||
use std::task::{Poll, Waker};
|
||||
|
||||
|
||||
@@ -29,12 +29,15 @@ const LIFECYCLE_MASK: usize = 0b11;
|
||||
const NOTIFIED: usize = 0b100;
|
||||
|
||||
/// The join handle is still around
|
||||
#[allow(clippy::unusual_byte_groupings)] // https://github.com/rust-lang/rust-clippy/issues/6556
|
||||
const JOIN_INTEREST: usize = 0b1_000;
|
||||
|
||||
/// A join handle waker has been set
|
||||
#[allow(clippy::unusual_byte_groupings)] // https://github.com/rust-lang/rust-clippy/issues/6556
|
||||
const JOIN_WAKER: usize = 0b10_000;
|
||||
|
||||
/// The task has been forcibly cancelled.
|
||||
#[allow(clippy::unusual_byte_groupings)] // https://github.com/rust-lang/rust-clippy/issues/6556
|
||||
const CANCELLED: usize = 0b100_000;
|
||||
|
||||
/// All bits
|
||||
@@ -52,7 +55,7 @@ const REF_ONE: usize = 1 << REF_COUNT_SHIFT;
|
||||
/// State a task is initialized with
|
||||
///
|
||||
/// A task is initialized with two references: one for the scheduler and one for
|
||||
/// the `JoinHandle`. As the task starts with a `JoinHandle`, `JOIN_INTERST` is
|
||||
/// the `JoinHandle`. As the task starts with a `JoinHandle`, `JOIN_INTEREST` is
|
||||
/// set. A new task is immediately pushed into the run queue for execution and
|
||||
/// starts with the `NOTIFIED` flag set.
|
||||
const INITIAL_STATE: usize = (REF_ONE * 2) | JOIN_INTEREST | NOTIFIED;
|
||||
@@ -64,7 +67,7 @@ impl State {
|
||||
pub(super) fn new() -> State {
|
||||
// A task is initialized with three references: one for the scheduler,
|
||||
// one for the `JoinHandle`, one for the task handle made available in
|
||||
// release. As the task starts with a `JoinHandle`, `JOIN_INTERST` is
|
||||
// release. As the task starts with a `JoinHandle`, `JOIN_INTEREST` is
|
||||
// set. A new task is immediately pushed into the run queue for
|
||||
// execution and starts with the `NOTIFIED` flag set.
|
||||
State {
|
||||
@@ -315,7 +318,7 @@ impl State {
|
||||
let prev = self.val.fetch_add(REF_ONE, Relaxed);
|
||||
|
||||
// If the reference count overflowed, abort.
|
||||
if prev > isize::max_value() as usize {
|
||||
if prev > isize::MAX as usize {
|
||||
process::abort();
|
||||
}
|
||||
}
|
||||
@@ -419,7 +422,7 @@ impl Snapshot {
|
||||
}
|
||||
|
||||
fn ref_inc(&mut self) {
|
||||
assert!(self.0 <= isize::max_value() as usize);
|
||||
assert!(self.0 <= isize::MAX as usize);
|
||||
self.0 += REF_ONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::future::Future;
|
||||
use crate::runtime::task::harness::Harness;
|
||||
use crate::runtime::task::{Header, Schedule};
|
||||
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::ops;
|
||||
@@ -44,12 +44,38 @@ impl<S> ops::Deref for WakerRef<'_, S> {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_trace! {
|
||||
macro_rules! trace {
|
||||
($harness:expr, $op:expr) => {
|
||||
if let Some(id) = $harness.id() {
|
||||
tracing::trace!(
|
||||
target: "tokio::task::waker",
|
||||
op = $op,
|
||||
task.id = id.into_u64(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_not_trace! {
|
||||
macro_rules! trace {
|
||||
($harness:expr, $op:expr) => {
|
||||
// noop
|
||||
let _ = &$harness;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn clone_waker<T, S>(ptr: *const ()) -> RawWaker
|
||||
where
|
||||
T: Future,
|
||||
S: Schedule,
|
||||
{
|
||||
let header = ptr as *const Header;
|
||||
let ptr = NonNull::new_unchecked(ptr as *mut Header);
|
||||
let harness = Harness::<T, S>::from_raw(ptr);
|
||||
trace!(harness, "waker.clone");
|
||||
(*header).state.ref_inc();
|
||||
raw_waker::<T, S>(header)
|
||||
}
|
||||
@@ -61,6 +87,7 @@ where
|
||||
{
|
||||
let ptr = NonNull::new_unchecked(ptr as *mut Header);
|
||||
let harness = Harness::<T, S>::from_raw(ptr);
|
||||
trace!(harness, "waker.drop");
|
||||
harness.drop_reference();
|
||||
}
|
||||
|
||||
@@ -71,6 +98,7 @@ where
|
||||
{
|
||||
let ptr = NonNull::new_unchecked(ptr as *mut Header);
|
||||
let harness = Harness::<T, S>::from_raw(ptr);
|
||||
trace!(harness, "waker.wake");
|
||||
harness.wake_by_val();
|
||||
}
|
||||
|
||||
@@ -82,6 +110,7 @@ where
|
||||
{
|
||||
let ptr = NonNull::new_unchecked(ptr as *mut Header);
|
||||
let harness = Harness::<T, S>::from_raw(ptr);
|
||||
trace!(harness, "waker.wake_by_ref");
|
||||
harness.wake_by_ref();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use crate::runtime::{Builder, Handle};
|
||||
|
||||
#[test]
|
||||
fn join_handle_cancel_on_shutdown() {
|
||||
let mut builder = loom::model::Builder::new();
|
||||
builder.preemption_bound = Some(2);
|
||||
builder.check(|| {
|
||||
use futures::future::FutureExt;
|
||||
|
||||
let rt = Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let handle = rt.block_on(async move { Handle::current() });
|
||||
|
||||
let jh1 = handle.spawn(futures::future::pending::<()>());
|
||||
|
||||
drop(rt);
|
||||
|
||||
let jh2 = handle.spawn(futures::future::pending::<()>());
|
||||
|
||||
let err1 = jh1.now_or_never().unwrap().unwrap_err();
|
||||
let err2 = jh2.now_or_never().unwrap().unwrap_err();
|
||||
assert!(err1.is_cancelled());
|
||||
assert!(err2.is_cancelled());
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,7 @@ cfg_loom! {
|
||||
mod loom_oneshot;
|
||||
mod loom_pool;
|
||||
mod loom_queue;
|
||||
mod loom_shutdown_join;
|
||||
}
|
||||
|
||||
cfg_not_loom! {
|
||||
|
||||
@@ -79,7 +79,7 @@ static CURRENT: TryLock<Option<Runtime>> = TryLock::new(None);
|
||||
|
||||
impl Runtime {
|
||||
fn tick(&self) -> usize {
|
||||
self.tick_max(usize::max_value())
|
||||
self.tick_max(usize::MAX)
|
||||
}
|
||||
|
||||
fn tick_max(&self, max: usize) -> usize {
|
||||
|
||||
@@ -90,11 +90,17 @@ impl Spawner {
|
||||
/// Spawns a future onto the thread pool
|
||||
pub(crate) fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F: crate::future::Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
let (task, handle) = task::joinable(future);
|
||||
self.shared.schedule(task, false);
|
||||
|
||||
if let Err(task) = self.shared.schedule(task, false) {
|
||||
// The newly spawned task could not be scheduled because the runtime
|
||||
// is shutting down. The task must be explicitly shutdown at this point.
|
||||
task.shutdown();
|
||||
}
|
||||
|
||||
handle
|
||||
}
|
||||
|
||||
|
||||
@@ -709,16 +709,22 @@ impl task::Schedule for Arc<Worker> {
|
||||
}
|
||||
|
||||
fn schedule(&self, task: Notified) {
|
||||
self.shared.schedule(task, false);
|
||||
// Because this is not a newly spawned task, if scheduling fails due to
|
||||
// the runtime shutting down, there is no special work that must happen
|
||||
// here.
|
||||
let _ = self.shared.schedule(task, false);
|
||||
}
|
||||
|
||||
fn yield_now(&self, task: Notified) {
|
||||
self.shared.schedule(task, true);
|
||||
// Because this is not a newly spawned task, if scheduling fails due to
|
||||
// the runtime shutting down, there is no special work that must happen
|
||||
// here.
|
||||
let _ = self.shared.schedule(task, true);
|
||||
}
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
pub(super) fn schedule(&self, task: Notified, is_yield: bool) {
|
||||
pub(super) fn schedule(&self, task: Notified, is_yield: bool) -> Result<(), Notified> {
|
||||
CURRENT.with(|maybe_cx| {
|
||||
if let Some(cx) = maybe_cx {
|
||||
// Make sure the task is part of the **current** scheduler.
|
||||
@@ -726,15 +732,16 @@ impl Shared {
|
||||
// And the current thread still holds a core
|
||||
if let Some(core) = cx.core.borrow_mut().as_mut() {
|
||||
self.schedule_local(core, task, is_yield);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, use the inject queue
|
||||
self.inject.push(task);
|
||||
self.inject.push(task)?;
|
||||
self.notify_parked();
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn schedule_local(&self, core: &mut Core, task: Notified, is_yield: bool) {
|
||||
@@ -823,7 +830,9 @@ impl Shared {
|
||||
}
|
||||
|
||||
// Drain the injection queue
|
||||
while self.inject.pop().is_some() {}
|
||||
while let Some(task) = self.inject.pop() {
|
||||
task.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
fn ptr_eq(&self, other: &Shared) -> bool {
|
||||
|
||||
+15
-10
@@ -9,7 +9,6 @@ use crate::signal::registry::{globals, EventId, EventInfo, Globals, Init, Storag
|
||||
use crate::signal::RxFuture;
|
||||
use crate::sync::watch;
|
||||
|
||||
use libc::c_int;
|
||||
use mio::net::UnixStream;
|
||||
use std::io::{self, Error, ErrorKind, Write};
|
||||
use std::pin::Pin;
|
||||
@@ -61,7 +60,7 @@ impl Init for OsExtraData {
|
||||
|
||||
/// Represents the specific kind of signal to listen for.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SignalKind(c_int);
|
||||
pub struct SignalKind(libc::c_int);
|
||||
|
||||
impl SignalKind {
|
||||
/// Allows for listening to any valid OS signal.
|
||||
@@ -74,8 +73,14 @@ impl SignalKind {
|
||||
/// // let signum = libc::OS_SPECIFIC_SIGNAL;
|
||||
/// let kind = SignalKind::from_raw(signum);
|
||||
/// ```
|
||||
pub fn from_raw(signum: c_int) -> Self {
|
||||
Self(signum)
|
||||
// Use `std::os::raw::c_int` on public API to prevent leaking a non-stable
|
||||
// type alias from libc.
|
||||
// `libc::c_int` and `std::os::raw::c_int` are currently the same type, and are
|
||||
// unlikely to change to other types, but technically libc can change this
|
||||
// in the future minor version.
|
||||
// See https://github.com/tokio-rs/tokio/issues/3767 for more.
|
||||
pub fn from_raw(signum: std::os::raw::c_int) -> Self {
|
||||
Self(signum as libc::c_int)
|
||||
}
|
||||
|
||||
/// Represents the SIGALRM signal.
|
||||
@@ -208,7 +213,7 @@ impl Default for SignalInfo {
|
||||
/// 2. Wake up the driver by writing a byte to a pipe
|
||||
///
|
||||
/// Those two operations should both be async-signal safe.
|
||||
fn action(globals: Pin<&'static Globals>, signal: c_int) {
|
||||
fn action(globals: Pin<&'static Globals>, signal: libc::c_int) {
|
||||
globals.record_event(signal as EventId);
|
||||
|
||||
// Send a wakeup, ignore any errors (anything reasonably possible is
|
||||
@@ -222,7 +227,7 @@ fn action(globals: Pin<&'static Globals>, signal: c_int) {
|
||||
///
|
||||
/// This will register the signal handler if it hasn't already been registered,
|
||||
/// returning any error along the way if that fails.
|
||||
fn signal_enable(signal: SignalKind, handle: Handle) -> io::Result<()> {
|
||||
fn signal_enable(signal: SignalKind, handle: &Handle) -> io::Result<()> {
|
||||
let signal = signal.0;
|
||||
if signal < 0 || signal_hook_registry::FORBIDDEN.contains(&signal) {
|
||||
return Err(Error::new(
|
||||
@@ -352,7 +357,7 @@ pub struct Signal {
|
||||
/// * If the signal is one of
|
||||
/// [`signal_hook::FORBIDDEN`](fn@signal_hook_registry::register#panics)
|
||||
pub fn signal(kind: SignalKind) -> io::Result<Signal> {
|
||||
let rx = signal_with_handle(kind, Handle::current())?;
|
||||
let rx = signal_with_handle(kind, &Handle::current())?;
|
||||
|
||||
Ok(Signal {
|
||||
inner: RxFuture::new(rx),
|
||||
@@ -361,7 +366,7 @@ pub fn signal(kind: SignalKind) -> io::Result<Signal> {
|
||||
|
||||
pub(crate) fn signal_with_handle(
|
||||
kind: SignalKind,
|
||||
handle: Handle,
|
||||
handle: &Handle,
|
||||
) -> io::Result<watch::Receiver<()>> {
|
||||
// Turn the signal delivery on once we are ready for it
|
||||
signal_enable(kind, handle)?;
|
||||
@@ -457,14 +462,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn signal_enable_error_on_invalid_input() {
|
||||
signal_enable(SignalKind::from_raw(-1), Handle::default()).unwrap_err();
|
||||
signal_enable(SignalKind::from_raw(-1), &Handle::default()).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_enable_error_on_forbidden_input() {
|
||||
signal_enable(
|
||||
SignalKind::from_raw(signal_hook_registry::FORBIDDEN[0]),
|
||||
Handle::default(),
|
||||
&Handle::default(),
|
||||
)
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::sync::watch;
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// A barrier enables multiple threads to synchronize the beginning of some computation.
|
||||
/// A barrier enables multiple tasks to synchronize the beginning of some computation.
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
@@ -52,10 +52,10 @@ struct BarrierState {
|
||||
}
|
||||
|
||||
impl Barrier {
|
||||
/// Creates a new barrier that can block a given number of threads.
|
||||
/// Creates a new barrier that can block a given number of tasks.
|
||||
///
|
||||
/// A barrier will block `n`-1 threads which call [`Barrier::wait`] and then wake up all
|
||||
/// threads at once when the `n`th thread calls `wait`.
|
||||
/// A barrier will block `n`-1 tasks which call [`Barrier::wait`] and then wake up all
|
||||
/// tasks at once when the `n`th task calls `wait`.
|
||||
pub fn new(mut n: usize) -> Barrier {
|
||||
let (waker, wait) = crate::sync::watch::channel(0);
|
||||
|
||||
@@ -79,11 +79,11 @@ impl Barrier {
|
||||
|
||||
/// Does not resolve until all tasks have rendezvoused here.
|
||||
///
|
||||
/// Barriers are re-usable after all threads have rendezvoused once, and can
|
||||
/// Barriers are re-usable after all tasks have rendezvoused once, and can
|
||||
/// be used continuously.
|
||||
///
|
||||
/// A single (arbitrary) future will receive a [`BarrierWaitResult`] that returns `true` from
|
||||
/// [`BarrierWaitResult::is_leader`] when returning from this function, and all other threads
|
||||
/// [`BarrierWaitResult::is_leader`] when returning from this function, and all other tasks
|
||||
/// will receive a result that will return `false` from `is_leader`.
|
||||
pub async fn wait(&self) -> BarrierWaitResult {
|
||||
// NOTE: we are taking a _synchronous_ lock here.
|
||||
@@ -129,14 +129,14 @@ impl Barrier {
|
||||
}
|
||||
}
|
||||
|
||||
/// A `BarrierWaitResult` is returned by `wait` when all threads in the `Barrier` have rendezvoused.
|
||||
/// A `BarrierWaitResult` is returned by `wait` when all tasks in the `Barrier` have rendezvoused.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BarrierWaitResult(bool);
|
||||
|
||||
impl BarrierWaitResult {
|
||||
/// Returns `true` if this thread from wait is the "leader thread".
|
||||
/// Returns `true` if this task from wait is the "leader task".
|
||||
///
|
||||
/// Only one thread will have `true` returned from their result, all other threads will have
|
||||
/// Only one task will have `true` returned from their result, all other tasks will have
|
||||
/// `false` returned.
|
||||
pub fn is_leader(&self) -> bool {
|
||||
self.0
|
||||
|
||||
@@ -428,6 +428,11 @@
|
||||
//! bounding of any kind.
|
||||
|
||||
cfg_sync! {
|
||||
/// Named future types.
|
||||
pub mod futures {
|
||||
pub use super::notify::Notified;
|
||||
}
|
||||
|
||||
mod barrier;
|
||||
pub use barrier::{Barrier, BarrierWaitResult};
|
||||
|
||||
@@ -436,7 +441,7 @@ cfg_sync! {
|
||||
pub mod mpsc;
|
||||
|
||||
mod mutex;
|
||||
pub use mutex::{Mutex, MutexGuard, TryLockError, OwnedMutexGuard};
|
||||
pub use mutex::{Mutex, MutexGuard, TryLockError, OwnedMutexGuard, MappedMutexGuard};
|
||||
|
||||
pub(crate) mod notify;
|
||||
pub use notify::Notify;
|
||||
|
||||
+299
-10
@@ -33,6 +33,22 @@ pub struct Permit<'a, T> {
|
||||
chan: &'a chan::Tx<T, Semaphore>,
|
||||
}
|
||||
|
||||
/// Owned permit to send one value into the channel.
|
||||
///
|
||||
/// This is identical to the [`Permit`] type, except that it moves the sender
|
||||
/// rather than borrowing it.
|
||||
///
|
||||
/// `OwnedPermit` values are returned by [`Sender::reserve_owned()`] and
|
||||
/// [`Sender::try_reserve_owned()`] and are used to guarantee channel capacity
|
||||
/// before generating a message to send.
|
||||
///
|
||||
/// [`Permit`]: Permit
|
||||
/// [`Sender::reserve_owned()`]: Sender::reserve_owned
|
||||
/// [`Sender::try_reserve_owned()`]: Sender::try_reserve_owned
|
||||
pub struct OwnedPermit<T> {
|
||||
chan: Option<chan::Tx<T, Semaphore>>,
|
||||
}
|
||||
|
||||
/// Receive values from the associated `Sender`.
|
||||
///
|
||||
/// Instances are created by the [`channel`](channel) function.
|
||||
@@ -49,7 +65,7 @@ pub struct Receiver<T> {
|
||||
/// with backpressure.
|
||||
///
|
||||
/// The channel will buffer up to the provided number of messages. Once the
|
||||
/// buffer is full, attempts to `send` new messages will wait until a message is
|
||||
/// buffer is full, attempts to send new messages will wait until a message is
|
||||
/// received from the channel. The provided buffer capacity must be at least 1.
|
||||
///
|
||||
/// All data sent on `Sender` will become available on `Receiver` in the same
|
||||
@@ -60,7 +76,7 @@ pub struct Receiver<T> {
|
||||
///
|
||||
/// If the `Receiver` is disconnected while trying to `send`, the `send` method
|
||||
/// will return a `SendError`. Similarly, if `Sender` is disconnected while
|
||||
/// trying to `recv`, the `recv` method will return a `RecvError`.
|
||||
/// trying to `recv`, the `recv` method will return `None`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@@ -229,10 +245,11 @@ impl<T> Receiver<T> {
|
||||
///
|
||||
/// To guarantee that no messages are dropped, after calling `close()`,
|
||||
/// `recv()` must be called until `None` is returned. If there are
|
||||
/// outstanding [`Permit`] values, the `recv` method will not return `None`
|
||||
/// until those are released.
|
||||
/// outstanding [`Permit`] or [`OwnedPermit`] values, the `recv` method will
|
||||
/// not return `None` until those are released.
|
||||
///
|
||||
/// [`Permit`]: Permit
|
||||
/// [`OwnedPermit`]: OwnedPermit
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -624,14 +641,98 @@ impl<T> Sender<T> {
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn reserve(&self) -> Result<Permit<'_, T>, SendError<()>> {
|
||||
match self.chan.semaphore().0.acquire(1).await {
|
||||
Ok(_) => {}
|
||||
Err(_) => return Err(SendError(())),
|
||||
}
|
||||
|
||||
self.reserve_inner().await?;
|
||||
Ok(Permit { chan: &self.chan })
|
||||
}
|
||||
|
||||
/// Wait for channel capacity, moving the `Sender` and returning an owned
|
||||
/// permit. Once capacity to send one message is available, it is reserved
|
||||
/// for the caller.
|
||||
///
|
||||
/// This moves the sender _by value_, and returns an owned permit that can
|
||||
/// be used to send a message into the channel. Unlike [`Sender::reserve`],
|
||||
/// this method may be used in cases where the permit must be valid for the
|
||||
/// `'static` lifetime. `Sender`s may be cloned cheaply (`Sender::clone` is
|
||||
/// essentially a reference count increment, comparable to [`Arc::clone`]),
|
||||
/// so when multiple [`OwnedPermit`]s are needed or the `Sender` cannot be
|
||||
/// moved, it can be cloned prior to calling `reserve_owned`.
|
||||
///
|
||||
/// If the channel is full, the function waits for the number of unreceived
|
||||
/// messages to become less than the channel capacity. Capacity to send one
|
||||
/// message is reserved for the caller. An [`OwnedPermit`] is returned to
|
||||
/// track the reserved capacity. The [`send`] function on [`OwnedPermit`]
|
||||
/// consumes the reserved capacity.
|
||||
///
|
||||
/// Dropping the [`OwnedPermit`] without sending a message releases the
|
||||
/// capacity back to the channel.
|
||||
///
|
||||
/// # Examples
|
||||
/// Sending a message using an [`OwnedPermit`]:
|
||||
/// ```
|
||||
/// use tokio::sync::mpsc;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx, mut rx) = mpsc::channel(1);
|
||||
///
|
||||
/// // Reserve capacity, moving the sender.
|
||||
/// let permit = tx.reserve_owned().await.unwrap();
|
||||
///
|
||||
/// // Send a message, consuming the permit and returning
|
||||
/// // the moved sender.
|
||||
/// let tx = permit.send(123);
|
||||
///
|
||||
/// // The value sent on the permit is received.
|
||||
/// assert_eq!(rx.recv().await.unwrap(), 123);
|
||||
///
|
||||
/// // The sender can now be used again.
|
||||
/// tx.send(456).await.unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// When multiple [`OwnedPermit`]s are needed, or the sender cannot be moved
|
||||
/// by value, it can be inexpensively cloned before calling `reserve_owned`:
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::mpsc;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx, mut rx) = mpsc::channel(1);
|
||||
///
|
||||
/// // Clone the sender and reserve capacity.
|
||||
/// let permit = tx.clone().reserve_owned().await.unwrap();
|
||||
///
|
||||
/// // Trying to send directly on the `tx` will fail due to no
|
||||
/// // available capacity.
|
||||
/// assert!(tx.try_send(123).is_err());
|
||||
///
|
||||
/// // Sending on the permit succeeds.
|
||||
/// permit.send(456);
|
||||
///
|
||||
/// // The value sent on the permit is received
|
||||
/// assert_eq!(rx.recv().await.unwrap(), 456);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Sender::reserve`]: Sender::reserve
|
||||
/// [`OwnedPermit`]: OwnedPermit
|
||||
/// [`send`]: OwnedPermit::send
|
||||
/// [`Arc::clone`]: std::sync::Arc::clone
|
||||
pub async fn reserve_owned(self) -> Result<OwnedPermit<T>, SendError<()>> {
|
||||
self.reserve_inner().await?;
|
||||
Ok(OwnedPermit {
|
||||
chan: Some(self.chan),
|
||||
})
|
||||
}
|
||||
|
||||
async fn reserve_inner(&self) -> Result<(), SendError<()>> {
|
||||
match self.chan.semaphore().0.acquire(1).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(SendError(())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to acquire a slot in the channel without waiting for the slot to become
|
||||
/// available.
|
||||
///
|
||||
@@ -684,6 +785,72 @@ impl<T> Sender<T> {
|
||||
Ok(Permit { chan: &self.chan })
|
||||
}
|
||||
|
||||
/// Try to acquire a slot in the channel without waiting for the slot to become
|
||||
/// available, returning an owned permit.
|
||||
///
|
||||
/// This moves the sender _by value_, and returns an owned permit that can
|
||||
/// be used to send a message into the channel. Unlike [`Sender::try_reserve`],
|
||||
/// this method may be used in cases where the permit must be valid for the
|
||||
/// `'static` lifetime. `Sender`s may be cloned cheaply (`Sender::clone` is
|
||||
/// essentially a reference count increment, comparable to [`Arc::clone`]),
|
||||
/// so when multiple [`OwnedPermit`]s are needed or the `Sender` cannot be
|
||||
/// moved, it can be cloned prior to calling `try_reserve_owned`.
|
||||
///
|
||||
/// If the channel is full this function will return a [`TrySendError`].
|
||||
/// Since the sender is taken by value, the `TrySendError` returned in this
|
||||
/// case contains the sender, so that it may be used again. Otherwise, if
|
||||
/// there is a slot available, this method will return an [`OwnedPermit`]
|
||||
/// that can then be used to [`send`] on the channel with a guaranteed slot.
|
||||
/// This function is similar to [`reserve_owned`] except it does not await
|
||||
/// for the slot to become available.
|
||||
///
|
||||
/// Dropping the [`OwnedPermit`] without sending a message releases the capacity back
|
||||
/// to the channel.
|
||||
///
|
||||
/// [`OwnedPermit`]: OwnedPermit
|
||||
/// [`send`]: OwnedPermit::send
|
||||
/// [`reserve_owned`]: Sender::reserve_owned
|
||||
/// [`Arc::clone`]: std::sync::Arc::clone
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::mpsc;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx, mut rx) = mpsc::channel(1);
|
||||
///
|
||||
/// // Reserve capacity
|
||||
/// let permit = tx.clone().try_reserve_owned().unwrap();
|
||||
///
|
||||
/// // Trying to send directly on the `tx` will fail due to no
|
||||
/// // available capacity.
|
||||
/// assert!(tx.try_send(123).is_err());
|
||||
///
|
||||
/// // Trying to reserve an additional slot on the `tx` will
|
||||
/// // fail because there is no capacity.
|
||||
/// assert!(tx.try_reserve().is_err());
|
||||
///
|
||||
/// // Sending on the permit succeeds
|
||||
/// permit.send(456);
|
||||
///
|
||||
/// // The value sent on the permit is received
|
||||
/// assert_eq!(rx.recv().await.unwrap(), 456);
|
||||
///
|
||||
/// }
|
||||
/// ```
|
||||
pub fn try_reserve_owned(self) -> Result<OwnedPermit<T>, TrySendError<Self>> {
|
||||
match self.chan.semaphore().0.try_acquire(1) {
|
||||
Ok(_) => {}
|
||||
Err(_) => return Err(TrySendError::Full(self)),
|
||||
}
|
||||
|
||||
Ok(OwnedPermit {
|
||||
chan: Some(self.chan),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if senders belong to the same channel.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -720,7 +887,7 @@ impl<T> Sender<T> {
|
||||
/// let permit = tx.reserve().await.unwrap();
|
||||
/// assert_eq!(tx.capacity(), 4);
|
||||
///
|
||||
/// // Sending and receiving a value increases the caapcity by one.
|
||||
/// // Sending and receiving a value increases the capacity by one.
|
||||
/// permit.send(());
|
||||
/// rx.recv().await.unwrap();
|
||||
/// assert_eq!(tx.capacity(), 5);
|
||||
@@ -804,6 +971,8 @@ impl<T> Drop for Permit<'_, T> {
|
||||
// Add the permit back to the semaphore
|
||||
semaphore.add_permit();
|
||||
|
||||
// If this is the last sender for this channel, wake the receiver so
|
||||
// that it can be notified that the channel is closed.
|
||||
if semaphore.is_closed() && semaphore.is_idle() {
|
||||
self.chan.wake_rx();
|
||||
}
|
||||
@@ -817,3 +986,123 @@ impl<T> fmt::Debug for Permit<'_, T> {
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Permit =====
|
||||
|
||||
impl<T> OwnedPermit<T> {
|
||||
/// Sends a value using the reserved capacity.
|
||||
///
|
||||
/// Capacity for the message has already been reserved. The message is sent
|
||||
/// to the receiver and the permit is consumed. The operation will succeed
|
||||
/// even if the receiver half has been closed. See [`Receiver::close`] for
|
||||
/// more details on performing a clean shutdown.
|
||||
///
|
||||
/// Unlike [`Permit::send`], this method returns the [`Sender`] from which
|
||||
/// the `OwnedPermit` was reserved.
|
||||
///
|
||||
/// [`Receiver::close`]: Receiver::close
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::mpsc;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx, mut rx) = mpsc::channel(1);
|
||||
///
|
||||
/// // Reserve capacity
|
||||
/// let permit = tx.reserve_owned().await.unwrap();
|
||||
///
|
||||
/// // Send a message on the permit, returning the sender.
|
||||
/// let tx = permit.send(456);
|
||||
///
|
||||
/// // The value sent on the permit is received
|
||||
/// assert_eq!(rx.recv().await.unwrap(), 456);
|
||||
///
|
||||
/// // We may now reuse `tx` to send another message.
|
||||
/// tx.send(789).await.unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn send(mut self, value: T) -> Sender<T> {
|
||||
let chan = self.chan.take().unwrap_or_else(|| {
|
||||
unreachable!("OwnedPermit channel is only taken when the permit is moved")
|
||||
});
|
||||
chan.send(value);
|
||||
|
||||
Sender { chan }
|
||||
}
|
||||
|
||||
/// Release the reserved capacity *without* sending a message, returning the
|
||||
/// [`Sender`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::mpsc;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx, rx) = mpsc::channel(1);
|
||||
///
|
||||
/// // Clone the sender and reserve capacity
|
||||
/// let permit = tx.clone().reserve_owned().await.unwrap();
|
||||
///
|
||||
/// // Trying to send on the original `tx` will fail, since the `permit`
|
||||
/// // has reserved all the available capacity.
|
||||
/// assert!(tx.try_send(123).is_err());
|
||||
///
|
||||
/// // Release the permit without sending a message, returning the clone
|
||||
/// // of the sender.
|
||||
/// let tx2 = permit.release();
|
||||
///
|
||||
/// // We may now reuse `tx` to send another message.
|
||||
/// tx.send(789).await.unwrap();
|
||||
/// # drop(rx); drop(tx2);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Sender`]: Sender
|
||||
pub fn release(mut self) -> Sender<T> {
|
||||
use chan::Semaphore;
|
||||
|
||||
let chan = self.chan.take().unwrap_or_else(|| {
|
||||
unreachable!("OwnedPermit channel is only taken when the permit is moved")
|
||||
});
|
||||
|
||||
// Add the permit back to the semaphore
|
||||
chan.semaphore().add_permit();
|
||||
Sender { chan }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for OwnedPermit<T> {
|
||||
fn drop(&mut self) {
|
||||
use chan::Semaphore;
|
||||
|
||||
// Are we still holding onto the sender?
|
||||
if let Some(chan) = self.chan.take() {
|
||||
let semaphore = chan.semaphore();
|
||||
|
||||
// Add the permit back to the semaphore
|
||||
semaphore.add_permit();
|
||||
|
||||
// If this `OwnedPermit` is holding the last sender for this
|
||||
// channel, wake the receiver so that it can be notified that the
|
||||
// channel is closed.
|
||||
if semaphore.is_closed() && semaphore.is_idle() {
|
||||
chan.wake_rx();
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for OwnedPermit<T> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("OwnedPermit")
|
||||
.field("chan", &self.chan)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +55,18 @@ impl<T> From<SendError<T>> for TrySendError<T> {
|
||||
|
||||
/// Error returned by `Receiver`.
|
||||
#[derive(Debug)]
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "This type is unused because recv returns an Option.")]
|
||||
pub struct RecvError(());
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl fmt::Display for RecvError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(fmt, "channel closed")
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl Error for RecvError {}
|
||||
|
||||
cfg_time! {
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
pub(super) mod block;
|
||||
|
||||
mod bounded;
|
||||
pub use self::bounded::{channel, Permit, Receiver, Sender};
|
||||
pub use self::bounded::{channel, OwnedPermit, Permit, Receiver, Sender};
|
||||
|
||||
mod chan;
|
||||
|
||||
|
||||
+198
-1
@@ -4,9 +4,9 @@ use crate::sync::batch_semaphore as semaphore;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, marker, mem};
|
||||
|
||||
/// An asynchronous `Mutex`-like type.
|
||||
///
|
||||
@@ -160,6 +160,19 @@ pub struct OwnedMutexGuard<T: ?Sized> {
|
||||
lock: Arc<Mutex<T>>,
|
||||
}
|
||||
|
||||
/// A handle to a held `Mutex` that has had a function applied to it via [`MutexGuard::map`].
|
||||
///
|
||||
/// This can be used to hold a subfield of the protected data.
|
||||
///
|
||||
/// [`MutexGuard::map`]: method@MutexGuard::map
|
||||
#[must_use = "if unused the Mutex will immediately unlock"]
|
||||
pub struct MappedMutexGuard<'a, T: ?Sized> {
|
||||
s: &'a semaphore::Semaphore,
|
||||
data: *mut T,
|
||||
// Needed to tell the borrow checker that we are holding a `&mut T`
|
||||
marker: marker::PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
// As long as T: Send, it's fine to send and share Mutex<T> between threads.
|
||||
// If T was not Send, sending and sharing a Mutex<T> would be bad, since you can
|
||||
// access T through Mutex<T>.
|
||||
@@ -167,6 +180,8 @@ unsafe impl<T> Send for Mutex<T> where T: ?Sized + Send {}
|
||||
unsafe impl<T> Sync for Mutex<T> where T: ?Sized + Send {}
|
||||
unsafe impl<T> Sync for MutexGuard<'_, T> where T: ?Sized + Send + Sync {}
|
||||
unsafe impl<T> Sync for OwnedMutexGuard<T> where T: ?Sized + Send + Sync {}
|
||||
unsafe impl<'a, T> Sync for MappedMutexGuard<'a, T> where T: ?Sized + Sync + 'a {}
|
||||
unsafe impl<'a, T> Send for MappedMutexGuard<'a, T> where T: ?Sized + Send + 'a {}
|
||||
|
||||
/// Error returned from the [`Mutex::try_lock`], [`RwLock::try_read`] and
|
||||
/// [`RwLock::try_write`] functions.
|
||||
@@ -451,6 +466,103 @@ where
|
||||
|
||||
// === impl MutexGuard ===
|
||||
|
||||
impl<'a, T: ?Sized> MutexGuard<'a, T> {
|
||||
/// Makes a new [`MappedMutexGuard`] for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the [`MutexGuard`] passed in already locked the mutex.
|
||||
///
|
||||
/// This is an associated function that needs to be used as `MutexGuard::map(...)`. A method
|
||||
/// would interfere with methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{Mutex, MutexGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let foo = Mutex::new(Foo(1));
|
||||
///
|
||||
/// {
|
||||
/// let mut mapped = MutexGuard::map(foo.lock().await, |f| &mut f.0);
|
||||
/// *mapped = 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(2), *foo.lock().await);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`MutexGuard`]: struct@MutexGuard
|
||||
/// [`MappedMutexGuard`]: struct@MappedMutexGuard
|
||||
#[inline]
|
||||
pub fn map<U, F>(mut this: Self, f: F) -> MappedMutexGuard<'a, U>
|
||||
where
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let s = &this.lock.s;
|
||||
mem::forget(this);
|
||||
MappedMutexGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to make a new [`MappedMutexGuard`] for a component of the locked data. The
|
||||
/// original guard is returned if the closure returns `None`.
|
||||
///
|
||||
/// This operation cannot fail as the [`MutexGuard`] passed in already locked the mutex.
|
||||
///
|
||||
/// This is an associated function that needs to be used as `MutexGuard::try_map(...)`. A
|
||||
/// method would interfere with methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::sync::{Mutex, MutexGuard};
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// struct Foo(u32);
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let foo = Mutex::new(Foo(1));
|
||||
///
|
||||
/// {
|
||||
/// let mut mapped = MutexGuard::try_map(foo.lock().await, |f| Some(&mut f.0))
|
||||
/// .expect("should not fail");
|
||||
/// *mapped = 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Foo(2), *foo.lock().await);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`MutexGuard`]: struct@MutexGuard
|
||||
/// [`MappedMutexGuard`]: struct@MappedMutexGuard
|
||||
#[inline]
|
||||
pub fn try_map<U, F>(mut this: Self, f: F) -> Result<MappedMutexGuard<'a, U>, Self>
|
||||
where
|
||||
F: FnOnce(&mut T) -> Option<&mut U>,
|
||||
{
|
||||
let data = match f(&mut *this) {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = &this.lock.s;
|
||||
mem::forget(this);
|
||||
Ok(MappedMutexGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
self.lock.s.release(1)
|
||||
@@ -514,3 +626,88 @@ impl<T: ?Sized + fmt::Display> fmt::Display for OwnedMutexGuard<T> {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
// === impl MappedMutexGuard ===
|
||||
|
||||
impl<'a, T: ?Sized> MappedMutexGuard<'a, T> {
|
||||
/// Makes a new [`MappedMutexGuard`] for a component of the locked data.
|
||||
///
|
||||
/// This operation cannot fail as the [`MappedMutexGuard`] passed in already locked the mutex.
|
||||
///
|
||||
/// This is an associated function that needs to be used as `MappedMutexGuard::map(...)`. A
|
||||
/// method would interfere with methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// [`MappedMutexGuard`]: struct@MappedMutexGuard
|
||||
#[inline]
|
||||
pub fn map<U, F>(mut this: Self, f: F) -> MappedMutexGuard<'a, U>
|
||||
where
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
let data = f(&mut *this) as *mut U;
|
||||
let s = this.s;
|
||||
mem::forget(this);
|
||||
MappedMutexGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to make a new [`MappedMutexGuard`] for a component of the locked data. The
|
||||
/// original guard is returned if the closure returns `None`.
|
||||
///
|
||||
/// This operation cannot fail as the [`MappedMutexGuard`] passed in already locked the mutex.
|
||||
///
|
||||
/// This is an associated function that needs to be used as `MappedMutexGuard::try_map(...)`. A
|
||||
/// method would interfere with methods of the same name on the contents of the locked data.
|
||||
///
|
||||
/// [`MappedMutexGuard`]: struct@MappedMutexGuard
|
||||
#[inline]
|
||||
pub fn try_map<U, F>(mut this: Self, f: F) -> Result<MappedMutexGuard<'a, U>, Self>
|
||||
where
|
||||
F: FnOnce(&mut T) -> Option<&mut U>,
|
||||
{
|
||||
let data = match f(&mut *this) {
|
||||
Some(data) => data as *mut U,
|
||||
None => return Err(this),
|
||||
};
|
||||
let s = this.s;
|
||||
mem::forget(this);
|
||||
Ok(MappedMutexGuard {
|
||||
s,
|
||||
data,
|
||||
marker: marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Drop for MappedMutexGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
self.s.release(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Deref for MappedMutexGuard<'a, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> DerefMut for MappedMutexGuard<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { &mut *self.data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for MappedMutexGuard<'a, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + fmt::Display> fmt::Display for MappedMutexGuard<'a, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ struct Waiter {
|
||||
_p: PhantomPinned,
|
||||
}
|
||||
|
||||
/// Future returned from `notified()`
|
||||
/// Future returned from [`Notify::notified()`]
|
||||
#[derive(Debug)]
|
||||
pub struct Notified<'a> {
|
||||
/// The `Notify` being received on.
|
||||
@@ -192,6 +192,10 @@ fn inc_num_notify_waiters_calls(data: usize) -> usize {
|
||||
data + (1 << NOTIFY_WAITERS_SHIFT)
|
||||
}
|
||||
|
||||
fn atomic_inc_num_notify_waiters_calls(data: &AtomicUsize) {
|
||||
data.fetch_add(1 << NOTIFY_WAITERS_SHIFT, SeqCst);
|
||||
}
|
||||
|
||||
impl Notify {
|
||||
/// Create a new `Notify`, initialized without a permit.
|
||||
///
|
||||
@@ -394,11 +398,9 @@ impl Notify {
|
||||
let curr = self.state.load(SeqCst);
|
||||
|
||||
if let EMPTY | NOTIFIED = get_state(curr) {
|
||||
// There are no waiting tasks. In this case, no synchronization is
|
||||
// established between `notify` and `notified().await`.
|
||||
// All we need to do is increment the number of times this
|
||||
// method was called.
|
||||
self.state.store(inc_num_notify_waiters_calls(curr), SeqCst);
|
||||
// There are no waiting tasks. All we need to do is increment the
|
||||
// number of times this method was called.
|
||||
atomic_inc_num_notify_waiters_calls(&self.state);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user