mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
161
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7147042534 | ||
|
|
634fcac2d9 | ||
|
|
e0c527f383 | ||
|
|
1160e8864c | ||
|
|
998a125717 | ||
|
|
cbfdc9d69e | ||
|
|
08337c5f79 | ||
|
|
87f4969fbc | ||
|
|
e385108920 | ||
|
|
677107d8d9 | ||
|
|
c8ecfc894d | ||
|
|
08ed41f339 | ||
|
|
90e1935c48 | ||
|
|
b877629cb1 | ||
|
|
0531549b6e | ||
|
|
38204f5fba | ||
|
|
8fa29cb00a | ||
|
|
b521cc2689 | ||
|
|
57c90c9750 | ||
|
|
d35ff7064f | ||
|
|
ab0791b817 | ||
|
|
959c5c997f | ||
|
|
845626410a | ||
|
|
3207479fa7 | ||
|
|
7601dc6d2a | ||
|
|
d1aa2df80e | ||
|
|
e979ad7f2d | ||
|
|
5ad3dd3378 | ||
|
|
60bd40d529 | ||
|
|
4a93af4d25 | ||
|
|
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 | ||
|
|
a5ee2f0d3d | ||
|
|
917aad684b | ||
|
|
e366cf9b3e | ||
|
|
3a02d34d3a | ||
|
|
adad8fc3cd | ||
|
|
08f1b67fcb | ||
|
|
28d6879897 | ||
|
|
1a72b28f53 | ||
|
|
1d5655272b | ||
|
|
5513b6b825 | ||
|
|
787aca1826 | ||
|
|
e89c8981f1 | ||
|
|
bf8c77bea1 | ||
|
|
0074b963b8 | ||
|
|
eabb7ce61c | ||
|
|
618d2bfc71 | ||
|
|
f6e4e85dfb | ||
|
|
f93bc9bad1 | ||
|
|
b05b9a1788 | ||
|
|
b42f21ec3e | ||
|
|
8fc49dc522 | ||
|
|
9ec3393650 | ||
|
|
f7c181c2c4 | ||
|
|
fee76ea7d5 | ||
|
|
1a80d6eee5 | ||
|
|
7384813979 | ||
|
|
a257a3a2b1 | ||
|
|
9a3603fa75 | ||
|
|
724ba348d1 | ||
|
|
0dc4769708 | ||
|
|
6f896d8846 | ||
|
|
10abc45da1 | ||
|
|
227b3e0d9c | ||
|
|
c659e4a757 | ||
|
|
8ed825fd49 | ||
|
|
63395f061e | ||
|
|
69b129b405 | ||
|
|
0bfcbc8be5 | ||
|
|
81f47e8866 | ||
|
|
b4918adbd8 | ||
|
|
b1310ad14d | ||
|
|
dcac336dc7 | ||
|
|
c39d9867bb | ||
|
|
e4f76688a0 | ||
|
|
e6103d6661 | ||
|
|
cc90a5c679 | ||
|
|
f107c4f49b | ||
|
|
e6a9167bb7 | ||
|
|
345b29ca11 | ||
|
|
b75d02a2b6 | ||
|
|
a1b4bdee61 | ||
|
|
edfff7551a | ||
|
|
1cda0f16a2 | ||
|
|
e40ec3e424 | ||
|
|
6919f7cede | ||
|
|
bcb95db4e2 | ||
|
|
8c5cde9bc3 | ||
|
|
b08b5edb2c | ||
|
|
88863a0c5d |
@@ -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
|
||||
@@ -82,14 +89,23 @@ jobs:
|
||||
sudo apt-get install -y valgrind
|
||||
|
||||
# Compile tests
|
||||
- name: cargo build
|
||||
- name: cargo build test-mem
|
||||
run: cargo build --features rt-net --bin test-mem
|
||||
working-directory: tests-integration
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind
|
||||
- name: Run valgrind test-mem
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
|
||||
|
||||
# Compile tests
|
||||
- name: cargo build test-process-signal
|
||||
run: cargo build --features rt-process-signal --bin test-process-signal
|
||||
working-directory: tests-integration
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind test-process-signal
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-process-signal
|
||||
|
||||
test-unstable:
|
||||
name: test tokio full --unstable
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -106,7 +122,7 @@ jobs:
|
||||
|
||||
# Run `tokio` with "unstable" cfg flag.
|
||||
- name: test tokio full --cfg unstable
|
||||
run: cargo test --features full
|
||||
run: cargo test --all-features
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
@@ -222,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
|
||||
@@ -249,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
|
||||
|
||||
@@ -268,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
|
||||
|
||||
@@ -124,17 +124,27 @@ arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
|
||||
When building documentation normally, the markers that list the features
|
||||
required for various parts of Tokio are missing. To build the documentation
|
||||
correctly, use this command:
|
||||
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
||||
```
|
||||
|
||||
There is currently a [bug in cargo] that means documentation cannot be built
|
||||
from the root of the workspace. If you `cd` into the `tokio` subdirectory the
|
||||
command shown above will work.
|
||||
|
||||
[bug in cargo]: https://github.com/rust-lang/cargo/issues/9274
|
||||
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -50,7 +50,15 @@ an asynchronous application.
|
||||
|
||||
## Example
|
||||
|
||||
A basic TCP echo server with Tokio:
|
||||
A basic TCP echo server with Tokio.
|
||||
|
||||
Make sure you activated the full features of the tokio crate on Cargo.toml:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1.8.0", features = ["full"] }
|
||||
```
|
||||
Then, on your main.rs:
|
||||
|
||||
```rust,no_run
|
||||
use tokio::net::TcpListener;
|
||||
@@ -58,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);
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Benchmark implementation details of the theaded scheduler. These benches are
|
||||
//! Benchmark implementation details of the threaded scheduler. These benches are
|
||||
//! intended to be used as a form of regression testing and not as a general
|
||||
//! purpose benchmark demonstrating real-world performance.
|
||||
|
||||
|
||||
+18
-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,15 @@ 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-ready"
|
||||
path = "named-pipe-ready.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,161 @@
|
||||
use std::io;
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn windows_main() -> io::Result<()> {
|
||||
use tokio::io::Interest;
|
||||
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 buf = {
|
||||
let mut read_buf = [0u8; 5];
|
||||
let mut read_buf_cursor = 0;
|
||||
|
||||
loop {
|
||||
server.readable().await?;
|
||||
|
||||
let buf = &mut read_buf[read_buf_cursor..];
|
||||
|
||||
match server.try_read(buf) {
|
||||
Ok(n) => {
|
||||
read_buf_cursor += n;
|
||||
|
||||
if read_buf_cursor == read_buf.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
read_buf
|
||||
};
|
||||
|
||||
{
|
||||
let write_buf = b"pong\n";
|
||||
let mut write_buf_cursor = 0;
|
||||
|
||||
loop {
|
||||
let buf = &write_buf[write_buf_cursor..];
|
||||
|
||||
if buf.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
server.writable().await?;
|
||||
|
||||
match server.try_write(buf) {
|
||||
Ok(n) => {
|
||||
write_buf_cursor += n;
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 read_buf = [0u8; 5];
|
||||
let mut read_buf_cursor = 0;
|
||||
let write_buf = b"ping\n";
|
||||
let mut write_buf_cursor = 0;
|
||||
|
||||
loop {
|
||||
let mut interest = Interest::READABLE;
|
||||
if write_buf_cursor < write_buf.len() {
|
||||
interest |= Interest::WRITABLE;
|
||||
}
|
||||
|
||||
let ready = client.ready(interest).await?;
|
||||
|
||||
if ready.is_readable() {
|
||||
let buf = &mut read_buf[read_buf_cursor..];
|
||||
|
||||
match client.try_read(buf) {
|
||||
Ok(n) => {
|
||||
read_buf_cursor += n;
|
||||
|
||||
if read_buf_cursor == read_buf.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ready.is_writable() {
|
||||
let buf = &write_buf[write_buf_cursor..];
|
||||
|
||||
if buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match client.try_write(buf) {
|
||||
Ok(n) => {
|
||||
write_buf_cursor += n;
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let buf = String::from_utf8_lossy(&read_buf).into_owned();
|
||||
|
||||
Ok::<_, io::Error>(buf)
|
||||
});
|
||||
|
||||
let (server, client) = tokio::try_join!(server, client)?;
|
||||
|
||||
assert_eq!(server?, *b"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,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)]
|
||||
| ^^^^^^^^^
|
||||
@@ -12,12 +12,27 @@ async fn main_attr_has_path_args() {}
|
||||
#[tokio::test]
|
||||
fn test_is_not_async() {}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_fn_has_args(_x: u8) {}
|
||||
|
||||
#[tokio::test(foo)]
|
||||
async fn test_attr_has_args() {}
|
||||
|
||||
#[tokio::test(foo = 123)]
|
||||
async fn test_unexpected_attr() {}
|
||||
|
||||
#[tokio::test(flavor = 123)]
|
||||
async fn test_flavor_not_string() {}
|
||||
|
||||
#[tokio::test(flavor = "foo")]
|
||||
async fn test_unknown_flavor() {}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", start_paused = false)]
|
||||
async fn test_multi_thread_with_start_paused() {}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
|
||||
async fn test_worker_threads_not_int() {}
|
||||
|
||||
#[tokio::test(flavor = "current_thread", worker_threads = 4)]
|
||||
async fn test_worker_threads_and_current_thread() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[test]
|
||||
async fn test_has_second_test_attr() {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
error: the async keyword is missing from the function declaration
|
||||
error: the `async` keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:4:1
|
||||
|
|
||||
4 | fn main_is_not_async() {}
|
||||
@@ -16,26 +16,56 @@ error: Must have specified ident
|
||||
9 | #[tokio::main(threadpool::bar)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: the async keyword is missing from the function declaration
|
||||
error: the `async` keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:13:1
|
||||
|
|
||||
13 | fn test_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: the test function cannot accept arguments
|
||||
--> $DIR/macros_invalid_input.rs:16:27
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
|
||||
--> $DIR/macros_invalid_input.rs:15:15
|
||||
|
|
||||
16 | async fn test_fn_has_args(_x: u8) {}
|
||||
| ^^^^^^
|
||||
15 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
|
||||
--> $DIR/macros_invalid_input.rs:18:15
|
||||
|
|
||||
18 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
18 | #[tokio::test(foo = 123)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: Failed to parse value of `flavor` as string.
|
||||
--> $DIR/macros_invalid_input.rs:21:24
|
||||
|
|
||||
21 | #[tokio::test(flavor = 123)]
|
||||
| ^^^
|
||||
|
||||
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
|
||||
--> $DIR/macros_invalid_input.rs:24:24
|
||||
|
|
||||
24 | #[tokio::test(flavor = "foo")]
|
||||
| ^^^^^
|
||||
|
||||
error: The `start_paused` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
|
||||
--> $DIR/macros_invalid_input.rs:27:55
|
||||
|
|
||||
27 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
|
||||
| ^^^^^
|
||||
|
||||
error: Failed to parse value of `worker_threads` as integer.
|
||||
--> $DIR/macros_invalid_input.rs:30:57
|
||||
|
|
||||
30 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
|
||||
| ^^^^^
|
||||
|
||||
error: The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[tokio::test(flavor = "multi_thread")]`
|
||||
--> $DIR/macros_invalid_input.rs:33:59
|
||||
|
|
||||
33 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
|
||||
| ^
|
||||
|
||||
error: second test attribute is supplied
|
||||
--> $DIR/macros_invalid_input.rs:22:1
|
||||
--> $DIR/macros_invalid_input.rs:37:1
|
||||
|
|
||||
22 | #[test]
|
||||
37 | #[test]
|
||||
| ^^^^^^^
|
||||
|
||||
@@ -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(());)
|
||||
|
|
||||
@@ -2,9 +2,18 @@
|
||||
fn compile_fail_full() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.pass("tests/pass/forward_args_and_output.rs");
|
||||
|
||||
#[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");
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
fn main() {}
|
||||
|
||||
// arguments and output type is forwarded so other macros can access them
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_fn_has_args(_x: u8) {}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_has_output() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -12,9 +12,15 @@ name = "test-cat"
|
||||
name = "test-mem"
|
||||
required-features = ["rt-net"]
|
||||
|
||||
[[bin]]
|
||||
name = "test-process-signal"
|
||||
required-features = ["rt-process-signal"]
|
||||
|
||||
[features]
|
||||
# For mem check
|
||||
rt-net = ["tokio/rt", "tokio/rt-multi-thread", "tokio/net"]
|
||||
# For test-process-signal
|
||||
rt-process-signal = ["rt", "tokio/process", "tokio/signal"]
|
||||
|
||||
full = [
|
||||
"macros",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// https://github.com/tokio-rs/tokio/issues/3550
|
||||
fn main() {
|
||||
for _ in 0..1000 {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
drop(rt);
|
||||
}
|
||||
}
|
||||
@@ -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,4 +1,4 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
+81
-53
@@ -1,7 +1,6 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum RuntimeFlavor {
|
||||
@@ -34,6 +33,7 @@ struct Configuration {
|
||||
flavor: Option<RuntimeFlavor>,
|
||||
worker_threads: Option<(usize, Span)>,
|
||||
start_paused: Option<(bool, Span)>,
|
||||
is_test: bool,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
@@ -47,6 +47,7 @@ impl Configuration {
|
||||
flavor: None,
|
||||
worker_threads: None,
|
||||
start_paused: None,
|
||||
is_test,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,16 +93,25 @@ impl Configuration {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn macro_name(&self) -> &'static str {
|
||||
if self.is_test {
|
||||
"tokio::test"
|
||||
} else {
|
||||
"tokio::main"
|
||||
}
|
||||
}
|
||||
|
||||
fn build(&self) -> Result<FinalConfig, syn::Error> {
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
use RuntimeFlavor::*;
|
||||
|
||||
let worker_threads = match (flavor, self.worker_threads) {
|
||||
(CurrentThread, Some((_, worker_threads_span))) => {
|
||||
return Err(syn::Error::new(
|
||||
worker_threads_span,
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor.",
|
||||
))
|
||||
let msg = format!(
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(worker_threads_span, msg));
|
||||
}
|
||||
(CurrentThread, None) => None,
|
||||
(Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
@@ -119,10 +129,11 @@ impl Configuration {
|
||||
|
||||
let start_paused = match (flavor, self.start_paused) {
|
||||
(Threaded, Some((_, start_paused_span))) => {
|
||||
return Err(syn::Error::new(
|
||||
start_paused_span,
|
||||
"The `start_paused` option requires the `current_thread` runtime flavor.",
|
||||
));
|
||||
let msg = format!(
|
||||
"The `start_paused` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
|
||||
self.macro_name(),
|
||||
);
|
||||
return Err(syn::Error::new(start_paused_span, msg));
|
||||
}
|
||||
(CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(_, None) => None,
|
||||
@@ -142,12 +153,12 @@ fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as integer: {}", field, e),
|
||||
format!("Failed to parse value of `{}` as integer: {}", field, e),
|
||||
)),
|
||||
},
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as integer.", field),
|
||||
format!("Failed to parse value of `{}` as integer.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -158,7 +169,7 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
|
||||
syn::Lit::Verbatim(s) => Ok(s.to_string()),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as string.", field),
|
||||
format!("Failed to parse value of `{}` as string.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -168,7 +179,7 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
|
||||
syn::Lit::Bool(b) => Ok(b.value),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as bool.", field),
|
||||
format!("Failed to parse value of `{}` as bool.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -179,24 +190,13 @@ fn parse_knobs(
|
||||
is_test: bool,
|
||||
rt_multi_thread: bool,
|
||||
) -> Result<TokenStream, syn::Error> {
|
||||
let sig = &mut input.sig;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
if sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return Err(syn::Error::new_spanned(sig.fn_token, msg));
|
||||
if input.sig.asyncness.take().is_none() {
|
||||
let msg = "the `async` keyword is missing from the function declaration";
|
||||
return Err(syn::Error::new_spanned(input.sig.fn_token, msg));
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let macro_name = if is_test {
|
||||
"tokio::test"
|
||||
} else {
|
||||
"tokio::main"
|
||||
};
|
||||
let mut config = Configuration::new(is_test, rt_multi_thread);
|
||||
let macro_name = config.macro_name();
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
@@ -208,20 +208,32 @@ fn parse_knobs(
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"worker_threads" => {
|
||||
config.set_worker_threads(namevalue.lit.clone(), namevalue.span())?;
|
||||
config.set_worker_threads(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
}
|
||||
"flavor" => {
|
||||
config.set_flavor(namevalue.lit.clone(), namevalue.span())?;
|
||||
config.set_flavor(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
}
|
||||
"start_paused" => {
|
||||
config.set_start_paused(namevalue.lit.clone(), namevalue.span())?;
|
||||
config.set_start_paused(
|
||||
namevalue.lit.clone(),
|
||||
syn::spanned::Spanned::span(&namevalue.lit),
|
||||
)?;
|
||||
}
|
||||
"core_threads" => {
|
||||
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name);
|
||||
let msg = format!(
|
||||
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`",
|
||||
name,
|
||||
);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
}
|
||||
@@ -266,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()
|
||||
},
|
||||
};
|
||||
@@ -281,26 +311,31 @@ fn parse_knobs(
|
||||
rt = quote! { #rt.start_paused(#v) };
|
||||
}
|
||||
|
||||
let header = {
|
||||
if is_test {
|
||||
quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
let header = if is_test {
|
||||
quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let result = quote! {
|
||||
#header
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
let body = &input.block;
|
||||
let brace_token = input.block.brace_token;
|
||||
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
|
||||
{
|
||||
#rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async #body)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
input.block.brace_token = brace_token;
|
||||
|
||||
let result = quote! {
|
||||
#header
|
||||
#input
|
||||
};
|
||||
|
||||
Ok(result.into())
|
||||
@@ -334,12 +369,5 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
}
|
||||
}
|
||||
|
||||
if !input.sig.inputs.is_empty() {
|
||||
let msg = "the test function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
@@ -167,6 +167,8 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note that `start_paused` requires the `test-util` feature to be enabled.
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the Tokio crate in your dependencies this macro will not work.
|
||||
@@ -257,6 +259,8 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note that `start_paused` requires the `test-util` feature to be enabled.
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the Tokio crate in your dependencies this macro will not work.
|
||||
|
||||
@@ -1,3 +1,39 @@
|
||||
# 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
|
||||
|
||||
- stream: documentation note for throttle `Unpin` ([#3600])
|
||||
|
||||
[#3600]: https://github.com/tokio-rs/tokio/pull/3600
|
||||
|
||||
# 0.1.4 (March 9, 2021)
|
||||
|
||||
Added
|
||||
|
||||
- signal: add `Signal` wrapper ([#3510])
|
||||
|
||||
Fixed
|
||||
|
||||
- stream: remove duplicate `doc_cfg` declaration ([#3561])
|
||||
- sync: yield initial value in `WatchStream` ([#3576])
|
||||
|
||||
[#3510]: https://github.com/tokio-rs/tokio/pull/3510
|
||||
[#3561]: https://github.com/tokio-rs/tokio/pull/3561
|
||||
[#3576]: https://github.com/tokio-rs/tokio/pull/3576
|
||||
|
||||
# 0.1.3 (February 5, 2021)
|
||||
|
||||
Added
|
||||
|
||||
@@ -6,13 +6,13 @@ name = "tokio-stream"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.3"
|
||||
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.3/tokio_stream"
|
||||
documentation = "https://docs.rs/tokio-stream/0.1.6/tokio_stream"
|
||||
description = """
|
||||
Utilities to work with `Stream` and `tokio`.
|
||||
"""
|
||||
@@ -30,16 +30,16 @@ signal = ["tokio/signal"]
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.2.0", features = ["sync"] }
|
||||
tokio-util = { version = "0.6.3", optional = true }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["sync"] }
|
||||
tokio-util = { version = "0.6.3", path = "../tokio-util", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.2.0", features = ["full", "test-util"] }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
|
||||
async-stream = "0.3"
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
proptest = "0.10.0"
|
||||
proptest = "1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -515,7 +515,7 @@ pub trait StreamExt: Stream {
|
||||
/// Skip elements from the underlying stream while the provided predicate
|
||||
/// resolves to `true`.
|
||||
///
|
||||
/// This function, like [`Iterator::skip_while`], will ignore elemets from the
|
||||
/// This function, like [`Iterator::skip_while`], will ignore elements from the
|
||||
/// stream until the predicate `f` resolves to `false`. Once one element
|
||||
/// returns false, the rest of the elements will be yielded.
|
||||
///
|
||||
|
||||
@@ -38,18 +38,21 @@ where
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = self.project();
|
||||
let next = futures_core::ready!(Pin::new(me.stream).poll_next(cx));
|
||||
let mut stream = Pin::new(me.stream);
|
||||
|
||||
match next {
|
||||
Some(v) => {
|
||||
if !(me.f)(v) {
|
||||
Poll::Ready(false)
|
||||
} else {
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
// Take a maximum of 32 items from the stream before yielding.
|
||||
for _ in 0..32 {
|
||||
match futures_core::ready!(stream.as_mut().poll_next(cx)) {
|
||||
Some(v) => {
|
||||
if !(me.f)(v) {
|
||||
return Poll::Ready(false);
|
||||
}
|
||||
}
|
||||
None => return Poll::Ready(true),
|
||||
}
|
||||
None => Poll::Ready(true),
|
||||
}
|
||||
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,18 +38,21 @@ where
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = self.project();
|
||||
let next = futures_core::ready!(Pin::new(me.stream).poll_next(cx));
|
||||
let mut stream = Pin::new(me.stream);
|
||||
|
||||
match next {
|
||||
Some(v) => {
|
||||
if (me.f)(v) {
|
||||
Poll::Ready(true)
|
||||
} else {
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
// Take a maximum of 32 items from the stream before yielding.
|
||||
for _ in 0..32 {
|
||||
match futures_core::ready!(stream.as_mut().poll_next(cx)) {
|
||||
Some(v) => {
|
||||
if (me.f)(v) {
|
||||
return Poll::Ready(true);
|
||||
}
|
||||
}
|
||||
None => return Poll::Ready(false),
|
||||
}
|
||||
None => Poll::Ready(false),
|
||||
}
|
||||
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ where
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// Stream for the [`throttle`](throttle) function.
|
||||
/// Stream for the [`throttle`](throttle) function. This object is `!Unpin`. If you need it to
|
||||
/// implement `Unpin` you can pin your throttle like this: `Box::pin(your_throttle)`.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct Throttle<T> {
|
||||
|
||||
@@ -69,7 +69,18 @@ impl<S: Stream> Stream for Timeout<S> {
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.stream.size_hint()
|
||||
let (lower, upper) = self.stream.size_hint();
|
||||
|
||||
// The timeout stream may insert an error before and after each message
|
||||
// from the underlying stream, but no more than one error between each
|
||||
// message. Hence the upper bound is computed as 2x+1.
|
||||
|
||||
// Using a helper function to enable use of question mark operator.
|
||||
fn twice_plus_one(value: Option<usize>) -> Option<usize> {
|
||||
value?.checked_mul(2)?.checked_add(1)
|
||||
}
|
||||
|
||||
(lower, twice_plus_one(upper))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -11,7 +11,7 @@ use tokio::sync::watch::error::RecvError;
|
||||
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// This stream will always start by yielding the current value when the WatchStream is polled,
|
||||
/// regardles of whether it was the initial value or sent afterwards.
|
||||
/// regardless of whether it was the initial value or sent afterwards.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -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,15 @@
|
||||
# 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])
|
||||
|
||||
[#3594]: https://github.com/tokio-rs/tokio/pull/3594
|
||||
|
||||
# 0.4.0 (December 23, 2020)
|
||||
|
||||
- Track `tokio` 1.0 release.
|
||||
|
||||
@@ -6,20 +6,20 @@ name = "tokio-test"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.0"
|
||||
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.0/tokio_test"
|
||||
documentation = "https://docs.rs/tokio-test/0.4.2/tokio_test"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
categories = ["asynchronous", "testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
async-stream = "0.3"
|
||||
|
||||
@@ -27,7 +27,7 @@ bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.2.0", path = "../tokio", features = ["full"] }
|
||||
futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{self, Duration, Instant, Sleep};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
use futures_core::{ready, Stream};
|
||||
use std::collections::VecDeque;
|
||||
@@ -69,8 +70,7 @@ struct Inner {
|
||||
waiting: Option<Instant>,
|
||||
sleep: Option<Pin<Box<Sleep>>>,
|
||||
read_wait: Option<Waker>,
|
||||
// rx: mpsc::UnboundedReceiver<Action>,
|
||||
rx: Pin<Box<dyn Stream<Item = Action> + Send>>,
|
||||
rx: UnboundedReceiverStream<Action>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
@@ -185,13 +185,9 @@ impl Handle {
|
||||
|
||||
impl Inner {
|
||||
fn new(actions: VecDeque<Action>) -> (Inner, Handle) {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let rx = Box::pin(async_stream::stream! {
|
||||
while let Some(item) = rx.recv().await {
|
||||
yield item;
|
||||
}
|
||||
});
|
||||
let rx = UnboundedReceiverStream::new(rx);
|
||||
|
||||
let inner = Inner {
|
||||
actions,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
|
||||
//! Tokio and Futures based testing utilites
|
||||
//! Tokio and Futures based testing utilities
|
||||
|
||||
pub mod io;
|
||||
|
||||
@@ -21,9 +21,9 @@ pub mod task;
|
||||
/// future completes.
|
||||
///
|
||||
/// For more information, see the documentation for
|
||||
/// [`tokio::runtime::current_thread::Runtime::block_on`][runtime-block-on].
|
||||
/// [`tokio::runtime::Runtime::block_on`][runtime-block-on].
|
||||
///
|
||||
/// [runtime-block-on]: https://docs.rs/tokio/0.2.0-alpha.2/tokio/runtime/current_thread/struct.Runtime.html#method.block_on
|
||||
/// [runtime-block-on]: https://docs.rs/tokio/1.3.0/tokio/runtime/struct.Runtime.html#method.block_on
|
||||
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
use tokio::runtime;
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ impl ThreadWaker {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears any previously received wakes, avoiding potential spurrious
|
||||
/// Clears any previously received wakes, avoiding potential spurious
|
||||
/// wake notifications. This should only be called immediately before running the
|
||||
/// task.
|
||||
fn clear(&self) {
|
||||
|
||||
@@ -1,3 +1,53 @@
|
||||
# 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
|
||||
|
||||
- util: makes `Framed` and `FramedStream` resumable after eof ([#3272])
|
||||
- util: add `PollSemaphore::{add_permits, available_permits}` ([#3683])
|
||||
|
||||
### Fixed
|
||||
|
||||
- chore: avoid allocation if `PollSemaphore` is unused ([#3634])
|
||||
|
||||
[#3272]: https://github.com/tokio-rs/tokio/pull/3272
|
||||
[#3634]: https://github.com/tokio-rs/tokio/pull/3634
|
||||
[#3683]: https://github.com/tokio-rs/tokio/pull/3683
|
||||
|
||||
# 0.6.5 (March 20, 2021)
|
||||
|
||||
### Fixed
|
||||
|
||||
- util: annotate time module as requiring `time` feature ([#3606])
|
||||
|
||||
[#3606]: https://github.com/tokio-rs/tokio/pull/3606
|
||||
|
||||
# 0.6.4 (March 9, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- codec: `AnyDelimiter` codec ([#3406])
|
||||
- sync: add pollable `mpsc::Sender` ([#3490])
|
||||
|
||||
### Fixed
|
||||
|
||||
- codec: `LinesCodec` should only return `MaxLineLengthExceeded` once per line ([#3556])
|
||||
- sync: fuse PollSemaphore ([#3578])
|
||||
|
||||
[#3406]: https://github.com/tokio-rs/tokio/pull/3406
|
||||
[#3490]: https://github.com/tokio-rs/tokio/pull/3490
|
||||
[#3556]: https://github.com/tokio-rs/tokio/pull/3556
|
||||
[#3578]: https://github.com/tokio-rs/tokio/pull/3578
|
||||
|
||||
# 0.6.3 (January 31, 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.3"
|
||||
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.3/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"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -47,3 +47,13 @@ macro_rules! cfg_rt {
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_time {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ impl Default for AnyDelimiterCodec {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occured while encoding or decoding a chunk.
|
||||
/// An error occurred while encoding or decoding a chunk.
|
||||
#[derive(Debug)]
|
||||
pub enum AnyDelimiterCodecError {
|
||||
/// The maximum chunk length was exceeded.
|
||||
|
||||
@@ -16,6 +16,20 @@ use std::io;
|
||||
/// implementing stateful streaming parsers. In many cases, though, this type
|
||||
/// will simply be a unit struct (e.g. `struct HttpDecoder`).
|
||||
///
|
||||
/// For some underlying data-sources, namely files and FIFOs,
|
||||
/// it's possible to temporarily read 0 bytes by reaching EOF.
|
||||
///
|
||||
/// In these cases `decode_eof` will be called until it signals
|
||||
/// fullfillment of all closing frames by returning `Ok(None)`.
|
||||
/// After that, repeated attempts to read from the [`Framed`] or [`FramedRead`]
|
||||
/// will not invoke `decode` or `decode_eof` again, until data can be read
|
||||
/// during a retry.
|
||||
///
|
||||
/// It is up to the Decoder to keep track of a restart after an EOF,
|
||||
/// and to decide how to handle such an event by, for example,
|
||||
/// allowing frames to cross EOF boundaries, re-emitting opening frames, or
|
||||
/// resetting the entire internal state.
|
||||
///
|
||||
/// [`Framed`]: crate::codec::Framed
|
||||
/// [`FramedRead`]: crate::codec::FramedRead
|
||||
pub trait Decoder {
|
||||
@@ -115,13 +129,18 @@ pub trait Decoder {
|
||||
/// This method defaults to calling `decode` and returns an error if
|
||||
/// `Ok(None)` is returned while there is unconsumed data in `buf`.
|
||||
/// Typically this doesn't need to be implemented unless the framing
|
||||
/// protocol differs near the end of the stream.
|
||||
/// protocol differs near the end of the stream, or if you need to construct
|
||||
/// frames _across_ eof boundaries on sources that can be resumed.
|
||||
///
|
||||
/// Note that the `buf` argument may be empty. If a previous call to
|
||||
/// `decode_eof` consumed all the bytes in the buffer, `decode_eof` will be
|
||||
/// called again until it returns `None`, indicating that there are no more
|
||||
/// frames to yield. This behavior enables returning finalization frames
|
||||
/// that may not be based on inbound data.
|
||||
///
|
||||
/// Once `None` has been returned, `decode_eof` won't be called again until
|
||||
/// an attempt to resume the stream has been made, where the underlying stream
|
||||
/// actually returned more data.
|
||||
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
match self.decode(buf)? {
|
||||
Some(frame) => Ok(Some(frame)),
|
||||
|
||||
@@ -52,6 +52,11 @@ where
|
||||
/// calling [`split`] on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
///
|
||||
/// Note that, for some byte sources, the stream can be resumed after an EOF
|
||||
/// by reading from it, even after it has returned `None`. Repeated attempts
|
||||
/// to do so, without new data available, continue to return `None` without
|
||||
/// creating more (closing) frames.
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Decode`]: crate::codec::Decoder
|
||||
|
||||
@@ -120,42 +120,97 @@ where
|
||||
|
||||
let mut pinned = self.project();
|
||||
let state: &mut ReadFrame = pinned.state.borrow_mut();
|
||||
// The following loops implements a state machine with each state corresponding
|
||||
// to a combination of the `is_readable` and `eof` flags. States persist across
|
||||
// loop entries and most state transitions occur with a return.
|
||||
//
|
||||
// The initial state is `reading`.
|
||||
//
|
||||
// | state | eof | is_readable |
|
||||
// |---------|-------|-------------|
|
||||
// | reading | false | false |
|
||||
// | framing | false | true |
|
||||
// | pausing | true | true |
|
||||
// | paused | true | false |
|
||||
//
|
||||
// `decode_eof`
|
||||
// returns `Some` read 0 bytes
|
||||
// │ │ │ │
|
||||
// │ ▼ │ ▼
|
||||
// ┌───────┐ `decode_eof` ┌──────┐
|
||||
// ┌──read 0 bytes──▶│pausing│─returns `None`─▶│paused│──┐
|
||||
// │ └───────┘ └──────┘ │
|
||||
// pending read┐ │ ┌──────┐ │ ▲ │
|
||||
// │ │ │ │ │ │ │ │
|
||||
// │ ▼ │ │ `decode` returns `Some`│ pending read
|
||||
// │ ╔═══════╗ ┌───────┐◀─┘ │
|
||||
// └──║reading║─read n>0 bytes─▶│framing│ │
|
||||
// ╚═══════╝ └───────┘◀──────read n>0 bytes┘
|
||||
// ▲ │
|
||||
// │ │
|
||||
// └─`decode` returns `None`─┘
|
||||
loop {
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
||||
// "readable". Readable is defined as not having returned `None`. If
|
||||
// the upstream has returned EOF, and the decoder is no longer
|
||||
// readable, it can be assumed that the decoder will never become
|
||||
// readable again, at which point the stream is terminated.
|
||||
// Repeatedly call `decode` or `decode_eof` while the buffer is "readable",
|
||||
// i.e. it _might_ contain data consumable as a frame or closing frame.
|
||||
// Both signal that there is no such data by returning `None`.
|
||||
//
|
||||
// If `decode` couldn't read a frame and the upstream source has returned eof,
|
||||
// `decode_eof` will attempt to decode the remaining bytes as closing frames.
|
||||
//
|
||||
// If the underlying AsyncRead is resumable, we may continue after an EOF,
|
||||
// but must finish emitting all of it's associated `decode_eof` frames.
|
||||
// Furthermore, we don't want to emit any `decode_eof` frames on retried
|
||||
// reads after an EOF unless we've actually read more data.
|
||||
if state.is_readable {
|
||||
// pausing or framing
|
||||
if state.eof {
|
||||
// pausing
|
||||
let frame = pinned.codec.decode_eof(&mut state.buffer)?;
|
||||
if frame.is_none() {
|
||||
state.is_readable = false; // prepare pausing -> paused
|
||||
}
|
||||
// implicit pausing -> pausing or pausing -> paused
|
||||
return Poll::Ready(frame.map(Ok));
|
||||
}
|
||||
|
||||
// framing
|
||||
trace!("attempting to decode a frame");
|
||||
|
||||
if let Some(frame) = pinned.codec.decode(&mut state.buffer)? {
|
||||
trace!("frame decoded from buffer");
|
||||
// implicit framing -> framing
|
||||
return Poll::Ready(Some(Ok(frame)));
|
||||
}
|
||||
|
||||
// framing -> reading
|
||||
state.is_readable = false;
|
||||
}
|
||||
|
||||
assert!(!state.eof);
|
||||
|
||||
// Otherwise, try to read more data and try again. Make sure we've
|
||||
// got room for at least one byte to read to ensure that we don't
|
||||
// get a spurious 0 that looks like EOF
|
||||
// reading or paused
|
||||
// If we can't build a frame yet, try to read more data and try again.
|
||||
// Make sure we've got room for at least one byte to read to ensure
|
||||
// that we don't get a spurious 0 that looks like EOF.
|
||||
state.buffer.reserve(1);
|
||||
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer)? {
|
||||
Poll::Ready(ct) => ct,
|
||||
// implicit reading -> reading or implicit paused -> paused
|
||||
Poll::Pending => return Poll::Pending,
|
||||
};
|
||||
if bytect == 0 {
|
||||
if state.eof {
|
||||
// We're already at an EOF, and since we've reached this path
|
||||
// we're also not readable. This implies that we've already finished
|
||||
// our `decode_eof` handling, so we can simply return `None`.
|
||||
// implicit paused -> paused
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
// prepare reading -> paused
|
||||
state.eof = true;
|
||||
} else {
|
||||
// prepare paused -> framing or noop reading -> framing
|
||||
state.eof = false;
|
||||
}
|
||||
|
||||
// paused -> framing or reading -> framing or reading -> pausing
|
||||
state.is_readable = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,7 +486,7 @@ impl LengthDelimitedCodec {
|
||||
// Skip the required bytes
|
||||
src.advance(self.builder.length_field_offset);
|
||||
|
||||
// match endianess
|
||||
// match endianness
|
||||
let n = if self.builder.length_field_is_big_endian {
|
||||
src.get_uint(field_len)
|
||||
} else {
|
||||
@@ -535,14 +535,14 @@ impl LengthDelimitedCodec {
|
||||
Ok(Some(n))
|
||||
}
|
||||
|
||||
fn decode_data(&self, n: usize, src: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
||||
fn decode_data(&self, n: usize, src: &mut BytesMut) -> Option<BytesMut> {
|
||||
// At this point, the buffer has already had the required capacity
|
||||
// reserved. All there is to do is read.
|
||||
if src.len() < n {
|
||||
return Ok(None);
|
||||
return None;
|
||||
}
|
||||
|
||||
Ok(Some(src.split_to(n)))
|
||||
Some(src.split_to(n))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@ impl Decoder for LengthDelimitedCodec {
|
||||
DecodeState::Data(n) => n,
|
||||
};
|
||||
|
||||
match self.decode_data(n, src)? {
|
||||
match self.decode_data(n, src) {
|
||||
Some(data) => {
|
||||
// Update the decode state
|
||||
self.state = DecodeState::Head;
|
||||
|
||||
@@ -203,12 +203,12 @@ impl Default for LinesCodec {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occured while encoding or decoding a line.
|
||||
/// An error occurred while encoding or decoding a line.
|
||||
#[derive(Debug)]
|
||||
pub enum LinesCodecError {
|
||||
/// The maximum line length was exceeded.
|
||||
MaxLineLengthExceeded,
|
||||
/// An IO error occured.
|
||||
/// An IO error occurred.
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ pub enum Either<L, R> {
|
||||
}
|
||||
|
||||
/// A small helper macro which reduces amount of boilerplate in the actual trait method implementation.
|
||||
/// It takes an invokation of method as an argument (e.g. `self.poll(cx)`), and redirects it to either
|
||||
/// It takes an invocation of method as an argument (e.g. `self.poll(cx)`), and redirects it to either
|
||||
/// enum variant held in `self`.
|
||||
macro_rules! delegate_call {
|
||||
($self:ident.$method:ident($($args:ident),+)) => {
|
||||
|
||||
@@ -45,13 +45,14 @@ cfg_rt! {
|
||||
pub mod context;
|
||||
}
|
||||
|
||||
cfg_time! {
|
||||
pub mod time;
|
||||
}
|
||||
|
||||
pub mod sync;
|
||||
|
||||
pub mod either;
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
pub mod time;
|
||||
|
||||
#[cfg(any(feature = "io", feature = "codec"))]
|
||||
mod util {
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! An asynchronously awaitable `CancellationToken`.
|
||||
//! The token allows to signal a cancellation request to one or more tasks.
|
||||
pub(crate) mod guard;
|
||||
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::loom::sync::Mutex;
|
||||
@@ -11,6 +12,8 @@ use core::ptr::NonNull;
|
||||
use core::sync::atomic::Ordering;
|
||||
use core::task::{Context, Poll, Waker};
|
||||
|
||||
use guard::DropGuard;
|
||||
|
||||
/// A token which can be used to signal a cancellation request to one or more
|
||||
/// tasks.
|
||||
///
|
||||
@@ -275,6 +278,14 @@ impl CancellationToken {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `DropGuard` for this token.
|
||||
///
|
||||
/// Returned guard will cancel this token (and all its children) on drop
|
||||
/// unless disarmed.
|
||||
pub fn drop_guard(self) -> DropGuard {
|
||||
DropGuard { inner: Some(self) }
|
||||
}
|
||||
|
||||
unsafe fn register(
|
||||
&self,
|
||||
wait_node: &mut ListNode<WaitQueueEntry>,
|
||||
@@ -764,8 +775,8 @@ impl CancellationTokenState {
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
||||
// So far the token is not cancelled. However it could be cancelld before
|
||||
// we get the chance to store the `Waker`. Therfore we need to check
|
||||
// So far the token is not cancelled. However it could be cancelled before
|
||||
// we get the chance to store the `Waker`. Therefore we need to check
|
||||
// for cancellation again inside the mutex.
|
||||
let mut guard = self.synchronized.lock().unwrap();
|
||||
if guard.is_cancelled {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
use crate::sync::CancellationToken;
|
||||
|
||||
/// A wrapper for cancellation token which automatically cancels
|
||||
/// it on drop. It is created using `drop_guard` method on the `CancellationToken`.
|
||||
#[derive(Debug)]
|
||||
pub struct DropGuard {
|
||||
pub(super) inner: Option<CancellationToken>,
|
||||
}
|
||||
|
||||
impl DropGuard {
|
||||
/// Returns stored cancellation token and removes this drop guard instance
|
||||
/// (i.e. it will no longer cancel token). Other guards for this token
|
||||
/// are not affected.
|
||||
pub fn disarm(mut self) -> CancellationToken {
|
||||
self.inner
|
||||
.take()
|
||||
.expect("`inner` can be only None in a destructor")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DropGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = &self.inner {
|
||||
inner.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +222,7 @@ impl<T> LinkedList<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the linked list doesn not contain any node
|
||||
/// Returns whether the linked list does not contain any node
|
||||
pub fn is_empty(&self) -> bool {
|
||||
if self.head.is_some() {
|
||||
return false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Synchronization primitives
|
||||
|
||||
mod cancellation_token;
|
||||
pub use cancellation_token::{CancellationToken, WaitForCancellationFuture};
|
||||
pub use cancellation_token::{guard::DropGuard, CancellationToken, WaitForCancellationFuture};
|
||||
|
||||
mod intrusive_double_linked_list;
|
||||
|
||||
|
||||
@@ -126,10 +126,7 @@ impl<T: Send + 'static> PollSender<T> {
|
||||
/// If this method returns `None`, then the channel is closed. (But it is
|
||||
/// not guaranteed to return `None` if the channel is closed.)
|
||||
pub fn clone_inner(&self) -> Option<Sender<T>> {
|
||||
match &self.sender {
|
||||
Some(sender) => Some((&**sender).clone()),
|
||||
None => None,
|
||||
}
|
||||
self.sender.as_ref().map(|sender| (&**sender).clone())
|
||||
}
|
||||
|
||||
/// Access the underlying `Sender`.
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore};
|
||||
use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore, TryAcquireError};
|
||||
|
||||
use super::ReusableBoxFuture;
|
||||
|
||||
@@ -12,17 +12,15 @@ use super::ReusableBoxFuture;
|
||||
/// [`Semaphore`]: tokio::sync::Semaphore
|
||||
pub struct PollSemaphore {
|
||||
semaphore: Arc<Semaphore>,
|
||||
permit_fut: ReusableBoxFuture<Result<OwnedSemaphorePermit, AcquireError>>,
|
||||
permit_fut: Option<ReusableBoxFuture<Result<OwnedSemaphorePermit, AcquireError>>>,
|
||||
}
|
||||
|
||||
impl PollSemaphore {
|
||||
/// Create a new `PollSemaphore`.
|
||||
pub fn new(semaphore: Arc<Semaphore>) -> Self {
|
||||
let fut = Arc::clone(&semaphore).acquire_owned();
|
||||
|
||||
Self {
|
||||
semaphore,
|
||||
permit_fut: ReusableBoxFuture::new(fut),
|
||||
permit_fut: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,16 +53,58 @@ impl PollSemaphore {
|
||||
/// the `Waker` from the `Context` passed to the most recent call is
|
||||
/// scheduled to receive a wakeup.
|
||||
pub fn poll_acquire(&mut self, cx: &mut Context<'_>) -> Poll<Option<OwnedSemaphorePermit>> {
|
||||
let result = ready!(self.permit_fut.poll(cx));
|
||||
let permit_future = match self.permit_fut.as_mut() {
|
||||
Some(fut) => fut,
|
||||
None => {
|
||||
// avoid allocations completely if we can grab a permit immediately
|
||||
match Arc::clone(&self.semaphore).try_acquire_owned() {
|
||||
Ok(permit) => return Poll::Ready(Some(permit)),
|
||||
Err(TryAcquireError::Closed) => return Poll::Ready(None),
|
||||
Err(TryAcquireError::NoPermits) => {}
|
||||
}
|
||||
|
||||
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
|
||||
self.permit_fut
|
||||
.get_or_insert(ReusableBoxFuture::new(next_fut))
|
||||
}
|
||||
};
|
||||
|
||||
let result = ready!(permit_future.poll(cx));
|
||||
|
||||
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
|
||||
self.permit_fut.set(next_fut);
|
||||
permit_future.set(next_fut);
|
||||
|
||||
match result {
|
||||
Ok(permit) => Poll::Ready(Some(permit)),
|
||||
Err(_closed) => Poll::Ready(None),
|
||||
Err(_closed) => {
|
||||
self.permit_fut = None;
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current number of available permits.
|
||||
///
|
||||
/// This is equivalent to the [`Semaphore::available_permits`] method on the
|
||||
/// `tokio::sync::Semaphore` type.
|
||||
///
|
||||
/// [`Semaphore::available_permits`]: tokio::sync::Semaphore::available_permits
|
||||
pub fn available_permits(&self) -> usize {
|
||||
self.semaphore.available_permits()
|
||||
}
|
||||
|
||||
/// Adds `n` new permits to the semaphore.
|
||||
///
|
||||
/// The maximum number of permits is `usize::MAX >> 3`, and this function
|
||||
/// will panic if the limit is exceeded.
|
||||
///
|
||||
/// This is equivalent to the [`Semaphore::add_permits`] method on the
|
||||
/// `tokio::sync::Semaphore` type.
|
||||
///
|
||||
/// [`Semaphore::add_permits`]: tokio::sync::Semaphore::add_permits
|
||||
pub fn add_permits(&self, n: usize) {
|
||||
self.semaphore.add_permits(n);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for PollSemaphore {
|
||||
@@ -88,3 +128,9 @@ impl fmt::Debug for PollSemaphore {
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Semaphore> for PollSemaphore {
|
||||
fn as_ref(&self) -> &Semaphore {
|
||||
&*self.semaphore
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,24 +30,24 @@ use std::task::{self, Poll, Waker};
|
||||
///
|
||||
/// Once delays have been configured, the `DelayQueue` is used via its
|
||||
/// [`Stream`] implementation. [`poll_expired`] is called. If an entry has reached its
|
||||
/// deadline, it is returned. If not, `Poll::Pending` indicating that the
|
||||
/// deadline, it is returned. If not, `Poll::Pending` is returned indicating that the
|
||||
/// current task will be notified once the deadline has been reached.
|
||||
///
|
||||
/// # `Stream` implementation
|
||||
///
|
||||
/// Items are retrieved from the queue via [`DelayQueue::poll_expired`]. If no delays have
|
||||
/// expired, no items are returned. In this case, `Pending` is returned and the
|
||||
/// expired, no items are returned. In this case, `Poll::Pending` is returned and the
|
||||
/// current task is registered to be notified once the next item's delay has
|
||||
/// expired.
|
||||
///
|
||||
/// If no items are in the queue, i.e. `is_empty()` returns `true`, then `poll`
|
||||
/// returns `Ready(None)`. This indicates that the stream has reached an end.
|
||||
/// returns `Poll::Ready(None)`. This indicates that the stream has reached an end.
|
||||
/// However, if a new item is inserted *after*, `poll` will once again start
|
||||
/// returning items or `Pending.
|
||||
/// returning items or `Poll::Pending`.
|
||||
///
|
||||
/// Items are returned ordered by their expirations. Items that are configured
|
||||
/// to expire first will be returned first. There are no ordering guarantees
|
||||
/// for items configured to expire the same instant. Also note that delays are
|
||||
/// for items configured to expire at the same instant. Also note that delays are
|
||||
/// rounded to the closest millisecond.
|
||||
///
|
||||
/// # Implementation
|
||||
@@ -152,7 +152,7 @@ pub struct DelayQueue<T> {
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
/// An entry in `DelayQueue` that has expired and removed.
|
||||
/// An entry in `DelayQueue` that has expired and been removed.
|
||||
///
|
||||
/// Values are returned by [`DelayQueue::poll_expired`].
|
||||
///
|
||||
@@ -211,7 +211,7 @@ struct Data<T> {
|
||||
const MAX_ENTRIES: usize = (1 << 30) - 1;
|
||||
|
||||
impl<T> DelayQueue<T> {
|
||||
/// Creates a new, empty, `DelayQueue`
|
||||
/// Creates a new, empty, `DelayQueue`.
|
||||
///
|
||||
/// The queue will not allocate storage until items are inserted into it.
|
||||
///
|
||||
@@ -239,15 +239,15 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::with_capacity(10);
|
||||
/// let mut delay_queue = DelayQueue::with_capacity(10);
|
||||
///
|
||||
/// // These insertions are done without further allocation
|
||||
/// for i in 0..10 {
|
||||
/// delay_queue.insert(i, Duration::from_secs(i));
|
||||
/// }
|
||||
/// // These insertions are done without further allocation
|
||||
/// for i in 0..10 {
|
||||
/// delay_queue.insert(i, Duration::from_secs(i));
|
||||
/// }
|
||||
///
|
||||
/// // This will make the queue allocate additional storage
|
||||
/// delay_queue.insert(11, Duration::from_secs(11));
|
||||
/// // This will make the queue allocate additional storage
|
||||
/// delay_queue.insert(11, Duration::from_secs(11));
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn with_capacity(capacity: usize) -> DelayQueue<T> {
|
||||
@@ -272,8 +272,8 @@ impl<T> DelayQueue<T> {
|
||||
/// `value` will be returned from [`poll_expired`]. If `when` has already been
|
||||
/// reached, then `value` is immediately made available to poll.
|
||||
///
|
||||
/// The return value represents the insertion and is used at an argument to
|
||||
/// [`remove`] and [`reset`]. Note that [`Key`] is token and is reused once
|
||||
/// The return value represents the insertion and is used as an argument to
|
||||
/// [`remove`] and [`reset`]. Note that [`Key`] is a token and is reused once
|
||||
/// `value` is removed from the queue either by calling [`poll_expired`] after
|
||||
/// `when` is reached or by calling [`remove`]. At this point, the caller
|
||||
/// must take care to not use the returned [`Key`] again as it may reference
|
||||
@@ -295,13 +295,13 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert_at(
|
||||
/// "foo", Instant::now() + Duration::from_secs(5));
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert_at(
|
||||
/// "foo", Instant::now() + Duration::from_secs(5));
|
||||
///
|
||||
/// // Remove the entry
|
||||
/// let item = delay_queue.remove(&key);
|
||||
/// assert_eq!(*item.get_ref(), "foo");
|
||||
/// // Remove the entry
|
||||
/// let item = delay_queue.remove(&key);
|
||||
/// assert_eq!(*item.get_ref(), "foo");
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
@@ -353,7 +353,7 @@ impl<T> DelayQueue<T> {
|
||||
|
||||
/// Attempts to pull out the next value of the delay queue, registering the
|
||||
/// current task for wakeup if the value is not yet available, and returning
|
||||
/// None if the queue is exhausted.
|
||||
/// `None` if the queue is exhausted.
|
||||
pub fn poll_expired(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
@@ -391,7 +391,7 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// `value` is stored in the queue until `timeout` duration has
|
||||
/// elapsed after `insert` was called. At that point, `value` will
|
||||
/// be returned from [`poll_expired`]. If `timeout` a Duration of
|
||||
/// be returned from [`poll_expired`]. If `timeout` is a `Duration` of
|
||||
/// zero, then `value` is immediately made available to poll.
|
||||
///
|
||||
/// The return value represents the insertion and is used as an
|
||||
@@ -419,12 +419,12 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
///
|
||||
/// // Remove the entry
|
||||
/// let item = delay_queue.remove(&key);
|
||||
/// assert_eq!(*item.get_ref(), "foo");
|
||||
/// // Remove the entry
|
||||
/// let item = delay_queue.remove(&key);
|
||||
/// assert_eq!(*item.get_ref(), "foo");
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
@@ -452,11 +452,12 @@ impl<T> DelayQueue<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the key fom the expired queue or the timer wheel
|
||||
/// depending on its expiration status
|
||||
/// Removes the key from the expired queue or the timer wheel
|
||||
/// depending on its expiration status.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the key is not contained in the expired queue or the wheel
|
||||
///
|
||||
/// Panics if the key is not contained in the expired queue or the wheel.
|
||||
fn remove_key(&mut self, key: &Key) {
|
||||
use crate::time::wheel::Stack;
|
||||
|
||||
@@ -488,12 +489,12 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
///
|
||||
/// // Remove the entry
|
||||
/// let item = delay_queue.remove(&key);
|
||||
/// assert_eq!(*item.get_ref(), "foo");
|
||||
/// // Remove the entry
|
||||
/// let item = delay_queue.remove(&key);
|
||||
/// assert_eq!(*item.get_ref(), "foo");
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn remove(&mut self, key: &Key) -> Expired<T> {
|
||||
@@ -531,14 +532,14 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
///
|
||||
/// // "foo" is scheduled to be returned in 5 seconds
|
||||
/// // "foo" is scheduled to be returned in 5 seconds
|
||||
///
|
||||
/// delay_queue.reset_at(&key, Instant::now() + Duration::from_secs(10));
|
||||
/// delay_queue.reset_at(&key, Instant::now() + Duration::from_secs(10));
|
||||
///
|
||||
/// // "foo" is now scheduled to be returned in 10 seconds
|
||||
/// // "foo" is now scheduled to be returned in 10 seconds
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn reset_at(&mut self, key: &Key, when: Instant) {
|
||||
@@ -559,7 +560,7 @@ impl<T> DelayQueue<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the next time poll as determined by the wheel
|
||||
/// Returns the next time to poll as determined by the wheel
|
||||
fn next_deadline(&mut self) -> Option<Instant> {
|
||||
self.wheel
|
||||
.poll_at()
|
||||
@@ -591,14 +592,14 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let key = delay_queue.insert("foo", Duration::from_secs(5));
|
||||
///
|
||||
/// // "foo" is scheduled to be returned in 5 seconds
|
||||
/// // "foo" is scheduled to be returned in 5 seconds
|
||||
///
|
||||
/// delay_queue.reset(&key, Duration::from_secs(10));
|
||||
/// delay_queue.reset(&key, Duration::from_secs(10));
|
||||
///
|
||||
/// // "foo"is now scheduled to be returned in 10 seconds
|
||||
/// // "foo"is now scheduled to be returned in 10 seconds
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn reset(&mut self, key: &Key, timeout: Duration) {
|
||||
@@ -621,15 +622,15 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
///
|
||||
/// delay_queue.insert("foo", Duration::from_secs(5));
|
||||
/// delay_queue.insert("foo", Duration::from_secs(5));
|
||||
///
|
||||
/// assert!(!delay_queue.is_empty());
|
||||
/// assert!(!delay_queue.is_empty());
|
||||
///
|
||||
/// delay_queue.clear();
|
||||
/// delay_queue.clear();
|
||||
///
|
||||
/// assert!(delay_queue.is_empty());
|
||||
/// assert!(delay_queue.is_empty());
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn clear(&mut self) {
|
||||
@@ -663,10 +664,10 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue: DelayQueue<i32> = DelayQueue::with_capacity(10);
|
||||
/// assert_eq!(delay_queue.len(), 0);
|
||||
/// delay_queue.insert(3, Duration::from_secs(5));
|
||||
/// assert_eq!(delay_queue.len(), 1);
|
||||
/// let mut delay_queue: DelayQueue<i32> = DelayQueue::with_capacity(10);
|
||||
/// assert_eq!(delay_queue.len(), 0);
|
||||
/// delay_queue.insert(3, Duration::from_secs(5));
|
||||
/// assert_eq!(delay_queue.len(), 1);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn len(&self) -> usize {
|
||||
@@ -698,12 +699,12 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
///
|
||||
/// delay_queue.insert("hello", Duration::from_secs(10));
|
||||
/// delay_queue.reserve(10);
|
||||
/// delay_queue.insert("hello", Duration::from_secs(10));
|
||||
/// delay_queue.reserve(10);
|
||||
///
|
||||
/// assert!(delay_queue.capacity() >= 11);
|
||||
/// assert!(delay_queue.capacity() >= 11);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
@@ -713,7 +714,7 @@ impl<T> DelayQueue<T> {
|
||||
/// Returns `true` if there are no items in the queue.
|
||||
///
|
||||
/// Note that this function returns `false` even if all items have not yet
|
||||
/// expired and a call to `poll` will return `Pending`.
|
||||
/// expired and a call to `poll` will return `Poll::Pending`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -723,11 +724,11 @@ impl<T> DelayQueue<T> {
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// assert!(delay_queue.is_empty());
|
||||
/// let mut delay_queue = DelayQueue::new();
|
||||
/// assert!(delay_queue.is_empty());
|
||||
///
|
||||
/// delay_queue.insert("hello", Duration::from_secs(5));
|
||||
/// assert!(!delay_queue.is_empty());
|
||||
/// delay_queue.insert("hello", Duration::from_secs(5));
|
||||
/// assert!(!delay_queue.is_empty());
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn is_empty(&self) -> bool {
|
||||
|
||||
@@ -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`");
|
||||
@@ -254,6 +254,29 @@ fn multi_frames_on_eof() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_eof_then_resume() {
|
||||
let mut task = task::spawn(());
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x01".to_vec()),
|
||||
Ok(b"".to_vec()),
|
||||
Ok(b"\x00\x00\x00\x02".to_vec()),
|
||||
Ok(b"".to_vec()),
|
||||
Ok(b"\x00\x00\x00\x03".to_vec()),
|
||||
};
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
|
||||
task.enter(|cx, _| {
|
||||
assert_read!(pin!(framed).poll_next(cx), 1);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
assert_read!(pin!(framed).poll_next(cx), 2);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
assert_read!(pin!(framed).poll_next(cx), 3);
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Mock ======
|
||||
|
||||
struct Mock {
|
||||
|
||||
@@ -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,272 @@
|
||||
# 1.8.2 (July 19, 2021)
|
||||
|
||||
Fixes a missed edge case from 1.8.1.
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: drop canceled future on next poll (#3965)
|
||||
|
||||
# 1.8.1 (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.8.0 (July 2, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `get_{ref,mut}` methods to `AsyncFdReadyGuard` and `AsyncFdReadyMutGuard` ([#3807])
|
||||
- io: efficient implementation of vectored writes for `BufWriter` ([#3163])
|
||||
- net: add ready/try methods to `NamedPipe{Client,Server}` ([#3866], [#3899])
|
||||
- sync: add `watch::Receiver::borrow_and_update` ([#3813])
|
||||
- sync: implement `From<T>` for `OnceCell<T>` ([#3877])
|
||||
- time: allow users to specify Interval behaviour when delayed ([#3721])
|
||||
|
||||
### Added (unstable)
|
||||
|
||||
- rt: add `tokio::task::Builder` ([#3881])
|
||||
|
||||
### Fixed
|
||||
|
||||
- net: handle HUP event with `UnixStream` ([#3898])
|
||||
|
||||
### Documented
|
||||
|
||||
- doc: document cancellation safety ([#3900])
|
||||
- time: add wait alias to sleep ([#3897])
|
||||
- time: document auto-advancing behaviour of runtime ([#3763])
|
||||
|
||||
[#3163]: https://github.com/tokio-rs/tokio/pull/3163
|
||||
[#3721]: https://github.com/tokio-rs/tokio/pull/3721
|
||||
[#3763]: https://github.com/tokio-rs/tokio/pull/3763
|
||||
[#3807]: https://github.com/tokio-rs/tokio/pull/3807
|
||||
[#3813]: https://github.com/tokio-rs/tokio/pull/3813
|
||||
[#3866]: https://github.com/tokio-rs/tokio/pull/3866
|
||||
[#3877]: https://github.com/tokio-rs/tokio/pull/3877
|
||||
[#3881]: https://github.com/tokio-rs/tokio/pull/3881
|
||||
[#3897]: https://github.com/tokio-rs/tokio/pull/3897
|
||||
[#3898]: https://github.com/tokio-rs/tokio/pull/3898
|
||||
[#3899]: https://github.com/tokio-rs/tokio/pull/3899
|
||||
[#3900]: https://github.com/tokio-rs/tokio/pull/3900
|
||||
|
||||
# 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
|
||||
|
||||
- runtime: remotely abort tasks on `JoinHandle::abort` ([#3934])
|
||||
|
||||
[#3934]: https://github.com/tokio-rs/tokio/pull/3934
|
||||
|
||||
# 1.5.0 (April 12, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- io: add `AsyncSeekExt::stream_position` ([#3650])
|
||||
- io: add `AsyncWriteExt::write_vectored` ([#3678])
|
||||
- io: add a `copy_bidirectional` utility ([#3572])
|
||||
- net: implement `IntoRawFd` for `TcpSocket` ([#3684])
|
||||
- sync: add `OnceCell` ([#3591])
|
||||
- sync: add `OwnedRwLockReadGuard` and `OwnedRwLockWriteGuard` ([#3340])
|
||||
- sync: add `Semaphore::is_closed` ([#3673])
|
||||
- sync: add `mpsc::Sender::capacity` ([#3690])
|
||||
- sync: allow configuring `RwLock` max reads ([#3644])
|
||||
- task: add `sync_scope` for `LocalKey` ([#3612])
|
||||
|
||||
### Fixed
|
||||
|
||||
- chore: try to avoid `noalias` attributes on intrusive linked list ([#3654])
|
||||
- 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
|
||||
|
||||
- io: clarify requirements of `AsyncFd` ([#3635])
|
||||
- runtime: fix unclear docs for `{Handle,Runtime}::block_on` ([#3628])
|
||||
- sync: document that `Semaphore` is fair ([#3693])
|
||||
- sync: improve doc on blocking mutex ([#3645])
|
||||
|
||||
[#3340]: https://github.com/tokio-rs/tokio/pull/3340
|
||||
[#3572]: https://github.com/tokio-rs/tokio/pull/3572
|
||||
[#3591]: https://github.com/tokio-rs/tokio/pull/3591
|
||||
[#3612]: https://github.com/tokio-rs/tokio/pull/3612
|
||||
[#3628]: https://github.com/tokio-rs/tokio/pull/3628
|
||||
[#3635]: https://github.com/tokio-rs/tokio/pull/3635
|
||||
[#3644]: https://github.com/tokio-rs/tokio/pull/3644
|
||||
[#3645]: https://github.com/tokio-rs/tokio/pull/3645
|
||||
[#3650]: https://github.com/tokio-rs/tokio/pull/3650
|
||||
[#3652]: https://github.com/tokio-rs/tokio/pull/3652
|
||||
[#3654]: https://github.com/tokio-rs/tokio/pull/3654
|
||||
[#3672]: https://github.com/tokio-rs/tokio/pull/3672
|
||||
[#3673]: https://github.com/tokio-rs/tokio/pull/3673
|
||||
[#3674]: https://github.com/tokio-rs/tokio/pull/3674
|
||||
[#3678]: https://github.com/tokio-rs/tokio/pull/3678
|
||||
[#3684]: https://github.com/tokio-rs/tokio/pull/3684
|
||||
[#3690]: https://github.com/tokio-rs/tokio/pull/3690
|
||||
[#3693]: https://github.com/tokio-rs/tokio/pull/3693
|
||||
|
||||
# 1.4.0 (March 20, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- macros: introduce biased argument for `select!` ([#3603])
|
||||
- runtime: add `Handle::block_on` ([#3569])
|
||||
|
||||
### Fixed
|
||||
|
||||
- runtime: avoid unnecessary polling of `block_on` future ([#3582])
|
||||
- runtime: fix memory leak/growth when creating many runtimes ([#3564])
|
||||
- runtime: mark `EnterGuard` with `must_use` ([#3609])
|
||||
|
||||
### Documented
|
||||
|
||||
- chore: mention fix for building docs in contributing guide ([#3618])
|
||||
- doc: add link to `PollSender` ([#3613])
|
||||
- doc: alias sleep to delay ([#3604])
|
||||
- sync: improve `Mutex` FIFO explanation ([#3615])
|
||||
- timer: fix double newline in module docs ([#3617])
|
||||
|
||||
[#3564]: https://github.com/tokio-rs/tokio/pull/3564
|
||||
[#3613]: https://github.com/tokio-rs/tokio/pull/3613
|
||||
[#3618]: https://github.com/tokio-rs/tokio/pull/3618
|
||||
[#3617]: https://github.com/tokio-rs/tokio/pull/3617
|
||||
[#3582]: https://github.com/tokio-rs/tokio/pull/3582
|
||||
[#3615]: https://github.com/tokio-rs/tokio/pull/3615
|
||||
[#3603]: https://github.com/tokio-rs/tokio/pull/3603
|
||||
[#3609]: https://github.com/tokio-rs/tokio/pull/3609
|
||||
[#3604]: https://github.com/tokio-rs/tokio/pull/3604
|
||||
[#3569]: https://github.com/tokio-rs/tokio/pull/3569
|
||||
|
||||
# 1.3.0 (March 9, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
+9
-4
@@ -7,12 +7,12 @@ name = "tokio"
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v1.0.x" git tag.
|
||||
version = "1.3.0"
|
||||
version = "1.8.2"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/tokio/1.3.0/tokio/"
|
||||
documentation = "https://docs.rs/tokio/1.8.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,17 +116,21 @@ 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" }
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
proptest = "0.10.0"
|
||||
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.4", features = ["futures", "checkpoint"] }
|
||||
loom = { version = "0.5", features = ["futures", "checkpoint"] }
|
||||
|
||||
[build-dependencies]
|
||||
autocfg = "1" # Needed for conditionally enabling `track-caller`
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
Copyright (c) 2021 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -228,7 +228,7 @@ It is only possible to implement `AsyncRead` and `AsyncWrite` for resource types
|
||||
themselves and not for `&Resource`. Implementing the traits for `&Resource`
|
||||
would permit concurrent operations to the resource. Because only a single waker
|
||||
is stored per direction, any concurrent usage would result in deadlocks. An
|
||||
alterate implementation would call for a `Vec<Waker>` but this would result in
|
||||
alternate implementation would call for a `Vec<Waker>` but this would result in
|
||||
memory leaks.
|
||||
|
||||
## Enabling reads and writes for `&TcpStream`
|
||||
@@ -268,9 +268,9 @@ select! {
|
||||
}
|
||||
```
|
||||
|
||||
It is also possible to sotre a `TcpStream` in an `Arc`.
|
||||
It is also possible to store a `TcpStream` in an `Arc`.
|
||||
|
||||
```rust
|
||||
let arc_stream = Arc::new(my_tcp_stream);
|
||||
let n = arc_stream.by_ref().read(buf).await?;
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
+43
-11
@@ -8,7 +8,8 @@ use std::{task::Context, task::Poll};
|
||||
/// Associates an IO object backed by a Unix file descriptor with the tokio
|
||||
/// reactor, allowing for readiness to be polled. The file descriptor must be of
|
||||
/// a type that can be used with the OS polling facilities (ie, `poll`, `epoll`,
|
||||
/// `kqueue`, etc), such as a network socket or pipe.
|
||||
/// `kqueue`, etc), such as a network socket or pipe, and the file descriptor
|
||||
/// must have the nonblocking mode set to true.
|
||||
///
|
||||
/// Creating an AsyncFd registers the file descriptor with the current tokio
|
||||
/// Reactor, allowing you to directly await the file descriptor being readable
|
||||
@@ -36,18 +37,19 @@ use std::{task::Context, task::Poll};
|
||||
///
|
||||
/// On some platforms, the readiness detecting mechanism relies on
|
||||
/// edge-triggered notifications. This means that the OS will only notify Tokio
|
||||
/// when the file descriptor transitions from not-ready to ready. Tokio
|
||||
/// internally tracks when it has received a ready notification, and when
|
||||
/// when the file descriptor transitions from not-ready to ready. For this to
|
||||
/// work you should first try to read or write and only poll for readiness
|
||||
/// if that fails with an error of [`std::io::ErrorKind::WouldBlock`].
|
||||
///
|
||||
/// Tokio internally tracks when it has received a ready notification, and when
|
||||
/// readiness checking functions like [`readable`] and [`writable`] are called,
|
||||
/// if the readiness flag is set, these async functions will complete
|
||||
/// immediately.
|
||||
///
|
||||
/// This however does mean that it is critical to ensure that this ready flag is
|
||||
/// cleared when (and only when) the file descriptor ceases to be ready. The
|
||||
/// [`AsyncFdReadyGuard`] returned from readiness checking functions serves this
|
||||
/// function; after calling a readiness-checking async function, you must use
|
||||
/// this [`AsyncFdReadyGuard`] to signal to tokio whether the file descriptor is no
|
||||
/// longer in a ready state.
|
||||
/// immediately. This however does mean that it is critical to ensure that this
|
||||
/// ready flag is cleared when (and only when) the file descriptor ceases to be
|
||||
/// ready. The [`AsyncFdReadyGuard`] returned from readiness checking functions
|
||||
/// serves this function; after calling a readiness-checking async function,
|
||||
/// you must use this [`AsyncFdReadyGuard`] to signal to tokio whether the file
|
||||
/// descriptor is no longer in a ready state.
|
||||
///
|
||||
/// ## Use with to a poll-based API
|
||||
///
|
||||
@@ -538,6 +540,16 @@ impl<'a, Inner: AsRawFd> AsyncFdReadyGuard<'a, Inner> {
|
||||
result => Ok(result),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the inner [`AsyncFd`].
|
||||
pub fn get_ref(&self) -> &AsyncFd<Inner> {
|
||||
self.async_fd
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the backing object of the inner [`AsyncFd`].
|
||||
pub fn get_inner(&self) -> &Inner {
|
||||
self.get_ref().get_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Inner: AsRawFd> AsyncFdReadyMutGuard<'a, Inner> {
|
||||
@@ -599,6 +611,26 @@ impl<'a, Inner: AsRawFd> AsyncFdReadyMutGuard<'a, Inner> {
|
||||
result => Ok(result),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the inner [`AsyncFd`].
|
||||
pub fn get_ref(&self) -> &AsyncFd<Inner> {
|
||||
self.async_fd
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner [`AsyncFd`].
|
||||
pub fn get_mut(&mut self) -> &mut AsyncFd<Inner> {
|
||||
self.async_fd
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the backing object of the inner [`AsyncFd`].
|
||||
pub fn get_inner(&self) -> &Inner {
|
||||
self.get_ref().get_ref()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the backing object of the inner [`AsyncFd`].
|
||||
pub fn get_inner_mut(&mut self) -> &mut Inner {
|
||||
self.get_mut().get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: std::fmt::Debug + AsRawFd> std::fmt::Debug for AsyncFdReadyGuard<'a, T> {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(not(feature = "net"), allow(unreachable_pub))]
|
||||
#![cfg_attr(not(feature = "net"), allow(dead_code, unreachable_pub))]
|
||||
|
||||
use crate::io::driver::Ready;
|
||||
|
||||
@@ -58,7 +58,7 @@ impl Interest {
|
||||
self.0.is_writable()
|
||||
}
|
||||
|
||||
/// Add together two `Interst` values.
|
||||
/// Add together two `Interest` values.
|
||||
///
|
||||
/// This function works from a `const` context.
|
||||
///
|
||||
|
||||
@@ -96,7 +96,7 @@ const ADDRESS: bit::Pack = bit::Pack::least_significant(24);
|
||||
//
|
||||
// The generation prevents a race condition where a slab slot is reused for a
|
||||
// new socket while the I/O driver is about to apply a readiness event. The
|
||||
// generaton value is checked when setting new readiness. If the generation do
|
||||
// generation value is checked when setting new readiness. If the generation do
|
||||
// not match, then the readiness event is discarded.
|
||||
const GENERATION: bit::Pack = ADDRESS.then(7);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(not(feature = "net"), allow(dead_code))]
|
||||
|
||||
use crate::io::driver::{Direction, Handle, Interest, ReadyEvent, ScheduledIo};
|
||||
use crate::util::slab;
|
||||
|
||||
@@ -12,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
|
||||
@@ -34,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)]
|
||||
@@ -233,7 +236,10 @@ cfg_io_readiness! {
|
||||
|
||||
crate::future::poll_fn(|cx| {
|
||||
if self.handle.inner().is_none() {
|
||||
return Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "reactor gone")));
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
crate::util::error::RUNTIME_SHUTTING_DOWN_ERROR
|
||||
)));
|
||||
}
|
||||
|
||||
Pin::new(&mut fut).poll(cx).map(Ok)
|
||||
|
||||
@@ -84,9 +84,9 @@ cfg_io_readiness! {
|
||||
|
||||
// The `ScheduledIo::readiness` (`AtomicUsize`) is packed full of goodness.
|
||||
//
|
||||
// | reserved | generation | driver tick | readinesss |
|
||||
// |----------+------------+--------------+------------|
|
||||
// | 1 bit | 7 bits + 8 bits + 16 bits |
|
||||
// | reserved | generation | driver tick | readiness |
|
||||
// |----------+------------+--------------+-----------|
|
||||
// | 1 bit | 7 bits + 8 bits + 16 bits |
|
||||
|
||||
const READINESS: bit::Pack = bit::Pack::least_significant(16);
|
||||
|
||||
@@ -443,7 +443,7 @@ cfg_io_readiness! {
|
||||
// Currently ready!
|
||||
let tick = TICK.unpack(curr) as u8;
|
||||
*state = State::Done;
|
||||
return Poll::Ready(ReadyEvent { ready, tick });
|
||||
return Poll::Ready(ReadyEvent { tick, ready });
|
||||
}
|
||||
|
||||
// Wasn't ready, take the lock (and check again while locked).
|
||||
@@ -462,7 +462,7 @@ cfg_io_readiness! {
|
||||
// Currently ready!
|
||||
let tick = TICK.unpack(curr) as u8;
|
||||
*state = State::Done;
|
||||
return Poll::Ready(ReadyEvent { ready, tick });
|
||||
return Poll::Ready(ReadyEvent { tick, ready });
|
||||
}
|
||||
|
||||
// Not ready even after locked, insert into list...
|
||||
|
||||
+1
-1
@@ -246,7 +246,7 @@ cfg_io_util! {
|
||||
pub(crate) mod seek;
|
||||
pub(crate) mod util;
|
||||
pub use util::{
|
||||
copy, copy_buf, duplex, empty, repeat, sink, AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt,
|
||||
copy, copy_bidirectional, copy_buf, duplex, empty, repeat, sink, AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt,
|
||||
BufReader, BufStream, BufWriter, DuplexStream, Empty, Lines, Repeat, Sink, Split, Take,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -121,6 +115,11 @@ impl<E: Source> PollEvented<E> {
|
||||
}
|
||||
|
||||
/// Returns a reference to the registration
|
||||
#[cfg(any(
|
||||
feature = "net",
|
||||
all(unix, feature = "process"),
|
||||
all(unix, feature = "signal"),
|
||||
))]
|
||||
pub(crate) fn registration(&self) -> &Registration {
|
||||
&self.registration
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ impl<'a> ReadBuf<'a> {
|
||||
|
||||
/// Creates a new `ReadBuf` from a fully uninitialized buffer.
|
||||
///
|
||||
/// Use `assume_init` if part of the buffer is known to be already inintialized.
|
||||
/// Use `assume_init` if part of the buffer is known to be already initialized.
|
||||
#[inline]
|
||||
pub fn uninit(buf: &'a mut [MaybeUninit<u8>]) -> ReadBuf<'a> {
|
||||
ReadBuf {
|
||||
@@ -85,7 +85,7 @@ impl<'a> ReadBuf<'a> {
|
||||
#[inline]
|
||||
pub fn take(&mut self, n: usize) -> ReadBuf<'_> {
|
||||
let max = std::cmp::min(self.remaining(), n);
|
||||
// Saftey: We don't set any of the `unfilled_mut` with `MaybeUninit::uninit`.
|
||||
// Safety: We don't set any of the `unfilled_mut` with `MaybeUninit::uninit`.
|
||||
unsafe { ReadBuf::uninit(&mut self.unfilled_mut()[..max]) }
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ impl<'a> ReadBuf<'a> {
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the filled region of the buffer would become larger than the intialized region.
|
||||
/// Panics if the filled region of the buffer would become larger than the initialized region.
|
||||
#[inline]
|
||||
pub fn set_filled(&mut self, n: usize) {
|
||||
assert!(
|
||||
|
||||
@@ -52,10 +52,10 @@ where
|
||||
|
||||
buf = &buf[..crate::io::blocking::MAX_BUF];
|
||||
|
||||
// Now there are two possibilites.
|
||||
// Now there are two possibilities.
|
||||
// If caller gave is binary buffer, we **should not** shrink it
|
||||
// anymore, because excessive shrinking hits performance.
|
||||
// If caller gave as binary buffer, we **must** additionaly
|
||||
// If caller gave as binary buffer, we **must** additionally
|
||||
// shrink it to strip incomplete char at the end of buffer.
|
||||
// that's why check we will perform now is allowed to have
|
||||
// false-positive.
|
||||
|
||||
@@ -105,8 +105,10 @@ cfg_io_util! {
|
||||
/// async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
|
||||
/// ```
|
||||
///
|
||||
/// This function does not provide any guarantees about whether it
|
||||
/// completes immediately or asynchronously
|
||||
/// This method 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
|
||||
@@ -136,6 +138,12 @@ cfg_io_util! {
|
||||
/// variant will be returned. If an error is returned then it must be
|
||||
/// guaranteed that no bytes were read.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. If you use it as the event in a
|
||||
/// [`tokio::select!`](crate::select) statement and some other branch
|
||||
/// completes first, then it is guaranteed that no data was read.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`][crate::fs::File]s implement `Read`:
|
||||
@@ -175,14 +183,19 @@ cfg_io_util! {
|
||||
/// Usually, only a single `read` syscall is issued, even if there is
|
||||
/// more space in the supplied buffer.
|
||||
///
|
||||
/// This function does not provide any guarantees about whether it
|
||||
/// completes immediately or asynchronously
|
||||
/// This method does not provide any guarantees about whether it
|
||||
/// completes immediately or asynchronously.
|
||||
///
|
||||
/// # 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
|
||||
///
|
||||
@@ -190,6 +203,12 @@ cfg_io_util! {
|
||||
/// variant will be returned. If an error is returned then it must be
|
||||
/// guaranteed that no bytes were read.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe. If you use it as the event in a
|
||||
/// [`tokio::select!`](crate::select) statement and some other branch
|
||||
/// completes first, then it is guaranteed that no data was read.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`] implements `Read` and [`BytesMut`] implements [`BufMut`]:
|
||||
@@ -254,6 +273,13 @@ cfg_io_util! {
|
||||
/// it has read, but it will never read more than would be necessary to
|
||||
/// completely fill the buffer.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is not cancellation safe. If the method is used as the
|
||||
/// event in a [`tokio::select!`](crate::select) statement and some
|
||||
/// other branch completes first, then some data may already have been
|
||||
/// read into `buf`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`][crate::fs::File]s implement `Read`:
|
||||
@@ -579,7 +605,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 +685,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(())
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -66,6 +66,17 @@ cfg_io_util! {
|
||||
{
|
||||
seek(self, pos)
|
||||
}
|
||||
|
||||
/// Creates a future which will return the current seek position from the
|
||||
/// start of the stream.
|
||||
///
|
||||
/// This is equivalent to `self.seek(SeekFrom::Current(0))`.
|
||||
fn stream_position(&mut self) -> Seek<'_, Self>
|
||||
where
|
||||
Self: Unpin,
|
||||
{
|
||||
self.seek(SeekFrom::Current(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -11,7 +12,9 @@ use crate::io::util::write_int::{
|
||||
WriteU128, WriteU128Le, WriteU16, WriteU16Le, WriteU32, WriteU32Le, WriteU64, WriteU64Le,
|
||||
WriteU8,
|
||||
};
|
||||
use crate::io::util::write_vectored::{write_vectored, WriteVectored};
|
||||
use crate::io::AsyncWrite;
|
||||
use std::io::IoSlice;
|
||||
|
||||
use bytes::Buf;
|
||||
|
||||
@@ -94,6 +97,13 @@ cfg_io_util! {
|
||||
/// It is **not** considered an error if the entire buffer could not be
|
||||
/// written to this writer.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancellation safe in the sense that if it is used as
|
||||
/// the event in a [`tokio::select!`](crate::select) statement and some
|
||||
/// other branch completes first, then it is guaranteed that no data was
|
||||
/// written to this `AsyncWrite`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -116,6 +126,53 @@ cfg_io_util! {
|
||||
write(self, src)
|
||||
}
|
||||
|
||||
/// Like [`write`], except that it writes from a slice of buffers.
|
||||
///
|
||||
/// Equivalent to:
|
||||
///
|
||||
/// ```ignore
|
||||
/// async fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize>;
|
||||
/// ```
|
||||
///
|
||||
/// See [`AsyncWrite::poll_write_vectored`] for more details.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancellation safe in the sense that if it is used as
|
||||
/// the event in a [`tokio::select!`](crate::select) statement and some
|
||||
/// other branch completes first, then it is guaranteed that no data was
|
||||
/// written to this `AsyncWrite`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::io::{self, AsyncWriteExt};
|
||||
/// use tokio::fs::File;
|
||||
/// use std::io::IoSlice;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut file = File::create("foo.txt").await?;
|
||||
///
|
||||
/// let bufs: &[_] = &[
|
||||
/// IoSlice::new(b"hello"),
|
||||
/// IoSlice::new(b" "),
|
||||
/// IoSlice::new(b"world"),
|
||||
/// ];
|
||||
///
|
||||
/// file.write_vectored(&bufs).await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`write`]: AsyncWriteExt::write
|
||||
fn write_vectored<'a, 'b>(&'a mut self, bufs: &'a [IoSlice<'b>]) -> WriteVectored<'a, 'b, Self>
|
||||
where
|
||||
Self: Unpin,
|
||||
{
|
||||
write_vectored(self, bufs)
|
||||
}
|
||||
|
||||
/// Writes a buffer into this writer, advancing the buffer's internal
|
||||
/// cursor.
|
||||
@@ -152,12 +209,20 @@ cfg_io_util! {
|
||||
/// It is **not** considered an error if the entire buffer could not be
|
||||
/// written to this writer.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancellation safe in the sense that if it is used as
|
||||
/// the event in a [`tokio::select!`](crate::select) statement and some
|
||||
/// other branch completes first, then it is guaranteed that no data was
|
||||
/// written to this `AsyncWrite`.
|
||||
///
|
||||
/// # 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};
|
||||
@@ -190,6 +255,70 @@ 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?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// If `write_all_buf` is used as the event in a
|
||||
/// [`tokio::select!`](crate::select) statement and some other branch
|
||||
/// completes first, then the data in the provided buffer may have been
|
||||
/// partially written. However, it is guaranteed that the provided
|
||||
/// buffer has been [advanced] by the amount of bytes that have been
|
||||
/// partially written.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`] implements [`AsyncWrite`] and [`Cursor`]`<&[u8]>` implements [`Buf`]:
|
||||
///
|
||||
/// [`File`]: crate::fs::File
|
||||
/// [`Buf`]: bytes::Buf
|
||||
/// [`Cursor`]: std::io::Cursor
|
||||
/// [advanced]: bytes::Buf::advance
|
||||
///
|
||||
/// ```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:
|
||||
@@ -203,6 +332,14 @@ cfg_io_util! {
|
||||
/// has been successfully written or such an error occurs. The first
|
||||
/// error generated from this method will be returned.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is not cancellation safe. If it is used as the event
|
||||
/// in a [`tokio::select!`](crate::select) statement and some other
|
||||
/// branch completes first, then the provided buffer may have been
|
||||
/// partially written, but future calls to `write_all` will start over
|
||||
/// from the beginning of the buffer.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return the first error that [`write`] returns.
|
||||
@@ -524,8 +661,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(())
|
||||
@@ -602,7 +739,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,
|
||||
@@ -833,8 +970,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(())
|
||||
@@ -911,7 +1048,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, IoSlice, 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>,
|
||||
@@ -150,6 +268,18 @@ impl<R: AsyncRead + AsyncWrite> AsyncWrite for BufReader<R> {
|
||||
self.get_pin_mut().poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
self.get_pin_mut().poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.get_ref().is_write_vectored()
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.get_pin_mut().poll_flush(cx)
|
||||
}
|
||||
|
||||
@@ -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, IoSlice, 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -123,6 +127,18 @@ impl<RW: AsyncRead + AsyncWrite> AsyncWrite for BufStream<RW> {
|
||||
self.project().inner.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
self.project().inner.poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.inner.is_write_vectored()
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.project().inner.poll_flush(cx)
|
||||
}
|
||||
@@ -142,6 +158,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, IoSlice, 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +133,72 @@ impl<W: AsyncWrite> AsyncWrite for BufWriter<W> {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
mut bufs: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if self.inner.is_write_vectored() {
|
||||
let total_len = bufs
|
||||
.iter()
|
||||
.fold(0usize, |acc, b| acc.saturating_add(b.len()));
|
||||
if total_len > self.buf.capacity() - self.buf.len() {
|
||||
ready!(self.as_mut().flush_buf(cx))?;
|
||||
}
|
||||
let me = self.as_mut().project();
|
||||
if total_len >= me.buf.capacity() {
|
||||
// It's more efficient to pass the slices directly to the
|
||||
// underlying writer than to buffer them.
|
||||
// The case when the total_len calculation saturates at
|
||||
// usize::MAX is also handled here.
|
||||
me.inner.poll_write_vectored(cx, bufs)
|
||||
} else {
|
||||
bufs.iter().for_each(|b| me.buf.extend_from_slice(b));
|
||||
Poll::Ready(Ok(total_len))
|
||||
}
|
||||
} else {
|
||||
// Remove empty buffers at the beginning of bufs.
|
||||
while bufs.first().map(|buf| buf.len()) == Some(0) {
|
||||
bufs = &bufs[1..];
|
||||
}
|
||||
if bufs.is_empty() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
// Flush if the first buffer doesn't fit.
|
||||
let first_len = bufs[0].len();
|
||||
if first_len > self.buf.capacity() - self.buf.len() {
|
||||
ready!(self.as_mut().flush_buf(cx))?;
|
||||
debug_assert!(self.buf.is_empty());
|
||||
}
|
||||
let me = self.as_mut().project();
|
||||
if first_len >= me.buf.capacity() {
|
||||
// The slice is at least as large as the buffering capacity,
|
||||
// so it's better to write it directly, bypassing the buffer.
|
||||
debug_assert!(me.buf.is_empty());
|
||||
return me.inner.poll_write(cx, &bufs[0]);
|
||||
} else {
|
||||
me.buf.extend_from_slice(&bufs[0]);
|
||||
bufs = &bufs[1..];
|
||||
}
|
||||
let mut total_written = first_len;
|
||||
debug_assert!(total_written != 0);
|
||||
// Append the buffers that fit in the internal buffer.
|
||||
for buf in bufs {
|
||||
if buf.len() > me.buf.capacity() - me.buf.len() {
|
||||
break;
|
||||
} else {
|
||||
me.buf.extend_from_slice(buf);
|
||||
total_written += buf.len();
|
||||
}
|
||||
}
|
||||
Poll::Ready(Ok(total_written))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
ready!(self.as_mut().flush_buf(cx))?;
|
||||
self.get_pin_mut().poll_flush(cx)
|
||||
@@ -142,6 +210,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>,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user