mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
228
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00500d1b35 | ||
|
|
647299866a | ||
|
|
a6051a61ec | ||
|
|
a8e0f0a919 | ||
|
|
7ae8135b62 | ||
|
|
353b0544a0 | ||
|
|
128495168d | ||
|
|
08548583b9 | ||
|
|
7707ba88ef | ||
|
|
72d6346c0d | ||
|
|
a85fdb884d | ||
|
|
c55d846f4b | ||
|
|
0acd06b42a | ||
|
|
4912943419 | ||
|
|
5e406a7a47 | ||
|
|
de33ee85ce | ||
|
|
874fc3320b | ||
|
|
ae67851f11 | ||
|
|
f927f01a34 | ||
|
|
49abfdb2ac | ||
|
|
479c545c20 | ||
|
|
34fcef258b | ||
|
|
7d11aa8668 | ||
|
|
0ea2307650 | ||
|
|
d0ebb41547 | ||
|
|
f5cb4c2042 | ||
|
|
4e39c9b818 | ||
|
|
97c2c4203c | ||
|
|
60366ca0fa | ||
|
|
850bfc9efa | ||
|
|
02b1117dca | ||
|
|
685da8dadd | ||
|
|
6a0e23c654 | ||
|
|
9d0c0dd22c | ||
|
|
6d5423f3e9 | ||
|
|
ebb8bab060 | ||
|
|
ce891a4df1 | ||
|
|
d869e16990 | ||
|
|
e1256d8ca4 | ||
|
|
a52f5071bf | ||
|
|
f1f8c3cde6 | ||
|
|
c2e843d928 | ||
|
|
a43ec09b55 | ||
|
|
90c2a510e2 | ||
|
|
f51ddc5958 | ||
|
|
47658a6da5 | ||
|
|
d7e3fcb9ee | ||
|
|
0b3918bce9 | ||
|
|
e309da0bee | ||
|
|
bbc8eb0f91 | ||
|
|
06c7e73e99 | ||
|
|
42de3bc7a4 | ||
|
|
20a2b9e263 | ||
|
|
ae4e8d7ad1 | ||
|
|
7a18ca2be0 | ||
|
|
4a7b7c52d1 | ||
|
|
fede3db76a | ||
|
|
2b23aa7389 | ||
|
|
382ee6bf5d | ||
|
|
24ed874e81 | ||
|
|
3965d91a5e | ||
|
|
a3ef4e4cf5 | ||
|
|
34eb47dde5 | ||
|
|
c8a484bbb2 | ||
|
|
9097ae548f | ||
|
|
d78655337a | ||
|
|
38605c5c85 | ||
|
|
fe2b997675 | ||
|
|
6d0ba19af5 | ||
|
|
cbb8fe6069 | ||
|
|
a9da220923 | ||
|
|
1c28c3b0a8 | ||
|
|
e31bd321ef | ||
|
|
c30ce1f65c | ||
|
|
a95378a850 | ||
|
|
ce173fdc91 | ||
|
|
e804f88d60 | ||
|
|
c153913211 | ||
|
|
358e4f9f80 | ||
|
|
adf822f5cc | ||
|
|
d14cbf9116 | ||
|
|
8bfb1c92ce | ||
|
|
b48fec9655 | ||
|
|
8dbc3c7937 | ||
|
|
7fbfa9b649 | ||
|
|
7d7b79e1d5 | ||
|
|
8f37544a79 | ||
|
|
43d0714898 | ||
|
|
16e272ea4b | ||
|
|
6d99e1c7de | ||
|
|
f73a2ad238 | ||
|
|
c793ead0c3 | ||
|
|
2696794771 | ||
|
|
cfd643d691 | ||
|
|
8d17261a4b | ||
|
|
423ecc187a | ||
|
|
fb28caa90c | ||
|
|
e88e64bcc0 | ||
|
|
3cc6ce7a99 | ||
|
|
81db03204d | ||
|
|
1644511bdf | ||
|
|
dc9742fbea | ||
|
|
12f1dffa2d | ||
|
|
871289b3e7 | ||
|
|
30b40ef518 | ||
|
|
22fa883296 | ||
|
|
00b6127f2e | ||
|
|
a249421abc | ||
|
|
1923350880 | ||
|
|
c90681bd8e | ||
|
|
24d0a0cfa8 | ||
|
|
07802b2c84 | ||
|
|
891de3271d | ||
|
|
8880222036 | ||
|
|
0893841f31 | ||
|
|
ec99e61945 | ||
|
|
f8c91f2ead | ||
|
|
b575082543 | ||
|
|
c4f620cb30 | ||
|
|
b047f647b7 | ||
|
|
2e05399f4b | ||
|
|
afe535283c | ||
|
|
ee597347c5 | ||
|
|
41ac1ae2bc | ||
|
|
60d81bbe10 | ||
|
|
b704c53b9c | ||
|
|
066965cd59 | ||
|
|
6259893094 | ||
|
|
43bd11bf2f | ||
|
|
d94ab62c54 | ||
|
|
a9a59ea90e | ||
|
|
c248167173 | ||
|
|
601a3ef93f | ||
|
|
fcdf9345bf | ||
|
|
4cf45c038b | ||
|
|
9730317e94 | ||
|
|
02311dcfa1 | ||
|
|
aa171f2aa9 | ||
|
|
c23c1ecbcb | ||
|
|
561a71ad63 | ||
|
|
242ea01189 | ||
|
|
1684e1c809 | ||
|
|
0ed4127d5c | ||
|
|
1e585ccb51 | ||
|
|
7ec6d88b21 | ||
|
|
13de30c53e | ||
|
|
496e889917 | ||
|
|
53ccfc1fd6 | ||
|
|
971ed2c6df | ||
|
|
dcb11118d2 | ||
|
|
3403be5e2e | ||
|
|
c6fc35aadf | ||
|
|
078d0a2ebc | ||
|
|
99d4061203 | ||
|
|
dfdfd61372 | ||
|
|
55d932a21f | ||
|
|
444660664b | ||
|
|
cf025ba45f | ||
|
|
4186b0aa38 | ||
|
|
760ae89401 | ||
|
|
56acde069f | ||
|
|
ffa5bdb22d | ||
|
|
a1d0681cd2 | ||
|
|
4dfbdbff7e | ||
|
|
c29f13b7a5 | ||
|
|
a0557840eb | ||
|
|
f25f12d576 | ||
|
|
0f70530ee7 | ||
|
|
3114d9e826 | ||
|
|
5467f0a573 | ||
|
|
555b74c7cd | ||
|
|
7ae5b7bd4f | ||
|
|
e09b90ea32 | ||
|
|
cb8f2ceb2e | ||
|
|
6866b24ca1 | ||
|
|
e7091fde78 | ||
|
|
2348f678e6 | ||
|
|
93f8cb8df2 | ||
|
|
1ac10fa80a | ||
|
|
ba8680d667 | ||
|
|
111894fef9 | ||
|
|
68f7eff39e | ||
|
|
207320dbbb | ||
|
|
3fd043931e | ||
|
|
4c4699be00 | ||
|
|
8d2e3bc575 | ||
|
|
20ef286553 | ||
|
|
2bc9a48152 | ||
|
|
c5a9ede157 | ||
|
|
ce0af8f7a1 | ||
|
|
be7462e50f | ||
|
|
1550dda5cf | ||
|
|
cbb14a7bb9 | ||
|
|
a0a356152e | ||
|
|
ea79c95c67 | ||
|
|
37f405bd3b | ||
|
|
7c254eca44 | ||
|
|
f4d6ed03d9 | ||
|
|
38ec4845d1 | ||
|
|
842d5565bd | ||
|
|
d74eabc7d7 | ||
|
|
6260ed907b | ||
|
|
048174012d | ||
|
|
38cab93330 | ||
|
|
171cb57fa1 | ||
|
|
c9f5bc2915 | ||
|
|
5cdb6f8fd6 | ||
|
|
5a1a6dc90c | ||
|
|
827077409c | ||
|
|
d600ab9a8f | ||
|
|
d9d909cb4c | ||
|
|
262b19ae96 | ||
|
|
347e18bc77 | ||
|
|
2e7e42bca7 | ||
|
|
0ccc09ac92 | ||
|
|
4e12299826 | ||
|
|
9d58b70151 | ||
|
|
fde72bf047 | ||
|
|
138eef3526 | ||
|
|
c393236dfd | ||
|
|
71da06097b | ||
|
|
8feebab7cd | ||
|
|
674985d9fb | ||
|
|
77e6bb8d06 | ||
|
|
d8490c1626 | ||
|
|
e9adac288e | ||
|
|
27bfe52bba | ||
|
|
6ccefb77e2 |
+7
-8
@@ -1,17 +1,17 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-1-release-amd64
|
||||
image: freebsd-12-2-release-amd64
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 12.0
|
||||
name: FreeBSD
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y curl
|
||||
- pkg install -y bash curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
- . $HOME/.cargo/env
|
||||
@@ -23,8 +23,7 @@ task:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all
|
||||
- cargo doc --all --no-deps
|
||||
# TODO: Re-enable
|
||||
# i686_test_script:
|
||||
# - . $HOME/.cargo/env
|
||||
# - |
|
||||
# cargo test --all --exclude tokio-macros --target i686-unknown-freebsd
|
||||
i686_test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
cargo test --all --target i686-unknown-freebsd
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Benchmark
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
bench:
|
||||
- rt_multi_threaded
|
||||
- sync_mpsc
|
||||
- sync_rwlock
|
||||
- sync_semaphore
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
# Run benchmark with `go test -bench` and stores the output to a file
|
||||
- name: Run benchmark
|
||||
run: cargo bench --bench ${{ matrix.bench }} | tee ../output.txt
|
||||
working-directory: benches
|
||||
|
||||
# Download previous benchmark result from cache (if exists)
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
|
||||
# Run `github-action-benchmark` action
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
name: ${{ matrix.bench }}
|
||||
# What benchmark tool the output.txt came from
|
||||
tool: 'cargo'
|
||||
# Where the output from the benchmark tool is stored
|
||||
output-file-path: output.txt
|
||||
# # Where the previous data file is stored
|
||||
# external-data-json-path: ./cache/benchmark-data.json
|
||||
# Workflow will fail when an alert happens
|
||||
fail-on-alert: true
|
||||
# GitHub API token to make a commit comment
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Enable alert commit comment
|
||||
comment-on-alert: true
|
||||
alert-comment-cc-users: '@tokio-rs/maintainers'
|
||||
auto-push: true
|
||||
|
||||
# Upload the updated cache file for the next job by actions/cache
|
||||
+44
-13
@@ -1,8 +1,8 @@
|
||||
on:
|
||||
push:
|
||||
branches: ["v0.2.x"]
|
||||
branches: ["master"]
|
||||
pull_request:
|
||||
branches: ["v0.2.x"]
|
||||
branches: ["master"]
|
||||
|
||||
name: CI
|
||||
|
||||
@@ -10,7 +10,7 @@ env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
nightly: nightly-2020-09-21
|
||||
minrust: 1.39.0
|
||||
minrust: 1.45.2
|
||||
|
||||
jobs:
|
||||
# Depends on all action sthat are required for a "successful" CI run.
|
||||
@@ -53,11 +53,6 @@ jobs:
|
||||
run: cargo test --features full
|
||||
working-directory: tokio
|
||||
|
||||
# Check `tokio` with `full + parking_lot` to make sure it compiles.
|
||||
- name: check tokio full,parking_lot
|
||||
run: cargo check --features full,parking_lot
|
||||
working-directory: tokio
|
||||
|
||||
# Test **all** crates in the workspace with all features.
|
||||
- name: test all --all-features
|
||||
run: cargo test --workspace --all-features
|
||||
@@ -91,7 +86,7 @@ jobs:
|
||||
run: cargo test --features full
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: '--cfg tokio_unstable'
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
miri:
|
||||
name: miri
|
||||
@@ -110,8 +105,23 @@ jobs:
|
||||
rm -rf tokio/tests
|
||||
|
||||
- name: miri
|
||||
run: cargo miri test --features rt-core,rt-threaded,rt-util,sync task
|
||||
run: cargo miri test --features rt,rt-multi-thread,sync task
|
||||
working-directory: tokio
|
||||
san:
|
||||
name: san
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- name: asan
|
||||
run: cargo test --all-features --target x86_64-unknown-linux-gnu --lib -- --test-threads 1
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: -Z sanitizer=address
|
||||
ASAN_OPTIONS: detect_leaks=0
|
||||
|
||||
cross:
|
||||
name: cross
|
||||
@@ -156,7 +166,7 @@ jobs:
|
||||
- name: check --each-feature --unstable
|
||||
run: cargo hack check --all --each-feature -Z avoid-dev-deps
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
minrust:
|
||||
name: minrust
|
||||
@@ -171,6 +181,26 @@ jobs:
|
||||
- name: "test --workspace --all-features"
|
||||
run: cargo check --workspace --all-features
|
||||
|
||||
minimal-versions:
|
||||
name: minimal-versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
- name: "check --all-features -Z minimal-versions"
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
cargo hack --remove-dev-deps --workspace
|
||||
# Update Cargo.lock to minimal version dependencies.
|
||||
cargo update -Z minimal-versions
|
||||
cargo check --all-features
|
||||
|
||||
fmt:
|
||||
name: fmt
|
||||
runs-on: ubuntu-latest
|
||||
@@ -196,7 +226,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
run: rustup update ${{ env.minrust }} && rustup default ${{ env.minrust }}
|
||||
- name: Install clippy
|
||||
run: rustup component add clippy
|
||||
|
||||
@@ -230,6 +260,7 @@ jobs:
|
||||
- loom_pool::group_b
|
||||
- loom_pool::group_c
|
||||
- loom_pool::group_d
|
||||
- time::driver
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
@@ -239,6 +270,6 @@ jobs:
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
@@ -14,15 +14,15 @@ the Rust programming language. It is:
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: https://github.com/tokio-rs/tokio/blob/master/LICENSE
|
||||
[azure-badge]: https://dev.azure.com/tokio-rs/Tokio/_apis/build/status/tokio-rs.tokio?branchName=master
|
||||
[azure-url]: https://dev.azure.com/tokio-rs/Tokio/_build/latest?definitionId=1&branchName=master
|
||||
[actions-badge]: https://github.com/tokio-rs/tokio/workflows/CI/badge.svg
|
||||
[actions-url]: https://github.com/tokio-rs/tokio/actions?query=workflow%3ACI+branch%3Amaster
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/tokio
|
||||
|
||||
@@ -157,7 +157,7 @@ several other libraries, including:
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.39.
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.45.
|
||||
The current Tokio version is not guaranteed to build on Rust versions earlier than the
|
||||
minimum supported version.
|
||||
|
||||
|
||||
+13
-5
@@ -5,22 +5,25 @@ publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.42"
|
||||
|
||||
[[bench]]
|
||||
name = "spawn"
|
||||
path = "spawn.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "mpsc"
|
||||
path = "mpsc.rs"
|
||||
name = "sync_mpsc"
|
||||
path = "sync_mpsc.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "scheduler"
|
||||
path = "scheduler.rs"
|
||||
name = "rt_multi_threaded"
|
||||
path = "rt_multi_threaded.rs"
|
||||
harness = false
|
||||
|
||||
|
||||
@@ -33,3 +36,8 @@ harness = false
|
||||
name = "sync_semaphore"
|
||||
path = "sync_semaphore.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "signal"
|
||||
path = "signal.rs"
|
||||
harness = false
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::sync::{mpsc, Arc};
|
||||
fn spawn_many(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
|
||||
let mut rt = rt();
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
@@ -68,7 +68,7 @@ fn yield_many(b: &mut Bencher) {
|
||||
fn ping_pong(b: &mut Bencher) {
|
||||
const NUM_PINGS: usize = 1_000;
|
||||
|
||||
let mut rt = rt();
|
||||
let rt = rt();
|
||||
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
@@ -111,7 +111,7 @@ fn ping_pong(b: &mut Bencher) {
|
||||
fn chained_spawn(b: &mut Bencher) {
|
||||
const ITER: usize = 1_000;
|
||||
|
||||
let mut rt = rt();
|
||||
let rt = rt();
|
||||
|
||||
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
@@ -139,9 +139,8 @@ fn chained_spawn(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.core_threads(4)
|
||||
runtime::Builder::new_multi_thread()
|
||||
.worker_threads(4)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
@@ -0,0 +1,95 @@
|
||||
//! Benchmark the delay in propagating OS signals to any listeners.
|
||||
#![cfg(unix)]
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::runtime;
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
struct Spinner {
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Future for Spinner {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if self.count > 3 {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
self.count += 1;
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spinner {
|
||||
fn new() -> Self {
|
||||
Self { count: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_signal(signal: libc::c_int) {
|
||||
use libc::{getpid, kill};
|
||||
|
||||
unsafe {
|
||||
assert_eq!(kill(getpid(), signal), 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn many_signals(bench: &mut Bencher) {
|
||||
let num_signals = 10;
|
||||
let (tx, mut rx) = mpsc::channel(num_signals);
|
||||
|
||||
// Intentionally single threaded to measure delays in propagating wakes
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let spawn_signal = |kind| {
|
||||
let tx = tx.clone();
|
||||
rt.spawn(async move {
|
||||
let mut signal = signal(kind).expect("failed to create signal");
|
||||
|
||||
while signal.recv().await.is_some() {
|
||||
if tx.send(()).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
for _ in 0..num_signals {
|
||||
// Pick some random signals which don't terminate the test harness
|
||||
spawn_signal(SignalKind::child());
|
||||
spawn_signal(SignalKind::io());
|
||||
}
|
||||
drop(tx);
|
||||
|
||||
// Turn the runtime for a while to ensure that all the spawned
|
||||
// tasks have been polled at least once
|
||||
rt.block_on(Spinner::new());
|
||||
|
||||
bench.iter(|| {
|
||||
rt.block_on(async {
|
||||
send_signal(libc::SIGCHLD);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
|
||||
send_signal(libc::SIGIO);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(signal_group, many_signals,);
|
||||
|
||||
benchmark_main!(signal_group);
|
||||
+8
-14
@@ -10,8 +10,7 @@ async fn work() -> usize {
|
||||
}
|
||||
|
||||
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
@@ -23,8 +22,7 @@ fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
@@ -36,25 +34,21 @@ fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn basic_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
let h = runtime.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap();
|
||||
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
let h = runtime.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ use tokio::sync::mpsc;
|
||||
type Medium = [usize; 64];
|
||||
type Large = [Medium; 64];
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create_1_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(1));
|
||||
@@ -24,7 +31,7 @@ fn create_100_000_medium(b: &mut Bencher) {
|
||||
|
||||
fn send_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
let (tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
|
||||
let _ = tx.try_send([0; 64]);
|
||||
|
||||
@@ -34,7 +41,7 @@ fn send_medium(b: &mut Bencher) {
|
||||
|
||||
fn send_large(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
let (tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
let _ = tx.try_send([[0; 64]; 64]);
|
||||
|
||||
@@ -43,18 +50,14 @@ fn send_large(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contention_bounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut tx = tx.clone();
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -70,18 +73,14 @@ fn contention_bounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contention_bounded_full(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut tx = tx.clone();
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -97,11 +96,7 @@ fn contention_bounded_full(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contention_unbounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
@@ -124,15 +119,11 @@ fn contention_unbounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn uncontented_bounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (mut tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -146,11 +137,7 @@ fn uncontented_bounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
+8
-13
@@ -3,9 +3,8 @@ use std::sync::Arc;
|
||||
use tokio::{sync::RwLock, task};
|
||||
|
||||
fn read_uncontended(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -22,9 +21,8 @@ fn read_uncontended(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -51,8 +49,7 @@ fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -78,9 +75,8 @@ fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -108,8 +104,7 @@ fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn read_concurrent_contended(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ use std::sync::Arc;
|
||||
use tokio::{sync::Semaphore, task};
|
||||
|
||||
fn uncontended(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -27,9 +26,8 @@ async fn task(s: Arc<Semaphore>) {
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_multi(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -51,8 +49,7 @@ fn uncontended_concurrent_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_single(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -73,9 +70,8 @@ fn uncontended_concurrent_single(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contended_concurrent_multi(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -97,8 +93,7 @@ fn contended_concurrent_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contended_concurrent_single(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
||||
+3
-3
@@ -7,11 +7,11 @@ edition = "2018"
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead.
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full", "tracing"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full", "tracing"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
|
||||
tokio-util = { version = "0.3.0", path = "../tokio-util", features = ["full"] }
|
||||
bytes = "0.5"
|
||||
tokio-util = { version = "0.5.0", path = "../tokio-util", features = ["full"] }
|
||||
bytes = "0.6"
|
||||
futures = "0.3.0"
|
||||
http = "0.2"
|
||||
serde = "1.0"
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
tracing::info!("server running on {}", addr);
|
||||
|
||||
|
||||
+3
-5
@@ -96,7 +96,6 @@ mod udp {
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::udp::{RecvHalf, SendHalf};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn connect(
|
||||
@@ -114,16 +113,15 @@ mod udp {
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
let (mut r, mut w) = socket.split();
|
||||
|
||||
future::try_join(send(stdin, &mut w), recv(stdout, &mut r)).await?;
|
||||
future::try_join(send(stdin, &socket), recv(stdout, &socket)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
writer: &mut SendHalf,
|
||||
writer: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
@@ -135,7 +133,7 @@ mod udp {
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
reader: &mut RecvHalf,
|
||||
reader: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
|
||||
@@ -26,7 +26,7 @@ struct Server {
|
||||
impl Server {
|
||||
async fn run(self) -> Result<(), io::Error> {
|
||||
let Server {
|
||||
mut socket,
|
||||
socket,
|
||||
mut buf,
|
||||
mut to_send,
|
||||
} = self;
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
|
||||
@@ -74,7 +74,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// above and must be associated with an event loop, so we pass in a handle
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
let mut listener = TcpListener::bind(listen_addr).await?;
|
||||
let listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Create the shared state of this server that will be shared amongst all
|
||||
|
||||
@@ -30,19 +30,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let mut server = TcpListener::bind(&addr).await?;
|
||||
let mut incoming = server.incoming();
|
||||
let server = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
while let Some(Ok(stream)) = incoming.next().await {
|
||||
loop {
|
||||
let (stream, _) = server.accept().await?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process(stream).await {
|
||||
println!("failed to process connection; error = {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process(stream: TcpStream) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
@@ -55,7 +55,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
.parse()?;
|
||||
|
||||
let mut socket = UdpSocket::bind(local_addr).await?;
|
||||
let socket = UdpSocket::bind(local_addr).await?;
|
||||
const MAX_DATAGRAM_SIZE: usize = 65_507;
|
||||
socket.connect(&remote_addr).await?;
|
||||
let data = get_stdin_data()?;
|
||||
|
||||
@@ -7,6 +7,7 @@ publish = false
|
||||
|
||||
[features]
|
||||
full = ["tokio/full"]
|
||||
rt = ["tokio/rt", "tokio/macros"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", optional = true }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn my_fn() {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,7 @@
|
||||
error: The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled.
|
||||
--> $DIR/macros_core_no_default.rs:3:1
|
||||
|
|
||||
3 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
@@ -4,7 +4,7 @@ error: the async keyword is missing from the function declaration
|
||||
4 | fn main_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`
|
||||
--> $DIR/macros_invalid_input.rs:6:15
|
||||
|
|
||||
6 | #[tokio::main(foo)]
|
||||
@@ -28,7 +28,7 @@ error: the test function cannot accept arguments
|
||||
16 | async fn test_fn_has_args(_x: u8) {}
|
||||
| ^^^^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`
|
||||
--> $DIR/macros_invalid_input.rs:18:15
|
||||
|
|
||||
18 | #[tokio::test(foo)]
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#[test]
|
||||
fn compile_fail() {
|
||||
fn compile_fail_full() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
#[cfg(all(feature = "rt", not(feature = "full")))]
|
||||
t.compile_fail("tests/fail/macros_core_no_default.rs");
|
||||
|
||||
drop(t);
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@ publish = false
|
||||
[features]
|
||||
full = [
|
||||
"macros",
|
||||
"rt-core",
|
||||
"rt-threaded",
|
||||
"rt",
|
||||
"rt-multi-thread",
|
||||
|
||||
"tokio/full",
|
||||
"tokio-test"
|
||||
]
|
||||
macros = ["tokio/macros"]
|
||||
sync = ["tokio/sync"]
|
||||
rt-core = ["tokio/rt-core"]
|
||||
rt-threaded = ["rt-core", "tokio/rt-threaded"]
|
||||
rt = ["tokio/rt"]
|
||||
rt-multi-thread = ["rt", "tokio/rt-multi-thread"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg(feature = "macros")]
|
||||
#![cfg(all(feature = "macros", feature = "rt"))]
|
||||
|
||||
#[tokio::main]
|
||||
async fn basic_main() -> usize {
|
||||
@@ -10,18 +10,15 @@ async fn generic_fun<T: Default>() -> T {
|
||||
T::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "rt-core")]
|
||||
mod spawn {
|
||||
#[tokio::main]
|
||||
async fn spawning() -> usize {
|
||||
let join = tokio::spawn(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
#[tokio::main]
|
||||
async fn spawning() -> usize {
|
||||
let join = tokio::spawn(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
}
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -72,7 +72,7 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
};
|
||||
|
||||
// Compose reading and writing concurrently.
|
||||
future::join3(write, read, cat)
|
||||
future::join3(write, read, cat.wait())
|
||||
.map(|(_, _, status)| status)
|
||||
.await
|
||||
}
|
||||
@@ -125,3 +125,26 @@ async fn status_closes_any_pipes() {
|
||||
|
||||
assert_ok!(child.await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn try_wait() {
|
||||
let mut child = cat().spawn().unwrap();
|
||||
|
||||
let id = child.id().expect("missing id");
|
||||
assert!(id > 0);
|
||||
|
||||
assert_eq!(None, assert_ok!(child.try_wait()));
|
||||
|
||||
// Drop the child's stdio handles so it can terminate
|
||||
drop(child.stdin.take());
|
||||
drop(child.stderr.take());
|
||||
drop(child.stdout.take());
|
||||
|
||||
assert_ok!(child.wait().await);
|
||||
|
||||
// test that the `.try_wait()` method is fused just like the stdlib
|
||||
assert!(assert_ok!(child.try_wait()).unwrap().success());
|
||||
|
||||
// Can't get id after process has exited
|
||||
assert_eq!(child.id(), None);
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "sync")]
|
||||
|
||||
use tokio::runtime;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn basic_shell_rt() {
|
||||
let (feed_tx, feed_rx) = mpsc::channel::<oneshot::Sender<()>>();
|
||||
|
||||
let th = thread::spawn(move || {
|
||||
for tx in feed_rx.iter() {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
for _ in 0..1_000 {
|
||||
let mut rt = runtime::Builder::new().build().unwrap();
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
feed_tx.send(tx).unwrap();
|
||||
|
||||
rt.block_on(rx).unwrap();
|
||||
}
|
||||
|
||||
drop(feed_tx);
|
||||
th.join().unwrap();
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
# 0.3.1 (October 25, 2020)
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix incorrect docs regarding `max_threads` option ([#3038])
|
||||
|
||||
# 0.3.0 (October 15, 2020)
|
||||
|
||||
- Track `tokio` 0.3 release.
|
||||
|
||||
### Changed
|
||||
- options are renamed to track `tokio` runtime builder fn names.
|
||||
- `#[tokio::main]` macro requires `rt-multi-thread` when no `flavor` is specified.
|
||||
|
||||
# 0.2.5 (February 27, 2019)
|
||||
|
||||
### Fixed
|
||||
@@ -30,9 +44,10 @@
|
||||
|
||||
- Initial release
|
||||
|
||||
[#2225]: https://github.com/tokio-rs/tokio/pull/2225
|
||||
[#2177]: https://github.com/tokio-rs/tokio/pull/2177
|
||||
[#2152]: https://github.com/tokio-rs/tokio/pull/2152
|
||||
[#2038]: https://github.com/tokio-rs/tokio/pull/2038
|
||||
[#2022]: https://github.com/tokio-rs/tokio/pull/2022
|
||||
[#1954]: https://github.com/tokio-rs/tokio/pull/1954
|
||||
[#2022]: https://github.com/tokio-rs/tokio/pull/2022
|
||||
[#2038]: https://github.com/tokio-rs/tokio/pull/2038
|
||||
[#2152]: https://github.com/tokio-rs/tokio/pull/2152
|
||||
[#2177]: https://github.com/tokio-rs/tokio/pull/2177
|
||||
[#2225]: https://github.com/tokio-rs/tokio/pull/2225
|
||||
[#3038]: https://github.com/tokio-rs/tokio/pull/3038
|
||||
|
||||
@@ -6,14 +6,14 @@ name = "tokio-macros"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.2.5"
|
||||
# - Create "v0.3.x" git tag.
|
||||
version = "0.3.1"
|
||||
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/0.2.5/tokio_macros"
|
||||
documentation = "https://docs.rs/tokio-macros/0.3.1/tokio_macros"
|
||||
description = """
|
||||
Tokio's proc macros.
|
||||
"""
|
||||
@@ -30,7 +30,7 @@ quote = "1"
|
||||
syn = { version = "1.0.3", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
+182
-241
@@ -1,18 +1,144 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use std::num::NonZeroUsize;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Runtime {
|
||||
Basic,
|
||||
enum RuntimeFlavor {
|
||||
CurrentThread,
|
||||
Threaded,
|
||||
}
|
||||
|
||||
impl RuntimeFlavor {
|
||||
fn from_str(s: &str) -> Result<RuntimeFlavor, String> {
|
||||
match s {
|
||||
"current_thread" => Ok(RuntimeFlavor::CurrentThread),
|
||||
"multi_thread" => Ok(RuntimeFlavor::Threaded),
|
||||
"single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()),
|
||||
"basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()),
|
||||
"threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()),
|
||||
_ => Err(format!("No such runtime flavor `{}`. The runtime flavors are `current_thread` and `multi_thread`.", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FinalConfig {
|
||||
flavor: RuntimeFlavor,
|
||||
worker_threads: Option<usize>,
|
||||
}
|
||||
|
||||
struct Configuration {
|
||||
rt_multi_thread_available: bool,
|
||||
default_flavor: RuntimeFlavor,
|
||||
flavor: Option<RuntimeFlavor>,
|
||||
worker_threads: Option<(usize, Span)>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
fn new(is_test: bool, rt_multi_thread: bool) -> Self {
|
||||
Configuration {
|
||||
rt_multi_thread_available: rt_multi_thread,
|
||||
default_flavor: match is_test {
|
||||
true => RuntimeFlavor::CurrentThread,
|
||||
false => RuntimeFlavor::Threaded,
|
||||
},
|
||||
flavor: None,
|
||||
worker_threads: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_flavor(&mut self, runtime: syn::Lit, span: Span) -> Result<(), syn::Error> {
|
||||
if self.flavor.is_some() {
|
||||
return Err(syn::Error::new(span, "`flavor` set multiple times."));
|
||||
}
|
||||
|
||||
let runtime_str = parse_string(runtime, span, "flavor")?;
|
||||
let runtime =
|
||||
RuntimeFlavor::from_str(&runtime_str).map_err(|err| syn::Error::new(span, err))?;
|
||||
self.flavor = Some(runtime);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_worker_threads(
|
||||
&mut self,
|
||||
worker_threads: syn::Lit,
|
||||
span: Span,
|
||||
) -> Result<(), syn::Error> {
|
||||
if self.worker_threads.is_some() {
|
||||
return Err(syn::Error::new(
|
||||
span,
|
||||
"`worker_threads` set multiple times.",
|
||||
));
|
||||
}
|
||||
|
||||
let worker_threads = parse_int(worker_threads, span, "worker_threads")?;
|
||||
if worker_threads == 0 {
|
||||
return Err(syn::Error::new(span, "`worker_threads` may not be 0."));
|
||||
}
|
||||
self.worker_threads = Some((worker_threads, span));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(&self) -> Result<FinalConfig, syn::Error> {
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
use RuntimeFlavor::*;
|
||||
match (flavor, self.worker_threads) {
|
||||
(CurrentThread, Some((_, worker_threads_span))) => Err(syn::Error::new(
|
||||
worker_threads_span,
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor.",
|
||||
)),
|
||||
(CurrentThread, None) => Ok(FinalConfig {
|
||||
flavor,
|
||||
worker_threads: None,
|
||||
}),
|
||||
(Threaded, worker_threads) if self.rt_multi_thread_available => Ok(FinalConfig {
|
||||
flavor,
|
||||
worker_threads: worker_threads.map(|(val, _span)| val),
|
||||
}),
|
||||
(Threaded, _) => {
|
||||
let msg = if self.flavor.is_none() {
|
||||
"The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled."
|
||||
} else {
|
||||
"The runtime flavor `multi_thread` requires the `rt-multi-thread` feature."
|
||||
};
|
||||
Err(syn::Error::new(Span::call_site(), msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error> {
|
||||
match int {
|
||||
syn::Lit::Int(lit) => match lit.base10_parse::<usize>() {
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as integer: {}", field, e),
|
||||
)),
|
||||
},
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as integer.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::Error> {
|
||||
match int {
|
||||
syn::Lit::Str(s) => Ok(s.value()),
|
||||
syn::Lit::Verbatim(s) => Ok(s.to_string()),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as string.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_knobs(
|
||||
mut input: syn::ItemFn,
|
||||
args: syn::AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_threaded: bool,
|
||||
rt_multi_thread: bool,
|
||||
) -> Result<TokenStream, syn::Error> {
|
||||
let sig = &mut input.sig;
|
||||
let body = &input.block;
|
||||
@@ -26,9 +152,12 @@ fn parse_knobs(
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let mut runtime = None;
|
||||
let mut core_threads = None;
|
||||
let mut max_threads = None;
|
||||
let macro_name = if is_test {
|
||||
"tokio::test"
|
||||
} else {
|
||||
"tokio::main"
|
||||
};
|
||||
let mut config = Configuration::new(is_test, rt_multi_thread);
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
@@ -39,65 +168,18 @@ fn parse_knobs(
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"core_threads" => {
|
||||
if rt_threaded {
|
||||
match &namevalue.lit {
|
||||
syn::Lit::Int(expr) => {
|
||||
let num = expr.base10_parse::<NonZeroUsize>().unwrap();
|
||||
if num.get() > 1 {
|
||||
runtime = Some(Runtime::Threaded);
|
||||
} else {
|
||||
runtime = Some(Runtime::Basic);
|
||||
}
|
||||
|
||||
if let Some(v) = max_threads {
|
||||
if v < num {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads cannot be less than core_threads",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
core_threads = Some(num);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"core_threads argument must be an int",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"core_threads can only be set with rt-threaded feature flag enabled",
|
||||
));
|
||||
}
|
||||
"worker_threads" => {
|
||||
config.set_worker_threads(namevalue.lit.clone(), namevalue.span())?;
|
||||
}
|
||||
"flavor" => {
|
||||
config.set_flavor(namevalue.lit.clone(), namevalue.span())?;
|
||||
}
|
||||
"core_threads" => {
|
||||
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
"max_threads" => match &namevalue.lit {
|
||||
syn::Lit::Int(expr) => {
|
||||
let num = expr.base10_parse::<NonZeroUsize>().unwrap();
|
||||
|
||||
if let Some(v) = core_threads {
|
||||
if num < v {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads cannot be less than core_threads",
|
||||
));
|
||||
}
|
||||
}
|
||||
max_threads = Some(num);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads argument must be an int",
|
||||
))
|
||||
}
|
||||
},
|
||||
name => {
|
||||
let msg = format!("Unknown attribute pair {} is specified; expected one of: `core_threads`, `max_threads`", name);
|
||||
let msg = format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
}
|
||||
@@ -108,16 +190,28 @@ fn parse_knobs(
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => {
|
||||
runtime = Some(runtime.unwrap_or_else(|| Runtime::Threaded))
|
||||
let name = ident.unwrap().to_string().to_lowercase();
|
||||
let msg = match name.as_str() {
|
||||
"threaded_scheduler" | "multi_thread" => {
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"multi_thread\")].",
|
||||
macro_name
|
||||
)
|
||||
}
|
||||
"basic_scheduler" | "current_thread" | "single_threaded" => {
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"current_thread\")].",
|
||||
macro_name
|
||||
)
|
||||
}
|
||||
"flavor" | "worker_threads" => {
|
||||
format!("The `{}` attribute requires an argument.", name)
|
||||
}
|
||||
"basic_scheduler" => runtime = Some(runtime.unwrap_or_else(|| Runtime::Basic)),
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name)
|
||||
}
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
other => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
@@ -128,15 +222,18 @@ fn parse_knobs(
|
||||
}
|
||||
}
|
||||
|
||||
let mut rt = quote! { tokio::runtime::Builder::new().basic_scheduler() };
|
||||
if rt_threaded && (runtime == Some(Runtime::Threaded) || (runtime.is_none() && !is_test)) {
|
||||
rt = quote! { #rt.threaded_scheduler() };
|
||||
}
|
||||
if let Some(v) = core_threads.map(|v| v.get()) {
|
||||
rt = quote! { #rt.core_threads(#v) };
|
||||
}
|
||||
if let Some(v) = max_threads.map(|v| v.get()) {
|
||||
rt = quote! { #rt.max_threads(#v) };
|
||||
let config = config.build()?;
|
||||
|
||||
let mut rt = match config.flavor {
|
||||
RuntimeFlavor::CurrentThread => quote! {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
},
|
||||
RuntimeFlavor::Threaded => quote! {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
},
|
||||
};
|
||||
if let Some(v) = config.worker_threads {
|
||||
rt = quote! { #rt.worker_threads(#v) };
|
||||
}
|
||||
|
||||
let header = {
|
||||
@@ -165,21 +262,21 @@ fn parse_knobs(
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
return syn::Error::new_spanned(&input.sig.ident, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, false, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
parse_knobs(input, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
@@ -199,161 +296,5 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_threaded: bool) -> T
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, true, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) mod old {
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
enum Runtime {
|
||||
Basic,
|
||||
Threaded,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let sig = &mut input.sig;
|
||||
let name = &sig.ident;
|
||||
let inputs = &sig.inputs;
|
||||
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 syn::Error::new_spanned(sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if name == "main" && !inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded | Runtime::Auto => quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic => quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let ret = &input.sig.output;
|
||||
let name = &input.sig.ident;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
if input.sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(&input.sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else 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();
|
||||
}
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded => quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
#(#attrs)*
|
||||
#vis fn #name() #ret {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic | Runtime::Auto => quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
#(#attrs)*
|
||||
#vis fn #name() #ret {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
+90
-164
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.2.5")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.3.1")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
# or
|
||||
/// [Builder](../tokio/runtime/struct.builder.html) directly.
|
||||
/// Marks async function to be executed by the selected runtime. This macro helps
|
||||
/// set up a `Runtime` without requiring the user to use
|
||||
/// [Runtime](../tokio/runtime/struct.Runtime.html) or
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html) directly.
|
||||
///
|
||||
/// ## Options:
|
||||
/// Note: This macro is designed to be simplistic and targets applications that
|
||||
/// do not require a complex setup. If the provided functionality is not
|
||||
/// sufficient, you may be interested in using
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html), which provides a more
|
||||
/// powerful interface.
|
||||
///
|
||||
/// If you want to set the number of worker threads used for asynchronous code, use the
|
||||
/// `core_threads` option.
|
||||
/// Note: This macro can be used on any function and not just the `main`
|
||||
/// function. Using it on a non-main function makes the function behave
|
||||
/// as if it was synchronous by starting a new runtime each time it is called.
|
||||
/// If the function is called often, it is preferable to create the runtime using
|
||||
/// the runtime builder so the runtime can be reused across calls.
|
||||
///
|
||||
/// - `core_threads=n` - Sets core threads to `n` (requires `rt-threaded` feature).
|
||||
/// - `max_threads=n` - Sets max threads to `n` (requires `rt-core` or `rt-threaded` feature).
|
||||
/// - `basic_scheduler` - Use the basic schduler (requires `rt-core`).
|
||||
/// # Multi-threaded runtime
|
||||
///
|
||||
/// To use the multi-threaded runtime, the macro can be configured using
|
||||
///
|
||||
/// ```
|
||||
/// #[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
||||
/// # async fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// The `worker_threads` option configures the number of worker threads, and
|
||||
/// defaults to the number of cpus on the system. This is the default flavor.
|
||||
///
|
||||
/// # Current thread runtime
|
||||
///
|
||||
/// To use the single-threaded runtime known as the `current_thread` runtime,
|
||||
/// the macro can be configured using
|
||||
///
|
||||
/// ```
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
@@ -43,7 +69,7 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using default
|
||||
/// ### Using the multi-thread runtime
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
@@ -56,8 +82,7 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new()
|
||||
/// .threaded_scheduler()
|
||||
/// tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
@@ -67,12 +92,12 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using basic scheduler
|
||||
/// ### Using current thread runtime
|
||||
///
|
||||
/// The basic scheduler is single-threaded.
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(basic_scheduler)]
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
@@ -82,8 +107,7 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new()
|
||||
/// .basic_scheduler()
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
@@ -93,10 +117,10 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Set number of core threads
|
||||
/// ### Set number of worker threads
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(core_threads = 2)]
|
||||
/// #[tokio::main(worker_threads = 2)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
@@ -106,9 +130,8 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new()
|
||||
/// .threaded_scheduler()
|
||||
/// .core_threads(2)
|
||||
/// tokio::runtime::Builder::new_multi_thread()
|
||||
/// .worker_threads(2)
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
@@ -120,14 +143,13 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.2 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
/// If you rename the tokio crate in your dependencies this macro will not work.
|
||||
/// If you must rename the 0.3 version of tokio because you're also using the
|
||||
/// 0.1 version of tokio, you _must_ make the tokio 0.3 crate available as
|
||||
/// `tokio` in the module where this macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, true)
|
||||
}
|
||||
|
||||
@@ -135,11 +157,6 @@ pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// without requiring the user to use [Runtime](../tokio/runtime/struct.Runtime.html) or
|
||||
/// [Builder](../tokio/runtime/struct.builder.html) directly.
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `basic_scheduler` - All tasks are executed on the current thread.
|
||||
/// - `threaded_scheduler` - Uses the multi-threaded scheduler. Used by default (requires `rt-threaded` feature).
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
@@ -149,7 +166,7 @@ pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
@@ -159,29 +176,7 @@ pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Runtime::new()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Select runtime
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(basic_scheduler)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new()
|
||||
/// .basic_scheduler()
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
@@ -194,80 +189,24 @@ pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.2 version of tokio because
|
||||
/// will not work. If you must rename the 0.3 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// tokio 0.3 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::old::main(args, item)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime. This macro helps set up a `Runtime`
|
||||
/// without requiring the user to use [Runtime](../tokio/runtime/struct.Runtime.html) or
|
||||
/// [Builder](../tokio/runtime/struct.builder.html) directly.
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new()
|
||||
/// .basic_scheduler()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.2 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, false)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `core_threads=n` - Sets core threads to `n` (requires `rt-threaded` feature).
|
||||
/// - `max_threads=n` - Sets max threads to `n` (requires `rt-core` or `rt-threaded` feature).
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Select runtime
|
||||
/// ### Multi-thread runtime
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(core_threads = 1)]
|
||||
/// #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
@@ -275,6 +214,8 @@ pub fn main_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// The default test runtime is single-threaded.
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
@@ -285,35 +226,19 @@ pub fn main_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.2 version of tokio because
|
||||
/// will not work. If you must rename the 0.3 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// tokio 0.3 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `basic_scheduler` - All tasks are executed on the current thread. Used by default.
|
||||
/// - `threaded_scheduler` - Use multi-threaded scheduler (requires `rt-threaded` feature).
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Select runtime
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(threaded_scheduler)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
@@ -324,40 +249,41 @@ pub fn test_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.2 version of tokio because
|
||||
/// will not work. If you must rename the 0.3 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// tokio 0.3 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::old::test(args, item)
|
||||
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// Always fails with the error message below.
|
||||
/// ```text
|
||||
/// The #[tokio::main] macro requires rt or rt-multi-thread.
|
||||
/// ```
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.2 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
pub fn main_fail(_args: TokenStream, _item: TokenStream) -> TokenStream {
|
||||
syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"The #[tokio::main] macro requires rt or rt-multi-thread.",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Always fails with the error message below.
|
||||
/// ```text
|
||||
/// The #[tokio::test] macro requires rt or rt-multi-thread.
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_fail(_args: TokenStream, _item: TokenStream) -> TokenStream {
|
||||
syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"The #[tokio::test] macro requires rt or rt-multi-thread.",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Implementation detail of the `select!` macro. This macro is **not** intended
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# 0.3.0 (October 15, 2020)
|
||||
|
||||
- Track `tokio` 0.3 release.
|
||||
|
||||
# 0.2.1 (April 17, 2020)
|
||||
|
||||
- Add `Future` and `Stream` implementations for `task::Spawn<T>`.
|
||||
|
||||
@@ -6,27 +6,27 @@ name = "tokio-test"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.1"
|
||||
# - Create "v0.3.x" git tag.
|
||||
version = "0.3.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-test/0.2.1/tokio_test"
|
||||
documentation = "https://docs.rs/tokio-test/0.3.0/tokio_test"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
categories = ["asynchronous", "testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["rt-core", "stream", "sync", "time", "test-util"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["rt", "stream", "sync", "time", "test-util"] }
|
||||
|
||||
bytes = "0.5.0"
|
||||
bytes = "0.6.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
+46
-35
@@ -18,11 +18,10 @@
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{self, Delay, Duration, Instant};
|
||||
use tokio::time::{self, Duration, Instant, Sleep};
|
||||
|
||||
use bytes::Buf;
|
||||
use futures_core::ready;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
@@ -68,7 +67,7 @@ enum Action {
|
||||
struct Inner {
|
||||
actions: VecDeque<Action>,
|
||||
waiting: Option<Instant>,
|
||||
sleep: Option<Delay>,
|
||||
sleep: Option<Sleep>,
|
||||
read_wait: Option<Waker>,
|
||||
rx: mpsc::UnboundedReceiver<Action>,
|
||||
}
|
||||
@@ -201,23 +200,24 @@ impl Inner {
|
||||
}
|
||||
|
||||
fn poll_action(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Action>> {
|
||||
self.rx.poll_recv(cx)
|
||||
use futures_core::stream::Stream;
|
||||
|
||||
Pin::new(&mut self.rx).poll_next(cx)
|
||||
}
|
||||
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
fn read(&mut self, dst: &mut ReadBuf<'_>) -> io::Result<()> {
|
||||
match self.action() {
|
||||
Some(&mut Action::Read(ref mut data)) => {
|
||||
// Figure out how much to copy
|
||||
let n = cmp::min(dst.len(), data.len());
|
||||
let n = cmp::min(dst.remaining(), data.len());
|
||||
|
||||
// Copy the data into the `dst` slice
|
||||
(&mut dst[..n]).copy_from_slice(&data[..n]);
|
||||
dst.put_slice(&data[..n]);
|
||||
|
||||
// Drain the data from the source
|
||||
data.drain(..n);
|
||||
|
||||
// Return the number of bytes read
|
||||
Ok(n)
|
||||
Ok(())
|
||||
}
|
||||
Some(&mut Action::ReadError(ref mut err)) => {
|
||||
// As the
|
||||
@@ -229,7 +229,7 @@ impl Inner {
|
||||
// Either waiting or expecting a write
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
None => Ok(0),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,8 +348,8 @@ impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
if let Some(ref mut sleep) = self.inner.sleep {
|
||||
ready!(Pin::new(sleep).poll(cx));
|
||||
@@ -358,29 +358,35 @@ impl AsyncRead for Mock {
|
||||
// If a sleep is set, it has already fired
|
||||
self.inner.sleep = None;
|
||||
|
||||
// Capture 'filled' to monitor if it changed
|
||||
let filled = buf.filled().len();
|
||||
|
||||
match self.inner.read(buf) {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if let Some(rem) = self.inner.remaining_wait() {
|
||||
let until = Instant::now() + rem;
|
||||
self.inner.sleep = Some(time::delay_until(until));
|
||||
self.inner.sleep = Some(time::sleep_until(until));
|
||||
} else {
|
||||
self.inner.read_wait = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
Ok(0) => {
|
||||
// TODO: Extract
|
||||
match ready!(self.inner.poll_action(cx)) {
|
||||
Some(action) => {
|
||||
self.inner.actions.push_back(action);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
return Poll::Ready(Ok(0));
|
||||
Ok(()) => {
|
||||
if buf.filled().len() == filled {
|
||||
match ready!(self.inner.poll_action(cx)) {
|
||||
Some(action) => {
|
||||
self.inner.actions.push_back(action);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
ret => return Poll::Ready(ret),
|
||||
Err(e) => return Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,7 +410,7 @@ impl AsyncWrite for Mock {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if let Some(rem) = self.inner.remaining_wait() {
|
||||
let until = Instant::now() + rem;
|
||||
self.inner.sleep = Some(time::delay_until(until));
|
||||
self.inner.sleep = Some(time::sleep_until(until));
|
||||
} else {
|
||||
panic!("unexpected WouldBlock");
|
||||
}
|
||||
@@ -434,16 +440,6 @@ impl AsyncWrite for Mock {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_write_buf<B: Buf>(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let n = ready!(self.poll_write(cx, buf.bytes()))?;
|
||||
buf.advance(n);
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
@@ -453,6 +449,21 @@ impl AsyncWrite for Mock {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that Mock isn't dropped with data "inside".
|
||||
impl Drop for Mock {
|
||||
fn drop(&mut self) {
|
||||
// Avoid double panicking, since makes debugging much harder.
|
||||
if std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.inner.actions.iter().for_each(|a| match a {
|
||||
Action::Read(data) => assert!(data.is_empty(), "There is still data left to read."),
|
||||
Action::Write(data) => assert!(data.is_empty(), "There is still data left to write."),
|
||||
_ => (),
|
||||
})
|
||||
}
|
||||
}
|
||||
/*
|
||||
/// Returns `true` if called from the context of a futures-rs Task
|
||||
fn is_task_ctx() -> bool {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.2.1")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.3.0")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
@@ -28,8 +28,7 @@ pub mod task;
|
||||
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
use tokio::runtime;
|
||||
|
||||
let mut rt = runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use tokio::stream::Stream;
|
||||
|
||||
/// TOOD: dox
|
||||
/// TODO: dox
|
||||
pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
Spawn {
|
||||
task: MockTask::new(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::time::{delay_until, Duration, Instant};
|
||||
use tokio::time::{sleep_until, Duration, Instant};
|
||||
use tokio_test::block_on;
|
||||
|
||||
#[test]
|
||||
@@ -18,10 +18,10 @@ fn async_fn() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delay() {
|
||||
fn test_sleep() {
|
||||
let deadline = Instant::now() + Duration::from_millis(100);
|
||||
|
||||
block_on(async {
|
||||
delay_until(deadline).await;
|
||||
sleep_until(deadline).await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,3 +70,17 @@ async fn write_error() {
|
||||
|
||||
mock.write_all(b"world!").await.expect("write 2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn mock_panics_read_data_left() {
|
||||
use tokio_test::io::Builder;
|
||||
Builder::new().read(b"read").build();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn mock_panics_write_data_left() {
|
||||
use tokio_test::io::Builder;
|
||||
Builder::new().write(b"write").build();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
# 0.5.1 (December 3, 2020)
|
||||
|
||||
### Added
|
||||
- io: `poll_read_buf` util fn (#2972).
|
||||
- io: `poll_write_buf` util fn with vectored write support (#3156).
|
||||
|
||||
# 0.5.0 (October 30, 2020)
|
||||
|
||||
### Changed
|
||||
- io: update `bytes` to 0.6 (#3071).
|
||||
|
||||
# 0.4.0 (October 15, 2020)
|
||||
|
||||
### Added
|
||||
- sync: `CancellationToken` for coordinating task cancellation (#2747).
|
||||
- rt: `TokioContext` sets the Tokio runtime for the duration of a future (#2791)
|
||||
- io: `StreamReader`/`ReaderStream` map between `AsyncRead` values and `Stream`
|
||||
of bytes (#2788).
|
||||
- time: `DelayQueue` to manage many delays (#2897).
|
||||
|
||||
# 0.3.1 (March 18, 2020)
|
||||
|
||||
### Fixed
|
||||
|
||||
+14
-9
@@ -7,13 +7,13 @@ name = "tokio-util"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.3.1"
|
||||
version = "0.5.1"
|
||||
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.3.1/tokio_util"
|
||||
documentation = "https://docs.rs/tokio-util/0.5.1/tokio_util"
|
||||
description = """
|
||||
Additional utilities for working with Tokio.
|
||||
"""
|
||||
@@ -24,27 +24,32 @@ categories = ["asynchronous"]
|
||||
default = []
|
||||
|
||||
# Shorthand for enabling everything
|
||||
full = ["codec", "udp", "compat"]
|
||||
full = ["codec", "compat", "io", "time", "net", "rt"]
|
||||
|
||||
net = ["tokio/net"]
|
||||
compat = ["futures-io",]
|
||||
codec = ["tokio/stream"]
|
||||
udp = ["tokio/udp"]
|
||||
time = ["tokio/time","slab"]
|
||||
io = []
|
||||
rt = ["tokio/rt"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.5", path = "../tokio" }
|
||||
tokio = { version = "0.3.4", path = "../tokio" }
|
||||
|
||||
bytes = "0.5.0"
|
||||
bytes = "0.6.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
futures-io = { version = "0.3.0", optional = true }
|
||||
log = "0.4"
|
||||
pin-project-lite = "0.1.4"
|
||||
pin-project-lite = "0.2.0"
|
||||
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.2.0", path = "../tokio-test" }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.3.0", path = "../tokio-test" }
|
||||
|
||||
futures = "0.3.0"
|
||||
futures-test = "0.3.5"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
+23
-3
@@ -18,11 +18,31 @@ macro_rules! cfg_compat {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_udp {
|
||||
macro_rules! cfg_net {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(all(feature = "udp", feature = "codec"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "udp", feature = "codec"))))]
|
||||
#[cfg(all(feature = "net", feature = "codec"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "net", feature = "codec"))))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_io {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "io")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_rt {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ use std::io;
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[tokio::main(core_threads = 1)]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), std::io::Error> {
|
||||
/// let my_async_read = File::open("filename.txt").await?;
|
||||
/// let my_stream_of_bytes = FramedRead::new(my_async_read, BytesCodec::new());
|
||||
|
||||
@@ -6,7 +6,7 @@ use tokio::{
|
||||
stream::Stream,
|
||||
};
|
||||
|
||||
use bytes::{Buf, BytesMut};
|
||||
use bytes::BytesMut;
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
@@ -118,6 +118,8 @@ where
|
||||
type Item = Result<U::Item, U::Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
use crate::util::poll_read_buf;
|
||||
|
||||
let mut pinned = self.project();
|
||||
let state: &mut ReadFrame = pinned.state.borrow_mut();
|
||||
loop {
|
||||
@@ -148,7 +150,7 @@ where
|
||||
// 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 pinned.inner.as_mut().poll_read_buf(cx, &mut state.buffer)? {
|
||||
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer)? {
|
||||
Poll::Ready(ct) => ct,
|
||||
Poll::Pending => return Poll::Pending,
|
||||
};
|
||||
@@ -187,6 +189,7 @@ where
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
use crate::util::poll_write_buf;
|
||||
trace!("flushing framed transport");
|
||||
let mut pinned = self.project();
|
||||
|
||||
@@ -194,8 +197,7 @@ where
|
||||
let WriteFrame { buffer } = pinned.state.borrow_mut();
|
||||
trace!("writing; remaining={}", buffer.len());
|
||||
|
||||
let buf = &buffer;
|
||||
let n = ready!(pinned.inner.as_mut().poll_write(cx, &buf))?;
|
||||
let n = ready!(poll_write_buf(pinned.inner.as_mut(), cx, buffer))?;
|
||||
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
@@ -205,8 +207,6 @@ where
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
|
||||
pinned.state.borrow_mut().buffer.advance(n);
|
||||
}
|
||||
|
||||
// Try flushing the underlying IO
|
||||
|
||||
@@ -100,6 +100,11 @@ impl<T, D> FramedRead<T, D> {
|
||||
pub fn read_buffer(&self) -> &BytesMut {
|
||||
&self.inner.state.buffer
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the read buffer.
|
||||
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.inner.state.buffer
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Compatibility between the `tokio::io` and `futures-io` versions of the
|
||||
//! `AsyncRead` and `AsyncWrite` traits.
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
@@ -19,7 +20,7 @@ pin_project! {
|
||||
/// `futures_io::AsyncRead` to implement `tokio::io::AsyncRead`.
|
||||
pub trait FuturesAsyncReadCompatExt: futures_io::AsyncRead {
|
||||
/// Wraps `self` with a compatibility layer that implements
|
||||
/// `tokio_io::AsyncWrite`.
|
||||
/// `tokio_io::AsyncRead`.
|
||||
fn compat(self) -> Compat<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -107,9 +108,18 @@ where
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
futures_io::AsyncRead::poll_read(self.project().inner, cx, buf)
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
// We can't trust the inner type to not peak at the bytes,
|
||||
// so we must defensively initialize the buffer.
|
||||
let slice = buf.initialize_unfilled();
|
||||
let n = ready!(futures_io::AsyncRead::poll_read(
|
||||
self.project().inner,
|
||||
cx,
|
||||
slice
|
||||
))?;
|
||||
buf.advance(n);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +130,15 @@ where
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
slice: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
tokio::io::AsyncRead::poll_read(self.project().inner, cx, buf)
|
||||
let mut buf = tokio::io::ReadBuf::new(slice);
|
||||
ready!(tokio::io::AsyncRead::poll_read(
|
||||
self.project().inner,
|
||||
cx,
|
||||
&mut buf
|
||||
))?;
|
||||
Poll::Ready(Ok(buf.filled().len()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Tokio context aware futures utilities.
|
||||
//!
|
||||
//! This module includes utilities around integrating tokio with other runtimes
|
||||
//! by allowing the context to be attached to futures. This allows spawning
|
||||
//! futures on other executors while still using tokio to drive them. This
|
||||
//! can be useful if you need to use a tokio based library in an executor/runtime
|
||||
//! that does not provide a tokio context.
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
pin_project! {
|
||||
/// `TokioContext` allows connecting a custom executor with the tokio runtime.
|
||||
///
|
||||
/// It contains a `Handle` to the runtime. A handle to the runtime can be
|
||||
/// obtain by calling the `Runtime::handle()` method.
|
||||
pub struct TokioContext<'a, F> {
|
||||
#[pin]
|
||||
inner: F,
|
||||
handle: &'a Runtime,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for TokioContext<'_, F> {
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = self.project();
|
||||
let handle = me.handle;
|
||||
let fut = me.inner;
|
||||
|
||||
let _enter = handle.enter();
|
||||
fut.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait extension that simplifies bundling a `Handle` with a `Future`.
|
||||
pub trait RuntimeExt {
|
||||
/// Convenience method that takes a Future and returns a `TokioContext`.
|
||||
///
|
||||
/// # Example: calling Tokio Runtime from a custom ThreadPool
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio_util::context::RuntimeExt;
|
||||
/// use tokio::time::{sleep, Duration};
|
||||
///
|
||||
/// let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let rt2 = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let fut = sleep(Duration::from_millis(2));
|
||||
///
|
||||
/// rt.block_on(
|
||||
/// rt2
|
||||
/// .wrap(async { sleep(Duration::from_millis(2)).await }),
|
||||
/// );
|
||||
///```
|
||||
fn wrap<F: Future>(&self, fut: F) -> TokioContext<'_, F>;
|
||||
}
|
||||
|
||||
impl RuntimeExt for Runtime {
|
||||
fn wrap<F: Future>(&self, fut: F) -> TokioContext<'_, F> {
|
||||
TokioContext {
|
||||
inner: fut,
|
||||
handle: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
//! Module defining an Either type.
|
||||
use std::{
|
||||
future::Future,
|
||||
io::SeekFrom,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf, Result};
|
||||
|
||||
/// Combines two different futures, streams, or sinks having the same associated types into a single type.
|
||||
///
|
||||
/// This type implements common asynchronous traits such as [`Future`] and those in Tokio.
|
||||
///
|
||||
/// [`Future`]: std::future::Future
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// The following code will not work:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # fn some_condition() -> bool { true }
|
||||
/// # async fn some_async_function() -> u32 { 10 }
|
||||
/// # async fn other_async_function() -> u32 { 20 }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let result = if some_condition() {
|
||||
/// some_async_function()
|
||||
/// } else {
|
||||
/// other_async_function() // <- Will print: "`if` and `else` have incompatible types"
|
||||
/// };
|
||||
///
|
||||
/// println!("Result is {}", result.await);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
// This is because although the output types for both futures is the same, the exact future
|
||||
// types are different, but the compiler must be able to choose a single type for the
|
||||
// `result` variable.
|
||||
///
|
||||
/// When the output type is the same, we can wrap each future in `Either` to avoid the
|
||||
/// issue:
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::either::Either;
|
||||
/// # fn some_condition() -> bool { true }
|
||||
/// # async fn some_async_function() -> u32 { 10 }
|
||||
/// # async fn other_async_function() -> u32 { 20 }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let result = if some_condition() {
|
||||
/// Either::Left(some_async_function())
|
||||
/// } else {
|
||||
/// Either::Right(other_async_function())
|
||||
/// };
|
||||
///
|
||||
/// let value = result.await;
|
||||
/// println!("Result is {}", value);
|
||||
/// # assert_eq!(value, 10);
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(missing_docs)] // Doc-comments for variants in this particular case don't make much sense.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Either<L, R> {
|
||||
Left(L),
|
||||
Right(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
|
||||
/// enum variant held in `self`.
|
||||
macro_rules! delegate_call {
|
||||
($self:ident.$method:ident($($args:ident),+)) => {
|
||||
unsafe {
|
||||
match $self.get_unchecked_mut() {
|
||||
Self::Left(l) => Pin::new_unchecked(l).$method($($args),+),
|
||||
Self::Right(r) => Pin::new_unchecked(r).$method($($args),+),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R, O> Future for Either<L, R>
|
||||
where
|
||||
L: Future<Output = O>,
|
||||
R: Future<Output = O>,
|
||||
{
|
||||
type Output = O;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
delegate_call!(self.poll(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> AsyncRead for Either<L, R>
|
||||
where
|
||||
L: AsyncRead,
|
||||
R: AsyncRead,
|
||||
{
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<Result<()>> {
|
||||
delegate_call!(self.poll_read(cx, buf))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> AsyncBufRead for Either<L, R>
|
||||
where
|
||||
L: AsyncBufRead,
|
||||
R: AsyncBufRead,
|
||||
{
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<&[u8]>> {
|
||||
delegate_call!(self.poll_fill_buf(cx))
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
delegate_call!(self.consume(amt))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> AsyncSeek for Either<L, R>
|
||||
where
|
||||
L: AsyncSeek,
|
||||
R: AsyncSeek,
|
||||
{
|
||||
fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> Result<()> {
|
||||
delegate_call!(self.start_seek(position))
|
||||
}
|
||||
|
||||
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<u64>> {
|
||||
delegate_call!(self.poll_complete(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> AsyncWrite for Either<L, R>
|
||||
where
|
||||
L: AsyncWrite,
|
||||
R: AsyncWrite,
|
||||
{
|
||||
fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
|
||||
delegate_call!(self.poll_write(cx, buf))
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<tokio::io::Result<()>> {
|
||||
delegate_call!(self.poll_flush(cx))
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<tokio::io::Result<()>> {
|
||||
delegate_call!(self.poll_shutdown(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> futures_core::stream::Stream for Either<L, R>
|
||||
where
|
||||
L: futures_core::stream::Stream,
|
||||
R: futures_core::stream::Stream<Item = L::Item>,
|
||||
{
|
||||
type Item = L::Item;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
delegate_call!(self.poll_next(cx))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tokio::{
|
||||
io::{repeat, AsyncReadExt, Repeat},
|
||||
stream::{once, Once, StreamExt},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn either_is_stream() {
|
||||
let mut either: Either<Once<u32>, Once<u32>> = Either::Left(once(1));
|
||||
|
||||
assert_eq!(Some(1u32), either.next().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn either_is_async_read() {
|
||||
let mut buffer = [0; 3];
|
||||
let mut either: Either<Repeat, Repeat> = Either::Right(repeat(0b101));
|
||||
|
||||
either.read_exact(&mut buffer).await.unwrap();
|
||||
assert_eq!(buffer, [0b101, 0b101, 0b101]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//! Helpers for IO related tasks.
|
||||
//!
|
||||
//! These types are often used in combination with hyper or reqwest, as they
|
||||
//! allow converting between a hyper [`Body`] and [`AsyncRead`].
|
||||
//!
|
||||
//! [`Body`]: https://docs.rs/hyper/0.13/hyper/struct.Body.html
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
|
||||
mod read_buf;
|
||||
mod reader_stream;
|
||||
mod stream_reader;
|
||||
|
||||
pub use self::read_buf::read_buf;
|
||||
pub use self::reader_stream::ReaderStream;
|
||||
pub use self::stream_reader::StreamReader;
|
||||
pub use crate::util::{poll_read_buf, poll_write_buf};
|
||||
@@ -0,0 +1,65 @@
|
||||
use bytes::BufMut;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
/// Read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
|
||||
///
|
||||
/// [`BufMut`]: bytes::BufMut
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::{Bytes, BytesMut};
|
||||
/// use tokio::stream;
|
||||
/// use tokio::io::Result;
|
||||
/// use tokio_util::io::{StreamReader, read_buf};
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a reader from an iterator. This particular reader will always be
|
||||
/// // ready.
|
||||
/// let mut read = StreamReader::new(stream::iter(vec![Result::Ok(Bytes::from_static(&[0, 1, 2, 3]))]));
|
||||
///
|
||||
/// let mut buf = BytesMut::new();
|
||||
/// let mut reads = 0;
|
||||
///
|
||||
/// loop {
|
||||
/// reads += 1;
|
||||
/// let n = read_buf(&mut read, &mut buf).await?;
|
||||
///
|
||||
/// if n == 0 {
|
||||
/// break;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // one or more reads might be necessary.
|
||||
/// assert!(reads >= 1);
|
||||
/// assert_eq!(&buf[..], &[0, 1, 2, 3]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn read_buf<R, B>(read: &mut R, buf: &mut B) -> io::Result<usize>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
B: BufMut,
|
||||
{
|
||||
return ReadBufFn(read, buf).await;
|
||||
|
||||
struct ReadBufFn<'a, R, B>(&'a mut R, &'a mut B);
|
||||
|
||||
impl<'a, R, B> Future for ReadBufFn<'a, R, B>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
B: BufMut,
|
||||
{
|
||||
type Output = io::Result<usize>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = &mut *self;
|
||||
crate::util::poll_read_buf(Pin::new(this.0), cx, this.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::stream::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
const CAPACITY: usize = 4096;
|
||||
|
||||
pin_project! {
|
||||
/// Convert an [`AsyncRead`] into a [`Stream`] of byte chunks.
|
||||
///
|
||||
/// This stream is fused. It performs the inverse operation of
|
||||
/// [`StreamReader`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// use tokio::stream::StreamExt;
|
||||
/// use tokio_util::io::ReaderStream;
|
||||
///
|
||||
/// // Create a stream of data.
|
||||
/// let data = b"hello, world!";
|
||||
/// let mut stream = ReaderStream::new(&data[..]);
|
||||
///
|
||||
/// // Read all of the chunks into a vector.
|
||||
/// let mut stream_contents = Vec::new();
|
||||
/// while let Some(chunk) = stream.next().await {
|
||||
/// stream_contents.extend_from_slice(&chunk?);
|
||||
/// }
|
||||
///
|
||||
/// // Once the chunks are concatenated, we should have the
|
||||
/// // original data.
|
||||
/// assert_eq!(stream_contents, data);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`StreamReader`]: crate::io::StreamReader
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
#[derive(Debug)]
|
||||
pub struct ReaderStream<R> {
|
||||
// Reader itself.
|
||||
//
|
||||
// This value is `None` if the stream has terminated.
|
||||
#[pin]
|
||||
reader: Option<R>,
|
||||
// Working buffer, used to optimize allocations.
|
||||
buf: BytesMut,
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncRead> ReaderStream<R> {
|
||||
/// Convert an [`AsyncRead`] into a [`Stream`] with item type
|
||||
/// `Result<Bytes, std::io::Error>`.
|
||||
///
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
pub fn new(reader: R) -> Self {
|
||||
ReaderStream {
|
||||
reader: Some(reader),
|
||||
buf: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncRead> Stream for ReaderStream<R> {
|
||||
type Item = std::io::Result<Bytes>;
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
use crate::util::poll_read_buf;
|
||||
|
||||
let mut this = self.as_mut().project();
|
||||
|
||||
let reader = match this.reader.as_pin_mut() {
|
||||
Some(r) => r,
|
||||
None => return Poll::Ready(None),
|
||||
};
|
||||
|
||||
if this.buf.capacity() == 0 {
|
||||
this.buf.reserve(CAPACITY);
|
||||
}
|
||||
|
||||
match poll_read_buf(reader, cx, &mut this.buf) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Err(err)) => {
|
||||
self.project().reader.set(None);
|
||||
Poll::Ready(Some(Err(err)))
|
||||
}
|
||||
Poll::Ready(Ok(0)) => {
|
||||
self.project().reader.set(None);
|
||||
Poll::Ready(None)
|
||||
}
|
||||
Poll::Ready(Ok(_)) => {
|
||||
let chunk = this.buf.split();
|
||||
Poll::Ready(Some(Ok(chunk.freeze())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
use bytes::Buf;
|
||||
use futures_core::stream::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncBufRead, AsyncRead, ReadBuf};
|
||||
|
||||
pin_project! {
|
||||
/// Convert a [`Stream`] of byte chunks into an [`AsyncRead`].
|
||||
///
|
||||
/// This type performs the inverse operation of [`ReaderStream`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::Bytes;
|
||||
/// use tokio::io::{AsyncReadExt, Result};
|
||||
/// use tokio_util::io::StreamReader;
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a stream from an iterator.
|
||||
/// let stream = tokio::stream::iter(vec![
|
||||
/// Result::Ok(Bytes::from_static(&[0, 1, 2, 3])),
|
||||
/// Result::Ok(Bytes::from_static(&[4, 5, 6, 7])),
|
||||
/// Result::Ok(Bytes::from_static(&[8, 9, 10, 11])),
|
||||
/// ]);
|
||||
///
|
||||
/// // Convert it to an AsyncRead.
|
||||
/// let mut read = StreamReader::new(stream);
|
||||
///
|
||||
/// // Read five bytes from the stream.
|
||||
/// let mut buf = [0; 5];
|
||||
/// read.read_exact(&mut buf).await?;
|
||||
/// assert_eq!(buf, [0, 1, 2, 3, 4]);
|
||||
///
|
||||
/// // Read the rest of the current chunk.
|
||||
/// assert_eq!(read.read(&mut buf).await?, 3);
|
||||
/// assert_eq!(&buf[..3], [5, 6, 7]);
|
||||
///
|
||||
/// // Read the next chunk.
|
||||
/// assert_eq!(read.read(&mut buf).await?, 4);
|
||||
/// assert_eq!(&buf[..4], [8, 9, 10, 11]);
|
||||
///
|
||||
/// // We have now reached the end.
|
||||
/// assert_eq!(read.read(&mut buf).await?, 0);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`ReaderStream`]: crate::io::ReaderStream
|
||||
#[derive(Debug)]
|
||||
pub struct StreamReader<S, B> {
|
||||
#[pin]
|
||||
inner: S,
|
||||
chunk: Option<B>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, E> StreamReader<S, B>
|
||||
where
|
||||
S: Stream<Item = Result<B, E>>,
|
||||
B: Buf,
|
||||
E: Into<std::io::Error>,
|
||||
{
|
||||
/// Convert a stream of byte chunks into an [`AsyncRead`](tokio::io::AsyncRead).
|
||||
///
|
||||
/// The item should be a [`Result`] with the ok variant being something that
|
||||
/// implements the [`Buf`] trait (e.g. `Vec<u8>` or `Bytes`). The error
|
||||
/// should be convertible into an [io error].
|
||||
///
|
||||
/// [`Result`]: std::result::Result
|
||||
/// [`Buf`]: bytes::Buf
|
||||
/// [io error]: std::io::Error
|
||||
pub fn new(stream: S) -> Self {
|
||||
Self {
|
||||
inner: stream,
|
||||
chunk: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do we have a chunk and is it non-empty?
|
||||
fn has_chunk(self: Pin<&mut Self>) -> bool {
|
||||
if let Some(chunk) = self.project().chunk {
|
||||
chunk.remaining() > 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, E> AsyncRead for StreamReader<S, B>
|
||||
where
|
||||
S: Stream<Item = Result<B, E>>,
|
||||
B: Buf,
|
||||
E: Into<std::io::Error>,
|
||||
{
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
if buf.remaining() == 0 {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
let inner_buf = match self.as_mut().poll_fill_buf(cx) {
|
||||
Poll::Ready(Ok(buf)) => buf,
|
||||
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
|
||||
Poll::Pending => return Poll::Pending,
|
||||
};
|
||||
let len = std::cmp::min(inner_buf.len(), buf.remaining());
|
||||
buf.put_slice(&inner_buf[..len]);
|
||||
|
||||
self.consume(len);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, E> AsyncBufRead for StreamReader<S, B>
|
||||
where
|
||||
S: Stream<Item = Result<B, E>>,
|
||||
B: Buf,
|
||||
E: Into<std::io::Error>,
|
||||
{
|
||||
fn poll_fill_buf(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
loop {
|
||||
if self.as_mut().has_chunk() {
|
||||
// This unwrap is very sad, but it can't be avoided.
|
||||
let buf = self.project().chunk.as_ref().unwrap().bytes();
|
||||
return Poll::Ready(Ok(buf));
|
||||
} else {
|
||||
match self.as_mut().project().inner.poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(chunk))) => {
|
||||
// Go around the loop in case the chunk is empty.
|
||||
*self.as_mut().project().chunk = Some(chunk);
|
||||
}
|
||||
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
|
||||
Poll::Ready(None) => return Poll::Ready(Ok(&[])),
|
||||
Poll::Pending => return Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
if amt > 0 {
|
||||
self.project()
|
||||
.chunk
|
||||
.as_mut()
|
||||
.expect("No chunk present")
|
||||
.advance(amt);
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
-2
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-util/0.3.1")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-util/0.5.1")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -24,14 +24,177 @@
|
||||
#[macro_use]
|
||||
mod cfg;
|
||||
|
||||
mod loom;
|
||||
|
||||
cfg_codec! {
|
||||
pub mod codec;
|
||||
}
|
||||
|
||||
cfg_udp! {
|
||||
cfg_net! {
|
||||
pub mod udp;
|
||||
}
|
||||
|
||||
cfg_compat! {
|
||||
pub mod compat;
|
||||
}
|
||||
|
||||
cfg_io! {
|
||||
pub mod io;
|
||||
}
|
||||
|
||||
cfg_rt! {
|
||||
pub mod context;
|
||||
}
|
||||
|
||||
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};
|
||||
|
||||
use bytes::{Buf, BufMut};
|
||||
use futures_core::ready;
|
||||
use std::io::{self, IoSlice};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Try to read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
|
||||
///
|
||||
/// [`BufMut`]: bytes::Buf
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::{Bytes, BytesMut};
|
||||
/// use tokio::stream;
|
||||
/// use tokio::io::Result;
|
||||
/// use tokio_util::io::{StreamReader, poll_read_buf};
|
||||
/// use futures::future::poll_fn;
|
||||
/// use std::pin::Pin;
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a reader from an iterator. This particular reader will always be
|
||||
/// // ready.
|
||||
/// let mut read = StreamReader::new(stream::iter(vec![Result::Ok(Bytes::from_static(&[0, 1, 2, 3]))]));
|
||||
///
|
||||
/// let mut buf = BytesMut::new();
|
||||
/// let mut reads = 0;
|
||||
///
|
||||
/// loop {
|
||||
/// reads += 1;
|
||||
/// let n = poll_fn(|cx| poll_read_buf(Pin::new(&mut read), cx, &mut buf)).await?;
|
||||
///
|
||||
/// if n == 0 {
|
||||
/// break;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // one or more reads might be necessary.
|
||||
/// assert!(reads >= 1);
|
||||
/// assert_eq!(&buf[..], &[0, 1, 2, 3]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
|
||||
pub fn poll_read_buf<T: AsyncRead, B: BufMut>(
|
||||
io: Pin<&mut T>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if !buf.has_remaining_mut() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let n = {
|
||||
let dst = buf.bytes_mut();
|
||||
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
|
||||
let mut buf = ReadBuf::uninit(dst);
|
||||
let ptr = buf.filled().as_ptr();
|
||||
ready!(io.poll_read(cx, &mut buf)?);
|
||||
|
||||
// Ensure the pointer does not change from under us
|
||||
assert_eq!(ptr, buf.filled().as_ptr());
|
||||
buf.filled().len()
|
||||
};
|
||||
|
||||
// Safety: This is guaranteed to be the number of initialized (and read)
|
||||
// bytes due to the invariants provided by `ReadBuf::filled`.
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
|
||||
/// Try to write data from an implementer of the [`Buf`] trait to an
|
||||
/// [`AsyncWrite`], advancing the buffer's internal cursor.
|
||||
///
|
||||
/// This function will use [vectored writes] when the [`AsyncWrite`] supports
|
||||
/// vectored writes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// [`File`] implements [`AsyncWrite`] and [`Cursor<&[u8]>`] implements
|
||||
/// [`Buf`]:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio_util::io::poll_write_buf;
|
||||
/// use tokio::io;
|
||||
/// use tokio::fs::File;
|
||||
///
|
||||
/// use bytes::Buf;
|
||||
/// use std::io::Cursor;
|
||||
/// use std::pin::Pin;
|
||||
/// use futures::future::poll_fn;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let mut file = File::create("foo.txt").await?;
|
||||
/// let mut buf = Cursor::new(b"data to write");
|
||||
///
|
||||
/// // Loop until the entire contents of the buffer are written to
|
||||
/// // the file.
|
||||
/// while buf.has_remaining() {
|
||||
/// poll_fn(|cx| poll_write_buf(Pin::new(&mut file), cx, &mut buf)).await?;
|
||||
/// }
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Buf`]: bytes::Buf
|
||||
/// [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
/// [`File`]: tokio::fs::File
|
||||
/// [vectored writes]: tokio::io::AsyncWrite::poll_write_vectored
|
||||
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
|
||||
pub fn poll_write_buf<T: AsyncWrite, B: Buf>(
|
||||
io: Pin<&mut T>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
const MAX_BUFS: usize = 64;
|
||||
|
||||
if !buf.has_remaining() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let n = if io.is_write_vectored() {
|
||||
let mut slices = [IoSlice::new(&[]); MAX_BUFS];
|
||||
let cnt = buf.bytes_vectored(&mut slices);
|
||||
ready!(io.poll_write_vectored(cx, &slices[..cnt]))?
|
||||
} else {
|
||||
ready!(io.poll_write(cx, buf.bytes()))?
|
||||
};
|
||||
|
||||
buf.advance(n);
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub(crate) use std::sync;
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::loom::sync::Mutex;
|
||||
use crate::util::intrusive_double_linked_list::{LinkedList, ListNode};
|
||||
use crate::sync::intrusive_double_linked_list::{LinkedList, ListNode};
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
@@ -37,14 +37,14 @@ use core::task::{Context, Poll, Waker};
|
||||
/// // The token was cancelled
|
||||
/// 5
|
||||
/// }
|
||||
/// _ = tokio::time::delay_for(std::time::Duration::from_secs(9999)) => {
|
||||
/// _ = tokio::time::sleep(std::time::Duration::from_secs(9999)) => {
|
||||
/// 99
|
||||
/// }
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tokio::time::delay_for(std::time::Duration::from_millis(10)).await;
|
||||
/// tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
/// token.cancel();
|
||||
/// });
|
||||
///
|
||||
@@ -129,6 +129,12 @@ impl Drop for CancellationToken {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CancellationToken {
|
||||
fn default() -> CancellationToken {
|
||||
CancellationToken::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CancellationToken {
|
||||
/// Creates a new CancellationToken in the non-cancelled state.
|
||||
pub fn new() -> CancellationToken {
|
||||
@@ -179,14 +185,14 @@ impl CancellationToken {
|
||||
/// // The token was cancelled
|
||||
/// 5
|
||||
/// }
|
||||
/// _ = tokio::time::delay_for(std::time::Duration::from_secs(9999)) => {
|
||||
/// _ = tokio::time::sleep(std::time::Duration::from_secs(9999)) => {
|
||||
/// 99
|
||||
/// }
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tokio::time::delay_for(std::time::Duration::from_millis(10)).await;
|
||||
/// tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
/// token.cancel();
|
||||
/// });
|
||||
///
|
||||
@@ -618,7 +624,7 @@ impl CancellationTokenState {
|
||||
|
||||
if removed_child {
|
||||
// If the token removed itself from the parents list, it can reset
|
||||
// the the parent ref status. If it is isn't able to do so, because the
|
||||
// the parent ref status. If it is isn't able to do so, because the
|
||||
// parent removed it from the list, there is no need to do this.
|
||||
// The parent ref acts as as another reference count. Therefore
|
||||
// removing this reference can free the object.
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Synchronization primitives
|
||||
|
||||
mod cancellation_token;
|
||||
pub use cancellation_token::{CancellationToken, WaitForCancellationFuture};
|
||||
|
||||
mod intrusive_double_linked_list;
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -5,14 +5,16 @@
|
||||
//! [`DelayQueue`]: struct@DelayQueue
|
||||
|
||||
use crate::time::wheel::{self, Wheel};
|
||||
use crate::time::{delay_until, Delay, Duration, Error, Instant};
|
||||
|
||||
use futures_core::ready;
|
||||
use tokio::time::{error::Error, sleep_until, Duration, Instant, Sleep};
|
||||
|
||||
use slab::Slab;
|
||||
use std::cmp;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::task::{self, Poll};
|
||||
use std::task::{self, Poll, Waker};
|
||||
|
||||
/// A queue of delayed elements.
|
||||
///
|
||||
@@ -50,8 +52,8 @@ use std::task::{self, Poll};
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// The [`DelayQueue`] is backed by a separate instance of the same timer wheel used internally by
|
||||
/// Tokio's standalone timer utilities such as [`delay_for`]. Because of this, it offers the same
|
||||
/// The [`DelayQueue`] is backed by a separate instance of a timer wheel similar to that used internally
|
||||
/// by Tokio's standalone timer utilities such as [`sleep`]. Because of this, it offers the same
|
||||
/// performance and scalability benefits.
|
||||
///
|
||||
/// State associated with each entry is stored in a [`slab`]. This amortizes the cost of allocation,
|
||||
@@ -65,7 +67,8 @@ use std::task::{self, Poll};
|
||||
/// Using `DelayQueue` to manage cache entries.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use tokio::time::{delay_queue, DelayQueue, Error};
|
||||
/// use tokio::time::error::Error;
|
||||
/// use tokio_util::time::{DelayQueue, delay_queue};
|
||||
///
|
||||
/// use futures::ready;
|
||||
/// use std::collections::HashMap;
|
||||
@@ -118,7 +121,7 @@ use std::task::{self, Poll};
|
||||
/// [`poll_expired`]: method@Self::poll_expired
|
||||
/// [`Stream::poll_expired`]: method@Self::poll_expired
|
||||
/// [`DelayQueue`]: struct@DelayQueue
|
||||
/// [`delay_for`]: fn@super::delay_for
|
||||
/// [`sleep`]: fn@tokio::time::sleep
|
||||
/// [`slab`]: slab
|
||||
/// [`capacity`]: method@Self::capacity
|
||||
/// [`reserve`]: method@Self::reserve
|
||||
@@ -135,13 +138,18 @@ pub struct DelayQueue<T> {
|
||||
expired: Stack<T>,
|
||||
|
||||
/// Delay expiring when the *first* item in the queue expires
|
||||
delay: Option<Delay>,
|
||||
delay: Option<Sleep>,
|
||||
|
||||
/// Wheel polling state
|
||||
poll: wheel::Poll,
|
||||
wheel_now: u64,
|
||||
|
||||
/// Instant at which the timer starts
|
||||
start: Instant,
|
||||
|
||||
/// Waker that is invoked when we potentially need to reset the timer.
|
||||
/// Because we lazily create the timer when the first entry is created, we
|
||||
/// need to awaken any poller that polled us before that point.
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
/// An entry in `DelayQueue` that has expired and removed.
|
||||
@@ -210,7 +218,7 @@ impl<T> DelayQueue<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use tokio::time::DelayQueue;
|
||||
/// # use tokio_util::time::DelayQueue;
|
||||
/// let delay_queue: DelayQueue<u32> = DelayQueue::new();
|
||||
/// ```
|
||||
pub fn new() -> DelayQueue<T> {
|
||||
@@ -226,7 +234,7 @@ impl<T> DelayQueue<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use tokio::time::DelayQueue;
|
||||
/// # use tokio_util::time::DelayQueue;
|
||||
/// # use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
@@ -248,8 +256,9 @@ impl<T> DelayQueue<T> {
|
||||
slab: Slab::with_capacity(capacity),
|
||||
expired: Stack::default(),
|
||||
delay: None,
|
||||
poll: wheel::Poll::new(0),
|
||||
wheel_now: 0,
|
||||
start: Instant::now(),
|
||||
waker: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +290,8 @@ impl<T> DelayQueue<T> {
|
||||
/// Basic usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::{DelayQueue, Duration, Instant};
|
||||
/// use tokio::time::{Duration, Instant};
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
@@ -326,11 +336,15 @@ impl<T> DelayQueue<T> {
|
||||
};
|
||||
|
||||
if should_set_delay {
|
||||
if let Some(waker) = self.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
let delay_time = self.start + Duration::from_millis(when);
|
||||
if let Some(ref mut delay) = &mut self.delay {
|
||||
delay.reset(delay_time);
|
||||
} else {
|
||||
self.delay = Some(delay_until(delay_time));
|
||||
self.delay = Some(sleep_until(delay_time));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +358,15 @@ impl<T> DelayQueue<T> {
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Result<Expired<T>, Error>>> {
|
||||
if !self
|
||||
.waker
|
||||
.as_ref()
|
||||
.map(|w| w.will_wake(cx.waker()))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.waker = Some(cx.waker().clone());
|
||||
}
|
||||
|
||||
let item = ready!(self.poll_idx(cx));
|
||||
Poll::Ready(item.map(|result| {
|
||||
result.map(|idx| {
|
||||
@@ -366,30 +389,32 @@ impl<T> DelayQueue<T> {
|
||||
/// This function is identical to `insert_at`, but takes a `Duration`
|
||||
/// instead of an `Instant`.
|
||||
///
|
||||
/// `value` is stored in the queue until `when` is reached. At which point,
|
||||
/// `value` will be returned from [`poll_expired`]. If `when` has already been
|
||||
/// reached, then `value` is immediately made available to poll.
|
||||
/// `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
|
||||
/// zero, 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
|
||||
/// `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
|
||||
/// a different item in the queue.
|
||||
/// 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 `timeout` has elapsed
|
||||
/// or by calling [`remove`]. At this point, the caller must not
|
||||
/// use the returned [`Key`] again as it may reference a different
|
||||
/// item in the queue.
|
||||
///
|
||||
/// See [type] level documentation for more details.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `timeout` is greater than the maximum supported
|
||||
/// duration.
|
||||
/// This function panics if `timeout` is greater than the maximum
|
||||
/// duration supported by the timer in the current `Runtime`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::DelayQueue;
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
@@ -458,7 +483,7 @@ impl<T> DelayQueue<T> {
|
||||
/// Basic usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::DelayQueue;
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
@@ -501,7 +526,8 @@ impl<T> DelayQueue<T> {
|
||||
/// Basic usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::{DelayQueue, Duration, Instant};
|
||||
/// use tokio::time::{Duration, Instant};
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
@@ -526,6 +552,7 @@ impl<T> DelayQueue<T> {
|
||||
|
||||
let next_deadline = self.next_deadline();
|
||||
if let (Some(ref mut delay), Some(deadline)) = (&mut self.delay, next_deadline) {
|
||||
// This should awaken us if necessary (ie, if already expired)
|
||||
delay.reset(deadline);
|
||||
}
|
||||
}
|
||||
@@ -557,7 +584,7 @@ impl<T> DelayQueue<T> {
|
||||
/// Basic usage
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::DelayQueue;
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
@@ -587,7 +614,7 @@ impl<T> DelayQueue<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::DelayQueue;
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
@@ -615,7 +642,7 @@ impl<T> DelayQueue<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::DelayQueue;
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
///
|
||||
/// let delay_queue: DelayQueue<i32> = DelayQueue::with_capacity(10);
|
||||
/// assert_eq!(delay_queue.capacity(), 10);
|
||||
@@ -629,7 +656,7 @@ impl<T> DelayQueue<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio::time::DelayQueue;
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
@@ -664,7 +691,7 @@ impl<T> DelayQueue<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::time::DelayQueue;
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
@@ -689,7 +716,7 @@ impl<T> DelayQueue<T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::time::DelayQueue;
|
||||
/// use tokio_util::time::DelayQueue;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
@@ -726,13 +753,13 @@ impl<T> DelayQueue<T> {
|
||||
|
||||
let now = crate::time::ms(delay.deadline() - self.start, crate::time::Round::Down);
|
||||
|
||||
self.poll = wheel::Poll::new(now);
|
||||
self.wheel_now = now;
|
||||
}
|
||||
|
||||
// We poll the wheel to get the next value out before finding the next deadline.
|
||||
let wheel_idx = self.wheel.poll(&mut self.poll, &mut self.slab);
|
||||
let wheel_idx = self.wheel.poll(self.wheel_now, &mut self.slab);
|
||||
|
||||
self.delay = self.next_deadline().map(delay_until);
|
||||
self.delay = self.next_deadline().map(sleep_until);
|
||||
|
||||
if let Some(idx) = wheel_idx {
|
||||
return Poll::Ready(Some(Ok(idx)));
|
||||
@@ -764,7 +791,6 @@ impl<T> Default for DelayQueue<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stream")]
|
||||
impl<T> futures_core::Stream for DelayQueue<T> {
|
||||
// DelayQueue seems much more specific, where a user may care that it
|
||||
// has reached capacity, so return those errors instead of panicking.
|
||||
@@ -0,0 +1,47 @@
|
||||
//! Additional utilities for tracking time.
|
||||
//!
|
||||
//! This module provides additional utilities for executing code after a set period
|
||||
//! of time. Currently there is only one:
|
||||
//!
|
||||
//! * `DelayQueue`: A queue where items are returned once the requested delay
|
||||
//! has expired.
|
||||
//!
|
||||
//! This type must be used from within the context of the `Runtime`.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
mod wheel;
|
||||
|
||||
#[doc(inline)]
|
||||
pub mod delay_queue;
|
||||
|
||||
pub use delay_queue::DelayQueue;
|
||||
|
||||
// ===== Internal utils =====
|
||||
|
||||
enum Round {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
/// Convert a `Duration` to milliseconds, rounding up and saturating at
|
||||
/// `u64::MAX`.
|
||||
///
|
||||
/// The saturating is fine because `u64::MAX` milliseconds are still many
|
||||
/// million years.
|
||||
#[inline]
|
||||
fn ms(duration: Duration, round: Round) -> u64 {
|
||||
const NANOS_PER_MILLI: u32 = 1_000_000;
|
||||
const MILLIS_PER_SEC: u64 = 1_000;
|
||||
|
||||
// Round up.
|
||||
let millis = match round {
|
||||
Round::Up => (duration.subsec_nanos() + NANOS_PER_MILLI - 1) / NANOS_PER_MILLI,
|
||||
Round::Down => duration.subsec_millis(),
|
||||
};
|
||||
|
||||
duration
|
||||
.as_secs()
|
||||
.saturating_mul(MILLIS_PER_SEC)
|
||||
.saturating_add(u64::from(millis))
|
||||
}
|
||||
@@ -51,13 +51,6 @@ pub(crate) enum InsertError {
|
||||
Invalid,
|
||||
}
|
||||
|
||||
/// Poll expirations from the wheel
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Poll {
|
||||
now: u64,
|
||||
expiration: Option<Expiration>,
|
||||
}
|
||||
|
||||
impl<T> Wheel<T>
|
||||
where
|
||||
T: Stack,
|
||||
@@ -136,19 +129,18 @@ where
|
||||
self.next_expiration().map(|expiration| expiration.deadline)
|
||||
}
|
||||
|
||||
pub(crate) fn poll(&mut self, poll: &mut Poll, store: &mut T::Store) -> Option<T::Owned> {
|
||||
/// Advances the timer up to the instant represented by `now`.
|
||||
pub(crate) fn poll(&mut self, now: u64, store: &mut T::Store) -> Option<T::Owned> {
|
||||
loop {
|
||||
if poll.expiration.is_none() {
|
||||
poll.expiration = self.next_expiration().and_then(|expiration| {
|
||||
if expiration.deadline > poll.now {
|
||||
None
|
||||
} else {
|
||||
Some(expiration)
|
||||
}
|
||||
});
|
||||
}
|
||||
let expiration = self.next_expiration().and_then(|expiration| {
|
||||
if expiration.deadline > now {
|
||||
None
|
||||
} else {
|
||||
Some(expiration)
|
||||
}
|
||||
});
|
||||
|
||||
match poll.expiration {
|
||||
match expiration {
|
||||
Some(ref expiration) => {
|
||||
if let Some(item) = self.poll_expiration(expiration, store) {
|
||||
return Some(item);
|
||||
@@ -157,12 +149,14 @@ where
|
||||
self.set_elapsed(expiration.deadline);
|
||||
}
|
||||
None => {
|
||||
self.set_elapsed(poll.now);
|
||||
// in this case the poll did not indicate an expiration
|
||||
// _and_ we were not able to find a next expiration in
|
||||
// the current list of timers. advance to the poll's
|
||||
// current time and do nothing else.
|
||||
self.set_elapsed(now);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
poll.expiration = None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +191,10 @@ where
|
||||
res
|
||||
}
|
||||
|
||||
/// iteratively find entries that are between the wheel's current
|
||||
/// time and the expiration time. for each in that population either
|
||||
/// return it for notification (in the case of the last level) or tier
|
||||
/// it down to the next level (in all other cases).
|
||||
pub(crate) fn poll_expiration(
|
||||
&mut self,
|
||||
expiration: &Expiration,
|
||||
@@ -251,15 +249,6 @@ fn level_for(elapsed: u64, when: u64) -> usize {
|
||||
significant / 6
|
||||
}
|
||||
|
||||
impl Poll {
|
||||
pub(crate) fn new(now: u64) -> Poll {
|
||||
Poll {
|
||||
now,
|
||||
expiration: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(loom)))]
|
||||
mod test {
|
||||
use super::*;
|
||||
+49
-17
@@ -1,17 +1,16 @@
|
||||
use crate::codec::{Decoder, Encoder};
|
||||
|
||||
use tokio::{net::UdpSocket, stream::Stream};
|
||||
use tokio::{io::ReadBuf, net::UdpSocket, stream::Stream};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use std::io;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{io, mem::MaybeUninit};
|
||||
|
||||
/// A unified `Stream` and `Sink` interface to an underlying `UdpSocket`, using
|
||||
/// A unified [`Stream`] and [`Sink`] interface to an underlying `UdpSocket`, using
|
||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
||||
///
|
||||
/// Raw UDP sockets work with datagrams, but higher-level code usually wants to
|
||||
@@ -20,13 +19,17 @@ use std::task::{Context, Poll};
|
||||
/// handle encoding and decoding of messages frames. Note that the incoming and
|
||||
/// outgoing frame types may be distinct.
|
||||
///
|
||||
/// This function returns a *single* object that is both `Stream` and `Sink`;
|
||||
/// This function returns a *single* object that is both [`Stream`] and [`Sink`];
|
||||
/// grouping this into a single object is often useful for layering things which
|
||||
/// require both read and write access to the underlying object.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
/// calling `split` on the `UdpFramed` returned by this method, which will break
|
||||
/// calling [`split`] on the `UdpFramed` returned by this method, which will break
|
||||
/// them into separate objects, allowing them to interact more easily.
|
||||
///
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`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")))]
|
||||
#[derive(Debug)]
|
||||
@@ -41,6 +44,9 @@ pub struct UdpFramed<C> {
|
||||
current_addr: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
const INITIAL_RD_CAPACITY: usize = 64 * 1024;
|
||||
const INITIAL_WR_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
type Item = Result<(C::Item, SocketAddr), C::Error>;
|
||||
|
||||
@@ -50,7 +56,7 @@ impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
pin.rd.reserve(INITIAL_RD_CAPACITY);
|
||||
|
||||
loop {
|
||||
// Are there are still bytes left in the read buffer to decode?
|
||||
// Are there still bytes left in the read buffer to decode?
|
||||
if pin.is_readable {
|
||||
if let Some(frame) = pin.codec.decode_eof(&mut pin.rd)? {
|
||||
let current_addr = pin
|
||||
@@ -69,13 +75,14 @@ impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
let addr = unsafe {
|
||||
// Convert `&mut [MaybeUnit<u8>]` to `&mut [u8]` because we will be
|
||||
// writing to it via `poll_recv_from` and therefore initializing the memory.
|
||||
let buf: &mut [u8] =
|
||||
&mut *(pin.rd.bytes_mut() as *mut [MaybeUninit<u8>] as *mut [u8]);
|
||||
let buf = &mut *(pin.rd.bytes_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::new(&mut pin.socket).poll_recv_from(cx, buf));
|
||||
|
||||
let (n, addr) = res?;
|
||||
pin.rd.advance_mut(n);
|
||||
assert_eq!(ptr, read.filled().as_ptr());
|
||||
let addr = res?;
|
||||
pin.rd.advance_mut(read.filled().len());
|
||||
addr
|
||||
};
|
||||
|
||||
@@ -148,15 +155,12 @@ impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
|
||||
}
|
||||
}
|
||||
|
||||
const INITIAL_RD_CAPACITY: usize = 64 * 1024;
|
||||
const INITIAL_WR_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
impl<C> UdpFramed<C> {
|
||||
/// 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> {
|
||||
UdpFramed {
|
||||
Self {
|
||||
socket,
|
||||
codec,
|
||||
out_addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0)),
|
||||
@@ -195,4 +199,32 @@ impl<C> UdpFramed<C> {
|
||||
pub fn into_inner(self) -> UdpSocket {
|
||||
self.socket
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying codec wrapped by
|
||||
/// `Framed`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec(&self) -> &C {
|
||||
&self.codec
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying codec wrapped by
|
||||
/// `UdpFramed`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec_mut(&mut self) -> &mut C {
|
||||
&mut self.codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the read buffer.
|
||||
pub fn read_buffer(&self) -> &BytesMut {
|
||||
&self.rd
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the read buffer.
|
||||
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.rd
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! UDP framing
|
||||
|
||||
mod frame;
|
||||
pub use self::frame::UdpFramed;
|
||||
pub use frame::UdpFramed;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#![cfg(feature = "rt")]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::time::*;
|
||||
use tokio_util::context::RuntimeExt;
|
||||
|
||||
#[test]
|
||||
fn tokio_context_with_another_runtime() {
|
||||
let rt1 = Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
// no timer!
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt2 = Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Without the `HandleExt.wrap()` there would be a panic because there is
|
||||
// no timer running, since it would be referencing runtime r1.
|
||||
let _ = rt1.block_on(rt2.wrap(async move { sleep(Duration::from_millis(2)).await }));
|
||||
}
|
||||
@@ -55,8 +55,8 @@ impl AsyncRead for DontReadIntoThis {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
_buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
_buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
use tokio_test::assert_ready;
|
||||
use tokio_test::task;
|
||||
use tokio_util::codec::{Decoder, FramedRead};
|
||||
@@ -185,8 +185,8 @@ fn read_partial_would_block_then_err() {
|
||||
#[test]
|
||||
fn huge_size() {
|
||||
let mut task = task::spawn(());
|
||||
let data = [0; 32 * 1024];
|
||||
let mut framed = FramedRead::new(Slice(&data[..]), BigDecoder);
|
||||
let data = &[0; 32 * 1024][..];
|
||||
let mut framed = FramedRead::new(data, BigDecoder);
|
||||
|
||||
task.enter(|cx, _| {
|
||||
assert_read!(pin!(framed).poll_next(cx), 0);
|
||||
@@ -212,7 +212,7 @@ fn huge_size() {
|
||||
#[test]
|
||||
fn data_remaining_is_error() {
|
||||
let mut task = task::spawn(());
|
||||
let slice = Slice(&[0; 5]);
|
||||
let slice = &[0; 5][..];
|
||||
let mut framed = FramedRead::new(slice, U32Decoder);
|
||||
|
||||
task.enter(|cx, _| {
|
||||
@@ -264,32 +264,19 @@ impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
use io::ErrorKind::WouldBlock;
|
||||
|
||||
match self.calls.pop_front() {
|
||||
Some(Ok(data)) => {
|
||||
debug_assert!(buf.len() >= data.len());
|
||||
buf[..data.len()].copy_from_slice(&data[..]);
|
||||
Ready(Ok(data.len()))
|
||||
debug_assert!(buf.remaining() >= data.len());
|
||||
buf.put_slice(&data);
|
||||
Ready(Ok(()))
|
||||
}
|
||||
Some(Err(ref e)) if e.kind() == WouldBlock => Pending,
|
||||
Some(Err(e)) => Ready(Err(e)),
|
||||
None => Ready(Ok(0)),
|
||||
None => Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this newtype is necessary because `&[u8]` does not currently implement `AsyncRead`
|
||||
struct Slice<'a>(&'a [u8]);
|
||||
|
||||
impl AsyncRead for Slice<'_> {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(&mut self.0).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
use tokio::stream::StreamExt;
|
||||
|
||||
/// produces at most `remaining` zeros, that returns error.
|
||||
@@ -16,18 +15,19 @@ impl AsyncRead for Reader {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
let this = Pin::into_inner(self);
|
||||
assert_ne!(buf.len(), 0);
|
||||
assert_ne!(buf.remaining(), 0);
|
||||
if this.remaining > 0 {
|
||||
let n = std::cmp::min(this.remaining, buf.len());
|
||||
let n = std::cmp::min(this.remaining, buf.remaining());
|
||||
let n = std::cmp::min(n, 31);
|
||||
for x in &mut buf[..n] {
|
||||
for x in &mut buf.initialize_unfilled_to(n)[..n] {
|
||||
*x = 0;
|
||||
}
|
||||
buf.advance(n);
|
||||
this.remaining -= n;
|
||||
Poll::Ready(Ok(n))
|
||||
Poll::Ready(Ok(()))
|
||||
} else {
|
||||
Poll::Ready(Err(std::io::Error::from_raw_os_error(22)))
|
||||
}
|
||||
@@ -37,11 +37,12 @@ impl AsyncRead for Reader {
|
||||
#[tokio::test]
|
||||
async fn correct_behavior_on_errors() {
|
||||
let reader = Reader { remaining: 8000 };
|
||||
let mut stream = tokio::io::reader_stream(reader);
|
||||
let mut stream = tokio_util::io::ReaderStream::new(reader);
|
||||
let mut zeros_received = 0;
|
||||
let mut had_error = false;
|
||||
loop {
|
||||
let item = stream.next().await.unwrap();
|
||||
println!("{:?}", item);
|
||||
match item {
|
||||
Ok(bytes) => {
|
||||
let bytes = &*bytes;
|
||||
@@ -1,14 +1,14 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use bytes::Bytes;
|
||||
use tokio::io::{stream_reader, AsyncReadExt};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::stream::iter;
|
||||
use tokio_util::io::StreamReader;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_stream_reader() -> std::io::Result<()> {
|
||||
let stream = iter(vec![
|
||||
Ok(Bytes::from_static(&[])),
|
||||
std::io::Result::Ok(Bytes::from_static(&[])),
|
||||
Ok(Bytes::from_static(&[0, 1, 2, 3])),
|
||||
Ok(Bytes::from_static(&[])),
|
||||
Ok(Bytes::from_static(&[4, 5, 6, 7])),
|
||||
@@ -17,7 +17,7 @@ async fn test_stream_reader() -> std::io::Result<()> {
|
||||
Ok(Bytes::from_static(&[])),
|
||||
]);
|
||||
|
||||
let mut read = stream_reader(stream);
|
||||
let mut read = StreamReader::new(stream);
|
||||
|
||||
let mut buf = [0; 5];
|
||||
read.read_exact(&mut buf).await?;
|
||||
@@ -1,6 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio_test::task;
|
||||
use tokio_test::{
|
||||
assert_err, assert_ok, assert_pending, assert_ready, assert_ready_err, assert_ready_ok,
|
||||
@@ -707,18 +707,18 @@ impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
dst: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
dst: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ready(Ok(Op::Data(data)))) => {
|
||||
debug_assert!(dst.len() >= data.len());
|
||||
dst[..data.len()].copy_from_slice(&data[..]);
|
||||
Ready(Ok(data.len()))
|
||||
debug_assert!(dst.remaining() >= data.len());
|
||||
dst.put_slice(&data);
|
||||
Ready(Ok(()))
|
||||
}
|
||||
Some(Ready(Ok(_))) => panic!(),
|
||||
Some(Ready(Err(e))) => Ready(Err(e)),
|
||||
Some(Pending) => Pending,
|
||||
None => Ready(Ok(0)),
|
||||
None => Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
#![cfg(tokio_unstable)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::pin;
|
||||
use tokio::sync::CancellationToken;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use core::future::Future;
|
||||
use core::task::{Context, Poll};
|
||||
@@ -186,8 +186,8 @@ fn drop_multiple_child_tokens() {
|
||||
for drop_first_child_first in &[true, false] {
|
||||
let token = CancellationToken::new();
|
||||
let mut child_tokens = [None, None, None];
|
||||
for i in 0..child_tokens.len() {
|
||||
child_tokens[i] = Some(token.child_token());
|
||||
for child in &mut child_tokens {
|
||||
*child = Some(token.child_token());
|
||||
}
|
||||
|
||||
assert!(!token.is_cancelled());
|
||||
@@ -2,8 +2,9 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use tokio::time::{self, delay_for, DelayQueue, Duration, Instant};
|
||||
use tokio::time::{self, sleep, sleep_until, Duration, Instant};
|
||||
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
|
||||
use tokio_util::time::DelayQueue;
|
||||
|
||||
macro_rules! poll {
|
||||
($queue:ident) => {
|
||||
@@ -28,7 +29,7 @@ async fn single_immediate_delay() {
|
||||
let _key = queue.insert_at("foo", Instant::now());
|
||||
|
||||
// Advance time by 1ms to handle thee rounding
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
|
||||
assert_ready_ok!(poll!(queue));
|
||||
|
||||
@@ -46,7 +47,7 @@ async fn multi_immediate_delays() {
|
||||
let _k = queue.insert_at("2", Instant::now());
|
||||
let _k = queue.insert_at("3", Instant::now());
|
||||
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
|
||||
let mut res = vec![];
|
||||
|
||||
@@ -58,7 +59,7 @@ async fn multi_immediate_delays() {
|
||||
let entry = assert_ready!(poll!(queue));
|
||||
assert!(entry.is_none());
|
||||
|
||||
res.sort();
|
||||
res.sort_unstable();
|
||||
|
||||
assert_eq!("1", res[0]);
|
||||
assert_eq!("2", res[1]);
|
||||
@@ -74,11 +75,11 @@ async fn single_short_delay() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
delay_for(ms(5)).await;
|
||||
sleep(ms(5)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
|
||||
@@ -106,9 +107,10 @@ async fn multi_delay_at_start() {
|
||||
assert_pending!(poll!(queue));
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
let start = Instant::now();
|
||||
for elapsed in 0..1200 {
|
||||
delay_for(ms(1)).await;
|
||||
let elapsed = elapsed + 1;
|
||||
tokio::time::sleep_until(start + ms(elapsed)).await;
|
||||
|
||||
if delays.contains(&elapsed) {
|
||||
assert!(queue.is_woken());
|
||||
@@ -116,7 +118,12 @@ async fn multi_delay_at_start() {
|
||||
assert_pending!(poll!(queue));
|
||||
} else if queue.is_woken() {
|
||||
let cascade = &[192, 960];
|
||||
assert!(cascade.contains(&elapsed), "elapsed={}", elapsed);
|
||||
assert!(
|
||||
cascade.contains(&elapsed),
|
||||
"elapsed={} dt={:?}",
|
||||
elapsed,
|
||||
Instant::now() - start
|
||||
);
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
}
|
||||
@@ -130,7 +137,7 @@ async fn insert_in_past_fires_immediately() {
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
let now = Instant::now();
|
||||
|
||||
delay_for(ms(10)).await;
|
||||
sleep(ms(10)).await;
|
||||
|
||||
queue.insert_at("foo", now);
|
||||
|
||||
@@ -150,7 +157,7 @@ async fn remove_entry() {
|
||||
let entry = queue.remove(&key);
|
||||
assert_eq!(entry.into_inner(), "foo");
|
||||
|
||||
delay_for(ms(10)).await;
|
||||
sleep(ms(10)).await;
|
||||
|
||||
let entry = assert_ready!(poll!(queue));
|
||||
assert!(entry.is_none());
|
||||
@@ -166,19 +173,19 @@ async fn reset_entry() {
|
||||
let key = queue.insert_at("foo", now + ms(5));
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
|
||||
queue.reset_at(&key, now + ms(10));
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(7)).await;
|
||||
sleep(ms(7)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(3)).await;
|
||||
sleep(ms(3)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
|
||||
@@ -197,16 +204,16 @@ async fn reset_much_later() {
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
|
||||
let now = Instant::now();
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
|
||||
let key = queue.insert_at("foo", now + ms(200));
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(3)).await;
|
||||
sleep(ms(3)).await;
|
||||
|
||||
queue.reset_at(&key, now + ms(5));
|
||||
queue.reset_at(&key, now + ms(10));
|
||||
|
||||
delay_for(ms(20)).await;
|
||||
sleep(ms(20)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
}
|
||||
@@ -219,21 +226,21 @@ async fn reset_twice() {
|
||||
let mut queue = task::spawn(DelayQueue::new());
|
||||
let now = Instant::now();
|
||||
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
|
||||
let key = queue.insert_at("foo", now + ms(200));
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(3)).await;
|
||||
sleep(ms(3)).await;
|
||||
|
||||
queue.reset_at(&key, now + ms(50));
|
||||
|
||||
delay_for(ms(20)).await;
|
||||
sleep(ms(20)).await;
|
||||
|
||||
queue.reset_at(&key, now + ms(40));
|
||||
|
||||
delay_for(ms(20)).await;
|
||||
sleep(ms(20)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
}
|
||||
@@ -246,7 +253,7 @@ async fn remove_expired_item() {
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
delay_for(ms(10)).await;
|
||||
sleep(ms(10)).await;
|
||||
|
||||
let key = queue.insert_at("foo", now);
|
||||
|
||||
@@ -272,7 +279,7 @@ async fn expires_before_last_insert() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(600)).await;
|
||||
sleep(ms(600)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
|
||||
@@ -297,18 +304,18 @@ async fn multi_reset() {
|
||||
queue.reset_at(&two, now + ms(350));
|
||||
queue.reset_at(&one, now + ms(400));
|
||||
|
||||
delay_for(ms(310)).await;
|
||||
sleep(ms(310)).await;
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(50)).await;
|
||||
sleep(ms(50)).await;
|
||||
|
||||
let entry = assert_ready_ok!(poll!(queue));
|
||||
assert_eq!(*entry.get_ref(), "two");
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(50)).await;
|
||||
sleep(ms(50)).await;
|
||||
|
||||
let entry = assert_ready_ok!(poll!(queue));
|
||||
assert_eq!(*entry.get_ref(), "one");
|
||||
@@ -332,7 +339,7 @@ async fn expire_first_key_when_reset_to_expire_earlier() {
|
||||
|
||||
queue.reset_at(&one, now + ms(100));
|
||||
|
||||
delay_for(ms(100)).await;
|
||||
sleep(ms(100)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
|
||||
@@ -355,7 +362,7 @@ async fn expire_second_key_when_reset_to_expire_earlier() {
|
||||
|
||||
queue.reset_at(&two, now + ms(100));
|
||||
|
||||
delay_for(ms(100)).await;
|
||||
sleep(ms(100)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
|
||||
@@ -377,7 +384,7 @@ async fn reset_first_expiring_item_to_expire_later() {
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
queue.reset_at(&one, now + ms(300));
|
||||
delay_for(ms(250)).await;
|
||||
sleep(ms(250)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
|
||||
@@ -399,11 +406,11 @@ async fn insert_before_first_after_poll() {
|
||||
|
||||
let _two = queue.insert_at("two", now + ms(100));
|
||||
|
||||
delay_for(ms(99)).await;
|
||||
sleep(ms(99)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
|
||||
@@ -425,7 +432,7 @@ async fn insert_after_ready_poll() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(100)).await;
|
||||
sleep(ms(100)).await;
|
||||
|
||||
assert!(queue.is_woken());
|
||||
|
||||
@@ -437,7 +444,7 @@ async fn insert_after_ready_poll() {
|
||||
queue.insert_at("foo", now + ms(500));
|
||||
}
|
||||
|
||||
res.sort();
|
||||
res.sort_unstable();
|
||||
|
||||
assert_eq!("1", res[0]);
|
||||
assert_eq!("2", res[1]);
|
||||
@@ -456,7 +463,7 @@ async fn reset_later_after_slot_starts() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(80)).await;
|
||||
sleep_until(now + Duration::from_millis(80)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
@@ -471,10 +478,10 @@ async fn reset_later_after_slot_starts() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(39)).await;
|
||||
sleep_until(now + Duration::from_millis(119)).await;
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
assert!(queue.is_woken());
|
||||
|
||||
let entry = assert_ready_ok!(poll!(queue)).into_inner();
|
||||
@@ -494,7 +501,7 @@ async fn reset_inserted_expired() {
|
||||
|
||||
assert_eq!(1, queue.len());
|
||||
|
||||
delay_for(ms(200)).await;
|
||||
sleep(ms(200)).await;
|
||||
|
||||
let entry = assert_ready_ok!(poll!(queue)).into_inner();
|
||||
assert_eq!(entry, "foo");
|
||||
@@ -514,7 +521,7 @@ async fn reset_earlier_after_slot_starts() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(80)).await;
|
||||
sleep_until(now + Duration::from_millis(80)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
@@ -529,10 +536,10 @@ async fn reset_earlier_after_slot_starts() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(39)).await;
|
||||
sleep_until(now + Duration::from_millis(119)).await;
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
delay_for(ms(1)).await;
|
||||
sleep(ms(1)).await;
|
||||
assert!(queue.is_woken());
|
||||
|
||||
let entry = assert_ready_ok!(poll!(queue)).into_inner();
|
||||
@@ -551,7 +558,7 @@ async fn insert_in_past_after_poll_fires_immediately() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
delay_for(ms(80)).await;
|
||||
sleep(ms(80)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
queue.insert_at("bar", now + ms(40));
|
||||
@@ -1,3 +1,5 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::{net::UdpSocket, stream::StreamExt};
|
||||
use tokio_util::codec::{Decoder, Encoder, LinesCodec};
|
||||
use tokio_util::udp::UdpFramed;
|
||||
|
||||
@@ -1,3 +1,144 @@
|
||||
# 0.3.5 (November 30, 2020)
|
||||
|
||||
### Fixed
|
||||
- rt: fix `shutdown_timeout(0)` (#3196).
|
||||
- time: fixed race condition with small sleeps (#3069).
|
||||
|
||||
### Added
|
||||
- io: `AsyncFd::with_interest()` (#3167).
|
||||
- signal: `CtrlC` stream on windows (#3186).
|
||||
|
||||
# 0.3.4 (November 18, 2020)
|
||||
|
||||
### Fixed
|
||||
- stream: `StreamMap` `Default` impl bound (#3093).
|
||||
- io: `AsyncFd::into_inner()` should deregister the FD (#3104).
|
||||
|
||||
### Changed
|
||||
- meta: `parking_lot` feature enabled with `full` (#3119).
|
||||
|
||||
### Added
|
||||
- io: `AsyncWrite` vectored writes (#3149).
|
||||
- net: TCP/UDP readiness and non-blocking ops (#3130, #2743, #3138).
|
||||
- net: TCP socket option (linger, send/recv buf size) (#3145, #3143).
|
||||
- net: PID field in `UCred` with solaris/illumos (#3085).
|
||||
- rt: `runtime::Handle` allows spawning onto a runtime (#3079).
|
||||
- sync: `Notify::notify_waiters()` (#3098).
|
||||
- sync: `acquire_many()`, `try_acquire_many()` to `Semaphore` (#3067).
|
||||
|
||||
# 0.3.3 (November 2, 2020)
|
||||
|
||||
Fixes a soundness hole by adding a missing `Send` bound to
|
||||
`Runtime::spawn_blocking()`.
|
||||
|
||||
### Fixed
|
||||
- rt: include missing `Send`, fixing soundness hole (#3089).
|
||||
- tracing: avoid huge trace span names (#3074).
|
||||
|
||||
### Added
|
||||
- net: `TcpSocket::reuseport()`, `TcpSocket::set_reuseport()` (#3083).
|
||||
- net: `TcpSocket::reuseaddr()` (#3093).
|
||||
- net: `TcpSocket::local_addr()` (#3093).
|
||||
- net: add pid to `UCred` (#2633).
|
||||
|
||||
# 0.3.2 (October 27, 2020)
|
||||
|
||||
Adds `AsyncFd` as a replacement for v0.2's `PollEvented`.
|
||||
|
||||
### Fixed
|
||||
- io: fix a potential deadlock when shutting down the I/O driver (#2903).
|
||||
- sync: `RwLockWriteGuard::downgrade()` bug (#2957).
|
||||
|
||||
### Added
|
||||
- io: `AsyncFd` for receiving readiness events on raw FDs (#2903).
|
||||
- net: `poll_*` function on `UdpSocket` (#2981).
|
||||
- net: `UdpSocket::take_error()` (#3051).
|
||||
- sync: `oneshot::Sender::poll_closed()` (#3032).
|
||||
|
||||
# 0.3.1 (October 21, 2020)
|
||||
|
||||
This release fixes an use-after-free in the IO driver. Additionally, the `read_buf`
|
||||
and `write_buf` methods have been added back to the IO traits, as the bytes crate
|
||||
is now on track to reach version 1.0 together with Tokio.
|
||||
|
||||
### Fixed
|
||||
- net: fix use-after-free (#3019).
|
||||
- fs: ensure buffered data is written on shutdown (#3009).
|
||||
|
||||
### Added
|
||||
- io: `copy_buf()` (#2884).
|
||||
- io: `AsyncReadExt::read_buf()`, `AsyncReadExt::write_buf()` for working with
|
||||
`Buf`/`BufMut` (#3003).
|
||||
- rt: `Runtime::spawn_blocking()` (#2980).
|
||||
- sync: `watch::Sender::is_closed()` (#2991).
|
||||
|
||||
# 0.3.0 (October 15, 2020)
|
||||
|
||||
This represents a 1.0 beta release. APIs are polished and future-proofed. APIs
|
||||
not included for 1.0 stabilization have been removed.
|
||||
|
||||
Biggest changes are:
|
||||
|
||||
- I/O driver internal rewrite. The windows implementation includes significant
|
||||
changes.
|
||||
- Runtime API is polished, especially with how it interacts with feature flag
|
||||
combinations.
|
||||
- Feature flags are simplified
|
||||
- `rt-core` and `rt-util` are combined to `rt`
|
||||
- `rt-threaded` is renamed to `rt-multi-thread` to match builder API
|
||||
- `tcp`, `udp`, `uds`, `dns` are combied to `net`.
|
||||
- `parking_lot` is included with `full`
|
||||
|
||||
### Changes
|
||||
- meta: Minimum supported Rust version is now 1.45.
|
||||
- io: `AsyncRead` trait now takes `ReadBuf` in order to safely handle reading
|
||||
into uninitialized memory (#2758).
|
||||
- io: Internal I/O driver storage is now able to compact (#2757).
|
||||
- rt: `Runtime::block_on` now takes `&self` (#2782).
|
||||
- sync: `watch` reworked to decouple receiving a change notification from
|
||||
receiving the value (#2814, #2806).
|
||||
- sync: `Notify::notify` is renamed to `notify_one` (#2822).
|
||||
- process: `Child::kill` is now an `async fn` that cleans zombies (#2823).
|
||||
- sync: use `const fn` constructors as possible (#2833, #2790)
|
||||
- signal: reduce cross-thread notification (#2835).
|
||||
- net: tcp,udp,uds types support operations with `&self` (#2828, #2919, #2934).
|
||||
- sync: blocking `mpsc` channel supports `send` with `&self` (#2861).
|
||||
- time: rename `delay_for` and `delay_until` to `sleep` and `sleep_until` (#2826).
|
||||
- io: upgrade to `mio` 0.7 (#2893).
|
||||
- io: `AsyncSeek` trait is tweaked (#2885).
|
||||
- fs: `File` operations take `&self` (#2930).
|
||||
- rt: runtime API, and `#[tokio::main]` macro polish (#2876)
|
||||
- rt: `Runtime::enter` uses an RAII guard instead of a closure (#2954).
|
||||
- net: the `from_std` function on all sockets no longer sets socket into non-blocking mode (#2893)
|
||||
|
||||
### Added
|
||||
- sync: `map` function to lock guards (#2445).
|
||||
- sync: `blocking_recv` and `blocking_send` fns to `mpsc` for use outside of Tokio (#2685).
|
||||
- rt: `Builder::thread_name_fn` for configuring thread names (#1921).
|
||||
- fs: impl `FromRawFd` and `FromRawHandle` for `File` (#2792).
|
||||
- process: `Child::wait` and `Child::try_wait` (#2796).
|
||||
- rt: support configuring thread keep-alive duration (#2809).
|
||||
- rt: `task::JoinHandle::abort` forcibly cancels a spawned task (#2474).
|
||||
- sync: `RwLock` write guard to read guard downgrading (#2733).
|
||||
- net: add `poll_*` functions that take `&self` to all net types (#2845)
|
||||
- sync: `get_mut()` for `Mutex`, `RwLock` (#2856).
|
||||
- sync: `mpsc::Sender::closed()` waits for `Receiver` half to close (#2840).
|
||||
- sync: `mpsc::Sender::is_closed()` returns true if `Receiver` half is closed (#2726).
|
||||
- stream: `iter` and `iter_mut` to `StreamMap` (#2890).
|
||||
- net: implement `AsRawSocket` on windows (#2911).
|
||||
- net: `TcpSocket` creates a socket without binding or listening (#2920).
|
||||
|
||||
### Removed
|
||||
- io: vectored ops are removed from `AsyncRead`, `AsyncWrite` traits (#2882).
|
||||
- io: `mio` is removed from the public API. `PollEvented` and` Registration` are
|
||||
removed (#2893).
|
||||
- io: remove `bytes` from public API. `Buf` and `BufMut` implementation are
|
||||
removed (#2908).
|
||||
- time: `DelayQueue` is moved to `tokio-util` (#2897).
|
||||
|
||||
### Fixed
|
||||
- io: `stdout` and `stderr` buffering on windows (#2734).
|
||||
|
||||
# 0.2.22 (July 21, 2020)
|
||||
|
||||
### Fixes
|
||||
|
||||
+56
-58
@@ -7,13 +7,13 @@ name = "tokio"
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.22"
|
||||
# - Create "v0.3.x" git tag.
|
||||
version = "0.3.5"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/tokio/0.2.22/tokio/"
|
||||
documentation = "https://docs.rs/tokio/0.3.5/tokio/"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
description = """
|
||||
@@ -29,92 +29,88 @@ default = []
|
||||
|
||||
# enable everything
|
||||
full = [
|
||||
"blocking",
|
||||
"dns",
|
||||
"fs",
|
||||
"io-driver",
|
||||
"io-util",
|
||||
"io-std",
|
||||
"macros",
|
||||
"net",
|
||||
"parking_lot",
|
||||
"process",
|
||||
"rt-core",
|
||||
"rt-util",
|
||||
"rt-threaded",
|
||||
"rt",
|
||||
"rt-multi-thread",
|
||||
"signal",
|
||||
"stream",
|
||||
"sync",
|
||||
"time",
|
||||
]
|
||||
|
||||
blocking = ["rt-core"]
|
||||
dns = ["rt-core"]
|
||||
fs = ["rt-core", "io-util"]
|
||||
io-driver = ["mio", "lazy_static"]
|
||||
io-util = ["memchr"]
|
||||
fs = []
|
||||
io-util = ["memchr", "bytes"]
|
||||
# stdin, stdout, stderr
|
||||
io-std = ["rt-core"]
|
||||
io-std = []
|
||||
macros = ["tokio-macros"]
|
||||
net = ["dns", "tcp", "udp", "uds"]
|
||||
process = [
|
||||
"io-driver",
|
||||
"libc",
|
||||
"mio-named-pipes",
|
||||
"signal",
|
||||
"winapi/consoleapi",
|
||||
"winapi/minwindef",
|
||||
"winapi/threadpoollegacyapiset",
|
||||
"winapi/winerror",
|
||||
]
|
||||
# Includes basic task execution capabilities
|
||||
rt-core = ["slab"]
|
||||
rt-util = []
|
||||
rt-threaded = [
|
||||
"num_cpus",
|
||||
"rt-core",
|
||||
]
|
||||
signal = [
|
||||
"io-driver",
|
||||
net = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio-uds",
|
||||
"mio/os-poll",
|
||||
"mio/os-util",
|
||||
"mio/tcp",
|
||||
"mio/udp",
|
||||
"mio/uds",
|
||||
]
|
||||
process = [
|
||||
"bytes",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio/os-poll",
|
||||
"mio/os-util",
|
||||
"mio/uds",
|
||||
"signal-hook-registry",
|
||||
"winapi/threadpoollegacyapiset",
|
||||
]
|
||||
# Includes basic task execution capabilities
|
||||
rt = ["slab"]
|
||||
rt-multi-thread = [
|
||||
"num_cpus",
|
||||
"rt",
|
||||
]
|
||||
signal = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio/os-poll",
|
||||
"mio/uds",
|
||||
"mio/os-util",
|
||||
"signal-hook-registry",
|
||||
"winapi/consoleapi",
|
||||
"winapi/minwindef",
|
||||
]
|
||||
stream = ["futures-core"]
|
||||
sync = ["fnv"]
|
||||
sync = []
|
||||
test-util = []
|
||||
tcp = ["io-driver", "iovec"]
|
||||
time = ["slab"]
|
||||
udp = ["io-driver"]
|
||||
uds = ["io-driver", "mio-uds", "libc"]
|
||||
time = []
|
||||
|
||||
[dependencies]
|
||||
tokio-macros = { version = "0.2.4", path = "../tokio-macros", optional = true }
|
||||
tokio-macros = { version = "0.3.0", path = "../tokio-macros", optional = true }
|
||||
|
||||
bytes = "0.5.0"
|
||||
pin-project-lite = "0.1.1"
|
||||
pin-project-lite = "0.2.0"
|
||||
|
||||
# Everything else is optional...
|
||||
fnv = { version = "1.0.6", optional = true }
|
||||
bytes = { version = "0.6.0", optional = true }
|
||||
futures-core = { version = "0.3.0", optional = true }
|
||||
lazy_static = { version = "1.0.2", optional = true }
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
memchr = { version = "2.2", optional = true }
|
||||
mio = { version = "0.6.20", optional = true }
|
||||
iovec = { version = "0.1.4", optional = true }
|
||||
mio = { version = "0.7.6", optional = true }
|
||||
num_cpus = { version = "1.8.0", optional = true }
|
||||
parking_lot = { version = "0.11.0", optional = true } # Not in full
|
||||
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
|
||||
tracing = { version = "0.1.16", default-features = false, features = ["std"], optional = true } # Not in full
|
||||
parking_lot = { version = "0.11.0", optional = true }
|
||||
slab = { version = "0.4.1", optional = true }
|
||||
tracing = { version = "0.1.21", default-features = false, features = ["std"], optional = true } # Not in full
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
mio-uds = { version = "0.6.5", optional = true }
|
||||
libc = { version = "0.2.42", optional = true }
|
||||
signal-hook-registry = { version = "1.1.1", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
mio-named-pipes = { version = "0.1.6", optional = true }
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
libc = { version = "0.2.42" }
|
||||
nix = { version = "0.19.0" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.winapi]
|
||||
version = "0.3.8"
|
||||
@@ -122,15 +118,17 @@ default-features = false
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = { version = "0.2.0", path = "../tokio-test" }
|
||||
tokio-test = { version = "0.3.0", path = "../tokio-test" }
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
futures-test = "0.3.0"
|
||||
proptest = "0.9.4"
|
||||
proptest = "0.10.0"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[target.'cfg(loom)'.dev-dependencies]
|
||||
loom = { version = "0.3.5", features = ["futures", "checkpoint"] }
|
||||
|
||||
[build-dependencies]
|
||||
autocfg = "1" # Needed for conditionally enabling `track-caller`
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
+4
-4
@@ -14,15 +14,15 @@ the Rust programming language. It is:
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: https://github.com/tokio-rs/tokio/blob/master/LICENSE
|
||||
[azure-badge]: https://dev.azure.com/tokio-rs/Tokio/_apis/build/status/tokio-rs.tokio?branchName=master
|
||||
[azure-url]: https://dev.azure.com/tokio-rs/Tokio/_build/latest?definitionId=1&branchName=master
|
||||
[actions-badge]: https://github.com/tokio-rs/tokio/workflows/CI/badge.svg
|
||||
[actions-url]: https://github.com/tokio-rs/tokio/actions?query=workflow%3ACI+branch%3Amaster
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/tokio
|
||||
|
||||
@@ -157,7 +157,7 @@ several other libraries, including:
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.39.
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.45.
|
||||
The current Tokio version is not guaranteed to build on Rust versions earlier than the
|
||||
minimum supported version.
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
use autocfg::AutoCfg;
|
||||
|
||||
fn main() {
|
||||
match AutoCfg::new() {
|
||||
Ok(ac) => {
|
||||
// The #[track_caller] attribute was stabilized in rustc 1.46.0.
|
||||
if ac.probe_rustc_version(1, 46) {
|
||||
autocfg::emit("tokio_track_caller")
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
// If we couldn't detect the compiler version and features, just
|
||||
// print a warning. This isn't a fatal error: we can still build
|
||||
// Tokio, we just can't enable cfgs automatically.
|
||||
println!(
|
||||
"cargo:warning=tokio: failed to detect compiler features: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
# Refactor I/O driver
|
||||
|
||||
Describes changes to the I/O driver for the Tokio 0.3 release.
|
||||
|
||||
## Goals
|
||||
|
||||
* Support `async fn` on I/O types with `&self`.
|
||||
* Refine the `Registration` API.
|
||||
|
||||
### Non-goals
|
||||
|
||||
* Implement `AsyncRead` / `AsyncWrite` for `&TcpStream` or other reference type.
|
||||
|
||||
## Overview
|
||||
|
||||
Currently, I/O types require `&mut self` for `async` functions. The reason for
|
||||
this is the task's waker is stored in the I/O resource's internal state
|
||||
(`ScheduledIo`) instead of in the future returned by the `async` function.
|
||||
Because of this limitation, I/O types limit the number of wakers to one per
|
||||
direction (a direction is either read-related events or write-related events).
|
||||
|
||||
Moving the waker from the internal I/O resource's state to the operation's
|
||||
future enables multiple wakers to be registered per operation. The "intrusive
|
||||
wake list" strategy used by `Notify` applies to this case, though there are some
|
||||
concerns unique to the I/O driver.
|
||||
|
||||
## Reworking the `Registration` type
|
||||
|
||||
While `Registration` is made private (per #2728), it remains in Tokio as an
|
||||
implementation detail backing I/O resources such as `TcpStream`. The API of
|
||||
`Registration` is updated to support waiting for an arbitrary interest set with
|
||||
`&self`. This supports concurrent waiters with a different readiness interest.
|
||||
|
||||
```rust
|
||||
struct Registration { ... }
|
||||
|
||||
// TODO: naming
|
||||
struct ReadyEvent {
|
||||
tick: u32,
|
||||
ready: mio::Ready,
|
||||
}
|
||||
|
||||
impl Registration {
|
||||
/// `interest` must be a super set of **all** interest sets specified in
|
||||
/// the other methods. This is the interest set passed to `mio`.
|
||||
pub fn new<T>(io: &T, interest: mio::Ready) -> io::Result<Registration>
|
||||
where T: mio::Evented;
|
||||
|
||||
/// Awaits for any readiness event included in `interest`. Returns a
|
||||
/// `ReadyEvent` representing the received readiness event.
|
||||
async fn readiness(&self, interest: mio::Ready) -> io::Result<ReadyEvent>;
|
||||
|
||||
/// Clears resource level readiness represented by the specified `ReadyEvent`
|
||||
async fn clear_readiness(&self, ready_event: ReadyEvent);
|
||||
```
|
||||
|
||||
A new registration is created for a `T: mio::Evented` and a `interest`. This
|
||||
creates a `ScheduledIo` entry with the I/O driver and registers the resource
|
||||
with `mio`.
|
||||
|
||||
Because Tokio uses **edge-triggered** notifications, the I/O driver only
|
||||
receives readiness from the OS once the ready state **changes**. The I/O driver
|
||||
must track each resource's known readiness state. This helps prevent syscalls
|
||||
when the process knows the syscall should return with `EWOULDBLOCK`.
|
||||
|
||||
A call to `readiness()` checks if the currently known resource readiness
|
||||
overlaps with `interest`. If it does, then the `readiness()` immediately
|
||||
returns. If it does not, then the task waits until the I/O driver receives a
|
||||
readiness event.
|
||||
|
||||
The pseudocode to perform a TCP read is as follows.
|
||||
|
||||
```rust
|
||||
async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
loop {
|
||||
// Await readiness
|
||||
let event = self.readiness(interest).await?;
|
||||
|
||||
match self.mio_socket.read(buf) {
|
||||
Ok(v) => return Ok(v),
|
||||
Err(ref e) if e.kind() == WouldBlock => {
|
||||
self.clear_readiness(event);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reworking the `ScheduledIo` type
|
||||
|
||||
The `ScheduledIo` type is switched to use an intrusive waker linked list. Each
|
||||
entry in the linked list includes the `interest` set passed to `readiness()`.
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ScheduledIo {
|
||||
/// Resource's known state packed with other state that must be
|
||||
/// atomically updated.
|
||||
readiness: AtomicUsize,
|
||||
|
||||
/// Tracks tasks waiting on the resource
|
||||
waiters: Mutex<Waiters>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Waiters {
|
||||
// List of intrusive waiters.
|
||||
list: LinkedList<Waiter>,
|
||||
|
||||
/// Waiter used by `AsyncRead` implementations.
|
||||
reader: Option<Waker>,
|
||||
|
||||
/// Waiter used by `AsyncWrite` implementations.
|
||||
writer: Option<Waker>,
|
||||
}
|
||||
|
||||
// This struct is contained by the **future** returned by `readiness()`.
|
||||
#[derive(Debug)]
|
||||
struct Waiter {
|
||||
/// Intrusive linked-list pointers
|
||||
pointers: linked_list::Pointers<Waiter>,
|
||||
|
||||
/// Waker for task waiting on I/O resource
|
||||
waiter: Option<Waker>,
|
||||
|
||||
/// Readiness events being waited on. This is
|
||||
/// the value passed to `readiness()`
|
||||
interest: mio::Ready,
|
||||
|
||||
/// Should not be `Unpin`.
|
||||
_p: PhantomPinned,
|
||||
}
|
||||
```
|
||||
|
||||
When an I/O event is received from `mio`, the associated resources' readiness is
|
||||
updated and the waiter list is iterated. All waiters with `interest` that
|
||||
overlap the received readiness event are notified. Any waiter with an `interest`
|
||||
that does not overlap the readiness event remains in the list.
|
||||
|
||||
## Cancel interest on drop
|
||||
|
||||
The future returned by `readiness()` uses an intrusive linked list to store the
|
||||
waker with `ScheduledIo`. Because `readiness()` can be called concurrently, many
|
||||
wakers may be stored simultaneously in the list. If the `readiness()` future is
|
||||
dropped early, it is essential that the waker is removed from the list. This
|
||||
prevents leaking memory.
|
||||
|
||||
## Race condition
|
||||
|
||||
Consider how many tasks may concurrently attempt I/O operations. This, combined
|
||||
with how Tokio uses edge-triggered events, can result in a race condition. Let's
|
||||
revisit the TCP read function:
|
||||
|
||||
```rust
|
||||
async fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
loop {
|
||||
// Await readiness
|
||||
let event = self.readiness(interest).await?;
|
||||
|
||||
match self.mio_socket.read(buf) {
|
||||
Ok(v) => return Ok(v),
|
||||
Err(ref e) if e.kind() == WouldBlock => {
|
||||
self.clear_readiness(event);
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If care is not taken, if between `mio_socket.read(buf)` returning and
|
||||
`clear_readiness(event)` is called, a readiness event arrives, the `read()`
|
||||
function could deadlock. This happens because the readiness event is received,
|
||||
`clear_readiness()` unsets the readiness event, and on the next iteration,
|
||||
`readiness().await` will block forever as a new readiness event is not received.
|
||||
|
||||
The current I/O driver handles this condition by always registering the task's
|
||||
waker before performing the operation. This is not ideal as it will result in
|
||||
unnecessary task notification.
|
||||
|
||||
Instead, we will use a strategy to prevent clearing readiness if an "unseen"
|
||||
readiness event has been received. The I/O driver will maintain a "tick" value.
|
||||
Every time the `mio` `poll()` function is called, the tick is incremented. Each
|
||||
readiness event has an associated tick. When the I/O driver sets the resource's
|
||||
readiness, the driver's tick is packed into the atomic `usize`.
|
||||
|
||||
The `ScheduledIo` readiness `AtomicUsize` is structured as:
|
||||
|
||||
```
|
||||
| reserved | generation | driver tick | readinesss |
|
||||
|----------+------------+--------------+------------|
|
||||
| 1 bit | 7 bits + 8 bits + 16 bits |
|
||||
```
|
||||
|
||||
The `reserved` and `generation` components exist today.
|
||||
|
||||
The `readiness()` function returns a `ReadyEvent` value. This value includes the
|
||||
`tick` component read with the resource's readiness value. When
|
||||
`clear_readiness()` is called, the `ReadyEvent` is provided. Readiness is only
|
||||
cleared if the current `tick` matches the `tick` included in the `ReadyEvent`.
|
||||
If the tick values do not match, the call to `readiness()` on the next iteration
|
||||
will not block and the new `tick` is included in the new `ReadyToken.`
|
||||
|
||||
TODO
|
||||
|
||||
## Implementing `AsyncRead` / `AsyncWrite`
|
||||
|
||||
The `AsyncRead` and `AsyncWrite` traits use a "poll" based API. This means that
|
||||
it is not possible to use an intrusive linked list to track the waker.
|
||||
Additionally, there is no future associated with the operation which means it is
|
||||
not possible to cancel interest in the readiness events.
|
||||
|
||||
To implement `AsyncRead` and `AsyncWrite`, `ScheduledIo` includes dedicated
|
||||
waker values for the read direction and the write direction. These values are
|
||||
used to store the waker. Specific `interest` is not tracked for `AsyncRead` and
|
||||
`AsyncWrite` implementations. It is assumed that only events of interest are:
|
||||
|
||||
* Read ready
|
||||
* Read closed
|
||||
* Write ready
|
||||
* Write closed
|
||||
|
||||
Note that "read closed" and "write closed" are only available with Mio 0.7. With
|
||||
Mio 0.6, things were a bit messy.
|
||||
|
||||
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
|
||||
memory leaks.
|
||||
|
||||
## Enabling reads and writes for `&TcpStream`
|
||||
|
||||
Instead of implementing `AsyncRead` and `AsyncWrite` for `&TcpStream`, a new
|
||||
function is added to `TcpStream`.
|
||||
|
||||
```rust
|
||||
impl TcpStream {
|
||||
/// Naming TBD
|
||||
fn by_ref(&self) -> TcpStreamRef<'_>;
|
||||
}
|
||||
|
||||
struct TcpStreamRef<'a> {
|
||||
stream: &'a TcpStream,
|
||||
|
||||
// `Waiter` is the node in the intrusive waiter linked-list
|
||||
read_waiter: Waiter,
|
||||
write_waiter: Waiter,
|
||||
}
|
||||
```
|
||||
|
||||
Now, `AsyncRead` and `AsyncWrite` can be implemented on `TcpStreamRef<'a>`. When
|
||||
the `TcpStreamRef` is dropped, all associated waker resources are cleaned up.
|
||||
|
||||
### Removing all the `split()` functions
|
||||
|
||||
With `TcpStream::by_ref()`, `TcpStream::split()` is no longer needed. Instead,
|
||||
it is possible to do something as follows.
|
||||
|
||||
```rust
|
||||
let rd = my_stream.by_ref();
|
||||
let wr = my_stream.by_ref();
|
||||
|
||||
select! {
|
||||
// use `rd` and `wr` in separate branches.
|
||||
}
|
||||
```
|
||||
|
||||
It is also possible to sotre 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,48 @@
|
||||
cfg_rt! {
|
||||
pub(crate) use crate::runtime::spawn_blocking;
|
||||
pub(crate) use crate::task::JoinHandle;
|
||||
}
|
||||
|
||||
cfg_not_rt! {
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub(crate) fn spawn_blocking<F, R>(_f: F) -> JoinHandle<R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
assert_send_sync::<JoinHandle<std::cell::Cell<()>>>();
|
||||
panic!("requires the `rt` Tokio feature flag")
|
||||
|
||||
}
|
||||
|
||||
pub(crate) struct JoinHandle<R> {
|
||||
_p: std::marker::PhantomData<R>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for JoinHandle<T> {}
|
||||
unsafe impl<T: Send> Sync for JoinHandle<T> {}
|
||||
|
||||
impl<R> Future for JoinHandle<R> {
|
||||
type Output = Result<R, std::io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for JoinHandle<T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("JoinHandle").finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_send_sync<T: Send + Sync>() {
|
||||
}
|
||||
}
|
||||
+10
-11
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(not(feature = "full"), allow(dead_code))]
|
||||
|
||||
//! Opt-in yield points for improved cooperative scheduling.
|
||||
//!
|
||||
//! A single call to [`poll`] on a top-level task may potentially do a lot of
|
||||
@@ -81,7 +83,7 @@ impl Budget {
|
||||
}
|
||||
}
|
||||
|
||||
cfg_rt_threaded! {
|
||||
cfg_rt_multi_thread! {
|
||||
impl Budget {
|
||||
fn has_remaining(self) -> bool {
|
||||
self.0.map(|budget| budget > 0).unwrap_or(true)
|
||||
@@ -96,14 +98,6 @@ pub(crate) fn budget<R>(f: impl FnOnce() -> R) -> R {
|
||||
with_budget(Budget::initial(), f)
|
||||
}
|
||||
|
||||
cfg_rt_threaded! {
|
||||
/// Set the current task's budget
|
||||
#[cfg(feature = "blocking")]
|
||||
pub(crate) fn set(budget: Budget) {
|
||||
CURRENT.with(|cell| cell.set(budget))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn with_budget<R>(budget: Budget, f: impl FnOnce() -> R) -> R {
|
||||
struct ResetGuard<'a> {
|
||||
@@ -128,14 +122,19 @@ fn with_budget<R>(budget: Budget, f: impl FnOnce() -> R) -> R {
|
||||
})
|
||||
}
|
||||
|
||||
cfg_rt_threaded! {
|
||||
cfg_rt_multi_thread! {
|
||||
/// Set the current task's budget
|
||||
pub(crate) fn set(budget: Budget) {
|
||||
CURRENT.with(|cell| cell.set(budget))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn has_budget_remaining() -> bool {
|
||||
CURRENT.with(|cell| cell.get().has_remaining())
|
||||
}
|
||||
}
|
||||
|
||||
cfg_blocking_impl! {
|
||||
cfg_rt! {
|
||||
/// Forcibly remove the budgeting constraints early.
|
||||
///
|
||||
/// Returns the remaining budget
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use crate::fs::asyncify;
|
||||
use std::path::Path;
|
||||
|
||||
/// Copies the contents of one file to another. This function will also copy the permission bits of the original file to the destination file.
|
||||
/// Copies the contents of one file to another. This function will also copy the permission bits
|
||||
/// of the original file to the destination file.
|
||||
/// This function will overwrite the contents of to.
|
||||
///
|
||||
/// This is the async equivalent of `std::fs::copy`.
|
||||
/// This is the async equivalent of [`std::fs::copy`][std].
|
||||
///
|
||||
/// [std]: fn@std::fs::copy
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
+133
-163
@@ -5,7 +5,8 @@
|
||||
use self::State::*;
|
||||
use crate::fs::{asyncify, sys};
|
||||
use crate::io::blocking::Buf;
|
||||
use crate::io::{AsyncRead, AsyncSeek, AsyncWrite};
|
||||
use crate::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
||||
use crate::sync::Mutex;
|
||||
|
||||
use std::fmt;
|
||||
use std::fs::{Metadata, Permissions};
|
||||
@@ -80,12 +81,18 @@ use std::task::Poll::*;
|
||||
/// ```
|
||||
pub struct File {
|
||||
std: Arc<sys::File>,
|
||||
inner: Mutex<Inner>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
state: State,
|
||||
|
||||
/// Errors from writes/flushes are returned in write/flush calls. If a write
|
||||
/// error is observed while performing a read, it is saved until the next
|
||||
/// write / flush call.
|
||||
last_write_err: Option<io::ErrorKind>,
|
||||
|
||||
pos: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -197,70 +204,11 @@ impl File {
|
||||
pub fn from_std(std: sys::File) -> File {
|
||||
File {
|
||||
std: Arc::new(std),
|
||||
state: State::Idle(Some(Buf::with_capacity(0))),
|
||||
last_write_err: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Seeks to an offset, in bytes, in a stream.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::File;
|
||||
/// use tokio::prelude::*;
|
||||
///
|
||||
/// use std::io::SeekFrom;
|
||||
///
|
||||
/// # async fn dox() -> std::io::Result<()> {
|
||||
/// let mut file = File::open("foo.txt").await?;
|
||||
/// file.seek(SeekFrom::Start(6)).await?;
|
||||
///
|
||||
/// let mut contents = vec![0u8; 10];
|
||||
/// file.read_exact(&mut contents).await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// The [`read_exact`] method is defined on the [`AsyncReadExt`] trait.
|
||||
///
|
||||
/// [`read_exact`]: fn@crate::io::AsyncReadExt::read_exact
|
||||
/// [`AsyncReadExt`]: trait@crate::io::AsyncReadExt
|
||||
pub async fn seek(&mut self, mut pos: SeekFrom) -> io::Result<u64> {
|
||||
self.complete_inflight().await;
|
||||
|
||||
let mut buf = match self.state {
|
||||
Idle(ref mut buf_cell) => buf_cell.take().unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// Factor in any unread data from the buf
|
||||
if !buf.is_empty() {
|
||||
let n = buf.discard_read();
|
||||
|
||||
if let SeekFrom::Current(ref mut offset) = pos {
|
||||
*offset += n;
|
||||
}
|
||||
}
|
||||
|
||||
let std = self.std.clone();
|
||||
|
||||
// Start the operation
|
||||
self.state = Busy(sys::run(move || {
|
||||
let res = (&*std).seek(pos);
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
|
||||
let (op, buf) = match self.state {
|
||||
Idle(_) => unreachable!(),
|
||||
Busy(ref mut rx) => rx.await.unwrap(),
|
||||
};
|
||||
|
||||
self.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Seek(res) => res,
|
||||
_ => unreachable!(),
|
||||
inner: Mutex::new(Inner {
|
||||
state: State::Idle(Some(Buf::with_capacity(0))),
|
||||
last_write_err: None,
|
||||
pos: 0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,8 +235,9 @@ impl File {
|
||||
///
|
||||
/// [`write_all`]: fn@crate::io::AsyncWriteExt::write_all
|
||||
/// [`AsyncWriteExt`]: trait@crate::io::AsyncWriteExt
|
||||
pub async fn sync_all(&mut self) -> io::Result<()> {
|
||||
self.complete_inflight().await;
|
||||
pub async fn sync_all(&self) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().await;
|
||||
inner.complete_inflight().await;
|
||||
|
||||
let std = self.std.clone();
|
||||
asyncify(move || std.sync_all()).await
|
||||
@@ -321,8 +270,9 @@ impl File {
|
||||
///
|
||||
/// [`write_all`]: fn@crate::io::AsyncWriteExt::write_all
|
||||
/// [`AsyncWriteExt`]: trait@crate::io::AsyncWriteExt
|
||||
pub async fn sync_data(&mut self) -> io::Result<()> {
|
||||
self.complete_inflight().await;
|
||||
pub async fn sync_data(&self) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().await;
|
||||
inner.complete_inflight().await;
|
||||
|
||||
let std = self.std.clone();
|
||||
asyncify(move || std.sync_data()).await
|
||||
@@ -358,10 +308,11 @@ impl File {
|
||||
///
|
||||
/// [`write_all`]: fn@crate::io::AsyncWriteExt::write_all
|
||||
/// [`AsyncWriteExt`]: trait@crate::io::AsyncWriteExt
|
||||
pub async fn set_len(&mut self, size: u64) -> io::Result<()> {
|
||||
self.complete_inflight().await;
|
||||
pub async fn set_len(&self, size: u64) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().await;
|
||||
inner.complete_inflight().await;
|
||||
|
||||
let mut buf = match self.state {
|
||||
let mut buf = match inner.state {
|
||||
Idle(ref mut buf_cell) => buf_cell.take().unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
@@ -374,7 +325,7 @@ impl File {
|
||||
|
||||
let std = self.std.clone();
|
||||
|
||||
self.state = Busy(sys::run(move || {
|
||||
inner.state = Busy(sys::run(move || {
|
||||
let res = if let Some(seek) = seek {
|
||||
(&*std).seek(seek).and_then(|_| std.set_len(size))
|
||||
} else {
|
||||
@@ -386,15 +337,17 @@ impl File {
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
|
||||
let (op, buf) = match self.state {
|
||||
let (op, buf) = match inner.state {
|
||||
Idle(_) => unreachable!(),
|
||||
Busy(ref mut rx) => rx.await?,
|
||||
};
|
||||
|
||||
self.state = Idle(Some(buf));
|
||||
inner.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Seek(res) => res.map(|_| ()),
|
||||
Operation::Seek(res) => res.map(|pos| {
|
||||
inner.pos = pos;
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -459,7 +412,7 @@ impl File {
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn into_std(mut self) -> sys::File {
|
||||
self.complete_inflight().await;
|
||||
self.inner.get_mut().complete_inflight().await;
|
||||
Arc::try_unwrap(self.std).expect("Arc::try_unwrap failed")
|
||||
}
|
||||
|
||||
@@ -526,42 +479,32 @@ impl File {
|
||||
let std = self.std.clone();
|
||||
asyncify(move || std.set_permissions(perm)).await
|
||||
}
|
||||
|
||||
async fn complete_inflight(&mut self) {
|
||||
use crate::future::poll_fn;
|
||||
|
||||
if let Err(e) = poll_fn(|cx| Pin::new(&mut *self).poll_flush(cx)).await {
|
||||
self.last_write_err = Some(e.kind());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for File {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, _buf: &mut [std::mem::MaybeUninit<u8>]) -> bool {
|
||||
// https://github.com/rust-lang/rust/blob/09c817eeb29e764cfc12d0a8d94841e3ffe34023/src/libstd/fs.rs#L668
|
||||
false
|
||||
}
|
||||
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
dst: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
dst: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
let me = self.get_mut();
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
loop {
|
||||
match self.state {
|
||||
match inner.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
if !buf.is_empty() {
|
||||
let n = buf.copy_to(dst);
|
||||
buf.copy_to(dst);
|
||||
*buf_cell = Some(buf);
|
||||
return Ready(Ok(n));
|
||||
return Ready(Ok(()));
|
||||
}
|
||||
|
||||
buf.ensure_capacity_for(dst);
|
||||
let std = self.std.clone();
|
||||
let std = me.std.clone();
|
||||
|
||||
self.state = Busy(sys::run(move || {
|
||||
inner.state = Busy(sys::run(move || {
|
||||
let res = buf.read_from(&mut &*std);
|
||||
(Operation::Read(res), buf)
|
||||
}));
|
||||
@@ -571,29 +514,32 @@ impl AsyncRead for File {
|
||||
|
||||
match op {
|
||||
Operation::Read(Ok(_)) => {
|
||||
let n = buf.copy_to(dst);
|
||||
self.state = Idle(Some(buf));
|
||||
return Ready(Ok(n));
|
||||
buf.copy_to(dst);
|
||||
inner.state = Idle(Some(buf));
|
||||
return Ready(Ok(()));
|
||||
}
|
||||
Operation::Read(Err(e)) => {
|
||||
assert!(buf.is_empty());
|
||||
|
||||
self.state = Idle(Some(buf));
|
||||
inner.state = Idle(Some(buf));
|
||||
return Ready(Err(e));
|
||||
}
|
||||
Operation::Write(Ok(_)) => {
|
||||
assert!(buf.is_empty());
|
||||
self.state = Idle(Some(buf));
|
||||
inner.state = Idle(Some(buf));
|
||||
continue;
|
||||
}
|
||||
Operation::Write(Err(e)) => {
|
||||
assert!(self.last_write_err.is_none());
|
||||
self.last_write_err = Some(e.kind());
|
||||
self.state = Idle(Some(buf));
|
||||
assert!(inner.last_write_err.is_none());
|
||||
inner.last_write_err = Some(e.kind());
|
||||
inner.state = Idle(Some(buf));
|
||||
}
|
||||
Operation::Seek(_) => {
|
||||
Operation::Seek(result) => {
|
||||
assert!(buf.is_empty());
|
||||
self.state = Idle(Some(buf));
|
||||
inner.state = Idle(Some(buf));
|
||||
if let Ok(pos) = result {
|
||||
inner.pos = pos;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -604,13 +550,13 @@ impl AsyncRead for File {
|
||||
}
|
||||
|
||||
impl AsyncSeek for File {
|
||||
fn start_seek(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
mut pos: SeekFrom,
|
||||
) -> Poll<io::Result<()>> {
|
||||
fn start_seek(self: Pin<&mut Self>, mut pos: SeekFrom) -> io::Result<()> {
|
||||
let me = self.get_mut();
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
loop {
|
||||
match self.state {
|
||||
match inner.state {
|
||||
Busy(_) => panic!("must wait for poll_complete before calling start_seek"),
|
||||
Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
@@ -623,49 +569,41 @@ impl AsyncSeek for File {
|
||||
}
|
||||
}
|
||||
|
||||
let std = self.std.clone();
|
||||
let std = me.std.clone();
|
||||
|
||||
self.state = Busy(sys::run(move || {
|
||||
inner.state = Busy(sys::run(move || {
|
||||
let res = (&*std).seek(pos);
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
|
||||
return Ready(Ok(()));
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
self.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {}
|
||||
Operation::Write(Err(e)) => {
|
||||
assert!(self.last_write_err.is_none());
|
||||
self.last_write_err = Some(e.kind());
|
||||
}
|
||||
Operation::Write(_) => {}
|
||||
Operation::Seek(_) => {}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
let inner = self.inner.get_mut();
|
||||
|
||||
loop {
|
||||
match self.state {
|
||||
Idle(_) => panic!("must call start_seek before calling poll_complete"),
|
||||
match inner.state {
|
||||
Idle(_) => return Poll::Ready(Ok(inner.pos)),
|
||||
Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
self.state = Idle(Some(buf));
|
||||
inner.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {}
|
||||
Operation::Write(Err(e)) => {
|
||||
assert!(self.last_write_err.is_none());
|
||||
self.last_write_err = Some(e.kind());
|
||||
assert!(inner.last_write_err.is_none());
|
||||
inner.last_write_err = Some(e.kind());
|
||||
}
|
||||
Operation::Write(_) => {}
|
||||
Operation::Seek(res) => return Ready(res),
|
||||
Operation::Seek(res) => {
|
||||
if let Ok(pos) = res {
|
||||
inner.pos = pos;
|
||||
}
|
||||
return Ready(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -675,16 +613,19 @@ impl AsyncSeek for File {
|
||||
|
||||
impl AsyncWrite for File {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
src: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if let Some(e) = self.last_write_err.take() {
|
||||
let me = self.get_mut();
|
||||
let inner = me.inner.get_mut();
|
||||
|
||||
if let Some(e) = inner.last_write_err.take() {
|
||||
return Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.state {
|
||||
match inner.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
@@ -695,9 +636,9 @@ impl AsyncWrite for File {
|
||||
};
|
||||
|
||||
let n = buf.copy_from(src);
|
||||
let std = self.std.clone();
|
||||
let std = me.std.clone();
|
||||
|
||||
self.state = Busy(sys::run(move || {
|
||||
inner.state = Busy(sys::run(move || {
|
||||
let res = if let Some(seek) = seek {
|
||||
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
|
||||
} else {
|
||||
@@ -711,7 +652,7 @@ impl AsyncWrite for File {
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
self.state = Idle(Some(buf));
|
||||
inner.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {
|
||||
@@ -737,27 +678,12 @@ impl AsyncWrite for File {
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
if let Some(e) = self.last_write_err.take() {
|
||||
return Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
let (op, buf) = match self.state {
|
||||
Idle(_) => return Ready(Ok(())),
|
||||
Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx))?,
|
||||
};
|
||||
|
||||
// The buffer is not used here
|
||||
self.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => Ready(Ok(())),
|
||||
Operation::Write(res) => Ready(res),
|
||||
Operation::Seek(_) => Ready(Ok(())),
|
||||
}
|
||||
let inner = self.inner.get_mut();
|
||||
inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
self.poll_flush(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,9 +708,53 @@ impl std::os::unix::io::AsRawFd for File {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::FromRawFd for File {
|
||||
unsafe fn from_raw_fd(fd: std::os::unix::io::RawFd) -> Self {
|
||||
sys::File::from_raw_fd(fd).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::AsRawHandle for File {
|
||||
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
|
||||
self.std.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::FromRawHandle for File {
|
||||
unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
|
||||
sys::File::from_raw_handle(handle).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
async fn complete_inflight(&mut self) {
|
||||
use crate::future::poll_fn;
|
||||
|
||||
if let Err(e) = poll_fn(|cx| Pin::new(&mut *self).poll_flush(cx)).await {
|
||||
self.last_write_err = Some(e.kind());
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
if let Some(e) = self.last_write_err.take() {
|
||||
return Ready(Err(e.into()));
|
||||
}
|
||||
|
||||
let (op, buf) = match self.state {
|
||||
Idle(_) => return Ready(Ok(())),
|
||||
Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx))?,
|
||||
};
|
||||
|
||||
// The buffer is not used here
|
||||
self.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => Ready(Ok(())),
|
||||
Operation::Write(res) => Ready(res),
|
||||
Operation::Seek(_) => Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@
|
||||
//! `std::io::ErrorKind::WouldBlock` if a *worker* thread can not be converted
|
||||
//! to a *backup* thread immediately.
|
||||
//!
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio-io/0.1/tokio_io/trait.AsyncRead.html
|
||||
//! [`AsyncRead`]: trait@crate::io::AsyncRead
|
||||
|
||||
mod canonicalize;
|
||||
pub use self::canonicalize::canonicalize;
|
||||
@@ -107,6 +107,6 @@ mod sys {
|
||||
pub(crate) use std::fs::File;
|
||||
|
||||
// TODO: don't rename
|
||||
pub(crate) use crate::runtime::spawn_blocking as run;
|
||||
pub(crate) use crate::task::JoinHandle as Blocking;
|
||||
pub(crate) use crate::blocking::spawn_blocking as run;
|
||||
pub(crate) use crate::blocking::JoinHandle as Blocking;
|
||||
}
|
||||
|
||||
@@ -383,8 +383,7 @@ impl OpenOptions {
|
||||
Ok(File::from_std(std))
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the the underlying std::fs::OpenOptions
|
||||
#[cfg(unix)]
|
||||
/// Returns a mutable reference to the underlying `std::fs::OpenOptions`
|
||||
pub(super) fn as_inner_mut(&mut self) -> &mut std::fs::OpenOptions {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::fs::dir_builder::DirBuilder;
|
||||
/// Unix-specific extensions to [`DirBuilder`].
|
||||
///
|
||||
/// [`DirBuilder`]: crate::fs::DirBuilder
|
||||
pub trait DirBuilderExt {
|
||||
pub trait DirBuilderExt: sealed::Sealed {
|
||||
/// Sets the mode to create new directories with.
|
||||
///
|
||||
/// This option defaults to 0o777.
|
||||
@@ -27,3 +27,10 @@ impl DirBuilderExt for DirBuilder {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Sealed for DirBuilder {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
#[doc(hidden)]
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use crate::fs::DirEntry;
|
||||
use std::os::unix::fs::DirEntryExt as _;
|
||||
|
||||
/// Unix-specific extension methods for [`fs::DirEntry`].
|
||||
///
|
||||
/// This mirrors the definition of [`std::os::unix::fs::DirEntryExt`].
|
||||
///
|
||||
/// [`fs::DirEntry`]: crate::fs::DirEntry
|
||||
/// [`std::os::unix::fs::DirEntryExt`]: std::os::unix::fs::DirEntryExt
|
||||
pub trait DirEntryExt: sealed::Sealed {
|
||||
/// Returns the underlying `d_ino` field in the contained `dirent`
|
||||
/// structure.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::fs;
|
||||
/// use tokio::fs::os::unix::DirEntryExt;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let mut entries = fs::read_dir(".").await?;
|
||||
/// while let Some(entry) = entries.next_entry().await? {
|
||||
/// // Here, `entry` is a `DirEntry`.
|
||||
/// println!("{:?}: {}", entry.file_name(), entry.ino());
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn ino(&self) -> u64;
|
||||
}
|
||||
|
||||
impl DirEntryExt for DirEntry {
|
||||
fn ino(&self) -> u64 {
|
||||
self.as_inner().ino()
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Sealed for DirEntry {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
#[doc(hidden)]
|
||||
pub trait Sealed {}
|
||||
}
|
||||
@@ -8,3 +8,6 @@ pub use self::open_options_ext::OpenOptionsExt;
|
||||
|
||||
mod dir_builder_ext;
|
||||
pub use self::dir_builder_ext::DirBuilderExt;
|
||||
|
||||
mod dir_entry_ext;
|
||||
pub use self::dir_entry_ext::DirEntryExt;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::fs::open_options::OpenOptions;
|
||||
use std::os::unix::fs::OpenOptionsExt as StdOpenOptionsExt;
|
||||
use std::os::unix::fs::OpenOptionsExt as _;
|
||||
|
||||
/// Unix-specific extensions to [`fs::OpenOptions`].
|
||||
///
|
||||
/// This mirrors the definition of [`std::os::unix::fs::OpenOptionsExt`].
|
||||
///
|
||||
///
|
||||
/// [`fs::OpenOptions`]: crate::fs::OpenOptions
|
||||
/// [`std::os::unix::fs::OpenOptionsExt`]: std::os::unix::fs::OpenOptionsExt
|
||||
pub trait OpenOptionsExt {
|
||||
pub trait OpenOptionsExt: sealed::Sealed {
|
||||
/// Sets the mode bits that a new file will be created with.
|
||||
///
|
||||
/// If a new file is created as part of an `OpenOptions::open` call then this
|
||||
@@ -77,3 +76,10 @@ impl OpenOptionsExt for OpenOptions {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Sealed for OpenOptions {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
#[doc(hidden)]
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,6 @@ pub use self::symlink_dir::symlink_dir;
|
||||
|
||||
mod symlink_file;
|
||||
pub use self::symlink_file::symlink_file;
|
||||
|
||||
mod open_options_ext;
|
||||
pub use self::open_options_ext::OpenOptionsExt;
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
use crate::fs::open_options::OpenOptions;
|
||||
use std::os::windows::fs::OpenOptionsExt as _;
|
||||
|
||||
/// Unix-specific extensions to [`fs::OpenOptions`].
|
||||
///
|
||||
/// This mirrors the definition of [`std::os::windows::fs::OpenOptionsExt`].
|
||||
///
|
||||
/// [`fs::OpenOptions`]: crate::fs::OpenOptions
|
||||
/// [`std::os::windows::fs::OpenOptionsExt`]: std::os::windows::fs::OpenOptionsExt
|
||||
pub trait OpenOptionsExt: sealed::Sealed {
|
||||
/// Overrides the `dwDesiredAccess` argument to the call to [`CreateFile`]
|
||||
/// with the specified value.
|
||||
///
|
||||
/// This will override the `read`, `write`, and `append` flags on the
|
||||
/// `OpenOptions` structure. This method provides fine-grained control over
|
||||
/// the permissions to read, write and append data, attributes (like hidden
|
||||
/// and system), and extended attributes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use tokio::fs::os::windows::OpenOptionsExt;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// // Open without read and write permission, for example if you only need
|
||||
/// // to call `stat` on the file
|
||||
/// let file = OpenOptions::new().access_mode(0).open("foo.txt").await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
|
||||
fn access_mode(&mut self, access: u32) -> &mut Self;
|
||||
|
||||
/// Overrides the `dwShareMode` argument to the call to [`CreateFile`] with
|
||||
/// the specified value.
|
||||
///
|
||||
/// By default `share_mode` is set to
|
||||
/// `FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE`. This allows
|
||||
/// other processes to read, write, and delete/rename the same file
|
||||
/// while it is open. Removing any of the flags will prevent other
|
||||
/// processes from performing the corresponding operation until the file
|
||||
/// handle is closed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use tokio::fs::os::windows::OpenOptionsExt;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// // Do not allow others to read or modify this file while we have it open
|
||||
/// // for writing.
|
||||
/// let file = OpenOptions::new()
|
||||
/// .write(true)
|
||||
/// .share_mode(0)
|
||||
/// .open("foo.txt").await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
|
||||
fn share_mode(&mut self, val: u32) -> &mut Self;
|
||||
|
||||
/// Sets extra flags for the `dwFileFlags` argument to the call to
|
||||
/// [`CreateFile2`] to the specified value (or combines it with
|
||||
/// `attributes` and `security_qos_flags` to set the `dwFlagsAndAttributes`
|
||||
/// for [`CreateFile`]).
|
||||
///
|
||||
/// Custom flags can only set flags, not remove flags set by Rust's options.
|
||||
/// This option overwrites any previously set custom flags.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winapi::um::winbase::FILE_FLAG_DELETE_ON_CLOSE;
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use tokio::fs::os::windows::OpenOptionsExt;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .create(true)
|
||||
/// .write(true)
|
||||
/// .custom_flags(FILE_FLAG_DELETE_ON_CLOSE)
|
||||
/// .open("foo.txt").await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
|
||||
/// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
|
||||
fn custom_flags(&mut self, flags: u32) -> &mut Self;
|
||||
|
||||
/// Sets the `dwFileAttributes` argument to the call to [`CreateFile2`] to
|
||||
/// the specified value (or combines it with `custom_flags` and
|
||||
/// `security_qos_flags` to set the `dwFlagsAndAttributes` for
|
||||
/// [`CreateFile`]).
|
||||
///
|
||||
/// If a _new_ file is created because it does not yet exist and
|
||||
/// `.create(true)` or `.create_new(true)` are specified, the new file is
|
||||
/// given the attributes declared with `.attributes()`.
|
||||
///
|
||||
/// If an _existing_ file is opened with `.create(true).truncate(true)`, its
|
||||
/// existing attributes are preserved and combined with the ones declared
|
||||
/// with `.attributes()`.
|
||||
///
|
||||
/// In all other cases the attributes get ignored.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winapi::um::winnt::FILE_ATTRIBUTE_HIDDEN;
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use tokio::fs::os::windows::OpenOptionsExt;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .write(true)
|
||||
/// .create(true)
|
||||
/// .attributes(FILE_ATTRIBUTE_HIDDEN)
|
||||
/// .open("foo.txt").await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
|
||||
/// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
|
||||
fn attributes(&mut self, val: u32) -> &mut Self;
|
||||
|
||||
/// Sets the `dwSecurityQosFlags` argument to the call to [`CreateFile2`] to
|
||||
/// the specified value (or combines it with `custom_flags` and `attributes`
|
||||
/// to set the `dwFlagsAndAttributes` for [`CreateFile`]).
|
||||
///
|
||||
/// By default `security_qos_flags` is not set. It should be specified when
|
||||
/// opening a named pipe, to control to which degree a server process can
|
||||
/// act on behalf of a client process (security impersonation level).
|
||||
///
|
||||
/// When `security_qos_flags` is not set, a malicious program can gain the
|
||||
/// elevated privileges of a privileged Rust process when it allows opening
|
||||
/// user-specified paths, by tricking it into opening a named pipe. So
|
||||
/// arguably `security_qos_flags` should also be set when opening arbitrary
|
||||
/// paths. However the bits can then conflict with other flags, specifically
|
||||
/// `FILE_FLAG_OPEN_NO_RECALL`.
|
||||
///
|
||||
/// For information about possible values, see [Impersonation Levels] on the
|
||||
/// Windows Dev Center site. The `SECURITY_SQOS_PRESENT` flag is set
|
||||
/// automatically when using this method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use winapi::um::winbase::SECURITY_IDENTIFICATION;
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use tokio::fs::os::windows::OpenOptionsExt;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .write(true)
|
||||
/// .create(true)
|
||||
///
|
||||
/// // Sets the flag value to `SecurityIdentification`.
|
||||
/// .security_qos_flags(SECURITY_IDENTIFICATION)
|
||||
///
|
||||
/// .open(r"\\.\pipe\MyPipe").await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
|
||||
/// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
|
||||
/// [Impersonation Levels]:
|
||||
/// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
|
||||
fn security_qos_flags(&mut self, flags: u32) -> &mut Self;
|
||||
}
|
||||
|
||||
impl OpenOptionsExt for OpenOptions {
|
||||
fn access_mode(&mut self, access: u32) -> &mut OpenOptions {
|
||||
self.as_inner_mut().access_mode(access);
|
||||
self
|
||||
}
|
||||
|
||||
fn share_mode(&mut self, share: u32) -> &mut OpenOptions {
|
||||
self.as_inner_mut().share_mode(share);
|
||||
self
|
||||
}
|
||||
|
||||
fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions {
|
||||
self.as_inner_mut().custom_flags(flags);
|
||||
self
|
||||
}
|
||||
|
||||
fn attributes(&mut self, attributes: u32) -> &mut OpenOptions {
|
||||
self.as_inner_mut().attributes(attributes);
|
||||
self
|
||||
}
|
||||
|
||||
fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions {
|
||||
self.as_inner_mut().security_qos_flags(flags);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Sealed for OpenOptions {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
#[doc(hidden)]
|
||||
pub trait Sealed {}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ use std::ffi::OsString;
|
||||
use std::fs::{FileType, Metadata};
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::DirEntryExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -55,8 +53,7 @@ impl ReadDir {
|
||||
poll_fn(|cx| self.poll_next_entry(cx)).await
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>> {
|
||||
fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>> {
|
||||
loop {
|
||||
match self.0 {
|
||||
State::Idle(ref mut std) => {
|
||||
@@ -234,11 +231,10 @@ impl DirEntry {
|
||||
let std = self.0.clone();
|
||||
asyncify(move || std.file_type()).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl DirEntryExt for DirEntry {
|
||||
fn ino(&self) -> u64 {
|
||||
self.0.ino()
|
||||
/// Returns a reference to the underlying `std::fs::DirEntry`
|
||||
#[cfg(unix)]
|
||||
pub(super) fn as_inner(&self) -> &std::fs::DirEntry {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::{io, path::Path};
|
||||
/// Creates a future which will open a file for reading and read the entire
|
||||
/// contents into a string and return said string.
|
||||
///
|
||||
/// This is the async equivalent of `std::fs::read_to_string`.
|
||||
/// This is the async equivalent of [`std::fs::read_to_string`][std].
|
||||
///
|
||||
/// [std]: fn@std::fs::read_to_string
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -5,7 +5,9 @@ use std::{io, path::Path};
|
||||
/// Creates a future that will open a file for writing and write the entire
|
||||
/// contents of `contents` to it.
|
||||
///
|
||||
/// This is the async equivalent of `std::fs::write`.
|
||||
/// This is the async equivalent of [`std::fs::write`][std].
|
||||
///
|
||||
/// [std]: fn@std::fs::write
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
use std::future::Future;
|
||||
|
||||
cfg_rt! {
|
||||
pub(crate) fn block_on<F: Future>(f: F) -> F::Output {
|
||||
let mut e = crate::runtime::enter::enter(false);
|
||||
e.block_on(f).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
cfg_not_rt! {
|
||||
pub(crate) fn block_on<F: Future>(f: F) -> F::Output {
|
||||
let mut park = crate::park::thread::CachedParkThread::new();
|
||||
park.block_on(f).unwrap()
|
||||
}
|
||||
}
|
||||
+16
-7
@@ -1,15 +1,24 @@
|
||||
#![allow(unused_imports, dead_code)]
|
||||
#![cfg_attr(not(feature = "macros"), allow(unreachable_pub))]
|
||||
|
||||
//! Asynchronous values.
|
||||
|
||||
mod maybe_done;
|
||||
pub use maybe_done::{maybe_done, MaybeDone};
|
||||
#[cfg(any(feature = "macros", feature = "process"))]
|
||||
pub(crate) mod maybe_done;
|
||||
|
||||
mod poll_fn;
|
||||
pub use poll_fn::poll_fn;
|
||||
|
||||
mod ready;
|
||||
pub(crate) use ready::{ok, Ready};
|
||||
cfg_not_loom! {
|
||||
mod ready;
|
||||
pub(crate) use ready::{ok, Ready};
|
||||
}
|
||||
|
||||
mod try_join;
|
||||
pub(crate) use try_join::try_join3;
|
||||
cfg_process! {
|
||||
mod try_join;
|
||||
pub(crate) use try_join::try_join3;
|
||||
}
|
||||
|
||||
cfg_sync! {
|
||||
mod block_on;
|
||||
pub(crate) use block_on::block_on;
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use sdt::pin::Pin;
|
||||
use std::future::Future;
|
||||
use std::marker;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Future for the [`pending()`] function.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
struct Pending<T> {
|
||||
_data: marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Creates a future which never resolves, representing a computation that never
|
||||
/// finishes.
|
||||
///
|
||||
/// The returned future will forever return [`Poll::Pending`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::future;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main {
|
||||
/// future::pending().await;
|
||||
/// unreachable!();
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn pending() -> ! {
|
||||
Pending {
|
||||
_data: marker::PhantomData,
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
impl<T> Future for Pending<T> {
|
||||
type Output = !;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<T> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Unpin for Pending<T> {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user