mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8be3e4d18 | ||
|
|
a517dbf605 |
+9
-8
@@ -1,17 +1,17 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-2-release-amd64
|
||||
image: freebsd-12-1-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
|
||||
name: FreeBSD 12.0
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y bash curl
|
||||
- pkg install -y curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
- . $HOME/.cargo/env
|
||||
@@ -21,9 +21,10 @@ task:
|
||||
rustc --version
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all --all-features
|
||||
- cargo test --all
|
||||
- cargo doc --all --no-deps
|
||||
i686_test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
cargo test --all --all-features --target i686-unknown-freebsd
|
||||
# TODO: Re-enable
|
||||
# i686_test_script:
|
||||
# - . $HOME/.cargo/env
|
||||
# - |
|
||||
# cargo test --all --exclude tokio-macros --target i686-unknown-freebsd
|
||||
|
||||
@@ -9,7 +9,12 @@ assignees: ''
|
||||
|
||||
**Version**
|
||||
List the versions of all `tokio` crates you are using. The easiest way to get
|
||||
this information is using `cargo tree` subcommand:
|
||||
this information is using `cargo-tree`.
|
||||
|
||||
`cargo install cargo-tree`
|
||||
(see install here: https://github.com/sfackler/cargo-tree)
|
||||
|
||||
Then:
|
||||
|
||||
`cargo tree | grep tokio`
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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
|
||||
+14
-68
@@ -1,8 +1,8 @@
|
||||
on:
|
||||
push:
|
||||
branches: ["master", "tokio-*.x"]
|
||||
branches: ["v0.2.x"]
|
||||
pull_request:
|
||||
branches: ["master", "tokio-*.x"]
|
||||
branches: ["v0.2.x"]
|
||||
|
||||
name: CI
|
||||
|
||||
@@ -10,7 +10,7 @@ env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
nightly: nightly-2020-09-21
|
||||
minrust: 1.45.2
|
||||
minrust: 1.39.0
|
||||
|
||||
jobs:
|
||||
# Depends on all action sthat are required for a "successful" CI run.
|
||||
@@ -28,7 +28,6 @@ jobs:
|
||||
- clippy
|
||||
- docs
|
||||
- loom
|
||||
- valgrind
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
@@ -54,6 +53,11 @@ 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
|
||||
@@ -68,28 +72,6 @@ jobs:
|
||||
run: cargo hack test --each-feature
|
||||
working-directory: tests-build
|
||||
|
||||
valgrind:
|
||||
name: valgrind
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
- name: Install Valgrind
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y valgrind
|
||||
|
||||
# Compile tests
|
||||
- name: cargo build
|
||||
run: cargo build --features rt-net --bin test-mem
|
||||
working-directory: tests-integration
|
||||
|
||||
# Run with valgrind
|
||||
- name: Run valgrind
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
|
||||
|
||||
test-unstable:
|
||||
name: test tokio full --unstable
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -109,7 +91,7 @@ jobs:
|
||||
run: cargo test --features full
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
RUSTFLAGS: '--cfg tokio_unstable'
|
||||
|
||||
miri:
|
||||
name: miri
|
||||
@@ -128,23 +110,8 @@ jobs:
|
||||
rm -rf tokio/tests
|
||||
|
||||
- name: miri
|
||||
run: cargo miri test --features rt,rt-multi-thread,sync task
|
||||
run: cargo miri test --features rt-core,rt-threaded,rt-util,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
|
||||
@@ -189,7 +156,7 @@ jobs:
|
||||
- name: check --each-feature --unstable
|
||||
run: cargo hack check --all --each-feature -Z avoid-dev-deps
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
RUSTFLAGS: --cfg tokio_unstable
|
||||
|
||||
minrust:
|
||||
name: minrust
|
||||
@@ -204,26 +171,6 @@ 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
|
||||
@@ -249,13 +196,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update ${{ env.minrust }} && rustup default ${{ env.minrust }}
|
||||
run: rustup update stable
|
||||
- name: Install clippy
|
||||
run: rustup component add clippy
|
||||
|
||||
# Run clippy
|
||||
- name: "clippy --all"
|
||||
run: cargo clippy --all --tests --all-features
|
||||
run: cargo clippy --all --tests
|
||||
|
||||
docs:
|
||||
name: docs
|
||||
@@ -283,7 +230,6 @@ jobs:
|
||||
- loom_pool::group_b
|
||||
- loom_pool::group_c
|
||||
- loom_pool::group_d
|
||||
- time::driver
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
@@ -293,6 +239,6 @@ jobs:
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Stress Test
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
stess-test:
|
||||
name: Stress Test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
stress-test:
|
||||
- simple_echo_tcp
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
- name: Install Valgrind
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y valgrind
|
||||
|
||||
# Compiles each of the stress test examples.
|
||||
- name: Compile stress test examples
|
||||
run: cargo build -p stress-test --release --example ${{ matrix.stress-test }}
|
||||
|
||||
# Runs each of the examples using Valgrind. Detects leaks and displays them.
|
||||
- name: Run valgrind
|
||||
run: valgrind --leak-check=full --show-leak-kinds=all ./target/release/examples/${{ matrix.stress-test }}
|
||||
+10
-47
@@ -427,14 +427,11 @@ _Adapted from the [Node.js contributing guide][node]_.
|
||||
|
||||
## Keeping track of issues and PRs
|
||||
|
||||
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
|
||||
section explains the meaning of various labels, as well as our [GitHub
|
||||
project][project]. The section is primarily targeted at maintainers. Most
|
||||
contributors aren't able to set these labels.
|
||||
The Tokio GitHub repository has a lot of issues and PRs, which is not easy to
|
||||
keep track of. This section explains the meaning of various labels, as well as
|
||||
our [GitHub project][project]. The section is primarily targeted at maintainers.
|
||||
|
||||
### Area
|
||||
|
||||
The area label describes the crates relevant to this issue or PR.
|
||||
**Area.** The area label describes the crates relevant to this issue or PR.
|
||||
|
||||
- **A-tokio** This issue concerns the main Tokio crate.
|
||||
- **A-tokio-util** This issue concerns the `tokio-util` crate.
|
||||
@@ -445,7 +442,7 @@ The area label describes the crates relevant to this issue or PR.
|
||||
be used for the procedural macros, and not `join!` or `select!`.
|
||||
- **A-ci** This issue concerns our GitHub Actions setup.
|
||||
|
||||
### Category
|
||||
**Category.** The category label describes the category.
|
||||
|
||||
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
|
||||
- **C-enhancement** This is a PR that adds a new features.
|
||||
@@ -463,7 +460,8 @@ The area label describes the crates relevant to this issue or PR.
|
||||
- **C-request** A non-feature request, e.g. "please add deprecation notices to
|
||||
`-alpha.*` versions of crates"
|
||||
|
||||
### Calls for participation
|
||||
**Call for participation.** I don't know why it's called `E-`. Many issues are
|
||||
missing a difficulty rating, and you should feel free to add one.
|
||||
|
||||
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
|
||||
or `C-feature-accepted`.
|
||||
@@ -475,13 +473,7 @@ The area label describes the crates relevant to this issue or PR.
|
||||
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
|
||||
example.
|
||||
|
||||
The "E-" prefix is the same as used in the Rust compiler repository. Some
|
||||
issues are missing a difficulty rating, but feel free to ask on our Discord
|
||||
server if you want to know how difficult an issue likely is.
|
||||
|
||||
### Module
|
||||
|
||||
The module label provides a more fine grained categorization than **Area**.
|
||||
**Module.** A more fine groaned categorization than area.
|
||||
|
||||
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
|
||||
- **M-codec** The `tokio_util::codec` module.
|
||||
@@ -494,14 +486,13 @@ The module label provides a more fine grained categorization than **Area**.
|
||||
- **M-process** The `tokio::process` module.
|
||||
- **M-runtime** The `tokio::runtime` module.
|
||||
- **M-signal** The `tokio::signal` module.
|
||||
- **M-stream** The `tokio::stream` module.
|
||||
- **M-sync** The `tokio::sync` module.
|
||||
- **M-task** The `tokio::task` module.
|
||||
- **M-time** The `tokio::time` module.
|
||||
- **M-tracing** Tracing support in Tokio.
|
||||
|
||||
### Topic
|
||||
|
||||
Some extra information.
|
||||
**Topic.** Some extra information.
|
||||
|
||||
- **T-docs** This is about documentation.
|
||||
- **T-performance** This is about performance.
|
||||
@@ -511,34 +502,6 @@ Any label not listed here is not in active use.
|
||||
|
||||
[project]: https://github.com/orgs/tokio-rs/projects/1
|
||||
|
||||
## LTS guarantees
|
||||
|
||||
Tokio ≥1.0.0 comes with LTS guarantees:
|
||||
|
||||
* A minimum of 5 years of maintenance.
|
||||
* A minimum of 3 years before a hypothetical 2.0 release.
|
||||
|
||||
The goal of these guarantees is to provide stability to the ecosystem.
|
||||
|
||||
## Mininum Supported Rust Version (MSRV)
|
||||
|
||||
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
|
||||
compiler release.
|
||||
* The MSRV will only be increased on 1.x releases.
|
||||
|
||||
## Versioning Policy
|
||||
|
||||
With Tokio ≥1.0.0:
|
||||
|
||||
* Patch (1.\_.x) releases _should only_ contain bug fixes or documentation
|
||||
changes. Besides this, these releases should not substantially change
|
||||
runtime behavior.
|
||||
* Minor (1.x) releases may contain new functionality, MSRV increases (see
|
||||
above), minor dependency updates, deprecations, and larger internal
|
||||
implementation changes.
|
||||
|
||||
This is as defined by [Semantic Versioning 2.0](https://semver.org/).
|
||||
|
||||
## Releasing
|
||||
|
||||
Since the Tokio project consists of a number of crates, many of which depend on
|
||||
|
||||
@@ -4,13 +4,11 @@ members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
"tokio-test",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
|
||||
# Internal
|
||||
"benches",
|
||||
"examples",
|
||||
"stress-test",
|
||||
"tests-build",
|
||||
"tests-integration",
|
||||
]
|
||||
|
||||
@@ -14,21 +14,22 @@ the Rust programming language. It is:
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
[![Build Status][azure-badge]][azure-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
|
||||
[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
|
||||
[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
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/tokio
|
||||
|
||||
[Website](https://tokio.rs) |
|
||||
[Guides](https://tokio.rs/tokio/tutorial) |
|
||||
[API Docs](https://docs.rs/tokio/latest/tokio) |
|
||||
[Roadmap](https://github.com/tokio-rs/tokio/blob/master/ROADMAP.md) |
|
||||
[Chat](https://discord.gg/tokio)
|
||||
|
||||
## Overview
|
||||
@@ -54,7 +55,7 @@ A basic TCP echo server with Tokio:
|
||||
|
||||
```rust,no_run
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::prelude::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -156,7 +157,7 @@ several other libraries, including:
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.45.
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.39.
|
||||
The current Tokio version is not guaranteed to build on Rust versions earlier than the
|
||||
minimum supported version.
|
||||
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# Tokio Roadmap
|
||||
|
||||
## A Roadmap to 1.0
|
||||
|
||||
The question of "why not 1.0?" has come up a few times. After all, Tokio 0.1 has
|
||||
been stable for three years. The short answer: because it isn't time. There is
|
||||
nobody who would rather ship a Tokio 1.0 than us. It also isn't something to rush.
|
||||
|
||||
After all, `async / await` only landed in the stable Rust channel weeks ago.
|
||||
There has been no significant production validation yet, except maybe fuchsia
|
||||
and that seems like a fairly specialized use case. This release of Tokio
|
||||
includes significant new code and new strategies with feature flags. Also, there
|
||||
are still big open questions, such as the [proposed changes][pr-1744] to
|
||||
`AsyncRead` and `AsyncWrite`.
|
||||
|
||||
Tokio 1.0 will be released as soon as the APIs are proven to handle real-world
|
||||
production cases.
|
||||
|
||||
### Tokio 1.0 in Q3 2020 with LTS support
|
||||
|
||||
The Tokio 1.0 release will be **no later** than Q3 2020. It will also come with
|
||||
"long-term support" guarantees:
|
||||
|
||||
* A minimum of 5 years of maintenance.
|
||||
* A minimum of 3 years before a hypothetical 2.0 release.
|
||||
|
||||
When Tokio 1.0 is released in Q3 2020, on-going support, security fixes, and
|
||||
critical bug fixes are guaranteed until **at least** Q3 2025. Tokio 2.0 will not
|
||||
be released until **at least** Q3 2023 (though, ideally there will never be a
|
||||
Tokio 2.0 release).
|
||||
|
||||
### How to get there
|
||||
|
||||
While Tokio 0.1 probably should have been a 1.0, Tokio 0.2 will be a **true**
|
||||
0.2 release. There will be breaking change releases every 2 ~ 3 months until 1.0.
|
||||
These changes will be **much** smaller than going from 0.1 -> 0.2. It is
|
||||
expected that the 1.0 release will look a lot like 0.2.
|
||||
|
||||
### What is expected to change
|
||||
|
||||
The biggest change will be the `AsyncRead` and `AsyncWrite` traits. Based on
|
||||
experience gained over the past 3 years, there are a couple of issues to
|
||||
address:
|
||||
|
||||
* Be able to **safely** use uninitialized memory as a read buffer.
|
||||
* Practical read vectored and write vectored APIs.
|
||||
|
||||
There are a few strategies to solve these problems. These strategies need to be
|
||||
investigated and the solution validated. You can see [this comment][pr-1744-comment] for a
|
||||
detailed statement of the problem.
|
||||
|
||||
The other major change, which has been in the works for a while, is updating
|
||||
Mio. Mio 0.6 was first released almost 4 years ago and has not had a breaking
|
||||
change since. Mio 0.7 has been in the works for a while. It includes a full
|
||||
rewrite of the windows support as well as a refined API. More will be written
|
||||
about this shortly.
|
||||
|
||||
Finally, now that the API is starting to stabilize, effort will be put into
|
||||
documentation. Tokio 0.2 is being released before updating the website and many
|
||||
of the old content will no longer be relevant. In the coming weeks, expect to
|
||||
see updates there.
|
||||
|
||||
So, we have our work cut out for us. We hope you enjoy this 0.2 release and are
|
||||
looking forward to your feedback and help.
|
||||
|
||||
[pr-1744]: https://github.com/tokio-rs/tokio/pull/1744
|
||||
[pr-1744-comment]: https://github.com/tokio-rs/tokio/pull/1744#issuecomment-553575438
|
||||
+5
-13
@@ -5,25 +5,22 @@ publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.2.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 = "sync_mpsc"
|
||||
path = "sync_mpsc.rs"
|
||||
name = "mpsc"
|
||||
path = "mpsc.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_multi_threaded"
|
||||
path = "rt_multi_threaded.rs"
|
||||
name = "scheduler"
|
||||
path = "scheduler.rs"
|
||||
harness = false
|
||||
|
||||
|
||||
@@ -36,8 +33,3 @@ harness = false
|
||||
name = "sync_semaphore"
|
||||
path = "sync_semaphore.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "signal"
|
||||
path = "signal.rs"
|
||||
harness = false
|
||||
|
||||
@@ -4,13 +4,6 @@ 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));
|
||||
@@ -30,38 +23,38 @@ fn create_100_000_medium(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn send_medium(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
let (mut tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
|
||||
let _ = rt.block_on(tx.send([0; 64]));
|
||||
let _ = tx.try_send([0; 64]);
|
||||
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
rx.try_recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn send_large(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
let (mut tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
let _ = rt.block_on(tx.send([[0; 64]; 64]));
|
||||
let _ = tx.try_send([[0; 64]; 64]);
|
||||
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
rx.try_recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
let mut tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -77,14 +70,18 @@ fn contention_bounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contention_bounded_full(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
let mut tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -100,7 +97,11 @@ fn contention_bounded_full(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contention_unbounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
@@ -123,11 +124,15 @@ fn contention_unbounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn uncontented_bounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
let (mut tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -141,7 +146,11 @@ fn uncontented_bounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
@@ -13,7 +13,7 @@ use std::sync::{mpsc, Arc};
|
||||
fn spawn_many(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
|
||||
let rt = rt();
|
||||
let mut 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 rt = rt();
|
||||
let mut 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 rt = rt();
|
||||
let mut rt = rt();
|
||||
|
||||
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
@@ -139,8 +139,9 @@ fn chained_spawn(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread()
|
||||
.worker_threads(4)
|
||||
runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.core_threads(4)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
@@ -1,95 +0,0 @@
|
||||
//! 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);
|
||||
+14
-8
@@ -10,7 +10,8 @@ async fn work() -> usize {
|
||||
}
|
||||
|
||||
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
@@ -22,7 +23,8 @@ fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
@@ -34,21 +36,25 @@ fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn basic_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let handle = runtime.handle();
|
||||
bench.iter(|| {
|
||||
let h = runtime.spawn(work());
|
||||
let h = handle.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap();
|
||||
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
bench.iter(|| {
|
||||
let h = runtime.spawn(work());
|
||||
let h = handle.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
+13
-8
@@ -3,8 +3,9 @@ use std::sync::Arc;
|
||||
use tokio::{sync::RwLock, task};
|
||||
|
||||
fn read_uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -21,8 +22,9 @@ fn read_uncontended(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -49,7 +51,8 @@ fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -75,8 +78,9 @@ fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -104,7 +108,8 @@ fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn read_concurrent_contended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ use std::sync::Arc;
|
||||
use tokio::{sync::Semaphore, task};
|
||||
|
||||
fn uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -26,8 +27,9 @@ async fn task(s: Arc<Semaphore>) {
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -49,7 +51,8 @@ fn uncontended_concurrent_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -70,8 +73,9 @@ fn uncontended_concurrent_single(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -93,7 +97,8 @@ fn contended_concurrent_multi(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
||||
+4
-18
@@ -7,23 +7,18 @@ edition = "2018"
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead.
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", features = ["full", "tracing"] }
|
||||
tokio-util = { version = "0.6.3", features = ["full"] }
|
||||
tokio-stream = { version = "0.1" }
|
||||
|
||||
async-stream = "0.3"
|
||||
tokio = { version = "0.2.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"] }
|
||||
bytes = "1.0.0"
|
||||
futures = { version = "0.3.0", features = ["thread-pool"]}
|
||||
tokio-util = { version = "0.3.0", path = "../tokio-util", features = ["full"] }
|
||||
bytes = "0.5"
|
||||
futures = "0.3.0"
|
||||
http = "0.2"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
httparse = "1.0"
|
||||
time = "0.1"
|
||||
once_cell = "1.5.2"
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
@@ -68,12 +63,3 @@ path = "udp-codec.rs"
|
||||
[[example]]
|
||||
name = "tinyhttp"
|
||||
path = "tinyhttp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "custom-executor"
|
||||
path = "custom-executor.rs"
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "custom-executor-tokio-context"
|
||||
path = "custom-executor-tokio-context.rs"
|
||||
|
||||
+7
-10
@@ -27,8 +27,8 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::{Stream, StreamExt};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
|
||||
|
||||
use futures::SinkExt;
|
||||
@@ -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 listener = TcpListener::bind(&addr).await?;
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
tracing::info!("server running on {}", addr);
|
||||
|
||||
@@ -101,6 +101,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// Shorthand for the transmit half of the message channel.
|
||||
type Tx = mpsc::UnboundedSender<String>;
|
||||
|
||||
/// Shorthand for the receive half of the message channel.
|
||||
type Rx = mpsc::UnboundedReceiver<String>;
|
||||
|
||||
/// Data that is shared between all peers in the chat server.
|
||||
///
|
||||
/// This is the set of `Tx` handles for all connected clients. Whenever a
|
||||
@@ -124,7 +127,7 @@ struct Peer {
|
||||
///
|
||||
/// This is used to receive messages from peers. When a message is received
|
||||
/// off of this `Rx`, it will be written to the socket.
|
||||
rx: Pin<Box<dyn Stream<Item = String> + Send>>,
|
||||
rx: Rx,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
@@ -156,17 +159,11 @@ impl Peer {
|
||||
let addr = lines.get_ref().peer_addr()?;
|
||||
|
||||
// Create a channel for this peer
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
// Add an entry for this `Peer` in the shared state map.
|
||||
state.lock().await.peers.insert(addr, tx);
|
||||
|
||||
let rx = Box::pin(async_stream::stream! {
|
||||
while let Some(item) = rx.recv().await {
|
||||
yield item;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Peer { lines, rx })
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -92,10 +92,11 @@ mod tcp {
|
||||
|
||||
mod udp {
|
||||
use bytes::Bytes;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
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(
|
||||
@@ -113,15 +114,16 @@ mod udp {
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
let (mut r, mut w) = socket.split();
|
||||
|
||||
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
|
||||
future::try_join(send(stdin, &mut w), recv(stdout, &mut r)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
writer: &UdpSocket,
|
||||
writer: &mut SendHalf,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
@@ -133,7 +135,7 @@ mod udp {
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
reader: &UdpSocket,
|
||||
reader: &mut RecvHalf,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// This example shows how to use the tokio runtime with any other executor
|
||||
//
|
||||
//It takes advantage from RuntimeExt which provides the extension to customize your
|
||||
//runtime.
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio_util::context::RuntimeExt;
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
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 {
|
||||
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
|
||||
println!("addr: {:?}", listener.local_addr());
|
||||
tx.send(()).unwrap();
|
||||
}));
|
||||
futures::executor::block_on(rx).unwrap();
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// This example shows how to use the tokio runtime with any other executor
|
||||
//
|
||||
// The main components are a spawn fn that will wrap futures in a special future
|
||||
// that will always enter the tokio context on poll. This only spawns one extra thread
|
||||
// to manage and run the tokio drivers in the background.
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
my_custom_runtime::spawn(async move {
|
||||
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
|
||||
|
||||
println!("addr: {:?}", listener.local_addr());
|
||||
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
futures::executor::block_on(rx).unwrap();
|
||||
}
|
||||
|
||||
mod my_custom_runtime {
|
||||
use once_cell::sync::Lazy;
|
||||
use std::future::Future;
|
||||
use tokio_util::context::TokioContext;
|
||||
|
||||
pub fn spawn(f: impl Future<Output = ()> + Send + 'static) {
|
||||
EXECUTOR.spawn(f);
|
||||
}
|
||||
|
||||
struct ThreadPool {
|
||||
inner: futures::executor::ThreadPool,
|
||||
rt: tokio::runtime::Runtime,
|
||||
}
|
||||
|
||||
static EXECUTOR: Lazy<ThreadPool> = Lazy::new(|| {
|
||||
// Spawn tokio runtime on a single background thread
|
||||
// enabling IO and timers.
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let inner = futures::executor::ThreadPool::builder().create().unwrap();
|
||||
|
||||
ThreadPool { inner, rt }
|
||||
});
|
||||
|
||||
impl ThreadPool {
|
||||
fn spawn(&self, f: impl Future<Output = ()> + Send + 'static) {
|
||||
let handle = self.rt.handle().clone();
|
||||
self.inner.spawn_ok(TokioContext::new(f, handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ struct Server {
|
||||
impl Server {
|
||||
async fn run(self) -> Result<(), io::Error> {
|
||||
let Server {
|
||||
socket,
|
||||
mut socket,
|
||||
mut buf,
|
||||
mut to_send,
|
||||
} = self;
|
||||
|
||||
+2
-2
@@ -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 listener = TcpListener::bind(&addr).await?;
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
@@ -55,7 +55,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// which will allow all of our clients to be processed concurrently.
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = vec![0; 1024];
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{BytesCodec, Decoder};
|
||||
|
||||
use std::env;
|
||||
@@ -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 listener = TcpListener::bind(&addr).await?;
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
|
||||
+3
-2
@@ -26,6 +26,7 @@ use tokio::io;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use futures::future::try_join;
|
||||
use futures::FutureExt;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
@@ -42,7 +43,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
let listener = TcpListener::bind(listen_addr).await?;
|
||||
let mut listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
|
||||
@@ -73,7 +74,7 @@ async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<
|
||||
wi.shutdown().await
|
||||
};
|
||||
|
||||
tokio::try_join!(client_to_server, server_to_client)?;
|
||||
try_join(client_to_server, server_to_client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{Framed, LinesCodec};
|
||||
|
||||
use futures::SinkExt;
|
||||
@@ -89,7 +89,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Create the shared state of this server that will be shared amongst all
|
||||
|
||||
@@ -20,7 +20,7 @@ use http::{header::HeaderValue, Request, Response, StatusCode};
|
||||
extern crate serde_derive;
|
||||
use std::{env, error::Error, fmt, io};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed};
|
||||
|
||||
#[tokio::main]
|
||||
@@ -30,17 +30,19 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let server = TcpListener::bind(&addr).await?;
|
||||
let mut server = TcpListener::bind(&addr).await?;
|
||||
let mut incoming = server.incoming();
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
let (stream, _) = server.accept().await?;
|
||||
while let Some(Ok(stream)) = incoming.next().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 socket = UdpSocket::bind(local_addr).await?;
|
||||
let mut socket = UdpSocket::bind(local_addr).await?;
|
||||
const MAX_DATAGRAM_SIZE: usize = 65_507;
|
||||
socket.connect(&remote_addr).await?;
|
||||
let data = get_stdin_data()?;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio::{io, time};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_util::codec::BytesCodec;
|
||||
use tokio_util::udp::UdpFramed;
|
||||
|
||||
@@ -45,7 +45,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let b = pong(&mut b);
|
||||
|
||||
// Run both futures simultaneously of `a` and `b` sending messages back and forth.
|
||||
match tokio::try_join!(a, b) {
|
||||
match futures::future::try_join(a, b).await {
|
||||
Err(e) => println!("an error occurred; error = {:?}", e),
|
||||
_ => println!("done!"),
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "stress-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio/", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
@@ -1,58 +0,0 @@
|
||||
//! Simple TCP echo server to check memory leaks using Valgrind.
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{TcpListener, TcpSocket},
|
||||
runtime::Builder,
|
||||
sync::oneshot,
|
||||
};
|
||||
|
||||
const TCP_ENDPOINT: &str = "127.0.0.1:8080";
|
||||
const NUM_MSGS: usize = 100;
|
||||
const MSG_SIZE: usize = 1024;
|
||||
|
||||
fn main() {
|
||||
let rt = Builder::new_multi_thread().enable_io().build().unwrap();
|
||||
let rt2 = Builder::new_multi_thread().enable_io().build().unwrap();
|
||||
|
||||
rt.spawn(async {
|
||||
let listener = TcpListener::bind(TCP_ENDPOINT).await.unwrap();
|
||||
let (mut socket, _) = listener.accept().await.unwrap();
|
||||
let (mut rd, mut wr) = socket.split();
|
||||
while tokio::io::copy(&mut rd, &mut wr).await.is_ok() {}
|
||||
});
|
||||
|
||||
// wait a bit so that the listener binds.
|
||||
sleep(Duration::from_millis(100));
|
||||
|
||||
// create a channel to let the main thread know that all the messages were sent and received.
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
rt2.spawn(async {
|
||||
let addr = TCP_ENDPOINT.parse().unwrap();
|
||||
let socket = TcpSocket::new_v4().unwrap();
|
||||
let mut stream = socket.connect(addr).await.unwrap();
|
||||
|
||||
let mut buff = [0; MSG_SIZE];
|
||||
for _ in 0..NUM_MSGS {
|
||||
let one_mega_random_bytes: Vec<u8> =
|
||||
(0..MSG_SIZE).map(|_| rand::random::<u8>()).collect();
|
||||
stream
|
||||
.write_all(one_mega_random_bytes.as_slice())
|
||||
.await
|
||||
.unwrap();
|
||||
stream.read(&mut buff).await.unwrap();
|
||||
}
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
loop {
|
||||
// check that we're done.
|
||||
match rx.try_recv() {
|
||||
Err(oneshot::error::TryRecvError::Empty) => (),
|
||||
Err(oneshot::error::TryRecvError::Closed) => panic!("channel got closed..."),
|
||||
Ok(()) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ publish = false
|
||||
|
||||
[features]
|
||||
full = ["tokio/full"]
|
||||
rt = ["tokio/rt", "tokio/macros"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", optional = true }
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn my_fn() {}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,7 +0,0 @@
|
||||
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 one of: `flavor`, `worker_threads`, `start_paused`
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
--> $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 one of: `flavor`, `worker_threads`, `start_paused`
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
--> $DIR/macros_invalid_input.rs:18:15
|
||||
|
|
||||
18 | #[tokio::test(foo)]
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
#[test]
|
||||
fn compile_fail_full() {
|
||||
fn compile_fail() {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -5,32 +5,24 @@ authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "test-cat"
|
||||
|
||||
[[bin]]
|
||||
name = "test-mem"
|
||||
required-features = ["rt-net"]
|
||||
|
||||
[features]
|
||||
# For mem check
|
||||
rt-net = ["tokio/rt", "tokio/rt-multi-thread", "tokio/net"]
|
||||
|
||||
full = [
|
||||
"macros",
|
||||
"rt",
|
||||
"rt-multi-thread",
|
||||
"rt-core",
|
||||
"rt-threaded",
|
||||
|
||||
"tokio/full",
|
||||
"tokio-test"
|
||||
]
|
||||
macros = ["tokio/macros"]
|
||||
sync = ["tokio/sync"]
|
||||
rt = ["tokio/rt"]
|
||||
rt-multi-thread = ["rt", "tokio/rt-multi-thread"]
|
||||
rt-core = ["tokio/rt-core"]
|
||||
rt-threaded = ["rt-core", "tokio/rt-threaded"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio" }
|
||||
tokio-test = { path = "../tokio-test", optional = true }
|
||||
doc-comment = "0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
use futures::future::poll_fn;
|
||||
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.enable_io()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(async {
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:0").await.unwrap();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
poll_fn(|cx| listener.poll_accept(cx)).await.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
drop(rt);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg(all(feature = "macros", feature = "rt"))]
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
#[tokio::main]
|
||||
async fn basic_main() -> usize {
|
||||
@@ -10,15 +10,18 @@ async fn generic_fun<T: Default>() -> T {
|
||||
T::default()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn spawning() -> usize {
|
||||
let join = tokio::spawn(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
#[cfg(feature = "rt-core")]
|
||||
mod spawn {
|
||||
#[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]
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::join;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio_test::assert_ok;
|
||||
|
||||
use futures::future::{self, FutureExt};
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
|
||||
fn cat() -> Command {
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_test-cat"));
|
||||
let mut me = env::current_exe().unwrap();
|
||||
me.pop();
|
||||
|
||||
if me.ends_with("deps") {
|
||||
me.pop();
|
||||
}
|
||||
|
||||
me.push("test-cat");
|
||||
|
||||
let mut cmd = Command::new(me);
|
||||
cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
cmd
|
||||
}
|
||||
@@ -65,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.wait())
|
||||
future::join3(write, read, cat)
|
||||
.map(|(_, _, status)| status)
|
||||
.await
|
||||
}
|
||||
@@ -118,75 +125,3 @@ 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);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pipe_from_one_command_to_another() {
|
||||
let mut first = cat().spawn().expect("first cmd");
|
||||
let mut third = cat().spawn().expect("third cmd");
|
||||
|
||||
// Convert ChildStdout to Stdio
|
||||
let second_stdin: Stdio = first
|
||||
.stdout
|
||||
.take()
|
||||
.expect("first.stdout")
|
||||
.try_into()
|
||||
.expect("first.stdout into Stdio");
|
||||
|
||||
// Convert ChildStdin to Stdio
|
||||
let second_stdout: Stdio = third
|
||||
.stdin
|
||||
.take()
|
||||
.expect("third.stdin")
|
||||
.try_into()
|
||||
.expect("third.stdin into Stdio");
|
||||
|
||||
let mut second = cat()
|
||||
.stdin(second_stdin)
|
||||
.stdout(second_stdout)
|
||||
.spawn()
|
||||
.expect("first cmd");
|
||||
|
||||
let msg = "hello world! please pipe this message through";
|
||||
|
||||
let mut stdin = first.stdin.take().expect("first.stdin");
|
||||
let write = async move { stdin.write_all(msg.as_bytes()).await };
|
||||
|
||||
let mut stdout = third.stdout.take().expect("third.stdout");
|
||||
let read = async move {
|
||||
let mut data = String::new();
|
||||
stdout.read_to_string(&mut data).await.map(|_| data)
|
||||
};
|
||||
|
||||
let (read, write, first_status, second_status, third_status) =
|
||||
join!(read, write, first.wait(), second.wait(), third.wait());
|
||||
|
||||
assert_eq!(msg, read.expect("read result"));
|
||||
write.expect("write result");
|
||||
|
||||
assert!(first_status.expect("first status").success());
|
||||
assert!(second_status.expect("second status").success());
|
||||
assert!(third_status.expect("third status").success());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#![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,21 +1,3 @@
|
||||
# 1.0.0 (December 23, 2020)
|
||||
|
||||
- track `tokio` 1.0 release.
|
||||
|
||||
# 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
|
||||
@@ -48,10 +30,9 @@
|
||||
|
||||
- Initial release
|
||||
|
||||
[#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
|
||||
[#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
|
||||
|
||||
@@ -6,14 +6,14 @@ name = "tokio-macros"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-macros-1.0.x" git tag.
|
||||
version = "1.0.0"
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.2.5"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-macros/1.0.0/tokio_macros"
|
||||
documentation = "https://docs.rs/tokio-macros/0.2.5/tokio_macros"
|
||||
description = """
|
||||
Tokio's proc macros.
|
||||
"""
|
||||
@@ -30,7 +30,7 @@ quote = "1"
|
||||
syn = { version = "1.0.3", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
+240
-226
@@ -1,183 +1,18 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum RuntimeFlavor {
|
||||
CurrentThread,
|
||||
enum Runtime {
|
||||
Basic,
|
||||
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>,
|
||||
start_paused: Option<bool>,
|
||||
}
|
||||
|
||||
struct Configuration {
|
||||
rt_multi_thread_available: bool,
|
||||
default_flavor: RuntimeFlavor,
|
||||
flavor: Option<RuntimeFlavor>,
|
||||
worker_threads: Option<(usize, Span)>,
|
||||
start_paused: Option<(bool, 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,
|
||||
start_paused: 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 set_start_paused(&mut self, start_paused: syn::Lit, span: Span) -> Result<(), syn::Error> {
|
||||
if self.start_paused.is_some() {
|
||||
return Err(syn::Error::new(span, "`start_paused` set multiple times."));
|
||||
}
|
||||
|
||||
let start_paused = parse_bool(start_paused, span, "start_paused")?;
|
||||
self.start_paused = Some((start_paused, span));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(&self) -> Result<FinalConfig, syn::Error> {
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
use RuntimeFlavor::*;
|
||||
|
||||
let worker_threads = match (flavor, self.worker_threads) {
|
||||
(CurrentThread, Some((_, worker_threads_span))) => {
|
||||
return Err(syn::Error::new(
|
||||
worker_threads_span,
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor.",
|
||||
))
|
||||
}
|
||||
(CurrentThread, None) => None,
|
||||
(Threaded, worker_threads) if self.rt_multi_thread_available => {
|
||||
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."
|
||||
};
|
||||
return Err(syn::Error::new(Span::call_site(), msg));
|
||||
}
|
||||
};
|
||||
|
||||
let start_paused = match (flavor, self.start_paused) {
|
||||
(Threaded, Some((_, start_paused_span))) => {
|
||||
return Err(syn::Error::new(
|
||||
start_paused_span,
|
||||
"The `start_paused` option requires the `current_thread` runtime flavor.",
|
||||
));
|
||||
}
|
||||
(CurrentThread, Some((start_paused, _))) => Some(start_paused),
|
||||
(_, None) => None,
|
||||
};
|
||||
|
||||
Ok(FinalConfig {
|
||||
flavor,
|
||||
worker_threads,
|
||||
start_paused,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Error> {
|
||||
match bool {
|
||||
syn::Lit::Bool(b) => Ok(b.value),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as bool.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_knobs(
|
||||
mut input: syn::ItemFn,
|
||||
args: syn::AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_multi_thread: bool,
|
||||
rt_threaded: bool,
|
||||
) -> Result<TokenStream, syn::Error> {
|
||||
let sig = &mut input.sig;
|
||||
let body = &input.block;
|
||||
@@ -191,12 +26,9 @@ fn parse_knobs(
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let macro_name = if is_test {
|
||||
"tokio::test"
|
||||
} else {
|
||||
"tokio::main"
|
||||
};
|
||||
let mut config = Configuration::new(is_test, rt_multi_thread);
|
||||
let mut runtime = None;
|
||||
let mut core_threads = None;
|
||||
let mut max_threads = None;
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
@@ -207,21 +39,65 @@ fn parse_knobs(
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"worker_threads" => {
|
||||
config.set_worker_threads(namevalue.lit.clone(), namevalue.span())?;
|
||||
}
|
||||
"flavor" => {
|
||||
config.set_flavor(namevalue.lit.clone(), namevalue.span())?;
|
||||
}
|
||||
"start_paused" => {
|
||||
config.set_start_paused(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));
|
||||
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",
|
||||
));
|
||||
}
|
||||
}
|
||||
"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 {} is specified; expected one of: `flavor`, `worker_threads`", name);
|
||||
let msg = format!("Unknown attribute pair {} is specified; expected one of: `core_threads`, `max_threads`", name);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
}
|
||||
@@ -232,28 +108,16 @@ fn parse_knobs(
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
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" | "start_paused" => {
|
||||
format!("The `{}` attribute requires an argument.", name)
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => {
|
||||
runtime = Some(runtime.unwrap_or_else(|| Runtime::Threaded))
|
||||
}
|
||||
"basic_scheduler" => runtime = Some(runtime.unwrap_or_else(|| Runtime::Basic)),
|
||||
name => {
|
||||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`", name)
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
@@ -264,21 +128,15 @@ fn parse_knobs(
|
||||
}
|
||||
}
|
||||
|
||||
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 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) = config.start_paused {
|
||||
rt = quote! { #rt.start_paused(#v) };
|
||||
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 header = {
|
||||
@@ -299,7 +157,7 @@ fn parse_knobs(
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async #body)
|
||||
.block_on(async { #body })
|
||||
}
|
||||
};
|
||||
|
||||
@@ -307,21 +165,21 @@ fn parse_knobs(
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_threaded: 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.ident, msg)
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
parse_knobs(input, args, false, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
@@ -341,5 +199,161 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool)
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().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()
|
||||
}
|
||||
}
|
||||
|
||||
+175
-134
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/1.0.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.2.5")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
# or
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html) directly.
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
/// ## Options:
|
||||
///
|
||||
/// 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.
|
||||
/// If you want to set the number of worker threads used for asynchronous code, use the
|
||||
/// `core_threads` option.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
|
||||
/// flag.
|
||||
///
|
||||
/// # 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() {}
|
||||
/// ```
|
||||
/// - `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`).
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
@@ -72,7 +43,7 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using the multi-thread runtime
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
@@ -85,7 +56,8 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_multi_thread()
|
||||
/// tokio::runtime::Builder::new()
|
||||
/// .threaded_scheduler()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
@@ -95,12 +67,12 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using current thread runtime
|
||||
/// ### Using basic scheduler
|
||||
///
|
||||
/// The basic scheduler is single-threaded.
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// #[tokio::main(basic_scheduler)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
@@ -110,7 +82,8 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// tokio::runtime::Builder::new()
|
||||
/// .basic_scheduler()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
@@ -120,10 +93,10 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Set number of worker threads
|
||||
/// ### Set number of core threads
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(worker_threads = 2)]
|
||||
/// #[tokio::main(core_threads = 2)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
@@ -133,8 +106,9 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_multi_thread()
|
||||
/// .worker_threads(2)
|
||||
/// tokio::runtime::Builder::new()
|
||||
/// .threaded_scheduler()
|
||||
/// .core_threads(2)
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
@@ -144,39 +118,16 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Configure the runtime to start with time paused
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "current_thread", start_paused = true)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .start_paused(true)
|
||||
/// .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 current version of Tokio because you're also using an
|
||||
/// older version of Tokio, you _must_ make the current version of Tokio
|
||||
/// 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.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(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, true)
|
||||
}
|
||||
|
||||
@@ -184,6 +135,11 @@ pub fn main(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
|
||||
@@ -193,7 +149,7 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
@@ -203,7 +159,29 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// 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()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
@@ -215,24 +193,81 @@ pub fn main(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 current version of Tokio because you're also using an
|
||||
/// older version of Tokio, you _must_ make the current version of Tokio
|
||||
/// 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.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_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
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 {
|
||||
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
|
||||
///
|
||||
/// ### Multi-thread runtime
|
||||
/// ### Select runtime
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
/// #[tokio::test(core_threads = 1)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
@@ -240,7 +275,44 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// The default test runtime is single-threaded.
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### 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_threaded(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]
|
||||
@@ -249,28 +321,24 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Configure the runtime to start with time paused
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(start_paused = true)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the Tokio crate in your dependencies this macro will not work.
|
||||
/// If you must rename the current version of Tokio because you're also using an
|
||||
/// older version of Tokio, you _must_ make the current version of Tokio
|
||||
/// 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.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(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, true)
|
||||
entry::old::test(args, item)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ```no_run
|
||||
@@ -282,43 +350,16 @@ pub fn test(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 current version of Tokio because you're also using an
|
||||
/// older version of Tokio, you _must_ make the current version of Tokio
|
||||
/// 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.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_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn test_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
}
|
||||
|
||||
/// Always fails with the error message below.
|
||||
/// ```text
|
||||
/// The #[tokio::main] macro requires rt or rt-multi-thread.
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
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
|
||||
/// to be used as part of the public API and is permitted to change.
|
||||
#[proc_macro]
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# 0.1.3 (February 5, 2021)
|
||||
|
||||
Added
|
||||
|
||||
- sync: add wrapper for broadcast and watch ([#3384], [#3504])
|
||||
|
||||
[#3384]: https://github.com/tokio-rs/tokio/pull/3384
|
||||
[#3504]: https://github.com/tokio-rs/tokio/pull/3504
|
||||
|
||||
# 0.1.2 (January 12, 2021)
|
||||
|
||||
Fixed
|
||||
|
||||
- docs: fix some wrappers missing in documentation ([#3378])
|
||||
|
||||
[#3378]: https://github.com/tokio-rs/tokio/pull/3378
|
||||
|
||||
# 0.1.1 (January 4, 2021)
|
||||
|
||||
Added
|
||||
|
||||
- add `Stream` wrappers ([#3343])
|
||||
|
||||
Fixed
|
||||
|
||||
- move `async-stream` to `dev-dependencies` ([#3366])
|
||||
|
||||
[#3366]: https://github.com/tokio-rs/tokio/pull/3366
|
||||
[#3343]: https://github.com/tokio-rs/tokio/pull/3343
|
||||
|
||||
# 0.1.0 (December 23, 2020)
|
||||
|
||||
- Initial release
|
||||
@@ -1,46 +0,0 @@
|
||||
[package]
|
||||
name = "tokio-stream"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-stream-0.1.x" git tag.
|
||||
version = "0.1.3"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-stream/0.1.3/tokio_stream"
|
||||
description = """
|
||||
Utilities to work with `Stream` and `tokio`.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[features]
|
||||
default = ["time"]
|
||||
time = ["tokio/time"]
|
||||
net = ["tokio/net"]
|
||||
io-util = ["tokio/io-util"]
|
||||
fs = ["tokio/fs"]
|
||||
sync = ["tokio/sync", "tokio-util"]
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.0", features = ["sync"] }
|
||||
tokio-util = { version = "0.6.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0", features = ["full", "test-util"] }
|
||||
async-stream = "0.3"
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
proptest = "0.10.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
@@ -1,25 +0,0 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,105 +0,0 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-stream/0.1.3")]
|
||||
#![allow(
|
||||
clippy::cognitive_complexity,
|
||||
clippy::large_enum_variant,
|
||||
clippy::needless_doctest_main
|
||||
)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
//! Stream utilities for Tokio.
|
||||
//!
|
||||
//! A `Stream` is an asynchronous sequence of values. It can be thought of as
|
||||
//! an asynchronous version of the standard library's `Iterator` trait.
|
||||
//!
|
||||
//! This crate provides helpers to work with them. For examples of usage and a more in-depth
|
||||
//! description of streams you can also refer to the [streams
|
||||
//! tutorial](https://tokio.rs/tokio/tutorial/streams) on the tokio website.
|
||||
//!
|
||||
//! # Iterating over a Stream
|
||||
//!
|
||||
//! Due to similarities with the standard library's `Iterator` trait, some new
|
||||
//! users may assume that they can use `for in` syntax to iterate over a
|
||||
//! `Stream`, but this is unfortunately not possible. Instead, you can use a
|
||||
//! `while let` loop as follows:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use tokio_stream::{self as stream, StreamExt};
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let mut stream = stream::iter(vec![0, 1, 2]);
|
||||
//!
|
||||
//! while let Some(value) = stream.next().await {
|
||||
//! println!("Got {}", value);
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Returning a Stream from a function
|
||||
//!
|
||||
//! A common way to stream values from a function is to pass in the sender
|
||||
//! half of a channel and use the receiver as the stream. This requires awaiting
|
||||
//! both futures to ensure progress is made. Another alternative is the
|
||||
//! [async-stream] crate, which contains macros that provide a `yield` keyword
|
||||
//! and allow you to return an `impl Stream`.
|
||||
//!
|
||||
//! [async-stream]: https://docs.rs/async-stream
|
||||
//!
|
||||
//! # Conversion to and from AsyncRead/AsyncWrite
|
||||
//!
|
||||
//! It is often desirable to convert a `Stream` into an [`AsyncRead`],
|
||||
//! especially when dealing with plaintext formats streamed over the network.
|
||||
//! The opposite conversion from an [`AsyncRead`] into a `Stream` is also
|
||||
//! another commonly required feature. To enable these conversions,
|
||||
//! [`tokio-util`] provides the [`StreamReader`] and [`ReaderStream`]
|
||||
//! types when the io feature is enabled.
|
||||
//!
|
||||
//! [`tokio-util`]: https://docs.rs/tokio-util/0.4/tokio_util/codec/index.html
|
||||
//! [`tokio::io`]: https://docs.rs/tokio/1.0/tokio/io/index.html
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/1.0/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/1.0/tokio/io/trait.AsyncWrite.html
|
||||
//! [`ReaderStream`]: https://docs.rs/tokio-util/0.4/tokio_util/io/struct.ReaderStream.html
|
||||
//! [`StreamReader`]: https://docs.rs/tokio-util/0.4/tokio_util/io/struct.StreamReader.html
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod wrappers;
|
||||
|
||||
mod stream_ext;
|
||||
pub use stream_ext::{collect::FromStream, StreamExt};
|
||||
|
||||
mod empty;
|
||||
pub use empty::{empty, Empty};
|
||||
|
||||
mod iter;
|
||||
pub use iter::{iter, Iter};
|
||||
|
||||
mod once;
|
||||
pub use once::{once, Once};
|
||||
|
||||
mod pending;
|
||||
pub use pending::{pending, Pending};
|
||||
|
||||
mod stream_map;
|
||||
pub use stream_map::StreamMap;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use futures_core::Stream;
|
||||
@@ -1,58 +0,0 @@
|
||||
macro_rules! cfg_fs {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "fs")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_io_util {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "io-util")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_net {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "net")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_time {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_sync {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "sync")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ready {
|
||||
($e:expr $(,)?) => {
|
||||
match $e {
|
||||
std::task::Poll::Ready(t) => t,
|
||||
std::task::Poll::Pending => return std::task::Poll::Pending,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Future for the [`all`](super::StreamExt::all) method.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct AllFuture<'a, St: ?Sized, F> {
|
||||
stream: &'a mut St,
|
||||
f: F,
|
||||
// Make this future `!Unpin` for compatibility with async trait methods.
|
||||
#[pin]
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, St: ?Sized, F> AllFuture<'a, St, F> {
|
||||
pub(super) fn new(stream: &'a mut St, f: F) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
f,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F> Future for AllFuture<'_, St, F>
|
||||
where
|
||||
St: ?Sized + Stream + Unpin,
|
||||
F: FnMut(St::Item) -> bool,
|
||||
{
|
||||
type Output = bool;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = self.project();
|
||||
let next = futures_core::ready!(Pin::new(me.stream).poll_next(cx));
|
||||
|
||||
match next {
|
||||
Some(v) => {
|
||||
if !(me.f)(v) {
|
||||
Poll::Ready(false)
|
||||
} else {
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
None => Poll::Ready(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Future for the [`any`](super::StreamExt::any) method.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct AnyFuture<'a, St: ?Sized, F> {
|
||||
stream: &'a mut St,
|
||||
f: F,
|
||||
// Make this future `!Unpin` for compatibility with async trait methods.
|
||||
#[pin]
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, St: ?Sized, F> AnyFuture<'a, St, F> {
|
||||
pub(super) fn new(stream: &'a mut St, f: F) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
f,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, F> Future for AnyFuture<'_, St, F>
|
||||
where
|
||||
St: ?Sized + Stream + Unpin,
|
||||
F: FnMut(St::Item) -> bool,
|
||||
{
|
||||
type Output = bool;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = self.project();
|
||||
let next = futures_core::ready!(Pin::new(me.stream).poll_next(cx));
|
||||
|
||||
match next {
|
||||
Some(v) => {
|
||||
if (me.f)(v) {
|
||||
Poll::Ready(true)
|
||||
} else {
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
None => Poll::Ready(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Future for the [`next`](super::StreamExt::next) method.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Next<'a, St: ?Sized> {
|
||||
stream: &'a mut St,
|
||||
// Make this future `!Unpin` for compatibility with async trait methods.
|
||||
#[pin]
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, St: ?Sized> Next<'a, St> {
|
||||
pub(super) fn new(stream: &'a mut St) -> Self {
|
||||
Next {
|
||||
stream,
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<St: ?Sized + Stream + Unpin> Future for Next<'_, St> {
|
||||
type Output = Option<St::Item>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = self.project();
|
||||
Pin::new(me.stream).poll_next(cx)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use crate::stream_ext::Next;
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
/// Future for the [`try_next`](super::StreamExt::try_next) method.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct TryNext<'a, St: ?Sized> {
|
||||
#[pin]
|
||||
inner: Next<'a, St>,
|
||||
// Make this future `!Unpin` for compatibility with async trait methods.
|
||||
#[pin]
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, St: ?Sized> TryNext<'a, St> {
|
||||
pub(super) fn new(stream: &'a mut St) -> Self {
|
||||
Self {
|
||||
inner: Next::new(stream),
|
||||
_pin: PhantomPinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E, St: ?Sized + Stream<Item = Result<T, E>> + Unpin> Future for TryNext<'_, St> {
|
||||
type Output = Result<Option<T>, E>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = self.project();
|
||||
me.inner.poll(cx).map(Option::transpose)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
//! Wrappers for Tokio types that implement `Stream`.
|
||||
|
||||
/// Error types for the wrappers.
|
||||
pub mod errors {
|
||||
cfg_sync! {
|
||||
pub use crate::wrappers::broadcast::BroadcastStreamRecvError;
|
||||
}
|
||||
}
|
||||
|
||||
mod mpsc_bounded;
|
||||
pub use mpsc_bounded::ReceiverStream;
|
||||
|
||||
mod mpsc_unbounded;
|
||||
pub use mpsc_unbounded::UnboundedReceiverStream;
|
||||
|
||||
cfg_sync! {
|
||||
mod broadcast;
|
||||
pub use broadcast::BroadcastStream;
|
||||
|
||||
mod watch;
|
||||
pub use watch::WatchStream;
|
||||
}
|
||||
|
||||
cfg_time! {
|
||||
mod interval;
|
||||
pub use interval::IntervalStream;
|
||||
}
|
||||
|
||||
cfg_net! {
|
||||
mod tcp_listener;
|
||||
pub use tcp_listener::TcpListenerStream;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix_listener;
|
||||
#[cfg(unix)]
|
||||
pub use unix_listener::UnixListenerStream;
|
||||
}
|
||||
|
||||
cfg_io_util! {
|
||||
mod split;
|
||||
pub use split::SplitStream;
|
||||
|
||||
mod lines;
|
||||
pub use lines::LinesStream;
|
||||
}
|
||||
|
||||
cfg_fs! {
|
||||
mod read_dir;
|
||||
pub use read_dir::ReadDirStream;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use std::pin::Pin;
|
||||
use tokio::sync::broadcast::error::RecvError;
|
||||
use tokio::sync::broadcast::Receiver;
|
||||
|
||||
use futures_core::Stream;
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
|
||||
use std::fmt;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// A wrapper around [`tokio::sync::broadcast::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// [`tokio::sync::broadcast::Receiver`]: struct@tokio::sync::broadcast::Receiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
|
||||
pub struct BroadcastStream<T> {
|
||||
inner: ReusableBoxFuture<(Result<T, RecvError>, Receiver<T>)>,
|
||||
}
|
||||
|
||||
/// An error returned from the inner stream of a [`BroadcastStream`].
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BroadcastStreamRecvError {
|
||||
/// The receiver lagged too far behind. Attempting to receive again will
|
||||
/// return the oldest message still retained by the channel.
|
||||
///
|
||||
/// Includes the number of skipped messages.
|
||||
Lagged(u64),
|
||||
}
|
||||
|
||||
async fn make_future<T: Clone>(mut rx: Receiver<T>) -> (Result<T, RecvError>, Receiver<T>) {
|
||||
let result = rx.recv().await;
|
||||
(result, rx)
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone + Send> BroadcastStream<T> {
|
||||
/// Create a new `BroadcastStream`.
|
||||
pub fn new(rx: Receiver<T>) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(make_future(rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone + Send> Stream for BroadcastStream<T> {
|
||||
type Item = Result<T, BroadcastStreamRecvError>;
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let (result, rx) = ready!(self.inner.poll(cx));
|
||||
self.inner.set(make_future(rx));
|
||||
match result {
|
||||
Ok(item) => Poll::Ready(Some(Ok(item))),
|
||||
Err(RecvError::Closed) => Poll::Ready(None),
|
||||
Err(RecvError::Lagged(n)) => {
|
||||
Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(n))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for BroadcastStream<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BroadcastStream").finish()
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
use crate::Stream;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::time::{Instant, Interval};
|
||||
|
||||
/// A wrapper around [`Interval`] that implements [`Stream`].
|
||||
///
|
||||
/// [`Interval`]: struct@tokio::time::Interval
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
pub struct IntervalStream {
|
||||
inner: Interval,
|
||||
}
|
||||
|
||||
impl IntervalStream {
|
||||
/// Create a new `IntervalStream`.
|
||||
pub fn new(interval: Interval) -> Self {
|
||||
Self { inner: interval }
|
||||
}
|
||||
|
||||
/// Get back the inner `Interval`.
|
||||
pub fn into_inner(self) -> Interval {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for IntervalStream {
|
||||
type Item = Instant;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Instant>> {
|
||||
self.inner.poll_tick(cx).map(Some)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(std::usize::MAX, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Interval> for IntervalStream {
|
||||
fn as_ref(&self) -> &Interval {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<Interval> for IntervalStream {
|
||||
fn as_mut(&mut self) -> &mut Interval {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use crate::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncBufRead, Lines};
|
||||
|
||||
pin_project! {
|
||||
/// A wrapper around [`tokio::io::Lines`] that implements [`Stream`].
|
||||
///
|
||||
/// [`tokio::io::Lines`]: struct@tokio::io::Lines
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
|
||||
pub struct LinesStream<R> {
|
||||
#[pin]
|
||||
inner: Lines<R>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> LinesStream<R> {
|
||||
/// Create a new `LinesStream`.
|
||||
pub fn new(lines: Lines<R>) -> Self {
|
||||
Self { inner: lines }
|
||||
}
|
||||
|
||||
/// Get back the inner `Lines`.
|
||||
pub fn into_inner(self) -> Lines<R> {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Obtain a pinned reference to the inner `Lines<R>`.
|
||||
#[allow(clippy::wrong_self_convention)] // https://github.com/rust-lang/rust-clippy/issues/4546
|
||||
pub fn as_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Lines<R>> {
|
||||
self.project().inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncBufRead> Stream for LinesStream<R> {
|
||||
type Item = io::Result<String>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.project()
|
||||
.inner
|
||||
.poll_next_line(cx)
|
||||
.map(Result::transpose)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AsRef<Lines<R>> for LinesStream<R> {
|
||||
fn as_ref(&self) -> &Lines<R> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AsMut<Lines<R>> for LinesStream<R> {
|
||||
fn as_mut(&mut self) -> &mut Lines<R> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
use crate::Stream;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
|
||||
/// A wrapper around [`tokio::sync::mpsc::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// [`tokio::sync::mpsc::Receiver`]: struct@tokio::sync::mpsc::Receiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
pub struct ReceiverStream<T> {
|
||||
inner: Receiver<T>,
|
||||
}
|
||||
|
||||
impl<T> ReceiverStream<T> {
|
||||
/// Create a new `ReceiverStream`.
|
||||
pub fn new(recv: Receiver<T>) -> Self {
|
||||
Self { inner: recv }
|
||||
}
|
||||
|
||||
/// Get back the inner `Receiver`.
|
||||
pub fn into_inner(self) -> Receiver<T> {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Closes the receiving half of a channel without dropping it.
|
||||
///
|
||||
/// This prevents any further messages from being sent on the channel while
|
||||
/// still enabling the receiver to drain messages that are buffered. Any
|
||||
/// outstanding [`Permit`] values will still be able to send messages.
|
||||
///
|
||||
/// To guarantee no messages are dropped, after calling `close()`, you must
|
||||
/// receive all items from the stream until `None` is returned.
|
||||
///
|
||||
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Stream for ReceiverStream<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<Receiver<T>> for ReceiverStream<T> {
|
||||
fn as_ref(&self) -> &Receiver<T> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<Receiver<T>> for ReceiverStream<T> {
|
||||
fn as_mut(&mut self) -> &mut Receiver<T> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
use crate::Stream;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
|
||||
/// A wrapper around [`tokio::sync::mpsc::UnboundedReceiver`] that implements [`Stream`].
|
||||
///
|
||||
/// [`tokio::sync::mpsc::UnboundedReceiver`]: struct@tokio::sync::mpsc::UnboundedReceiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
pub struct UnboundedReceiverStream<T> {
|
||||
inner: UnboundedReceiver<T>,
|
||||
}
|
||||
|
||||
impl<T> UnboundedReceiverStream<T> {
|
||||
/// Create a new `UnboundedReceiverStream`.
|
||||
pub fn new(recv: UnboundedReceiver<T>) -> Self {
|
||||
Self { inner: recv }
|
||||
}
|
||||
|
||||
/// Get back the inner `UnboundedReceiver`.
|
||||
pub fn into_inner(self) -> UnboundedReceiver<T> {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Closes the receiving half of a channel without dropping it.
|
||||
///
|
||||
/// This prevents any further messages from being sent on the channel while
|
||||
/// still enabling the receiver to drain messages that are buffered.
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Stream for UnboundedReceiverStream<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.inner.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<UnboundedReceiver<T>> for UnboundedReceiverStream<T> {
|
||||
fn as_ref(&self) -> &UnboundedReceiver<T> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<UnboundedReceiver<T>> for UnboundedReceiverStream<T> {
|
||||
fn as_mut(&mut self) -> &mut UnboundedReceiver<T> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use crate::Stream;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::fs::{DirEntry, ReadDir};
|
||||
|
||||
/// A wrapper around [`tokio::fs::ReadDir`] that implements [`Stream`].
|
||||
///
|
||||
/// [`tokio::fs::ReadDir`]: struct@tokio::fs::ReadDir
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
|
||||
pub struct ReadDirStream {
|
||||
inner: ReadDir,
|
||||
}
|
||||
|
||||
impl ReadDirStream {
|
||||
/// Create a new `ReadDirStream`.
|
||||
pub fn new(read_dir: ReadDir) -> Self {
|
||||
Self { inner: read_dir }
|
||||
}
|
||||
|
||||
/// Get back the inner `ReadDir`.
|
||||
pub fn into_inner(self) -> ReadDir {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for ReadDirStream {
|
||||
type Item = io::Result<DirEntry>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.inner.poll_next_entry(cx).map(Result::transpose)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<ReadDir> for ReadDirStream {
|
||||
fn as_ref(&self) -> &ReadDir {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<ReadDir> for ReadDirStream {
|
||||
fn as_mut(&mut self) -> &mut ReadDir {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use crate::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncBufRead, Split};
|
||||
|
||||
pin_project! {
|
||||
/// A wrapper around [`tokio::io::Split`] that implements [`Stream`].
|
||||
///
|
||||
/// [`tokio::io::Split`]: struct@tokio::io::Split
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
|
||||
pub struct SplitStream<R> {
|
||||
#[pin]
|
||||
inner: Split<R>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> SplitStream<R> {
|
||||
/// Create a new `SplitStream`.
|
||||
pub fn new(split: Split<R>) -> Self {
|
||||
Self { inner: split }
|
||||
}
|
||||
|
||||
/// Get back the inner `Split`.
|
||||
pub fn into_inner(self) -> Split<R> {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Obtain a pinned reference to the inner `Split<R>`.
|
||||
#[allow(clippy::wrong_self_convention)] // https://github.com/rust-lang/rust-clippy/issues/4546
|
||||
pub fn as_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Split<R>> {
|
||||
self.project().inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncBufRead> Stream for SplitStream<R> {
|
||||
type Item = io::Result<Vec<u8>>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.project()
|
||||
.inner
|
||||
.poll_next_segment(cx)
|
||||
.map(Result::transpose)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AsRef<Split<R>> for SplitStream<R> {
|
||||
fn as_ref(&self) -> &Split<R> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AsMut<Split<R>> for SplitStream<R> {
|
||||
fn as_mut(&mut self) -> &mut Split<R> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use crate::Stream;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
/// A wrapper around [`TcpListener`] that implements [`Stream`].
|
||||
///
|
||||
/// [`TcpListener`]: struct@tokio::net::TcpListener
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "net")))]
|
||||
pub struct TcpListenerStream {
|
||||
inner: TcpListener,
|
||||
}
|
||||
|
||||
impl TcpListenerStream {
|
||||
/// Create a new `TcpListenerStream`.
|
||||
pub fn new(listener: TcpListener) -> Self {
|
||||
Self { inner: listener }
|
||||
}
|
||||
|
||||
/// Get back the inner `TcpListener`.
|
||||
pub fn into_inner(self) -> TcpListener {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for TcpListenerStream {
|
||||
type Item = io::Result<TcpStream>;
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<io::Result<TcpStream>>> {
|
||||
match self.inner.poll_accept(cx) {
|
||||
Poll::Ready(Ok((stream, _))) => Poll::Ready(Some(Ok(stream))),
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Some(Err(err))),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<TcpListener> for TcpListenerStream {
|
||||
fn as_ref(&self) -> &TcpListener {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<TcpListener> for TcpListenerStream {
|
||||
fn as_mut(&mut self) -> &mut TcpListener {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use crate::Stream;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
|
||||
/// A wrapper around [`UnixListener`] that implements [`Stream`].
|
||||
///
|
||||
/// [`UnixListener`]: struct@tokio::net::UnixListener
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "net"))))]
|
||||
pub struct UnixListenerStream {
|
||||
inner: UnixListener,
|
||||
}
|
||||
|
||||
impl UnixListenerStream {
|
||||
/// Create a new `UnixListenerStream`.
|
||||
pub fn new(listener: UnixListener) -> Self {
|
||||
Self { inner: listener }
|
||||
}
|
||||
|
||||
/// Get back the inner `UnixListener`.
|
||||
pub fn into_inner(self) -> UnixListener {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for UnixListenerStream {
|
||||
type Item = io::Result<UnixStream>;
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<io::Result<UnixStream>>> {
|
||||
match self.inner.poll_accept(cx) {
|
||||
Poll::Ready(Ok((stream, _))) => Poll::Ready(Some(Ok(stream))),
|
||||
Poll::Ready(Err(err)) => Poll::Ready(Some(Err(err))),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<UnixListener> for UnixListenerStream {
|
||||
fn as_ref(&self) -> &UnixListener {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<UnixListener> for UnixListenerStream {
|
||||
fn as_mut(&mut self) -> &mut UnixListener {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
use std::pin::Pin;
|
||||
use tokio::sync::watch::Receiver;
|
||||
|
||||
use futures_core::Stream;
|
||||
use tokio_util::sync::ReusableBoxFuture;
|
||||
|
||||
use std::fmt;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::watch::error::RecvError;
|
||||
|
||||
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
|
||||
///
|
||||
/// [`tokio::sync::watch::Receiver`]: struct@tokio::sync::watch::Receiver
|
||||
/// [`Stream`]: trait@crate::Stream
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
|
||||
pub struct WatchStream<T> {
|
||||
inner: ReusableBoxFuture<(Result<(), RecvError>, Receiver<T>)>,
|
||||
}
|
||||
|
||||
async fn make_future<T: Clone + Send + Sync>(
|
||||
mut rx: Receiver<T>,
|
||||
) -> (Result<(), RecvError>, Receiver<T>) {
|
||||
let result = rx.changed().await;
|
||||
(result, rx)
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone + Unpin + Send + Sync> WatchStream<T> {
|
||||
/// Create a new `WatchStream`.
|
||||
pub fn new(rx: Receiver<T>) -> Self {
|
||||
Self {
|
||||
inner: ReusableBoxFuture::new(make_future(rx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static + Send + Sync> Stream for WatchStream<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let (result, rx) = ready!(self.inner.poll(cx));
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let received = (*rx.borrow()).clone();
|
||||
self.inner.set(make_future(rx));
|
||||
Poll::Ready(Some(received))
|
||||
}
|
||||
Err(_) => {
|
||||
self.inner.set(make_future(rx));
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Unpin for WatchStream<T> {}
|
||||
|
||||
impl<T> fmt::Debug for WatchStream<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("WatchStream").finish()
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
#[allow(dead_code)]
|
||||
type BoxStream<T> = std::pin::Pin<Box<dyn tokio_stream::Stream<Item = T>>>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn require_send<T: Send>(_t: &T) {}
|
||||
#[allow(dead_code)]
|
||||
fn require_sync<T: Sync>(_t: &T) {}
|
||||
#[allow(dead_code)]
|
||||
fn require_unpin<T: Unpin>(_t: &T) {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct Invalid;
|
||||
|
||||
trait AmbiguousIfSend<A> {
|
||||
fn some_item(&self) {}
|
||||
}
|
||||
impl<T: ?Sized> AmbiguousIfSend<()> for T {}
|
||||
impl<T: ?Sized + Send> AmbiguousIfSend<Invalid> for T {}
|
||||
|
||||
trait AmbiguousIfSync<A> {
|
||||
fn some_item(&self) {}
|
||||
}
|
||||
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
|
||||
impl<T: ?Sized + Sync> AmbiguousIfSync<Invalid> for T {}
|
||||
|
||||
trait AmbiguousIfUnpin<A> {
|
||||
fn some_item(&self) {}
|
||||
}
|
||||
impl<T: ?Sized> AmbiguousIfUnpin<()> for T {}
|
||||
impl<T: ?Sized + Unpin> AmbiguousIfUnpin<Invalid> for T {}
|
||||
|
||||
macro_rules! into_todo {
|
||||
($typ:ty) => {{
|
||||
let x: $typ = todo!();
|
||||
x
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! async_assert_fn {
|
||||
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): Send & Sync) => {
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
const _: fn() = || {
|
||||
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
|
||||
require_send(&f);
|
||||
require_sync(&f);
|
||||
};
|
||||
};
|
||||
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): Send & !Sync) => {
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
const _: fn() = || {
|
||||
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
|
||||
require_send(&f);
|
||||
AmbiguousIfSync::some_item(&f);
|
||||
};
|
||||
};
|
||||
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): !Send & Sync) => {
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
const _: fn() = || {
|
||||
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
|
||||
AmbiguousIfSend::some_item(&f);
|
||||
require_sync(&f);
|
||||
};
|
||||
};
|
||||
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): !Send & !Sync) => {
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
const _: fn() = || {
|
||||
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
|
||||
AmbiguousIfSend::some_item(&f);
|
||||
AmbiguousIfSync::some_item(&f);
|
||||
};
|
||||
};
|
||||
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): !Unpin) => {
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
const _: fn() = || {
|
||||
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
|
||||
AmbiguousIfUnpin::some_item(&f);
|
||||
};
|
||||
};
|
||||
($($f:ident $(< $($generic:ty),* > )? )::+($($arg:ty),*): Unpin) => {
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
const _: fn() = || {
|
||||
let f = $($f $(::<$($generic),*>)? )::+( $( into_todo!($arg) ),* );
|
||||
require_unpin(&f);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async_assert_fn!(tokio_stream::empty<Rc<u8>>(): Send & Sync);
|
||||
async_assert_fn!(tokio_stream::pending<Rc<u8>>(): Send & Sync);
|
||||
async_assert_fn!(tokio_stream::iter(std::vec::IntoIter<u8>): Send & Sync);
|
||||
|
||||
async_assert_fn!(tokio_stream::StreamExt::next(&mut BoxStream<()>): !Unpin);
|
||||
async_assert_fn!(tokio_stream::StreamExt::try_next(&mut BoxStream<Result<(), ()>>): !Unpin);
|
||||
async_assert_fn!(tokio_stream::StreamExt::all(&mut BoxStream<()>, fn(())->bool): !Unpin);
|
||||
async_assert_fn!(tokio_stream::StreamExt::any(&mut BoxStream<()>, fn(())->bool): !Unpin);
|
||||
async_assert_fn!(tokio_stream::StreamExt::fold(&mut BoxStream<()>, (), fn((), ())->()): !Unpin);
|
||||
async_assert_fn!(tokio_stream::StreamExt::collect<Vec<()>>(&mut BoxStream<()>): !Unpin);
|
||||
@@ -1,15 +0,0 @@
|
||||
use async_stream::stream;
|
||||
use tokio::sync::mpsc::{self, UnboundedSender};
|
||||
use tokio_stream::Stream;
|
||||
|
||||
pub fn unbounded_channel_stream<T: Unpin>() -> (UnboundedSender<T>, impl Stream<Item = T>) {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
let stream = stream! {
|
||||
while let Some(item) = rx.recv().await {
|
||||
yield item;
|
||||
}
|
||||
};
|
||||
|
||||
(tx, stream)
|
||||
}
|
||||
@@ -1,11 +1,3 @@
|
||||
# 0.4.0 (December 23, 2020)
|
||||
|
||||
- Track `tokio` 1.0 release.
|
||||
|
||||
# 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,29 +6,27 @@ name = "tokio-test"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.0"
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.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-test/0.4.0/tokio_test"
|
||||
documentation = "https://docs.rs/tokio-test/0.2.1/tokio_test"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
categories = ["asynchronous", "testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
async-stream = "0.3"
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["rt-core", "stream", "sync", "time", "test-util"] }
|
||||
|
||||
bytes = "1.0.0"
|
||||
bytes = "0.5.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
+39
-61
@@ -18,13 +18,13 @@
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{self, Duration, Instant, Sleep};
|
||||
use tokio::time::{self, Delay, Duration, Instant};
|
||||
|
||||
use futures_core::{ready, Stream};
|
||||
use bytes::Buf;
|
||||
use futures_core::ready;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -64,13 +64,13 @@ enum Action {
|
||||
WriteError(Option<Arc<io::Error>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
actions: VecDeque<Action>,
|
||||
waiting: Option<Instant>,
|
||||
sleep: Option<Pin<Box<Sleep>>>,
|
||||
sleep: Option<Delay>,
|
||||
read_wait: Option<Waker>,
|
||||
// rx: mpsc::UnboundedReceiver<Action>,
|
||||
rx: Pin<Box<dyn Stream<Item = Action> + Send>>,
|
||||
rx: mpsc::UnboundedReceiver<Action>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
@@ -185,13 +185,7 @@ impl Handle {
|
||||
|
||||
impl Inner {
|
||||
fn new(actions: VecDeque<Action>) -> (Inner, Handle) {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
let rx = Box::pin(async_stream::stream! {
|
||||
while let Some(item) = rx.recv().await {
|
||||
yield item;
|
||||
}
|
||||
});
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let inner = Inner {
|
||||
actions,
|
||||
@@ -207,22 +201,23 @@ impl Inner {
|
||||
}
|
||||
|
||||
fn poll_action(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Action>> {
|
||||
Pin::new(&mut self.rx).poll_next(cx)
|
||||
self.rx.poll_recv(cx)
|
||||
}
|
||||
|
||||
fn read(&mut self, dst: &mut ReadBuf<'_>) -> io::Result<()> {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
match self.action() {
|
||||
Some(&mut Action::Read(ref mut data)) => {
|
||||
// Figure out how much to copy
|
||||
let n = cmp::min(dst.remaining(), data.len());
|
||||
let n = cmp::min(dst.len(), data.len());
|
||||
|
||||
// Copy the data into the `dst` slice
|
||||
dst.put_slice(&data[..n]);
|
||||
(&mut dst[..n]).copy_from_slice(&data[..n]);
|
||||
|
||||
// Drain the data from the source
|
||||
data.drain(..n);
|
||||
|
||||
Ok(())
|
||||
// Return the number of bytes read
|
||||
Ok(n)
|
||||
}
|
||||
Some(&mut Action::ReadError(ref mut err)) => {
|
||||
// As the
|
||||
@@ -234,7 +229,7 @@ impl Inner {
|
||||
// Either waiting or expecting a write
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
None => Ok(()),
|
||||
None => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,8 +348,8 @@ impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
loop {
|
||||
if let Some(ref mut sleep) = self.inner.sleep {
|
||||
ready!(Pin::new(sleep).poll(cx));
|
||||
@@ -363,35 +358,29 @@ 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(Box::pin(time::sleep_until(until)));
|
||||
self.inner.sleep = Some(time::delay_until(until));
|
||||
} else {
|
||||
self.inner.read_wait = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
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(()));
|
||||
}
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
Err(e) => return Poll::Ready(Err(e)),
|
||||
ret => return Poll::Ready(ret),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,7 +404,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(Box::pin(time::sleep_until(until)));
|
||||
self.inner.sleep = Some(time::delay_until(until));
|
||||
} else {
|
||||
panic!("unexpected WouldBlock");
|
||||
}
|
||||
@@ -445,6 +434,16 @@ 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(()))
|
||||
}
|
||||
@@ -454,21 +453,6 @@ 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 {
|
||||
@@ -490,9 +474,3 @@ fn is_task_ctx() -> bool {
|
||||
r
|
||||
}
|
||||
*/
|
||||
|
||||
impl fmt::Debug for Inner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Inner {{...}}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.4.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.2.1")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
@@ -28,7 +28,8 @@ pub mod task;
|
||||
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
use tokio::runtime;
|
||||
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
let mut rt = runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -9,9 +9,9 @@ use std::pin::Pin;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use tokio_stream::Stream;
|
||||
use tokio::stream::Stream;
|
||||
|
||||
/// TODO: dox
|
||||
/// TOOD: dox
|
||||
pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
Spawn {
|
||||
task: MockTask::new(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::time::{sleep_until, Duration, Instant};
|
||||
use tokio::time::{delay_until, Duration, Instant};
|
||||
use tokio_test::block_on;
|
||||
|
||||
#[test]
|
||||
@@ -18,10 +18,10 @@ fn async_fn() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sleep() {
|
||||
fn test_delay() {
|
||||
let deadline = Instant::now() + Duration::from_millis(100);
|
||||
|
||||
block_on(async {
|
||||
sleep_until(deadline).await;
|
||||
delay_until(deadline).await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,17 +70,3 @@ 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,62 +1,3 @@
|
||||
# 0.6.3 (January 31, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- sync: add `ReusableBoxFuture` utility ([#3464])
|
||||
|
||||
### Changed
|
||||
|
||||
- sync: use `ReusableBoxFuture` for `PollSemaphore` ([#3463])
|
||||
- deps: remove `async-stream` dependency ([#3463])
|
||||
- deps: remove `tokio-stream` dependency ([#3487])
|
||||
|
||||
# 0.6.2 (January 21, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- sync: add pollable `Semaphore` ([#3444])
|
||||
|
||||
### Fixed
|
||||
|
||||
- time: fix panics on updating `DelayQueue` entries ([#3270])
|
||||
|
||||
# 0.6.1 (January 12, 2021)
|
||||
|
||||
### Added
|
||||
|
||||
- codec: `get_ref()`, `get_mut()`, `get_pin_mut()` and `into_inner()` for
|
||||
`Framed`, `FramedRead`, `FramedWrite` and `StreamReader` ([#3364]).
|
||||
- codec: `write_buffer()` and `write_buffer_mut()` for `Framed` and
|
||||
`FramedWrite` ([#3387]).
|
||||
|
||||
# 0.6.0 (December 23, 2020)
|
||||
|
||||
### Changed
|
||||
- depend on `tokio` 1.0.
|
||||
|
||||
### Added
|
||||
- rt: add constructors to `TokioContext` (#3221).
|
||||
|
||||
# 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
|
||||
@@ -84,13 +25,6 @@
|
||||
|
||||
- Initial release
|
||||
|
||||
[#3487]: https://github.com/tokio-rs/tokio/pull/3487
|
||||
[#3464]: https://github.com/tokio-rs/tokio/pull/3464
|
||||
[#3463]: https://github.com/tokio-rs/tokio/pull/3463
|
||||
[#3444]: https://github.com/tokio-rs/tokio/pull/3444
|
||||
[#3387]: https://github.com/tokio-rs/tokio/pull/3387
|
||||
[#3364]: https://github.com/tokio-rs/tokio/pull/3364
|
||||
[#3270]: https://github.com/tokio-rs/tokio/pull/3270
|
||||
[#2326]: https://github.com/tokio-rs/tokio/pull/2326
|
||||
[#2215]: https://github.com/tokio-rs/tokio/pull/2215
|
||||
[#2198]: https://github.com/tokio-rs/tokio/pull/2198
|
||||
|
||||
+11
-21
@@ -6,14 +6,14 @@ name = "tokio-util"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "tokio-util-0.6.x" git tag.
|
||||
version = "0.6.3"
|
||||
# - Create "v0.2.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-util/0.6.3/tokio_util"
|
||||
documentation = "https://docs.rs/tokio-util/0.3.1/tokio_util"
|
||||
description = """
|
||||
Additional utilities for working with Tokio.
|
||||
"""
|
||||
@@ -24,37 +24,27 @@ categories = ["asynchronous"]
|
||||
default = []
|
||||
|
||||
# Shorthand for enabling everything
|
||||
full = ["codec", "compat", "io", "time", "net", "rt"]
|
||||
full = ["codec", "udp", "compat"]
|
||||
|
||||
net = ["tokio/net"]
|
||||
compat = ["futures-io",]
|
||||
codec = []
|
||||
time = ["tokio/time","slab"]
|
||||
io = []
|
||||
rt = ["tokio/rt"]
|
||||
|
||||
__docs_rs = ["futures-util"]
|
||||
codec = ["tokio/stream"]
|
||||
udp = ["tokio/udp"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0.0", features = ["sync"] }
|
||||
tokio = { version = "0.2.5", path = "../tokio" }
|
||||
|
||||
bytes = "1.0.0"
|
||||
bytes = "0.5.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
futures-io = { version = "0.3.0", optional = true }
|
||||
futures-util = { version = "0.3.0", optional = true }
|
||||
log = "0.4"
|
||||
pin-project-lite = "0.2.0"
|
||||
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
|
||||
pin-project-lite = "0.1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0.0", features = ["full"] }
|
||||
tokio-test = { version = "0.4.0" }
|
||||
tokio-stream = { version = "0.1" }
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.2.0", path = "../tokio-test" }
|
||||
|
||||
async-stream = "0.3.0"
|
||||
futures = "0.3.0"
|
||||
futures-test = "0.3.5"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
+3
-23
@@ -18,31 +18,11 @@ macro_rules! cfg_compat {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_net {
|
||||
macro_rules! cfg_udp {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[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")))]
|
||||
#[cfg(all(feature = "udp", feature = "codec"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "udp", feature = "codec"))))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ use std::io;
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # #[tokio::main(core_threads = 1)]
|
||||
/// # 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());
|
||||
|
||||
@@ -153,7 +153,7 @@ pub trait Decoder {
|
||||
/// calling `split` on the [`Framed`] returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Framed`]: crate::codec::Framed
|
||||
fn framed<T: AsyncRead + AsyncWrite + Sized>(self, io: T) -> Framed<T, Self>
|
||||
|
||||
@@ -2,8 +2,10 @@ use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
use crate::codec::framed_impl::{FramedImpl, RWFrames, ReadFrame, WriteFrame};
|
||||
|
||||
use futures_core::Stream;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_sink::Sink;
|
||||
@@ -20,7 +22,7 @@ pin_project! {
|
||||
/// You can create a `Framed` instance by using the [`Decoder::framed`] adapter, or
|
||||
/// by using the `new` function seen below.
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Decoder::framed`]: crate::codec::Decoder::framed()
|
||||
@@ -52,7 +54,7 @@ where
|
||||
/// calling [`split`] on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Decode`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
@@ -86,7 +88,7 @@ where
|
||||
/// calling [`split`] on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Decode`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
@@ -131,7 +133,7 @@ impl<T, U> Framed<T, U> {
|
||||
/// calling [`split`] on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Decoder`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
@@ -170,16 +172,6 @@ impl<T, U> Framed<T, U> {
|
||||
&mut self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a pinned mutable reference to the underlying I/O stream wrapped by
|
||||
/// `Framed`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
|
||||
self.project().inner.project().inner
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying codec wrapped by
|
||||
/// `Framed`.
|
||||
///
|
||||
@@ -208,16 +200,6 @@ impl<T, U> Framed<T, U> {
|
||||
&mut self.inner.state.read.buffer
|
||||
}
|
||||
|
||||
/// Returns a reference to the write buffer.
|
||||
pub fn write_buffer(&self) -> &BytesMut {
|
||||
&self.inner.state.write.buffer
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the write buffer.
|
||||
pub fn write_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.inner.state.write.buffer
|
||||
}
|
||||
|
||||
/// Consumes the `Framed`, returning its underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
|
||||
use futures_core::Stream;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use bytes::{Buf, BytesMut};
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
@@ -116,8 +118,6 @@ 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 +148,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 poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer)? {
|
||||
let bytect = match pinned.inner.as_mut().poll_read_buf(cx, &mut state.buffer)? {
|
||||
Poll::Ready(ct) => ct,
|
||||
Poll::Pending => return Poll::Pending,
|
||||
};
|
||||
@@ -187,7 +187,6 @@ 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();
|
||||
|
||||
@@ -195,7 +194,8 @@ where
|
||||
let WriteFrame { buffer } = pinned.state.borrow_mut();
|
||||
trace!("writing; remaining={}", buffer.len());
|
||||
|
||||
let n = ready!(poll_write_buf(pinned.inner.as_mut(), cx, buffer))?;
|
||||
let buf = &buffer;
|
||||
let n = ready!(pinned.inner.as_mut().poll_write(cx, &buf))?;
|
||||
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
@@ -205,6 +205,8 @@ where
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
|
||||
pinned.state.borrow_mut().buffer.advance(n);
|
||||
}
|
||||
|
||||
// Try flushing the underlying IO
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::codec::framed_impl::{FramedImpl, ReadFrame};
|
||||
use crate::codec::Decoder;
|
||||
|
||||
use futures_core::Stream;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::{io::AsyncRead, stream::Stream};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_sink::Sink;
|
||||
@@ -14,7 +13,7 @@ use std::task::{Context, Poll};
|
||||
pin_project! {
|
||||
/// A [`Stream`] of messages decoded from an [`AsyncRead`].
|
||||
///
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
pub struct FramedRead<T, D> {
|
||||
#[pin]
|
||||
@@ -78,16 +77,6 @@ impl<T, D> FramedRead<T, D> {
|
||||
&mut self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a pinned mutable reference to the underlying I/O stream wrapped by
|
||||
/// `FramedRead`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
|
||||
self.project().inner.project().inner
|
||||
}
|
||||
|
||||
/// Consumes the `FramedRead`, returning its underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
@@ -111,11 +100,6 @@ 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,10 +1,8 @@
|
||||
use crate::codec::encoder::Encoder;
|
||||
use crate::codec::framed_impl::{FramedImpl, WriteFrame};
|
||||
|
||||
use futures_core::Stream;
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio::{io::AsyncWrite, stream::Stream};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_sink::Sink;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
@@ -59,16 +57,6 @@ impl<T, E> FramedWrite<T, E> {
|
||||
&mut self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a pinned mutable reference to the underlying I/O stream wrapped by
|
||||
/// `FramedWrite`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
|
||||
self.project().inner.project().inner
|
||||
}
|
||||
|
||||
/// Consumes the `FramedWrite`, returning its underlying I/O stream.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying stream
|
||||
@@ -87,16 +75,6 @@ impl<T, E> FramedWrite<T, E> {
|
||||
pub fn encoder_mut(&mut self) -> &mut E {
|
||||
&mut self.inner.codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the write buffer.
|
||||
pub fn write_buffer(&self) -> &BytesMut {
|
||||
&self.inner.state.buffer
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the write buffer.
|
||||
pub fn write_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.inner.state.buffer
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
//! Specifically, given the following:
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::io::{AsyncRead, AsyncWrite};
|
||||
//! use tokio::prelude::*;
|
||||
//! use tokio_util::codec::{Framed, LengthDelimitedCodec};
|
||||
//!
|
||||
//! use futures::SinkExt;
|
||||
|
||||
+8
-252
@@ -1,262 +1,18 @@
|
||||
//! Adaptors from AsyncRead/AsyncWrite to Stream/Sink
|
||||
//!
|
||||
//! Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
//! wants to batch these into meaningful chunks, called "frames".
|
||||
//! Raw I/O objects work with byte sequences, but higher-level code
|
||||
//! usually wants to batch these into meaningful chunks, called
|
||||
//! "frames".
|
||||
//!
|
||||
//! This module contains adapters to go from streams of bytes, [`AsyncRead`] and
|
||||
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
|
||||
//! Framed streams are also known as transports.
|
||||
//!
|
||||
//! # The Decoder trait
|
||||
//!
|
||||
//! A [`Decoder`] is used together with [`FramedRead`] or [`Framed`] to turn an
|
||||
//! [`AsyncRead`] into a [`Stream`]. The job of the decoder trait is to specify
|
||||
//! how sequences of bytes are turned into a sequence of frames, and to
|
||||
//! determine where the boundaries between frames are. The job of the
|
||||
//! `FramedRead` is to repeatedly switch between reading more data from the IO
|
||||
//! resource, and asking the decoder whether we have received enough data to
|
||||
//! decode another frame of data.
|
||||
//!
|
||||
//! The main method on the `Decoder` trait is the [`decode`] method. This method
|
||||
//! takes as argument the data that has been read so far, and when it is called,
|
||||
//! it will be in one of the following situations:
|
||||
//!
|
||||
//! 1. The buffer contains less than a full frame.
|
||||
//! 2. The buffer contains exactly a full frame.
|
||||
//! 3. The buffer contains more than a full frame.
|
||||
//!
|
||||
//! In the first situation, the decoder should return `Ok(None)`.
|
||||
//!
|
||||
//! In the second situation, the decoder should clear the provided buffer and
|
||||
//! return `Ok(Some(the_decoded_frame))`.
|
||||
//!
|
||||
//! In the third situation, the decoder should use a method such as [`split_to`]
|
||||
//! or [`advance`] to modify the buffer such that the frame is removed from the
|
||||
//! buffer, but any data in the buffer after that frame should still remain in
|
||||
//! the buffer. The decoder should also return `Ok(Some(the_decoded_frame))` in
|
||||
//! this case.
|
||||
//!
|
||||
//! Finally the decoder may return an error if the data is invalid in some way.
|
||||
//! The decoder should _not_ return an error just because it has yet to receive
|
||||
//! a full frame.
|
||||
//!
|
||||
//! It is guaranteed that, from one call to `decode` to another, the provided
|
||||
//! buffer will contain the exact same data as before, except that if more data
|
||||
//! has arrived through the IO resource, that data will have been appended to
|
||||
//! the buffer. This means that reading frames from a `FramedRead` is
|
||||
//! essentially equivalent to the following loop:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use tokio::io::AsyncReadExt;
|
||||
//! # // This uses async_stream to create an example that compiles.
|
||||
//! # fn foo() -> impl futures_core::Stream<Item = std::io::Result<bytes::BytesMut>> { async_stream::try_stream! {
|
||||
//! # use tokio_util::codec::Decoder;
|
||||
//! # let mut decoder = tokio_util::codec::BytesCodec::new();
|
||||
//! # let io_resource = &mut &[0u8, 1, 2, 3][..];
|
||||
//!
|
||||
//! let mut buf = bytes::BytesMut::new();
|
||||
//! loop {
|
||||
//! // The read_buf call will append to buf rather than overwrite existing data.
|
||||
//! let len = io_resource.read_buf(&mut buf).await?;
|
||||
//!
|
||||
//! if len == 0 {
|
||||
//! while let Some(frame) = decoder.decode_eof(&mut buf)? {
|
||||
//! yield frame;
|
||||
//! }
|
||||
//! break;
|
||||
//! }
|
||||
//!
|
||||
//! while let Some(frame) = decoder.decode(&mut buf)? {
|
||||
//! yield frame;
|
||||
//! }
|
||||
//! }
|
||||
//! # }}
|
||||
//! ```
|
||||
//! The example above uses `yield` whenever the `Stream` produces an item.
|
||||
//!
|
||||
//! ## Example decoder
|
||||
//!
|
||||
//! As an example, consider a protocol that can be used to send strings where
|
||||
//! each frame is a four byte integer that contains the length of the frame,
|
||||
//! followed by that many bytes of string data. The decoder fails with an error
|
||||
//! if the string data is not valid utf-8 or too long.
|
||||
//!
|
||||
//! Such a decoder can be written like this:
|
||||
//! ```
|
||||
//! use tokio_util::codec::Decoder;
|
||||
//! use bytes::{BytesMut, Buf};
|
||||
//!
|
||||
//! struct MyStringDecoder {}
|
||||
//!
|
||||
//! const MAX: usize = 8 * 1024 * 1024;
|
||||
//!
|
||||
//! impl Decoder for MyStringDecoder {
|
||||
//! type Item = String;
|
||||
//! type Error = std::io::Error;
|
||||
//!
|
||||
//! fn decode(
|
||||
//! &mut self,
|
||||
//! src: &mut BytesMut
|
||||
//! ) -> Result<Option<Self::Item>, Self::Error> {
|
||||
//! if src.len() < 4 {
|
||||
//! // Not enough data to read length marker.
|
||||
//! return Ok(None);
|
||||
//! }
|
||||
//!
|
||||
//! // Read length marker.
|
||||
//! let mut length_bytes = [0u8; 4];
|
||||
//! length_bytes.copy_from_slice(&src[..4]);
|
||||
//! let length = u32::from_le_bytes(length_bytes) as usize;
|
||||
//!
|
||||
//! // Check that the length is not too large to avoid a denial of
|
||||
//! // service attack where the server runs out of memory.
|
||||
//! if length > MAX {
|
||||
//! return Err(std::io::Error::new(
|
||||
//! std::io::ErrorKind::InvalidData,
|
||||
//! format!("Frame of length {} is too large.", length)
|
||||
//! ));
|
||||
//! }
|
||||
//!
|
||||
//! if src.len() < 4 + length {
|
||||
//! // The full string has not yet arrived.
|
||||
//! //
|
||||
//! // We reserve more space in the buffer. This is not strictly
|
||||
//! // necessary, but is a good idea performance-wise.
|
||||
//! src.reserve(4 + length - src.len());
|
||||
//!
|
||||
//! // We inform the Framed that we need more bytes to form the next
|
||||
//! // frame.
|
||||
//! return Ok(None);
|
||||
//! }
|
||||
//!
|
||||
//! // Use advance to modify src such that it no longer contains
|
||||
//! // this frame.
|
||||
//! let data = src[4..4 + length].to_vec();
|
||||
//! src.advance(4 + length);
|
||||
//!
|
||||
//! // Convert the data to a string, or fail if it is not valid utf-8.
|
||||
//! match String::from_utf8(data) {
|
||||
//! Ok(string) => Ok(Some(string)),
|
||||
//! Err(utf8_error) => {
|
||||
//! Err(std::io::Error::new(
|
||||
//! std::io::ErrorKind::InvalidData,
|
||||
//! utf8_error.utf8_error(),
|
||||
//! ))
|
||||
//! },
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # The Encoder trait
|
||||
//!
|
||||
//! An [`Encoder`] is used together with [`FramedWrite`] or [`Framed`] to turn
|
||||
//! an [`AsyncWrite`] into a [`Sink`]. The job of the encoder trait is to
|
||||
//! specify how frames are turned into a sequences of bytes. The job of the
|
||||
//! `FramedWrite` is to take the resulting sequence of bytes and write it to the
|
||||
//! IO resource.
|
||||
//!
|
||||
//! The main method on the `Encoder` trait is the [`encode`] method. This method
|
||||
//! takes an item that is being written, and a buffer to write the item to. The
|
||||
//! buffer may already contain data, and in this case, the encoder should append
|
||||
//! the new frame the to buffer rather than overwrite the existing data.
|
||||
//!
|
||||
//! It is guaranteed that, from one call to `encode` to another, the provided
|
||||
//! buffer will contain the exact same data as before, except that some of the
|
||||
//! data may have been removed from the front of the buffer. Writing to a
|
||||
//! `FramedWrite` is essentially equivalent to the following loop:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use tokio::io::AsyncWriteExt;
|
||||
//! use bytes::Buf; // for advance
|
||||
//! # use tokio_util::codec::Encoder;
|
||||
//! # async fn next_frame() -> bytes::Bytes { bytes::Bytes::new() }
|
||||
//! # async fn no_more_frames() { }
|
||||
//! # #[tokio::main] async fn main() -> std::io::Result<()> {
|
||||
//! # let mut io_resource = tokio::io::sink();
|
||||
//! # let mut encoder = tokio_util::codec::BytesCodec::new();
|
||||
//!
|
||||
//! const MAX: usize = 8192;
|
||||
//!
|
||||
//! let mut buf = bytes::BytesMut::new();
|
||||
//! loop {
|
||||
//! tokio::select! {
|
||||
//! num_written = io_resource.write(&buf), if !buf.is_empty() => {
|
||||
//! buf.advance(num_written?);
|
||||
//! },
|
||||
//! frame = next_frame(), if buf.len() < MAX => {
|
||||
//! encoder.encode(frame, &mut buf)?;
|
||||
//! },
|
||||
//! _ = no_more_frames() => {
|
||||
//! io_resource.write_all(&buf).await?;
|
||||
//! io_resource.shutdown().await?;
|
||||
//! return Ok(());
|
||||
//! },
|
||||
//! }
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//! Here the `next_frame` method corresponds to any frames you write to the
|
||||
//! `FramedWrite`. The `no_more_frames` method corresponds to closing the
|
||||
//! `FramedWrite` with [`SinkExt::close`].
|
||||
//!
|
||||
//! ## Example encoder
|
||||
//!
|
||||
//! As an example, consider a protocol that can be used to send strings where
|
||||
//! each frame is a four byte integer that contains the length of the frame,
|
||||
//! followed by that many bytes of string data. The encoder will fail if the
|
||||
//! string is too long.
|
||||
//!
|
||||
//! Such an encoder can be written like this:
|
||||
//! ```
|
||||
//! use tokio_util::codec::Encoder;
|
||||
//! use bytes::BytesMut;
|
||||
//!
|
||||
//! struct MyStringEncoder {}
|
||||
//!
|
||||
//! const MAX: usize = 8 * 1024 * 1024;
|
||||
//!
|
||||
//! impl Encoder<String> for MyStringEncoder {
|
||||
//! type Error = std::io::Error;
|
||||
//!
|
||||
//! fn encode(&mut self, item: String, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
//! // Don't send a string if it is longer than the other end will
|
||||
//! // accept.
|
||||
//! if item.len() > MAX {
|
||||
//! return Err(std::io::Error::new(
|
||||
//! std::io::ErrorKind::InvalidData,
|
||||
//! format!("Frame of length {} is too large.", item.len())
|
||||
//! ));
|
||||
//! }
|
||||
//!
|
||||
//! // Convert the length into a byte array.
|
||||
//! // The cast to u32 cannot overflow due to the length check above.
|
||||
//! let len_slice = u32::to_le_bytes(item.len() as u32);
|
||||
//!
|
||||
//! // Reserve space in the buffer.
|
||||
//! dst.reserve(4 + item.len());
|
||||
//!
|
||||
//! // Write the length and string to the buffer.
|
||||
//! dst.extend_from_slice(&len_slice);
|
||||
//! dst.extend_from_slice(item.as_bytes());
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//! This module contains adapters to go from streams of bytes,
|
||||
//! [`AsyncRead`] and [`AsyncWrite`], to framed streams implementing
|
||||
//! [`Sink`] and [`Stream`]. Framed streams are also known as
|
||||
//! transports.
|
||||
//!
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
//! [`Stream`]: futures_core::Stream
|
||||
//! [`Stream`]: tokio::stream::Stream
|
||||
//! [`Sink`]: futures_sink::Sink
|
||||
//! [`SinkExt::close`]: https://docs.rs/futures/0.3/futures/sink/trait.SinkExt.html#method.close
|
||||
//! [`FramedRead`]: struct@crate::codec::FramedRead
|
||||
//! [`FramedWrite`]: struct@crate::codec::FramedWrite
|
||||
//! [`Framed`]: struct@crate::codec::Framed
|
||||
//! [`Decoder`]: trait@crate::codec::Decoder
|
||||
//! [`decode`]: fn@crate::codec::Decoder::decode
|
||||
//! [`encode`]: fn@crate::codec::Encoder::encode
|
||||
//! [`split_to`]: fn@bytes::BytesMut::split_to
|
||||
//! [`advance`]: fn@bytes::Buf::advance
|
||||
|
||||
mod bytes_codec;
|
||||
pub use self::bytes_codec::BytesCodec;
|
||||
|
||||
+10
-26
@@ -1,6 +1,5 @@
|
||||
//! 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;
|
||||
@@ -20,7 +19,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::AsyncRead`.
|
||||
/// `tokio_io::AsyncWrite`.
|
||||
fn compat(self) -> Compat<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -48,7 +47,7 @@ impl<T: futures_io::AsyncWrite> FuturesAsyncWriteCompatExt for T {}
|
||||
|
||||
/// Extension trait that allows converting a type implementing
|
||||
/// `tokio::io::AsyncRead` to implement `futures_io::AsyncRead`.
|
||||
pub trait TokioAsyncReadCompatExt: tokio::io::AsyncRead {
|
||||
pub trait Tokio02AsyncReadCompatExt: tokio::io::AsyncRead {
|
||||
/// Wraps `self` with a compatibility layer that implements
|
||||
/// `futures_io::AsyncRead`.
|
||||
fn compat(self) -> Compat<Self>
|
||||
@@ -59,11 +58,11 @@ pub trait TokioAsyncReadCompatExt: tokio::io::AsyncRead {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: tokio::io::AsyncRead> TokioAsyncReadCompatExt for T {}
|
||||
impl<T: tokio::io::AsyncRead> Tokio02AsyncReadCompatExt for T {}
|
||||
|
||||
/// Extension trait that allows converting a type implementing
|
||||
/// `tokio::io::AsyncWrite` to implement `futures_io::AsyncWrite`.
|
||||
pub trait TokioAsyncWriteCompatExt: tokio::io::AsyncWrite {
|
||||
pub trait Tokio02AsyncWriteCompatExt: tokio::io::AsyncWrite {
|
||||
/// Wraps `self` with a compatibility layer that implements
|
||||
/// `futures_io::AsyncWrite`.
|
||||
fn compat_write(self) -> Compat<Self>
|
||||
@@ -74,7 +73,7 @@ pub trait TokioAsyncWriteCompatExt: tokio::io::AsyncWrite {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: tokio::io::AsyncWrite> TokioAsyncWriteCompatExt for T {}
|
||||
impl<T: tokio::io::AsyncWrite> Tokio02AsyncWriteCompatExt for T {}
|
||||
|
||||
// === impl Compat ===
|
||||
|
||||
@@ -108,18 +107,9 @@ where
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
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(()))
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
futures_io::AsyncRead::poll_read(self.project().inner, cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,15 +120,9 @@ where
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
slice: &mut [u8],
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
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()))
|
||||
tokio::io::AsyncRead::poll_read(self.project().inner, cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
//! 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::{Handle, Runtime};
|
||||
|
||||
pin_project! {
|
||||
/// `TokioContext` allows running futures that must be inside Tokio's
|
||||
/// context on a non-Tokio runtime.
|
||||
///
|
||||
/// It contains a [`Handle`] to the runtime. A handle to the runtime can be
|
||||
/// obtain by calling the [`Runtime::handle()`] method.
|
||||
///
|
||||
/// Note that the `TokioContext` wrapper only works if the `Runtime` it is
|
||||
/// connected to has not yet been destroyed. You must keep the `Runtime`
|
||||
/// alive until the future has finished executing.
|
||||
///
|
||||
/// **Warning:** If `TokioContext` is used together with a [current thread]
|
||||
/// runtime, that runtime must be inside a call to `block_on` for the
|
||||
/// wrapped future to work. For this reason, it is recommended to use a
|
||||
/// [multi thread] runtime, even if you configure it to only spawn one
|
||||
/// worker thread.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This example creates two runtimes, but only [enables time] on one of
|
||||
/// them. It then uses the context of the runtime with the timer enabled to
|
||||
/// execute a [`sleep`] future on the runtime with timing disabled.
|
||||
/// ```
|
||||
/// use tokio::time::{sleep, Duration};
|
||||
/// use tokio_util::context::RuntimeExt;
|
||||
///
|
||||
/// // This runtime has timers enabled.
|
||||
/// let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // This runtime has timers disabled.
|
||||
/// let rt2 = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // Wrap the sleep future in the context of rt.
|
||||
/// let fut = rt.wrap(async { sleep(Duration::from_millis(2)).await });
|
||||
///
|
||||
/// // Execute the future on rt2.
|
||||
/// rt2.block_on(fut);
|
||||
/// ```
|
||||
///
|
||||
/// [`Handle`]: struct@tokio::runtime::Handle
|
||||
/// [`Runtime::handle()`]: fn@tokio::runtime::Runtime::handle
|
||||
/// [`RuntimeExt`]: trait@crate::context::RuntimeExt
|
||||
/// [`new_static`]: fn@Self::new_static
|
||||
/// [`sleep`]: fn@tokio::time::sleep
|
||||
/// [current thread]: fn@tokio::runtime::Builder::new_current_thread
|
||||
/// [enables time]: fn@tokio::runtime::Builder::enable_time
|
||||
/// [multi thread]: fn@tokio::runtime::Builder::new_multi_thread
|
||||
pub struct TokioContext<F> {
|
||||
#[pin]
|
||||
inner: F,
|
||||
handle: Handle,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> TokioContext<F> {
|
||||
/// Associate the provided future with the context of the runtime behind
|
||||
/// the provided `Handle`.
|
||||
///
|
||||
/// This constructor uses a `'static` lifetime to opt-out of checking that
|
||||
/// the runtime still exists.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This is the same as the example above, but uses the `new` constructor
|
||||
/// rather than [`RuntimeExt::wrap`].
|
||||
///
|
||||
/// [`RuntimeExt::wrap`]: fn@RuntimeExt::wrap
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::time::{sleep, Duration};
|
||||
/// use tokio_util::context::TokioContext;
|
||||
///
|
||||
/// // This runtime has timers enabled.
|
||||
/// let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // This runtime has timers disabled.
|
||||
/// let rt2 = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let fut = TokioContext::new(
|
||||
/// async { sleep(Duration::from_millis(2)).await },
|
||||
/// rt.handle().clone(),
|
||||
/// );
|
||||
///
|
||||
/// // Execute the future on rt2.
|
||||
/// rt2.block_on(fut);
|
||||
/// ```
|
||||
pub fn new(future: F, handle: Handle) -> TokioContext<F> {
|
||||
TokioContext {
|
||||
inner: future,
|
||||
handle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain a reference to the handle inside this `TokioContext`.
|
||||
pub fn handle(&self) -> &Handle {
|
||||
&self.handle
|
||||
}
|
||||
|
||||
/// Remove the association between the Tokio runtime and the wrapped future.
|
||||
pub fn into_inner(self) -> F {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait that simplifies bundling a `Handle` with a `Future`.
|
||||
pub trait RuntimeExt {
|
||||
/// Create a [`TokioContext`] that wraps the provided future and runs it in
|
||||
/// this runtime's context.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This example creates two runtimes, but only [enables time] on one of
|
||||
/// them. It then uses the context of the runtime with the timer enabled to
|
||||
/// execute a [`sleep`] future on the runtime with timing disabled.
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::time::{sleep, Duration};
|
||||
/// use tokio_util::context::RuntimeExt;
|
||||
///
|
||||
/// // This runtime has timers enabled.
|
||||
/// let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // This runtime has timers disabled.
|
||||
/// let rt2 = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // Wrap the sleep future in the context of rt.
|
||||
/// let fut = rt.wrap(async { sleep(Duration::from_millis(2)).await });
|
||||
///
|
||||
/// // Execute the future on rt2.
|
||||
/// rt2.block_on(fut);
|
||||
/// ```
|
||||
///
|
||||
/// [`TokioContext`]: struct@crate::context::TokioContext
|
||||
/// [`sleep`]: fn@tokio::time::sleep
|
||||
/// [enables time]: fn@tokio::runtime::Builder::enable_time
|
||||
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.handle().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
//! 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};
|
||||
use tokio_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]);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
//! 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};
|
||||
@@ -1,65 +0,0 @@
|
||||
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 as 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
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`]: futures_core::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`]: futures_core::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())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
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`]: futures_core::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> StreamReader<S, B> {
|
||||
/// Gets a reference to the underlying stream.
|
||||
///
|
||||
/// It is inadvisable to directly read from the underlying stream.
|
||||
pub fn get_ref(&self) -> &S {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the underlying stream.
|
||||
///
|
||||
/// It is inadvisable to directly read from the underlying stream.
|
||||
pub fn get_mut(&mut self) -> &mut S {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
/// Gets a pinned mutable reference to the underlying stream.
|
||||
///
|
||||
/// It is inadvisable to directly read from the underlying stream.
|
||||
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut S> {
|
||||
self.project().inner
|
||||
}
|
||||
|
||||
/// Consumes this `BufWriter`, returning the underlying stream.
|
||||
///
|
||||
/// Note that any leftover data in the internal buffer is lost.
|
||||
pub fn into_inner(self) -> S {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
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().chunk();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-165
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-util/0.6.3")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-util/0.3.1")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -24,177 +24,14 @@
|
||||
#[macro_use]
|
||||
mod cfg;
|
||||
|
||||
mod loom;
|
||||
|
||||
cfg_codec! {
|
||||
pub mod codec;
|
||||
}
|
||||
|
||||
cfg_net! {
|
||||
cfg_udp! {
|
||||
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 as 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.chunk_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.chunks_vectored(&mut slices);
|
||||
ready!(io.poll_write_vectored(cx, &slices[..cnt]))?
|
||||
} else {
|
||||
ready!(io.poll_write(cx, buf.chunk()))?
|
||||
};
|
||||
|
||||
buf.advance(n);
|
||||
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) use std::sync;
|
||||
@@ -1,12 +0,0 @@
|
||||
//! Synchronization primitives
|
||||
|
||||
mod cancellation_token;
|
||||
pub use cancellation_token::{CancellationToken, WaitForCancellationFuture};
|
||||
|
||||
mod intrusive_double_linked_list;
|
||||
|
||||
mod poll_semaphore;
|
||||
pub use poll_semaphore::PollSemaphore;
|
||||
|
||||
mod reusable_box;
|
||||
pub use reusable_box::ReusableBoxFuture;
|
||||
@@ -1,89 +0,0 @@
|
||||
use futures_core::{ready, Stream};
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore};
|
||||
|
||||
use super::ReusableBoxFuture;
|
||||
|
||||
/// A wrapper around [`Semaphore`] that provides a `poll_acquire` method.
|
||||
///
|
||||
/// [`Semaphore`]: tokio::sync::Semaphore
|
||||
pub struct PollSemaphore {
|
||||
semaphore: Arc<Semaphore>,
|
||||
permit_fut: ReusableBoxFuture<Result<OwnedSemaphorePermit, AcquireError>>,
|
||||
}
|
||||
|
||||
impl PollSemaphore {
|
||||
/// Create a new `PollSemaphore`.
|
||||
pub fn new(semaphore: Arc<Semaphore>) -> Self {
|
||||
let fut = Arc::clone(&semaphore).acquire_owned();
|
||||
|
||||
Self {
|
||||
semaphore,
|
||||
permit_fut: ReusableBoxFuture::new(fut),
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the semaphore.
|
||||
pub fn close(&self) {
|
||||
self.semaphore.close()
|
||||
}
|
||||
|
||||
/// Obtain a clone of the inner semaphore.
|
||||
pub fn clone_inner(&self) -> Arc<Semaphore> {
|
||||
self.semaphore.clone()
|
||||
}
|
||||
|
||||
/// Get back the inner semaphore.
|
||||
pub fn into_inner(self) -> Arc<Semaphore> {
|
||||
self.semaphore
|
||||
}
|
||||
|
||||
/// Poll to acquire a permit from the semaphore.
|
||||
///
|
||||
/// This can return the following values:
|
||||
///
|
||||
/// - `Poll::Pending` if a permit is not currently available.
|
||||
/// - `Poll::Ready(Some(permit))` if a permit was acquired.
|
||||
/// - `Poll::Ready(None)` if the semaphore has been closed.
|
||||
///
|
||||
/// When this method returns `Poll::Pending`, the current task is scheduled
|
||||
/// to receive a wakeup when a permit becomes available, or when the
|
||||
/// semaphore is closed. Note that on multiple calls to `poll_acquire`, only
|
||||
/// the `Waker` from the `Context` passed to the most recent call is
|
||||
/// scheduled to receive a wakeup.
|
||||
pub fn poll_acquire(&mut self, cx: &mut Context<'_>) -> Poll<Option<OwnedSemaphorePermit>> {
|
||||
match ready!(self.permit_fut.poll(cx)) {
|
||||
Ok(permit) => {
|
||||
let next_fut = Arc::clone(&self.semaphore).acquire_owned();
|
||||
self.permit_fut.set(next_fut);
|
||||
Poll::Ready(Some(permit))
|
||||
}
|
||||
Err(_closed) => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for PollSemaphore {
|
||||
type Item = OwnedSemaphorePermit;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<OwnedSemaphorePermit>> {
|
||||
Pin::into_inner(self).poll_acquire(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for PollSemaphore {
|
||||
fn clone(&self) -> PollSemaphore {
|
||||
PollSemaphore::new(self.clone_inner())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PollSemaphore {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PollSemaphore")
|
||||
.field("semaphore", &self.semaphore)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
use std::alloc::Layout;
|
||||
use std::future::Future;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::task::{Context, Poll};
|
||||
use std::{fmt, panic};
|
||||
|
||||
/// A reusable `Pin<Box<dyn Future<Output = T> + Send>>`.
|
||||
///
|
||||
/// This type lets you replace the future stored in the box without
|
||||
/// reallocating when the size and alignment permits this.
|
||||
pub struct ReusableBoxFuture<T> {
|
||||
boxed: NonNull<dyn Future<Output = T> + Send>,
|
||||
}
|
||||
|
||||
impl<T> ReusableBoxFuture<T> {
|
||||
/// Create a new `ReusableBoxFuture<T>` containing the provided future.
|
||||
pub fn new<F>(future: F) -> Self
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
let boxed: Box<dyn Future<Output = T> + Send> = Box::new(future);
|
||||
|
||||
let boxed = Box::into_raw(boxed);
|
||||
|
||||
// SAFETY: Box::into_raw does not return null pointers.
|
||||
let boxed = unsafe { NonNull::new_unchecked(boxed) };
|
||||
|
||||
Self { boxed }
|
||||
}
|
||||
|
||||
/// Replace the future currently stored in this box.
|
||||
///
|
||||
/// This reallocates if and only if the layout of the provided future is
|
||||
/// different from the layout of the currently stored future.
|
||||
pub fn set<F>(&mut self, future: F)
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
if let Err(future) = self.try_set(future) {
|
||||
*self = Self::new(future);
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the future currently stored in this box.
|
||||
///
|
||||
/// This function never reallocates, but returns an error if the provided
|
||||
/// future has a different size or alignment from the currently stored
|
||||
/// future.
|
||||
pub fn try_set<F>(&mut self, future: F) -> Result<(), F>
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
// SAFETY: The pointer is not dangling.
|
||||
let self_layout = {
|
||||
let dyn_future: &(dyn Future<Output = T> + Send) = unsafe { self.boxed.as_ref() };
|
||||
Layout::for_value(dyn_future)
|
||||
};
|
||||
|
||||
if Layout::new::<F>() == self_layout {
|
||||
// SAFETY: We just checked that the layout of F is correct.
|
||||
unsafe {
|
||||
self.set_same_layout(future);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(future)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the current future.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function requires that the layout of the provided future is the
|
||||
/// same as `self.layout`.
|
||||
unsafe fn set_same_layout<F>(&mut self, future: F)
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
// Drop the existing future, catching any panics.
|
||||
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
ptr::drop_in_place(self.boxed.as_ptr());
|
||||
}));
|
||||
|
||||
// Overwrite the future behind the pointer. This is safe because the
|
||||
// allocation was allocated with the same size and alignment as the type F.
|
||||
let self_ptr: *mut F = self.boxed.as_ptr() as *mut F;
|
||||
ptr::write(self_ptr, future);
|
||||
|
||||
// Update the vtable of self.boxed. The pointer is not null because we
|
||||
// just got it from self.boxed, which is not null.
|
||||
self.boxed = NonNull::new_unchecked(self_ptr);
|
||||
|
||||
// If the old future's destructor panicked, resume unwinding.
|
||||
match result {
|
||||
Ok(()) => {}
|
||||
Err(payload) => {
|
||||
panic::resume_unwind(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a pinned reference to the underlying future.
|
||||
pub fn get_pin(&mut self) -> Pin<&mut (dyn Future<Output = T> + Send)> {
|
||||
// SAFETY: The user of this box cannot move the box, and we do not move it
|
||||
// either.
|
||||
unsafe { Pin::new_unchecked(self.boxed.as_mut()) }
|
||||
}
|
||||
|
||||
/// Poll the future stored inside this box.
|
||||
pub fn poll(&mut self, cx: &mut Context<'_>) -> Poll<T> {
|
||||
self.get_pin().poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for ReusableBoxFuture<T> {
|
||||
type Output = T;
|
||||
|
||||
/// Poll the future stored inside this box.
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
|
||||
Pin::into_inner(self).get_pin().poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// The future stored inside ReusableBoxFuture<T> must be Send.
|
||||
unsafe impl<T> Send for ReusableBoxFuture<T> {}
|
||||
|
||||
// The only method called on self.boxed is poll, which takes &mut self, so this
|
||||
// struct being Sync does not permit any invalid access to the Future, even if
|
||||
// the future is not Sync.
|
||||
unsafe impl<T> Sync for ReusableBoxFuture<T> {}
|
||||
|
||||
// Just like a Pin<Box<dyn Future>> is always Unpin, so is this type.
|
||||
impl<T> Unpin for ReusableBoxFuture<T> {}
|
||||
|
||||
impl<T> Drop for ReusableBoxFuture<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
drop(Box::from_raw(self.boxed.as_ptr()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for ReusableBoxFuture<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ReusableBoxFuture").finish()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user