mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
126
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d35c907f6 | ||
|
|
ad8fb41103 | ||
|
|
2330edc875 | ||
|
|
fe2664a4e1 | ||
|
|
1eefbc250a | ||
|
|
a66017f049 | ||
|
|
aa6597ba66 | ||
|
|
ce0e9c67cf | ||
|
|
575938d457 | ||
|
|
0deaeb8494 | ||
|
|
a8dda19da4 | ||
|
|
be9fdb697d | ||
|
|
2ee9520d10 | ||
|
|
7d28e4cdbb | ||
|
|
0b83b3b8cc | ||
|
|
f95ad18980 | ||
|
|
eee3ca65d6 | ||
|
|
564c943309 | ||
|
|
c7671a0384 | ||
|
|
2893359988 | ||
|
|
5e5f513542 | ||
|
|
1b70507894 | ||
|
|
78f2340d25 | ||
|
|
e41e6cddbb | ||
|
|
b99b00eb30 | ||
|
|
d948ccedfc | ||
|
|
3ecaf9fd9a | ||
|
|
abd4c00255 | ||
|
|
c5861ef62f | ||
|
|
59e4b35f49 | ||
|
|
d74d17307d | ||
|
|
8efa62013b | ||
|
|
fcce78b33a | ||
|
|
79d25b0a48 | ||
|
|
3f29212cb7 | ||
|
|
48961fac89 | ||
|
|
a7833e3007 | ||
|
|
1f862d2e95 | ||
|
|
9149d7bfae | ||
|
|
4c55419453 | ||
|
|
f26f444f42 | ||
|
|
be2cb7a5ce | ||
|
|
df20c162ae | ||
|
|
c1ec469ad2 | ||
|
|
b01b2dacf2 | ||
|
|
68717c7efa | ||
|
|
69e62ef89e | ||
|
|
16c2e0983c | ||
|
|
4b1d76ec8f | ||
|
|
9646b4bce3 | ||
|
|
f60860af7e | ||
|
|
2a30e13f38 | ||
|
|
52cd240053 | ||
|
|
473ddaa277 | ||
|
|
9706ca92a8 | ||
|
|
fc7a4b3c6e | ||
|
|
e01391351b | ||
|
|
57dffb9dfe | ||
|
|
62023dffe5 | ||
|
|
0707f4c192 | ||
|
|
0dbba13984 | ||
|
|
a125ebd745 | ||
|
|
00500d1b35 | ||
|
|
647299866a | ||
|
|
a6051a61ec | ||
|
|
a8e0f0a919 | ||
|
|
7ae8135b62 | ||
|
|
353b0544a0 | ||
|
|
128495168d | ||
|
|
08548583b9 | ||
|
|
7707ba88ef | ||
|
|
72d6346c0d | ||
|
|
a85fdb884d | ||
|
|
c55d846f4b | ||
|
|
0acd06b42a | ||
|
|
4912943419 | ||
|
|
5e406a7a47 | ||
|
|
de33ee85ce | ||
|
|
874fc3320b | ||
|
|
ae67851f11 | ||
|
|
f927f01a34 | ||
|
|
49abfdb2ac | ||
|
|
479c545c20 | ||
|
|
34fcef258b | ||
|
|
7d11aa8668 | ||
|
|
0ea2307650 | ||
|
|
d0ebb41547 | ||
|
|
f5cb4c2042 | ||
|
|
4e39c9b818 | ||
|
|
97c2c4203c | ||
|
|
60366ca0fa | ||
|
|
850bfc9efa | ||
|
|
02b1117dca | ||
|
|
685da8dadd | ||
|
|
6a0e23c654 | ||
|
|
9d0c0dd22c | ||
|
|
6d5423f3e9 | ||
|
|
ebb8bab060 | ||
|
|
ce891a4df1 | ||
|
|
d869e16990 | ||
|
|
e1256d8ca4 | ||
|
|
a52f5071bf | ||
|
|
f1f8c3cde6 | ||
|
|
c2e843d928 | ||
|
|
a43ec09b55 | ||
|
|
90c2a510e2 | ||
|
|
f51ddc5958 | ||
|
|
47658a6da5 | ||
|
|
d7e3fcb9ee | ||
|
|
0b3918bce9 | ||
|
|
e309da0bee | ||
|
|
bbc8eb0f91 | ||
|
|
06c7e73e99 | ||
|
|
42de3bc7a4 | ||
|
|
20a2b9e263 | ||
|
|
ae4e8d7ad1 | ||
|
|
7a18ca2be0 | ||
|
|
4a7b7c52d1 | ||
|
|
fede3db76a | ||
|
|
2b23aa7389 | ||
|
|
382ee6bf5d | ||
|
|
24ed874e81 | ||
|
|
3965d91a5e | ||
|
|
a3ef4e4cf5 | ||
|
|
34eb47dde5 | ||
|
|
c8a484bbb2 |
+7
-8
@@ -1,17 +1,17 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-1-release-amd64
|
||||
image: freebsd-12-2-release-amd64
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 12.0
|
||||
name: FreeBSD
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y curl
|
||||
- pkg install -y bash curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
- . $HOME/.cargo/env
|
||||
@@ -23,8 +23,7 @@ task:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all
|
||||
- cargo doc --all --no-deps
|
||||
# TODO: Re-enable
|
||||
# i686_test_script:
|
||||
# - . $HOME/.cargo/env
|
||||
# - |
|
||||
# cargo test --all --exclude tokio-macros --target i686-unknown-freebsd
|
||||
i686_test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
cargo test --all --target i686-unknown-freebsd
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Benchmark
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
bench:
|
||||
- rt_multi_threaded
|
||||
- sync_mpsc
|
||||
- sync_rwlock
|
||||
- sync_semaphore
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
# Run benchmark with `go test -bench` and stores the output to a file
|
||||
- name: Run benchmark
|
||||
run: cargo bench --bench ${{ matrix.bench }} | tee ../output.txt
|
||||
working-directory: benches
|
||||
|
||||
# Download previous benchmark result from cache (if exists)
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
|
||||
# Run `github-action-benchmark` action
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
name: ${{ matrix.bench }}
|
||||
# What benchmark tool the output.txt came from
|
||||
tool: 'cargo'
|
||||
# Where the output from the benchmark tool is stored
|
||||
output-file-path: output.txt
|
||||
# # Where the previous data file is stored
|
||||
# external-data-json-path: ./cache/benchmark-data.json
|
||||
# Workflow will fail when an alert happens
|
||||
fail-on-alert: true
|
||||
# GitHub API token to make a commit comment
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Enable alert commit comment
|
||||
comment-on-alert: true
|
||||
alert-comment-cc-users: '@tokio-rs/maintainers'
|
||||
auto-push: true
|
||||
|
||||
# Upload the updated cache file for the next job by actions/cache
|
||||
@@ -1,8 +1,8 @@
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
branches: ["master", "tokio-*.x"]
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
branches: ["master", "tokio-*.x"]
|
||||
|
||||
name: CI
|
||||
|
||||
@@ -53,11 +53,6 @@ jobs:
|
||||
run: cargo test --features full
|
||||
working-directory: tokio
|
||||
|
||||
# Check `tokio` with `full + parking_lot` to make sure it compiles.
|
||||
- name: check tokio full,parking_lot
|
||||
run: cargo check --features full,parking_lot
|
||||
working-directory: tokio
|
||||
|
||||
# Test **all** crates in the workspace with all features.
|
||||
- name: test all --all-features
|
||||
run: cargo test --workspace --all-features
|
||||
@@ -186,6 +181,26 @@ jobs:
|
||||
- name: "test --workspace --all-features"
|
||||
run: cargo check --workspace --all-features
|
||||
|
||||
minimal-versions:
|
||||
name: minimal-versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
- name: "check --all-features -Z minimal-versions"
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
cargo hack --remove-dev-deps --workspace
|
||||
# Update Cargo.lock to minimal version dependencies.
|
||||
cargo update -Z minimal-versions
|
||||
cargo check --all-features
|
||||
|
||||
fmt:
|
||||
name: fmt
|
||||
runs-on: ubuntu-latest
|
||||
@@ -245,6 +260,7 @@ jobs:
|
||||
- loom_pool::group_b
|
||||
- loom_pool::group_c
|
||||
- loom_pool::group_d
|
||||
- time::driver
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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 }}
|
||||
@@ -486,7 +486,6 @@ missing a difficulty rating, and you should feel free to add one.
|
||||
- **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.
|
||||
|
||||
@@ -4,11 +4,13 @@ members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
"tokio-test",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
|
||||
# Internal
|
||||
"benches",
|
||||
"examples",
|
||||
"stress-test",
|
||||
"tests-build",
|
||||
"tests-integration",
|
||||
]
|
||||
|
||||
@@ -14,22 +14,21 @@ the Rust programming language. It is:
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: https://github.com/tokio-rs/tokio/blob/master/LICENSE
|
||||
[azure-badge]: https://dev.azure.com/tokio-rs/Tokio/_apis/build/status/tokio-rs.tokio?branchName=master
|
||||
[azure-url]: https://dev.azure.com/tokio-rs/Tokio/_build/latest?definitionId=1&branchName=master
|
||||
[actions-badge]: https://github.com/tokio-rs/tokio/workflows/CI/badge.svg
|
||||
[actions-url]: https://github.com/tokio-rs/tokio/actions?query=workflow%3ACI+branch%3Amaster
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/tokio
|
||||
|
||||
[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
|
||||
@@ -55,7 +54,7 @@ A basic TCP echo server with Tokio:
|
||||
|
||||
```rust,no_run
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
# 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
-5
@@ -5,7 +5,7 @@ publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
@@ -17,13 +17,13 @@ path = "spawn.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "mpsc"
|
||||
path = "mpsc.rs"
|
||||
name = "sync_mpsc"
|
||||
path = "sync_mpsc.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "scheduler"
|
||||
path = "scheduler.rs"
|
||||
name = "rt_multi_threaded"
|
||||
path = "rt_multi_threaded.rs"
|
||||
harness = false
|
||||
|
||||
|
||||
|
||||
@@ -30,22 +30,26 @@ 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 _ = tx.try_send([0; 64]);
|
||||
let _ = rt.block_on(tx.send([0; 64]));
|
||||
|
||||
rx.try_recv().unwrap();
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn send_large(b: &mut Bencher) {
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
let _ = tx.try_send([[0; 64]; 64]);
|
||||
let _ = rt.block_on(tx.send([[0; 64]; 64]));
|
||||
|
||||
rx.try_recv().unwrap();
|
||||
rt.block_on(rx.recv()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
+18
-4
@@ -7,18 +7,23 @@ edition = "2018"
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead.
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full", "tracing"] }
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing"] }
|
||||
tokio-util = { version = "0.6.0", path = "../tokio-util", features = ["full"] }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
|
||||
async-stream = "0.3"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
|
||||
tokio-util = { version = "0.4.0", path = "../tokio-util", features = ["full"] }
|
||||
bytes = "0.5"
|
||||
futures = "0.3.0"
|
||||
bytes = "1.0.0"
|
||||
futures = { version = "0.3.0", features = ["thread-pool"]}
|
||||
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"
|
||||
@@ -63,3 +68,12 @@ 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"
|
||||
|
||||
+9
-6
@@ -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;
|
||||
@@ -101,9 +101,6 @@ 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
|
||||
@@ -127,7 +124,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: Rx,
|
||||
rx: Pin<Box<dyn Stream<Item = String> + Send>>,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
@@ -159,11 +156,17 @@ impl Peer {
|
||||
let addr = lines.get_ref().peer_addr()?;
|
||||
|
||||
// Create a channel for this peer
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, mut 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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
fn main() {}
|
||||
|
||||
// Disabled while future of UdpFramed is decided on.
|
||||
// See https://github.com/tokio-rs/tokio/issues/2830
|
||||
/*
|
||||
//! This example leverages `BytesCodec` to create a UDP client and server which
|
||||
//! speak a custom protocol.
|
||||
//!
|
||||
@@ -14,8 +9,8 @@ fn main() {}
|
||||
#![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;
|
||||
|
||||
@@ -83,4 +78,3 @@ async fn pong(socket: &mut UdpFramed<BytesCodec>) -> Result<(), io::Error> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[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"
|
||||
@@ -0,0 +1,58 @@
|
||||
//! 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
# 1.0.0 (December 23, 2020)
|
||||
|
||||
- track `tokio` 1.0 release.
|
||||
|
||||
# 0.3.1 (October 25, 2020)
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -6,14 +6,14 @@ name = "tokio-macros"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.3.x" git tag.
|
||||
version = "0.3.1"
|
||||
# - Create "tokio-macros-1.0.x" git tag.
|
||||
version = "1.0.0"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-macros/0.3.1/tokio_macros"
|
||||
documentation = "https://docs.rs/tokio-macros/1.0.0/tokio_macros"
|
||||
description = """
|
||||
Tokio's proc macros.
|
||||
"""
|
||||
@@ -30,7 +30,7 @@ quote = "1"
|
||||
syn = { version = "1.0.3", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -193,17 +193,23 @@ fn parse_knobs(
|
||||
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)
|
||||
},
|
||||
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)
|
||||
},
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"current_thread\")].",
|
||||
macro_name
|
||||
)
|
||||
}
|
||||
"flavor" | "worker_threads" => {
|
||||
format!("The `{}` attribute requires an argument.", name)
|
||||
},
|
||||
}
|
||||
name => {
|
||||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name)
|
||||
},
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
|
||||
+29
-23
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.3.1")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/1.0.0")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
# or
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html) directly.
|
||||
///
|
||||
@@ -35,6 +35,12 @@ use proc_macro::TokenStream;
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html), which provides a more
|
||||
/// powerful interface.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// # Multi-threaded runtime
|
||||
///
|
||||
/// To use the multi-threaded runtime, the macro can be configured using
|
||||
@@ -47,6 +53,9 @@ use proc_macro::TokenStream;
|
||||
/// 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,
|
||||
@@ -137,10 +146,10 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro will not work.
|
||||
/// If you must rename the 0.2 version of tokio because you're also using the
|
||||
/// 0.1 version of tokio, you _must_ make the tokio 0.2 crate available as
|
||||
/// `tokio` in the module where this macro is expanded.
|
||||
/// If you rename the Tokio crate in your dependencies this macro will not work.
|
||||
/// If you must rename the 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.
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
@@ -182,11 +191,10 @@ 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 0.2 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
/// If you rename the Tokio crate in your dependencies this macro will not work.
|
||||
/// If you must rename the 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.
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
@@ -208,7 +216,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// The default test runtime is multi-threaded.
|
||||
/// The default test runtime is single-threaded.
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
@@ -219,11 +227,10 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.2 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
/// If you rename the Tokio crate in your dependencies this macro will not work.
|
||||
/// If you must rename the 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.
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, true)
|
||||
@@ -242,11 +249,10 @@ 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 0.2 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.2 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
/// If you rename the Tokio crate in your dependencies this macro will not work.
|
||||
/// If you must rename the 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.
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# 0.1.0 (December 23, 2020)
|
||||
|
||||
- Initial release
|
||||
@@ -0,0 +1,37 @@
|
||||
[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.0"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-stream/0.1.0/tokio_stream"
|
||||
description = """
|
||||
Utilities to work with `Stream` and `tokio`.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[features]
|
||||
default = ["time"]
|
||||
time = ["tokio/time"]
|
||||
|
||||
[dependencies]
|
||||
futures-core = { version = "0.3.0" }
|
||||
pin-project-lite = "0.2.0"
|
||||
tokio = { version = "1.0", path = "../tokio", features = ["sync"] }
|
||||
async-stream = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
|
||||
proptest = "0.10.0"
|
||||
@@ -0,0 +1,25 @@
|
||||
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,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::{Fuse, Stream};
|
||||
use crate::{Fuse, Stream};
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
@@ -26,7 +26,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from a [`Stream`](crate::stream::Stream).
|
||||
/// Convert from a [`Stream`](crate::Stream).
|
||||
///
|
||||
/// This trait is not intended to be used directly. Instead, call
|
||||
/// [`StreamExt::collect()`](super::StreamExt::collect).
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::pin::Pin;
|
||||
@@ -24,7 +24,7 @@ unsafe impl<T> Sync for Empty<T> {}
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::pin::Pin;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
@@ -8,6 +8,7 @@ use core::task::{Context, Poll};
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct Iter<I> {
|
||||
iter: I,
|
||||
yield_amt: usize,
|
||||
}
|
||||
|
||||
impl<I> Unpin for Iter<I> {}
|
||||
@@ -20,7 +21,7 @@ impl<I> Unpin for Iter<I> {}
|
||||
///
|
||||
/// ```
|
||||
/// # async fn dox() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter(vec![17, 19]);
|
||||
///
|
||||
@@ -35,6 +36,7 @@ where
|
||||
{
|
||||
Iter {
|
||||
iter: i.into_iter(),
|
||||
yield_amt: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +47,18 @@ where
|
||||
type Item = I::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<I::Item>> {
|
||||
let coop = ready!(crate::coop::poll_proceed(cx));
|
||||
coop.made_progress();
|
||||
Poll::Ready(self.iter.next())
|
||||
// TODO: add coop back
|
||||
if self.yield_amt >= 32 {
|
||||
self.yield_amt = 0;
|
||||
|
||||
cx.waker().wake_by_ref();
|
||||
|
||||
Poll::Pending
|
||||
} else {
|
||||
self.yield_amt += 1;
|
||||
|
||||
Poll::Ready(self.iter.next())
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
@@ -1,9 +1,34 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-stream/0.1.0")]
|
||||
#![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 module provides helpers to work with them. For examples of usage and a more in-depth
|
||||
//! 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.
|
||||
//!
|
||||
@@ -15,7 +40,7 @@
|
||||
//! `while let` loop as follows:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use tokio::stream::{self, StreamExt};
|
||||
//! use tokio_stream::{self as stream, StreamExt};
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
@@ -46,13 +71,16 @@
|
||||
//! [`tokio-util`] provides the [`StreamReader`] and [`ReaderStream`]
|
||||
//! types when the io feature is enabled.
|
||||
//!
|
||||
//! [tokio-util]: https://docs.rs/tokio-util/0.3/tokio_util/codec/index.html
|
||||
//! [`tokio::io`]: crate::io
|
||||
//! [`AsyncRead`]: crate::io::AsyncRead
|
||||
//! [`AsyncWrite`]: crate::io::AsyncWrite
|
||||
//! [`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;
|
||||
|
||||
mod all;
|
||||
use all::AllFuture;
|
||||
|
||||
@@ -120,16 +148,47 @@ use take_while::TakeWhile;
|
||||
cfg_time! {
|
||||
mod timeout;
|
||||
use timeout::Timeout;
|
||||
use crate::time::Duration;
|
||||
use tokio::time::Duration;
|
||||
mod throttle;
|
||||
use crate::stream::throttle::{throttle, Throttle};
|
||||
use crate::throttle::{throttle, Throttle};
|
||||
}
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use futures_core::Stream;
|
||||
|
||||
/// An extension trait for `Stream`s that provides a variety of convenient
|
||||
/// combinator functions.
|
||||
/// An extension trait for the [`Stream`] trait that provides a variety of
|
||||
/// convenient combinator functions.
|
||||
///
|
||||
/// Be aware that the `Stream` trait in Tokio is a re-export of the trait found
|
||||
/// in the [futures] crate, however both Tokio and futures provide separate
|
||||
/// `StreamExt` utility traits, and some utilities are only available on one of
|
||||
/// these traits. Click [here][futures-StreamExt] to see the other `StreamExt`
|
||||
/// trait in the futures crate.
|
||||
///
|
||||
/// If you need utilities from both `StreamExt` traits, you should prefer to
|
||||
/// import one of them, and use the other through the fully qualified call
|
||||
/// syntax. For example:
|
||||
/// ```
|
||||
/// // import one of the traits:
|
||||
/// use futures::stream::StreamExt;
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {
|
||||
///
|
||||
/// let a = tokio_stream::iter(vec![1, 3, 5]);
|
||||
/// let b = tokio_stream::iter(vec![2, 4, 6]);
|
||||
///
|
||||
/// // use the fully qualified call syntax for the other trait:
|
||||
/// let merged = tokio_stream::StreamExt::merge(a, b);
|
||||
///
|
||||
/// // use normal call notation for futures::stream::StreamExt::collect
|
||||
/// let output: Vec<_> = merged.collect().await;
|
||||
/// assert_eq!(output, vec![1, 2, 3, 4, 5, 6]);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`Stream`]: crate::Stream
|
||||
/// [futures]: https://docs.rs/futures
|
||||
/// [futures-StreamExt]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html
|
||||
pub trait StreamExt: Stream {
|
||||
/// Consumes and returns the next value in the stream or `None` if the
|
||||
/// stream is finished.
|
||||
@@ -152,7 +211,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter(1..=3);
|
||||
///
|
||||
@@ -188,7 +247,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter(vec![Ok(1), Ok(2), Err("nope")]);
|
||||
///
|
||||
@@ -220,7 +279,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let stream = stream::iter(1..=3);
|
||||
/// let mut stream = stream.map(|x| x + 3);
|
||||
@@ -253,16 +312,17 @@ pub trait StreamExt: Stream {
|
||||
///
|
||||
/// For merging multiple streams, consider using [`StreamMap`] instead.
|
||||
///
|
||||
/// [`StreamMap`]: crate::stream::StreamMap
|
||||
/// [`StreamMap`]: crate::StreamMap
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::StreamExt;
|
||||
/// use tokio_stream::{StreamExt, Stream};
|
||||
/// use tokio::sync::mpsc;
|
||||
/// use tokio::time;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
/// use std::pin::Pin;
|
||||
///
|
||||
/// # /*
|
||||
/// #[tokio::main]
|
||||
@@ -270,8 +330,21 @@ pub trait StreamExt: Stream {
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// # time::pause();
|
||||
/// let (tx1, rx1) = mpsc::channel(10);
|
||||
/// let (tx2, rx2) = mpsc::channel(10);
|
||||
/// let (tx1, mut rx1) = mpsc::channel::<usize>(10);
|
||||
/// let (tx2, mut rx2) = mpsc::channel::<usize>(10);
|
||||
///
|
||||
/// // Convert the channels to a `Stream`.
|
||||
/// let rx1 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx1.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// let rx2 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx2.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// let mut rx = rx1.merge(rx2);
|
||||
///
|
||||
@@ -334,7 +407,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let stream = stream::iter(1..=8);
|
||||
/// let mut evens = stream.filter(|x| x % 2 == 0);
|
||||
@@ -370,7 +443,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let stream = stream::iter(1..=8);
|
||||
/// let mut evens = stream.filter_map(|x| {
|
||||
@@ -402,7 +475,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{Stream, StreamExt};
|
||||
/// use tokio_stream::{Stream, StreamExt};
|
||||
///
|
||||
/// use std::pin::Pin;
|
||||
/// use std::task::{Context, Poll};
|
||||
@@ -467,7 +540,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter(1..=10).take(3);
|
||||
///
|
||||
@@ -496,7 +569,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter(1..=10).take_while(|x| *x <= 3);
|
||||
///
|
||||
@@ -522,7 +595,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter(1..=10).skip(7);
|
||||
///
|
||||
@@ -553,7 +626,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// let mut stream = stream::iter(vec![1,2,3,4,1]).skip_while(|x| *x < 3);
|
||||
///
|
||||
/// assert_eq!(Some(3), stream.next().await);
|
||||
@@ -596,7 +669,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let a = [1, 2, 3];
|
||||
///
|
||||
@@ -611,7 +684,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let a = [1, 2, 3];
|
||||
///
|
||||
@@ -655,7 +728,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let a = [1, 2, 3];
|
||||
///
|
||||
@@ -670,7 +743,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// let a = [1, 2, 3];
|
||||
///
|
||||
@@ -699,7 +772,7 @@ pub trait StreamExt: Stream {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
@@ -739,7 +812,7 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, *};
|
||||
/// use tokio_stream::{self as stream, *};
|
||||
///
|
||||
/// let s = stream::iter(vec![1u8, 2, 3]);
|
||||
/// let sum = s.fold(0, |acc, x| acc + x).await;
|
||||
@@ -766,7 +839,9 @@ pub trait StreamExt: Stream {
|
||||
/// `collect` streams all values, awaiting as needed. Values are pushed into
|
||||
/// a collection. A number of different target collection types are
|
||||
/// supported, including [`Vec`](std::vec::Vec),
|
||||
/// [`String`](std::string::String), and [`Bytes`](bytes::Bytes).
|
||||
/// [`String`](std::string::String), and [`Bytes`].
|
||||
///
|
||||
/// [`Bytes`]: https://docs.rs/bytes/0.6.0/bytes/struct.Bytes.html
|
||||
///
|
||||
/// # `Result`
|
||||
///
|
||||
@@ -785,7 +860,7 @@ pub trait StreamExt: Stream {
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
@@ -802,7 +877,7 @@ pub trait StreamExt: Stream {
|
||||
/// Collecting a stream of `Result` values
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
@@ -858,11 +933,12 @@ pub trait StreamExt: Stream {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
/// use std::time::Duration;
|
||||
/// # let int_stream = stream::iter(1..=3);
|
||||
///
|
||||
/// let mut int_stream = int_stream.timeout(Duration::from_secs(1));
|
||||
/// let int_stream = int_stream.timeout(Duration::from_secs(1));
|
||||
/// tokio::pin!(int_stream);
|
||||
///
|
||||
/// // When no items time out, we get the 3 elements in succession:
|
||||
/// assert_eq!(int_stream.try_next().await, Ok(Some(1)));
|
||||
@@ -903,10 +979,11 @@ pub trait StreamExt: Stream {
|
||||
/// Create a throttled stream.
|
||||
/// ```rust,no_run
|
||||
/// use std::time::Duration;
|
||||
/// use tokio::stream::StreamExt;
|
||||
/// use tokio_stream::StreamExt;
|
||||
///
|
||||
/// # async fn dox() {
|
||||
/// let mut item_stream = futures::stream::repeat("one").throttle(Duration::from_secs(2));
|
||||
/// let item_stream = futures::stream::repeat("one").throttle(Duration::from_secs(2));
|
||||
/// tokio::pin!(item_stream);
|
||||
///
|
||||
/// loop {
|
||||
/// // The string will be produced at most every 2 seconds
|
||||
@@ -0,0 +1,18 @@
|
||||
macro_rules! cfg_time {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! ready {
|
||||
($e:expr $(,)?) => {
|
||||
match $e {
|
||||
std::task::Poll::Ready(t) => t,
|
||||
std::task::Poll::Pending => return std::task::Poll::Pending,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::{Fuse, Stream};
|
||||
use crate::{Fuse, Stream};
|
||||
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::{self, Iter, Stream};
|
||||
use crate::{Iter, Stream};
|
||||
|
||||
use core::option;
|
||||
use core::pin::Pin;
|
||||
@@ -20,7 +20,7 @@ impl<I> Unpin for Once<I> {}
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
@@ -35,7 +35,7 @@ impl<I> Unpin for Once<I> {}
|
||||
/// ```
|
||||
pub fn once<T>(value: T) -> Once<T> {
|
||||
Once {
|
||||
iter: stream::iter(Some(value).into_iter()),
|
||||
iter: crate::iter(Some(value).into_iter()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::pin::Pin;
|
||||
@@ -16,7 +16,7 @@ unsafe impl<T> Sync for Pending<T> {}
|
||||
/// Creates a stream that is never ready
|
||||
///
|
||||
/// The returned stream is never ready. Attempting to call
|
||||
/// [`next()`](crate::stream::StreamExt::next) will never complete. Use
|
||||
/// [`next()`](crate::StreamExt::next) will never complete. Use
|
||||
/// [`stream::empty()`](super::empty()) to obtain a stream that is is
|
||||
/// immediately empty but returns no values.
|
||||
///
|
||||
@@ -25,7 +25,7 @@ unsafe impl<T> Sync for Pending<T> {}
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::stream::{self, StreamExt};
|
||||
/// use tokio_stream::{self as stream, StreamExt};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::hash::Hash;
|
||||
@@ -42,9 +42,9 @@ use std::task::{Context, Poll};
|
||||
/// to be merged, it may be advisable to use tasks sending values on a shared
|
||||
/// [`mpsc`] channel.
|
||||
///
|
||||
/// [`StreamExt::merge`]: crate::stream::StreamExt::merge
|
||||
/// [`mpsc`]: crate::sync::mpsc
|
||||
/// [`pin!`]: macro@pin
|
||||
/// [`StreamExt::merge`]: crate::StreamExt::merge
|
||||
/// [`mpsc`]: https://docs.rs/tokio/1.0/tokio/sync/mpsc/index.html
|
||||
/// [`pin!`]: https://docs.rs/tokio/1.0/tokio/macro.pin.html
|
||||
/// [`Box::pin`]: std::boxed::Box::pin
|
||||
///
|
||||
/// # Examples
|
||||
@@ -52,13 +52,27 @@ use std::task::{Context, Poll};
|
||||
/// Merging two streams, then remove them after receiving the first value
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamExt, StreamMap};
|
||||
/// use tokio_stream::{StreamExt, StreamMap, Stream};
|
||||
/// use tokio::sync::mpsc;
|
||||
/// use std::pin::Pin;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let (tx1, rx1) = mpsc::channel(10);
|
||||
/// let (tx2, rx2) = mpsc::channel(10);
|
||||
/// let (tx1, mut rx1) = mpsc::channel::<usize>(10);
|
||||
/// let (tx2, mut rx2) = mpsc::channel::<usize>(10);
|
||||
///
|
||||
/// // Convert the channels to a `Stream`.
|
||||
/// let rx1 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx1.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// let rx2 = Box::pin(async_stream::stream! {
|
||||
/// while let Some(item) = rx2.recv().await {
|
||||
/// yield item;
|
||||
/// }
|
||||
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
|
||||
///
|
||||
/// tokio::spawn(async move {
|
||||
/// tx1.send(1).await.unwrap();
|
||||
@@ -103,7 +117,7 @@ use std::task::{Context, Poll};
|
||||
/// sent to the client over a socket.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::stream::{Stream, StreamExt, StreamMap};
|
||||
/// use tokio_stream::{Stream, StreamExt, StreamMap};
|
||||
///
|
||||
/// enum Command {
|
||||
/// Join(String),
|
||||
@@ -112,13 +126,13 @@ use std::task::{Context, Poll};
|
||||
///
|
||||
/// fn commands() -> impl Stream<Item = Command> {
|
||||
/// // Streams in user commands by parsing `stdin`.
|
||||
/// # tokio::stream::pending()
|
||||
/// # tokio_stream::pending()
|
||||
/// }
|
||||
///
|
||||
/// // Join a channel, returns a stream of messages received on the channel.
|
||||
/// fn join(channel: &str) -> impl Stream<Item = String> + Unpin {
|
||||
/// // left as an exercise to the reader
|
||||
/// # tokio::stream::pending()
|
||||
/// # tokio_stream::pending()
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
@@ -156,7 +170,7 @@ use std::task::{Context, Poll};
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct StreamMap<K, V> {
|
||||
/// Streams stored in the map
|
||||
entries: Vec<(K, V)>,
|
||||
@@ -170,7 +184,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
@@ -193,7 +207,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
@@ -217,7 +231,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, Pending};
|
||||
/// use tokio_stream::{StreamMap, Pending};
|
||||
///
|
||||
/// let map: StreamMap<&str, Pending<()>> = StreamMap::new();
|
||||
/// ```
|
||||
@@ -233,7 +247,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, Pending};
|
||||
/// use tokio_stream::{StreamMap, Pending};
|
||||
///
|
||||
/// let map: StreamMap<&str, Pending<()>> = StreamMap::with_capacity(10);
|
||||
/// ```
|
||||
@@ -250,7 +264,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
@@ -273,7 +287,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
@@ -296,7 +310,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
@@ -320,7 +334,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, Pending};
|
||||
/// use tokio_stream::{StreamMap, Pending};
|
||||
///
|
||||
/// let map: StreamMap<i32, Pending<()>> = StreamMap::with_capacity(100);
|
||||
/// assert!(map.capacity() >= 100);
|
||||
@@ -334,7 +348,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut a = StreamMap::new();
|
||||
/// assert_eq!(a.len(), 0);
|
||||
@@ -367,7 +381,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut a = StreamMap::new();
|
||||
/// a.insert(1, pending::<i32>());
|
||||
@@ -388,7 +402,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
///
|
||||
@@ -416,7 +430,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
/// map.insert(1, pending::<i32>());
|
||||
@@ -445,7 +459,7 @@ impl<K, V> StreamMap<K, V> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::stream::{StreamMap, pending};
|
||||
/// use tokio_stream::{StreamMap, pending};
|
||||
///
|
||||
/// let mut map = StreamMap::new();
|
||||
/// map.insert(1, pending::<i32>());
|
||||
@@ -476,7 +490,7 @@ where
|
||||
fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<Option<(usize, V::Item)>> {
|
||||
use Poll::*;
|
||||
|
||||
let start = crate::util::thread_rng_n(self.entries.len() as u32) as usize;
|
||||
let start = self::rand::thread_rng_n(self.entries.len() as u32) as usize;
|
||||
let mut idx = start;
|
||||
|
||||
for _ in 0..self.entries.len() {
|
||||
@@ -513,6 +527,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Default for StreamMap<K, V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Stream for StreamMap<K, V>
|
||||
where
|
||||
K: Clone + Unpin,
|
||||
@@ -547,3 +567,98 @@ where
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
mod rand {
|
||||
use std::cell::Cell;
|
||||
|
||||
mod loom {
|
||||
#[cfg(not(loom))]
|
||||
pub(crate) mod rand {
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
pub(crate) fn seed() -> u64 {
|
||||
let rand_state = RandomState::new();
|
||||
|
||||
let mut hasher = rand_state.build_hasher();
|
||||
|
||||
// Hash some unique-ish data to generate some new state
|
||||
COUNTER.fetch_add(1, Relaxed).hash(&mut hasher);
|
||||
|
||||
// Get the seed
|
||||
hasher.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(loom)]
|
||||
pub(crate) mod rand {
|
||||
pub(crate) fn seed() -> u64 {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fast random number generate
|
||||
///
|
||||
/// Implement xorshift64+: 2 32-bit xorshift sequences added together.
|
||||
/// Shift triplet [17,7,16] was calculated as indicated in Marsaglia's
|
||||
/// Xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
|
||||
/// This generator passes the SmallCrush suite, part of TestU01 framework:
|
||||
/// http://simul.iro.umontreal.ca/testu01/tu01.html
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FastRand {
|
||||
one: Cell<u32>,
|
||||
two: Cell<u32>,
|
||||
}
|
||||
|
||||
impl FastRand {
|
||||
/// Initialize a new, thread-local, fast random number generator.
|
||||
pub(crate) fn new(seed: u64) -> FastRand {
|
||||
let one = (seed >> 32) as u32;
|
||||
let mut two = seed as u32;
|
||||
|
||||
if two == 0 {
|
||||
// This value cannot be zero
|
||||
two = 1;
|
||||
}
|
||||
|
||||
FastRand {
|
||||
one: Cell::new(one),
|
||||
two: Cell::new(two),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fastrand_n(&self, n: u32) -> u32 {
|
||||
// This is similar to fastrand() % n, but faster.
|
||||
// See https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
|
||||
let mul = (self.fastrand() as u64).wrapping_mul(n as u64);
|
||||
(mul >> 32) as u32
|
||||
}
|
||||
|
||||
fn fastrand(&self) -> u32 {
|
||||
let mut s1 = self.one.get();
|
||||
let s0 = self.two.get();
|
||||
|
||||
s1 ^= s1 << 17;
|
||||
s1 = s1 ^ s0 ^ s1 >> 7 ^ s0 >> 16;
|
||||
|
||||
self.one.set(s0);
|
||||
self.two.set(s1);
|
||||
|
||||
s0.wrapping_add(s1)
|
||||
}
|
||||
}
|
||||
|
||||
// Used by `StreamMap`
|
||||
pub(crate) fn thread_rng_n(n: u32) -> u32 {
|
||||
thread_local! {
|
||||
static THREAD_RNG: FastRand = FastRand::new(loom::rand::seed());
|
||||
}
|
||||
|
||||
THREAD_RNG.with(|rng| rng.fastrand_n(n))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::cmp;
|
||||
use core::fmt;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::Stream;
|
||||
use crate::Stream;
|
||||
|
||||
use core::fmt;
|
||||
use core::pin::Pin;
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Slow down a stream by enforcing a delay between items.
|
||||
|
||||
use crate::stream::Stream;
|
||||
use crate::time::{Duration, Instant, Sleep};
|
||||
use crate::Stream;
|
||||
use tokio::time::{Duration, Instant, Sleep};
|
||||
|
||||
use std::future::Future;
|
||||
use std::marker::Unpin;
|
||||
@@ -14,14 +14,8 @@ pub(super) fn throttle<T>(duration: Duration, stream: T) -> Throttle<T>
|
||||
where
|
||||
T: Stream,
|
||||
{
|
||||
let delay = if duration == Duration::from_millis(0) {
|
||||
None
|
||||
} else {
|
||||
Some(Sleep::new_timeout(Instant::now() + duration, duration))
|
||||
};
|
||||
|
||||
Throttle {
|
||||
delay,
|
||||
delay: tokio::time::sleep_until(Instant::now() + duration),
|
||||
duration,
|
||||
has_delayed: true,
|
||||
stream,
|
||||
@@ -33,8 +27,8 @@ pin_project! {
|
||||
#[derive(Debug)]
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct Throttle<T> {
|
||||
// `None` when duration is zero.
|
||||
delay: Option<Sleep>,
|
||||
#[pin]
|
||||
delay: Sleep,
|
||||
duration: Duration,
|
||||
|
||||
// Set to true when `delay` has returned ready, but `stream` hasn't.
|
||||
@@ -75,23 +69,29 @@ impl<T: Unpin> Throttle<T> {
|
||||
impl<T: Stream> Stream for Throttle<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
if !self.has_delayed && self.delay.is_some() {
|
||||
ready!(Pin::new(self.as_mut().project().delay.as_mut().unwrap()).poll(cx));
|
||||
*self.as_mut().project().has_delayed = true;
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut me = self.project();
|
||||
let dur = *me.duration;
|
||||
|
||||
if !*me.has_delayed && !is_zero(dur) {
|
||||
ready!(me.delay.as_mut().poll(cx));
|
||||
*me.has_delayed = true;
|
||||
}
|
||||
|
||||
let value = ready!(self.as_mut().project().stream.poll_next(cx));
|
||||
let value = ready!(me.stream.poll_next(cx));
|
||||
|
||||
if value.is_some() {
|
||||
let dur = self.duration;
|
||||
if let Some(ref mut delay) = self.as_mut().project().delay {
|
||||
delay.reset(Instant::now() + dur);
|
||||
if !is_zero(dur) {
|
||||
me.delay.reset(Instant::now() + dur);
|
||||
}
|
||||
|
||||
*self.as_mut().project().has_delayed = false;
|
||||
*me.has_delayed = false;
|
||||
}
|
||||
|
||||
Poll::Ready(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_zero(dur: Duration) -> bool {
|
||||
dur == Duration::from_millis(0)
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::stream::{Fuse, Stream};
|
||||
use crate::time::{error::Elapsed, Instant, Sleep};
|
||||
use crate::{Fuse, Stream};
|
||||
use tokio::time::{Instant, Sleep};
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
pin_project! {
|
||||
@@ -14,16 +15,21 @@ pin_project! {
|
||||
pub struct Timeout<S> {
|
||||
#[pin]
|
||||
stream: Fuse<S>,
|
||||
#[pin]
|
||||
deadline: Sleep,
|
||||
duration: Duration,
|
||||
poll_deadline: bool,
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by `Timeout`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Elapsed(());
|
||||
|
||||
impl<S: Stream> Timeout<S> {
|
||||
pub(super) fn new(stream: S, duration: Duration) -> Self {
|
||||
let next = Instant::now() + duration;
|
||||
let deadline = Sleep::new_timeout(next, duration);
|
||||
let deadline = tokio::time::sleep_until(next);
|
||||
|
||||
Timeout {
|
||||
stream: Fuse::new(stream),
|
||||
@@ -37,22 +43,24 @@ impl<S: Stream> Timeout<S> {
|
||||
impl<S: Stream> Stream for Timeout<S> {
|
||||
type Item = Result<S::Item, Elapsed>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match self.as_mut().project().stream.poll_next(cx) {
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let me = self.project();
|
||||
|
||||
match me.stream.poll_next(cx) {
|
||||
Poll::Ready(v) => {
|
||||
if v.is_some() {
|
||||
let next = Instant::now() + self.duration;
|
||||
self.as_mut().project().deadline.reset(next);
|
||||
*self.as_mut().project().poll_deadline = true;
|
||||
let next = Instant::now() + *me.duration;
|
||||
me.deadline.reset(next);
|
||||
*me.poll_deadline = true;
|
||||
}
|
||||
return Poll::Ready(v.map(Ok));
|
||||
}
|
||||
Poll::Pending => {}
|
||||
};
|
||||
|
||||
if self.poll_deadline {
|
||||
ready!(Pin::new(self.as_mut().project().deadline).poll(cx));
|
||||
*self.as_mut().project().poll_deadline = false;
|
||||
if *me.poll_deadline {
|
||||
ready!(me.deadline.poll(cx));
|
||||
*me.poll_deadline = false;
|
||||
return Poll::Ready(Some(Err(Elapsed::new())));
|
||||
}
|
||||
|
||||
@@ -63,3 +71,25 @@ impl<S: Stream> Stream for Timeout<S> {
|
||||
self.stream.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Elapsed =====
|
||||
|
||||
impl Elapsed {
|
||||
pub(crate) fn new() -> Self {
|
||||
Elapsed(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Elapsed {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
"deadline has elapsed".fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Elapsed {}
|
||||
|
||||
impl From<Elapsed> for std::io::Error {
|
||||
fn from(_err: Elapsed) -> std::io::Error {
|
||||
std::io::ErrorKind::TimedOut.into()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::stream::{Next, Stream};
|
||||
use crate::{Next, Stream};
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomPinned;
|
||||
@@ -0,0 +1,105 @@
|
||||
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,7 +1,12 @@
|
||||
use tokio::stream::{self, Stream, StreamExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{self as stream, Stream, StreamExt};
|
||||
use tokio_test::{assert_pending, assert_ready, task};
|
||||
|
||||
mod support {
|
||||
pub(crate) mod mpsc;
|
||||
}
|
||||
|
||||
use support::mpsc;
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_usage() {
|
||||
let one = stream::iter(vec![1, 2, 3]);
|
||||
@@ -36,8 +41,8 @@ async fn basic_usage() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn pending_first() {
|
||||
let (tx1, rx1) = mpsc::unbounded_channel();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel();
|
||||
let (tx1, rx1) = mpsc::unbounded_channel_stream();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let mut stream = task::spawn(rx1.chain(rx2));
|
||||
assert_eq!(stream.size_hint(), (0, None));
|
||||
@@ -74,7 +79,7 @@ async fn pending_first() {
|
||||
fn size_overflow() {
|
||||
struct Monster;
|
||||
|
||||
impl tokio::stream::Stream for Monster {
|
||||
impl tokio_stream::Stream for Monster {
|
||||
type Item = ();
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
@@ -1,7 +1,12 @@
|
||||
use tokio::stream::{self, StreamExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{self as stream, StreamExt};
|
||||
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok, task};
|
||||
|
||||
mod support {
|
||||
pub(crate) mod mpsc;
|
||||
}
|
||||
|
||||
use support::mpsc;
|
||||
|
||||
#[allow(clippy::let_unit_value)]
|
||||
#[tokio::test]
|
||||
async fn empty_unit() {
|
||||
@@ -37,7 +42,7 @@ async fn empty_result() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_vec_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let mut fut = task::spawn(rx.collect::<Vec<i32>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
@@ -58,7 +63,8 @@ async fn collect_vec_items() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_string_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let mut fut = task::spawn(rx.collect::<String>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
@@ -79,7 +85,8 @@ async fn collect_string_items() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_str_items() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let mut fut = task::spawn(rx.collect::<String>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
@@ -100,7 +107,8 @@ async fn collect_str_items() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_results_ok() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let mut fut = task::spawn(rx.collect::<Result<String, &str>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
@@ -121,7 +129,8 @@ async fn collect_results_ok() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn collect_results_err() {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let mut fut = task::spawn(rx.collect::<Result<String, &str>>());
|
||||
|
||||
assert_pending!(fut.poll());
|
||||
@@ -1,4 +1,4 @@
|
||||
use tokio::stream::{self, Stream, StreamExt};
|
||||
use tokio_stream::{self as stream, Stream, StreamExt};
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_usage() {
|
||||
@@ -1,4 +1,4 @@
|
||||
use tokio::stream::{Stream, StreamExt};
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -1,4 +1,4 @@
|
||||
use tokio::stream;
|
||||
use tokio_stream as stream;
|
||||
use tokio_test::task;
|
||||
|
||||
use std::iter;
|
||||
@@ -1,8 +1,13 @@
|
||||
use tokio::stream::{self, Stream, StreamExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{self as stream, Stream, StreamExt};
|
||||
use tokio_test::task;
|
||||
use tokio_test::{assert_pending, assert_ready};
|
||||
|
||||
mod support {
|
||||
pub(crate) mod mpsc;
|
||||
}
|
||||
|
||||
use support::mpsc;
|
||||
|
||||
#[tokio::test]
|
||||
async fn merge_sync_streams() {
|
||||
let mut s = stream::iter(vec![0, 2, 4, 6]).merge(stream::iter(vec![1, 3, 5]));
|
||||
@@ -18,8 +23,8 @@ async fn merge_sync_streams() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn merge_async_streams() {
|
||||
let (tx1, rx1) = mpsc::unbounded_channel();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel();
|
||||
let (tx1, rx1) = mpsc::unbounded_channel_stream();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let mut rx = task::spawn(rx1.merge(rx2));
|
||||
|
||||
@@ -57,7 +62,7 @@ async fn merge_async_streams() {
|
||||
fn size_overflow() {
|
||||
struct Monster;
|
||||
|
||||
impl tokio::stream::Stream for Monster {
|
||||
impl tokio_stream::Stream for Monster {
|
||||
type Item = ();
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
@@ -1,4 +1,4 @@
|
||||
use tokio::stream::{self, Stream, StreamExt};
|
||||
use tokio_stream::{self as stream, Stream, StreamExt};
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_usage() {
|
||||
@@ -1,4 +1,4 @@
|
||||
use tokio::stream::{self, Stream, StreamExt};
|
||||
use tokio_stream::{self as stream, Stream, StreamExt};
|
||||
use tokio_test::{assert_pending, task};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1,7 +1,12 @@
|
||||
use tokio::stream::{self, pending, Stream, StreamExt, StreamMap};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap};
|
||||
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
|
||||
|
||||
mod support {
|
||||
pub(crate) mod mpsc;
|
||||
}
|
||||
|
||||
use support::mpsc;
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
macro_rules! assert_ready_some {
|
||||
@@ -38,7 +43,8 @@ async fn empty() {
|
||||
#[tokio::test]
|
||||
async fn single_entry() {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
let rx = Box::pin(rx);
|
||||
|
||||
assert_ready_none!(map.poll_next());
|
||||
|
||||
@@ -76,8 +82,11 @@ async fn single_entry() {
|
||||
#[tokio::test]
|
||||
async fn multiple_entries() {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let (tx1, rx1) = mpsc::unbounded_channel();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel();
|
||||
let (tx1, rx1) = mpsc::unbounded_channel_stream();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let rx1 = Box::pin(rx1);
|
||||
let rx2 = Box::pin(rx2);
|
||||
|
||||
map.insert("foo", rx1);
|
||||
map.insert("bar", rx2);
|
||||
@@ -132,7 +141,9 @@ async fn multiple_entries() {
|
||||
#[tokio::test]
|
||||
async fn insert_remove() {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let rx = Box::pin(rx);
|
||||
|
||||
assert_ready_none!(map.poll_next());
|
||||
|
||||
@@ -160,8 +171,11 @@ async fn insert_remove() {
|
||||
#[tokio::test]
|
||||
async fn replace() {
|
||||
let mut map = task::spawn(StreamMap::new());
|
||||
let (tx1, rx1) = mpsc::unbounded_channel();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel();
|
||||
let (tx1, rx1) = mpsc::unbounded_channel_stream();
|
||||
let (tx2, rx2) = mpsc::unbounded_channel_stream();
|
||||
|
||||
let rx1 = Box::pin(rx1);
|
||||
let rx2 = Box::pin(rx2);
|
||||
|
||||
assert!(map.insert("foo", rx1).is_none());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use tokio::stream::{self, StreamExt};
|
||||
use tokio::time::{self, sleep, Duration};
|
||||
use tokio_stream::{self, StreamExt};
|
||||
use tokio_test::*;
|
||||
|
||||
use futures::StreamExt as _;
|
||||
@@ -78,7 +78,7 @@ async fn return_elapsed_errors_only_once() {
|
||||
// error is returned.
|
||||
assert_pending!(stream.poll_next());
|
||||
//
|
||||
time::advance(ms(50)).await;
|
||||
time::advance(ms(51)).await;
|
||||
let v = assert_ready!(stream.poll_next());
|
||||
assert!(v.unwrap().is_err()); // timeout!
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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,8 +1,8 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio::time;
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_test::*;
|
||||
|
||||
use std::time::Duration;
|
||||
@@ -1,3 +1,7 @@
|
||||
# 0.4.0 (December 23, 2020)
|
||||
|
||||
- Track `tokio` 1.0 release.
|
||||
|
||||
# 0.3.0 (October 15, 2020)
|
||||
|
||||
- Track `tokio` 0.3 release.
|
||||
|
||||
@@ -6,27 +6,29 @@ name = "tokio-test"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.3.x" git tag.
|
||||
version = "0.3.0"
|
||||
# - Create "tokio-test-0.4.x" git tag.
|
||||
version = "0.4.0"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-test/0.3.0/tokio_test"
|
||||
documentation = "https://docs.rs/tokio-test/0.4.0/tokio_test"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
categories = ["asynchronous", "testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["rt", "stream", "sync", "time", "test-util"] }
|
||||
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"
|
||||
|
||||
bytes = "0.5.0"
|
||||
bytes = "1.0.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
+20
-9
@@ -22,8 +22,9 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{self, Duration, Instant, Sleep};
|
||||
|
||||
use futures_core::ready;
|
||||
use futures_core::{ready, Stream};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -63,13 +64,13 @@ enum Action {
|
||||
WriteError(Option<Arc<io::Error>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
actions: VecDeque<Action>,
|
||||
waiting: Option<Instant>,
|
||||
sleep: Option<Sleep>,
|
||||
sleep: Option<Pin<Box<Sleep>>>,
|
||||
read_wait: Option<Waker>,
|
||||
rx: mpsc::UnboundedReceiver<Action>,
|
||||
// rx: mpsc::UnboundedReceiver<Action>,
|
||||
rx: Pin<Box<dyn Stream<Item = Action> + Send>>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
@@ -184,7 +185,13 @@ impl Handle {
|
||||
|
||||
impl Inner {
|
||||
fn new(actions: VecDeque<Action>) -> (Inner, Handle) {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
let rx = Box::pin(async_stream::stream! {
|
||||
while let Some(item) = rx.recv().await {
|
||||
yield item;
|
||||
}
|
||||
});
|
||||
|
||||
let inner = Inner {
|
||||
actions,
|
||||
@@ -200,8 +207,6 @@ impl Inner {
|
||||
}
|
||||
|
||||
fn poll_action(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Action>> {
|
||||
use futures_core::stream::Stream;
|
||||
|
||||
Pin::new(&mut self.rx).poll_next(cx)
|
||||
}
|
||||
|
||||
@@ -365,7 +370,7 @@ impl AsyncRead for Mock {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if let Some(rem) = self.inner.remaining_wait() {
|
||||
let until = Instant::now() + rem;
|
||||
self.inner.sleep = Some(time::sleep_until(until));
|
||||
self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
|
||||
} else {
|
||||
self.inner.read_wait = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
@@ -410,7 +415,7 @@ impl AsyncWrite for Mock {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if let Some(rem) = self.inner.remaining_wait() {
|
||||
let until = Instant::now() + rem;
|
||||
self.inner.sleep = Some(time::sleep_until(until));
|
||||
self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
|
||||
} else {
|
||||
panic!("unexpected WouldBlock");
|
||||
}
|
||||
@@ -485,3 +490,9 @@ 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.3.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.4.0")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
|
||||
@@ -9,7 +9,7 @@ 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
|
||||
pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
# 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
|
||||
|
||||
+18
-11
@@ -6,14 +6,14 @@ name = "tokio-util"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.4.0"
|
||||
# - Create "tokio-util-0.6.x" git tag.
|
||||
version = "0.6.0"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-util/0.4.0/tokio_util"
|
||||
documentation = "https://docs.rs/tokio-util/0.6.0/tokio_util"
|
||||
description = """
|
||||
Additional utilities for working with Tokio.
|
||||
"""
|
||||
@@ -24,32 +24,39 @@ categories = ["asynchronous"]
|
||||
default = []
|
||||
|
||||
# Shorthand for enabling everything
|
||||
full = ["codec", "compat", "io", "time"]
|
||||
full = ["codec", "compat", "io", "time", "net", "rt"]
|
||||
|
||||
net = ["tokio/net"]
|
||||
compat = ["futures-io",]
|
||||
codec = ["tokio/stream"]
|
||||
codec = []
|
||||
time = ["tokio/time","slab"]
|
||||
io = []
|
||||
rt = ["tokio/rt"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.3.0", path = "../tokio" }
|
||||
__docs_rs = ["futures-util"]
|
||||
|
||||
bytes = "0.5.0"
|
||||
[dependencies]
|
||||
tokio = { version = "1.0.0", path = "../tokio" }
|
||||
tokio-stream = { version = "0.1", path = "../tokio-stream" }
|
||||
|
||||
bytes = "1.0.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.1.4"
|
||||
pin-project-lite = "0.2.0"
|
||||
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.3.0", path = "../tokio-test" }
|
||||
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.4.0", path = "../tokio-test" }
|
||||
|
||||
futures = "0.3.0"
|
||||
futures-test = "0.3.5"
|
||||
|
||||
async-stream = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
Copyright (c) 2020 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -18,17 +18,15 @@ macro_rules! cfg_compat {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
macro_rules! cfg_udp {
|
||||
macro_rules! cfg_net {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(all(feature = "udp", feature = "codec"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "udp", feature = "codec"))))]
|
||||
#[cfg(all(feature = "net", feature = "codec"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "net", feature = "codec"))))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
macro_rules! cfg_io {
|
||||
($($item:item)*) => {
|
||||
|
||||
@@ -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`]: tokio::stream::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,10 +2,8 @@ use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
use crate::codec::framed_impl::{FramedImpl, RWFrames, ReadFrame, WriteFrame};
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_stream::Stream;
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_sink::Sink;
|
||||
@@ -22,7 +20,7 @@ pin_project! {
|
||||
/// You can create a `Framed` instance by using the [`Decoder::framed`] adapter, or
|
||||
/// by using the `new` function seen below.
|
||||
///
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Decoder::framed`]: crate::codec::Decoder::framed()
|
||||
@@ -54,7 +52,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`]: tokio::stream::Stream
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Decode`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
@@ -88,7 +86,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`]: tokio::stream::Stream
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Decode`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
@@ -133,7 +131,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`]: tokio::stream::Stream
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Decoder`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_stream::Stream;
|
||||
|
||||
use bytes::{Buf, BytesMut};
|
||||
use bytes::BytesMut;
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
@@ -150,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(cx, pinned.inner.as_mut(), &mut state.buffer)? {
|
||||
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer)? {
|
||||
Poll::Ready(ct) => ct,
|
||||
Poll::Pending => return Poll::Pending,
|
||||
};
|
||||
@@ -189,6 +187,7 @@ where
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
use crate::util::poll_write_buf;
|
||||
trace!("flushing framed transport");
|
||||
let mut pinned = self.project();
|
||||
|
||||
@@ -196,8 +195,7 @@ where
|
||||
let WriteFrame { buffer } = pinned.state.borrow_mut();
|
||||
trace!("writing; remaining={}", buffer.len());
|
||||
|
||||
let buf = &buffer;
|
||||
let n = ready!(pinned.inner.as_mut().poll_write(cx, &buf))?;
|
||||
let n = ready!(poll_write_buf(pinned.inner.as_mut(), cx, buffer))?;
|
||||
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
@@ -207,8 +205,6 @@ where
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
|
||||
pinned.state.borrow_mut().buffer.advance(n);
|
||||
}
|
||||
|
||||
// Try flushing the underlying IO
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::codec::framed_impl::{FramedImpl, ReadFrame};
|
||||
use crate::codec::Decoder;
|
||||
|
||||
use tokio::{io::AsyncRead, stream::Stream};
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio_stream::Stream;
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_sink::Sink;
|
||||
@@ -13,7 +14,7 @@ use std::task::{Context, Poll};
|
||||
pin_project! {
|
||||
/// A [`Stream`] of messages decoded from an [`AsyncRead`].
|
||||
///
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
pub struct FramedRead<T, D> {
|
||||
#[pin]
|
||||
@@ -100,6 +101,11 @@ impl<T, D> FramedRead<T, D> {
|
||||
pub fn read_buffer(&self) -> &BytesMut {
|
||||
&self.inner.state.buffer
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the read buffer.
|
||||
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.inner.state.buffer
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::codec::encoder::Encoder;
|
||||
use crate::codec::framed_impl::{FramedImpl, WriteFrame};
|
||||
|
||||
use tokio::{io::AsyncWrite, stream::Stream};
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio_stream::Stream;
|
||||
|
||||
use futures_sink::Sink;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
//! Specifically, given the following:
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::prelude::*;
|
||||
//! use tokio::io::{AsyncRead, AsyncWrite};
|
||||
//! use tokio_util::codec::{Framed, LengthDelimitedCodec};
|
||||
//!
|
||||
//! use futures::SinkExt;
|
||||
|
||||
+252
-8
@@ -1,18 +1,262 @@
|
||||
//! 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.
|
||||
//! 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(())
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
//! [`Stream`]: tokio::stream::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;
|
||||
|
||||
@@ -20,7 +20,7 @@ pin_project! {
|
||||
/// `futures_io::AsyncRead` to implement `tokio::io::AsyncRead`.
|
||||
pub trait FuturesAsyncReadCompatExt: futures_io::AsyncRead {
|
||||
/// Wraps `self` with a compatibility layer that implements
|
||||
/// `tokio_io::AsyncWrite`.
|
||||
/// `tokio_io::AsyncRead`.
|
||||
fn compat(self) -> Compat<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -48,7 +48,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 Tokio02AsyncReadCompatExt: tokio::io::AsyncRead {
|
||||
pub trait TokioAsyncReadCompatExt: tokio::io::AsyncRead {
|
||||
/// Wraps `self` with a compatibility layer that implements
|
||||
/// `futures_io::AsyncRead`.
|
||||
fn compat(self) -> Compat<Self>
|
||||
@@ -59,11 +59,11 @@ pub trait Tokio02AsyncReadCompatExt: tokio::io::AsyncRead {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: tokio::io::AsyncRead> Tokio02AsyncReadCompatExt for T {}
|
||||
impl<T: tokio::io::AsyncRead> TokioAsyncReadCompatExt for T {}
|
||||
|
||||
/// Extension trait that allows converting a type implementing
|
||||
/// `tokio::io::AsyncWrite` to implement `futures_io::AsyncWrite`.
|
||||
pub trait Tokio02AsyncWriteCompatExt: tokio::io::AsyncWrite {
|
||||
pub trait TokioAsyncWriteCompatExt: tokio::io::AsyncWrite {
|
||||
/// Wraps `self` with a compatibility layer that implements
|
||||
/// `futures_io::AsyncWrite`.
|
||||
fn compat_write(self) -> Compat<Self>
|
||||
@@ -74,7 +74,7 @@ pub trait Tokio02AsyncWriteCompatExt: tokio::io::AsyncWrite {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: tokio::io::AsyncWrite> Tokio02AsyncWriteCompatExt for T {}
|
||||
impl<T: tokio::io::AsyncWrite> TokioAsyncWriteCompatExt for T {}
|
||||
|
||||
// === impl Compat ===
|
||||
|
||||
|
||||
+133
-21
@@ -12,21 +12,123 @@ use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::runtime::{Handle, Runtime};
|
||||
|
||||
pin_project! {
|
||||
/// `TokioContext` allows connecting a custom executor with the tokio runtime.
|
||||
/// `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.
|
||||
pub struct TokioContext<'a, F> {
|
||||
/// 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: &'a Runtime,
|
||||
handle: Handle,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for TokioContext<'_, F> {
|
||||
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> {
|
||||
@@ -39,40 +141,50 @@ impl<F: Future> Future for TokioContext<'_, F> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait extension that simplifies bundling a `Handle` with a `Future`.
|
||||
/// Extension trait that simplifies bundling a `Handle` with a `Future`.
|
||||
pub trait RuntimeExt {
|
||||
/// Convenience method that takes a Future and returns a `TokioContext`.
|
||||
/// Create a [`TokioContext`] that wraps the provided future and runs it in
|
||||
/// this runtime's context.
|
||||
///
|
||||
/// # Example: calling Tokio Runtime from a custom ThreadPool
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio_util::context::RuntimeExt;
|
||||
/// 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();
|
||||
///
|
||||
/// let fut = sleep(Duration::from_millis(2));
|
||||
/// // Wrap the sleep future in the context of rt.
|
||||
/// let fut = rt.wrap(async { sleep(Duration::from_millis(2)).await });
|
||||
///
|
||||
/// rt.block_on(
|
||||
/// rt2
|
||||
/// .wrap(async { sleep(Duration::from_millis(2)).await }),
|
||||
/// );
|
||||
///```
|
||||
fn wrap<F: Future>(&self, fut: F) -> TokioContext<'_, F>;
|
||||
/// // 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> {
|
||||
fn wrap<F: Future>(&self, fut: F) -> TokioContext<F> {
|
||||
TokioContext {
|
||||
inner: fut,
|
||||
handle: self,
|
||||
handle: self.handle().clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,10 +167,8 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tokio::{
|
||||
io::{repeat, AsyncReadExt, Repeat},
|
||||
stream::{once, Once, StreamExt},
|
||||
};
|
||||
use tokio::io::{repeat, AsyncReadExt, Repeat};
|
||||
use tokio_stream::{once, Once, StreamExt};
|
||||
|
||||
#[tokio::test]
|
||||
async fn either_is_stream() {
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
//! [`Body`]: https://docs.rs/hyper/0.13/hyper/struct.Body.html
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
|
||||
mod poll_read_buf;
|
||||
mod read_buf;
|
||||
mod reader_stream;
|
||||
mod stream_reader;
|
||||
|
||||
pub use self::poll_read_buf::poll_read_buf;
|
||||
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,90 +0,0 @@
|
||||
use bytes::BufMut;
|
||||
use futures_core::ready;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
|
||||
/// Try to read data from an `AsyncRead` into an implementer of the [`Buf`] trait.
|
||||
///
|
||||
/// [`Buf`]: bytes::Buf
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::{Bytes, BytesMut};
|
||||
/// use tokio::stream;
|
||||
/// use tokio::io::Result;
|
||||
/// use tokio_util::io::{StreamReader, poll_read_buf};
|
||||
/// use futures::future::poll_fn;
|
||||
/// use std::pin::Pin;
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a reader from an iterator. This particular reader will always be
|
||||
/// // ready.
|
||||
/// let mut read = StreamReader::new(stream::iter(vec![Result::Ok(Bytes::from_static(&[0, 1, 2, 3]))]));
|
||||
///
|
||||
/// let mut buf = BytesMut::new();
|
||||
/// let mut reads = 0;
|
||||
///
|
||||
/// loop {
|
||||
/// reads += 1;
|
||||
/// let n = poll_fn(|cx| poll_read_buf(Pin::new(&mut read), cx, &mut buf)).await?;
|
||||
///
|
||||
/// if n == 0 {
|
||||
/// break;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // one or more reads might be necessary.
|
||||
/// assert!(reads >= 1);
|
||||
/// assert_eq!(&buf[..], &[0, 1, 2, 3]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn poll_read_buf<R, B>(
|
||||
read: Pin<&mut R>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>>
|
||||
where
|
||||
R: AsyncRead,
|
||||
B: BufMut,
|
||||
{
|
||||
if !buf.has_remaining_mut() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let n = {
|
||||
let mut buf = ReadBuf::uninit(buf.bytes_mut());
|
||||
let before = buf.filled().as_ptr();
|
||||
|
||||
ready!(read.poll_read(cx, &mut buf)?);
|
||||
|
||||
// This prevents a malicious read implementation from swapping out the
|
||||
// buffer being read, which would allow `filled` to be advanced without
|
||||
// actually initializing the provided buffer.
|
||||
//
|
||||
// We avoid this by asserting that the `ReadBuf` instance wraps the same
|
||||
// memory address both before and after the poll. Which will panic in
|
||||
// case its swapped.
|
||||
//
|
||||
// See https://github.com/tokio-rs/tokio/issues/2827 for more info.
|
||||
assert! {
|
||||
std::ptr::eq(before, buf.filled().as_ptr()),
|
||||
"Read buffer must not be changed during a read poll. \
|
||||
See https://github.com/tokio-rs/tokio/issues/2827 for more info."
|
||||
};
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -5,15 +5,15 @@ use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
/// Read data from an `AsyncRead` into an implementer of the [`Buf`] trait.
|
||||
/// Read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
|
||||
///
|
||||
/// [`Buf`]: bytes::Buf
|
||||
/// [`BufMut`]: bytes::BufMut
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::{Bytes, BytesMut};
|
||||
/// use tokio::stream;
|
||||
/// use tokio_stream as stream;
|
||||
/// use tokio::io::Result;
|
||||
/// use tokio_util::io::{StreamReader, read_buf};
|
||||
/// # #[tokio::main]
|
||||
@@ -59,7 +59,7 @@ where
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = &mut *self;
|
||||
super::poll_read_buf(Pin::new(this.0), cx, this.1)
|
||||
crate::util::poll_read_buf(Pin::new(this.0), cx, this.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pin_project! {
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
/// use tokio::stream::StreamExt;
|
||||
/// use tokio_stream::StreamExt;
|
||||
/// use tokio_util::io::ReaderStream;
|
||||
///
|
||||
/// // Create a stream of data.
|
||||
@@ -40,7 +40,7 @@ pin_project! {
|
||||
///
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`StreamReader`]: crate::io::StreamReader
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
#[derive(Debug)]
|
||||
pub struct ReaderStream<R> {
|
||||
// Reader itself.
|
||||
@@ -58,7 +58,7 @@ impl<R: AsyncRead> ReaderStream<R> {
|
||||
/// `Result<Bytes, std::io::Error>`.
|
||||
///
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
pub fn new(reader: R) -> Self {
|
||||
ReaderStream {
|
||||
reader: Some(reader),
|
||||
@@ -83,7 +83,7 @@ impl<R: AsyncRead> Stream for ReaderStream<R> {
|
||||
this.buf.reserve(CAPACITY);
|
||||
}
|
||||
|
||||
match poll_read_buf(cx, reader, &mut this.buf) {
|
||||
match poll_read_buf(reader, cx, &mut this.buf) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Err(err)) => {
|
||||
self.project().reader.set(None);
|
||||
|
||||
@@ -21,7 +21,7 @@ pin_project! {
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a stream from an iterator.
|
||||
/// let stream = tokio::stream::iter(vec![
|
||||
/// 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])),
|
||||
@@ -51,7 +51,7 @@ pin_project! {
|
||||
/// ```
|
||||
///
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
/// [`ReaderStream`]: crate::io::ReaderStream
|
||||
#[derive(Debug)]
|
||||
pub struct StreamReader<S, B> {
|
||||
@@ -131,7 +131,7 @@ where
|
||||
loop {
|
||||
if self.as_mut().has_chunk() {
|
||||
// This unwrap is very sad, but it can't be avoided.
|
||||
let buf = self.project().chunk.as_ref().unwrap().bytes();
|
||||
let buf = self.project().chunk.as_ref().unwrap().chunk();
|
||||
return Poll::Ready(Ok(buf));
|
||||
} else {
|
||||
match self.as_mut().project().inner.poll_next(cx) {
|
||||
|
||||
+126
-19
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-util/0.4.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-util/0.6.0")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -30,14 +30,9 @@ cfg_codec! {
|
||||
pub mod codec;
|
||||
}
|
||||
|
||||
/*
|
||||
Disabled due to removal of poll_ functions on UdpSocket.
|
||||
|
||||
See https://github.com/tokio-rs/tokio/issues/2830
|
||||
cfg_udp! {
|
||||
cfg_net! {
|
||||
pub mod udp;
|
||||
}
|
||||
*/
|
||||
|
||||
cfg_compat! {
|
||||
pub mod compat;
|
||||
@@ -60,34 +55,146 @@ pub mod time;
|
||||
|
||||
#[cfg(any(feature = "io", feature = "codec"))]
|
||||
mod util {
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
|
||||
use bytes::BufMut;
|
||||
use bytes::{Buf, BufMut};
|
||||
use futures_core::ready;
|
||||
use std::io;
|
||||
use std::io::{self, IoSlice};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pub(crate) fn poll_read_buf<T: AsyncRead>(
|
||||
cx: &mut Context<'_>,
|
||||
/// 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>,
|
||||
buf: &mut impl BufMut,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if !buf.has_remaining_mut() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
let orig = buf.bytes_mut().as_ptr() as *const u8;
|
||||
let mut b = ReadBuf::uninit(buf.bytes_mut());
|
||||
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)?);
|
||||
|
||||
ready!(io.poll_read(cx, &mut b))?;
|
||||
let n = b.filled().len();
|
||||
// Ensure the pointer does not change from under us
|
||||
assert_eq!(ptr, buf.filled().as_ptr());
|
||||
buf.filled().len()
|
||||
};
|
||||
|
||||
// Safety: we can assume `n` bytes were read, since they are in`filled`.
|
||||
assert_eq!(orig, b.filled().as_ptr());
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::cmp;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::task::{self, Poll};
|
||||
use std::task::{self, Poll, Waker};
|
||||
|
||||
/// A queue of delayed elements.
|
||||
///
|
||||
@@ -138,13 +138,18 @@ pub struct DelayQueue<T> {
|
||||
expired: Stack<T>,
|
||||
|
||||
/// Delay expiring when the *first* item in the queue expires
|
||||
delay: Option<Sleep>,
|
||||
delay: Option<Pin<Box<Sleep>>>,
|
||||
|
||||
/// Wheel polling state
|
||||
wheel_now: u64,
|
||||
|
||||
/// Instant at which the timer starts
|
||||
start: Instant,
|
||||
|
||||
/// Waker that is invoked when we potentially need to reset the timer.
|
||||
/// Because we lazily create the timer when the first entry is created, we
|
||||
/// need to awaken any poller that polled us before that point.
|
||||
waker: Option<Waker>,
|
||||
}
|
||||
|
||||
/// An entry in `DelayQueue` that has expired and removed.
|
||||
@@ -253,6 +258,7 @@ impl<T> DelayQueue<T> {
|
||||
delay: None,
|
||||
wheel_now: 0,
|
||||
start: Instant::now(),
|
||||
waker: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,11 +336,15 @@ impl<T> DelayQueue<T> {
|
||||
};
|
||||
|
||||
if should_set_delay {
|
||||
if let Some(waker) = self.waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
let delay_time = self.start + Duration::from_millis(when);
|
||||
if let Some(ref mut delay) = &mut self.delay {
|
||||
delay.reset(delay_time);
|
||||
delay.as_mut().reset(delay_time);
|
||||
} else {
|
||||
self.delay = Some(sleep_until(delay_time));
|
||||
self.delay = Some(Box::pin(sleep_until(delay_time)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +358,15 @@ impl<T> DelayQueue<T> {
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Result<Expired<T>, Error>>> {
|
||||
if !self
|
||||
.waker
|
||||
.as_ref()
|
||||
.map(|w| w.will_wake(cx.waker()))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.waker = Some(cx.waker().clone());
|
||||
}
|
||||
|
||||
let item = ready!(self.poll_idx(cx));
|
||||
Poll::Ready(item.map(|result| {
|
||||
result.map(|idx| {
|
||||
@@ -533,7 +552,8 @@ impl<T> DelayQueue<T> {
|
||||
|
||||
let next_deadline = self.next_deadline();
|
||||
if let (Some(ref mut delay), Some(deadline)) = (&mut self.delay, next_deadline) {
|
||||
delay.reset(deadline);
|
||||
// This should awaken us if necessary (ie, if already expired)
|
||||
delay.as_mut().reset(deadline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,7 +759,7 @@ impl<T> DelayQueue<T> {
|
||||
// We poll the wheel to get the next value out before finding the next deadline.
|
||||
let wheel_idx = self.wheel.poll(self.wheel_now, &mut self.slab);
|
||||
|
||||
self.delay = self.next_deadline().map(sleep_until);
|
||||
self.delay = self.next_deadline().map(|when| Box::pin(sleep_until(when)));
|
||||
|
||||
if let Some(idx) = wheel_idx {
|
||||
return Poll::Ready(Some(Ok(idx)));
|
||||
@@ -771,7 +791,6 @@ impl<T> Default for DelayQueue<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stream")]
|
||||
impl<T> futures_core::Stream for DelayQueue<T> {
|
||||
// DelayQueue seems much more specific, where a user may care that it
|
||||
// has reached capacity, so return those errors instead of panicking.
|
||||
|
||||
+51
-18
@@ -1,17 +1,17 @@
|
||||
use crate::codec::{Decoder, Encoder};
|
||||
|
||||
use tokio::{net::UdpSocket, stream::Stream};
|
||||
use tokio::{io::ReadBuf, net::UdpSocket};
|
||||
use tokio_stream::Stream;
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use std::io;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{io, mem::MaybeUninit};
|
||||
|
||||
/// A unified `Stream` and `Sink` interface to an underlying `UdpSocket`, using
|
||||
/// A unified [`Stream`] and [`Sink`] interface to an underlying `UdpSocket`, using
|
||||
/// the `Encoder` and `Decoder` traits to encode and decode frames.
|
||||
///
|
||||
/// Raw UDP sockets work with datagrams, but higher-level code usually wants to
|
||||
@@ -20,13 +20,17 @@ use std::task::{Context, Poll};
|
||||
/// handle encoding and decoding of messages frames. Note that the incoming and
|
||||
/// outgoing frame types may be distinct.
|
||||
///
|
||||
/// This function returns a *single* object that is both `Stream` and `Sink`;
|
||||
/// This function returns a *single* object that is both [`Stream`] and [`Sink`];
|
||||
/// grouping this into a single object is often useful for layering things which
|
||||
/// require both read and write access to the underlying object.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
/// calling `split` on the `UdpFramed` returned by this method, which will break
|
||||
/// calling [`split`] on the `UdpFramed` returned by this method, which will break
|
||||
/// them into separate objects, allowing them to interact more easily.
|
||||
///
|
||||
/// [`Stream`]: tokio_stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
|
||||
#[must_use = "sinks do nothing unless polled"]
|
||||
#[cfg_attr(docsrs, doc(all(feature = "codec", feature = "udp")))]
|
||||
#[derive(Debug)]
|
||||
@@ -41,6 +45,9 @@ pub struct UdpFramed<C> {
|
||||
current_addr: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
const INITIAL_RD_CAPACITY: usize = 64 * 1024;
|
||||
const INITIAL_WR_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
type Item = Result<(C::Item, SocketAddr), C::Error>;
|
||||
|
||||
@@ -50,7 +57,7 @@ impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
pin.rd.reserve(INITIAL_RD_CAPACITY);
|
||||
|
||||
loop {
|
||||
// Are there are still bytes left in the read buffer to decode?
|
||||
// Are there still bytes left in the read buffer to decode?
|
||||
if pin.is_readable {
|
||||
if let Some(frame) = pin.codec.decode_eof(&mut pin.rd)? {
|
||||
let current_addr = pin
|
||||
@@ -69,13 +76,14 @@ impl<C: Decoder + Unpin> Stream for UdpFramed<C> {
|
||||
let addr = unsafe {
|
||||
// Convert `&mut [MaybeUnit<u8>]` to `&mut [u8]` because we will be
|
||||
// writing to it via `poll_recv_from` and therefore initializing the memory.
|
||||
let buf: &mut [u8] =
|
||||
&mut *(pin.rd.bytes_mut() as *mut [MaybeUninit<u8>] as *mut [u8]);
|
||||
let buf = &mut *(pin.rd.chunk_mut() as *mut _ as *mut [MaybeUninit<u8>]);
|
||||
let mut read = ReadBuf::uninit(buf);
|
||||
let ptr = read.filled().as_ptr();
|
||||
let res = ready!(Pin::new(&mut pin.socket).poll_recv_from(cx, &mut read));
|
||||
|
||||
let res = ready!(Pin::new(&mut pin.socket).poll_recv_from(cx, buf));
|
||||
|
||||
let (n, addr) = res?;
|
||||
pin.rd.advance_mut(n);
|
||||
assert_eq!(ptr, read.filled().as_ptr());
|
||||
let addr = res?;
|
||||
pin.rd.advance_mut(read.filled().len());
|
||||
addr
|
||||
};
|
||||
|
||||
@@ -123,7 +131,7 @@ impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
|
||||
..
|
||||
} = *self;
|
||||
|
||||
let n = ready!(socket.poll_send_to(cx, &wr, &out_addr))?;
|
||||
let n = ready!(socket.poll_send_to(cx, &wr, *out_addr))?;
|
||||
|
||||
let wrote_all = n == self.wr.len();
|
||||
self.wr.clear();
|
||||
@@ -148,15 +156,12 @@ impl<I, C: Encoder<I> + Unpin> Sink<(I, SocketAddr)> for UdpFramed<C> {
|
||||
}
|
||||
}
|
||||
|
||||
const INITIAL_RD_CAPACITY: usize = 64 * 1024;
|
||||
const INITIAL_WR_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
impl<C> UdpFramed<C> {
|
||||
/// Create a new `UdpFramed` backed by the given socket and codec.
|
||||
///
|
||||
/// See struct level documentation for more details.
|
||||
pub fn new(socket: UdpSocket, codec: C) -> UdpFramed<C> {
|
||||
UdpFramed {
|
||||
Self {
|
||||
socket,
|
||||
codec,
|
||||
out_addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0)),
|
||||
@@ -195,4 +200,32 @@ impl<C> UdpFramed<C> {
|
||||
pub fn into_inner(self) -> UdpSocket {
|
||||
self.socket
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying codec wrapped by
|
||||
/// `Framed`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec(&self) -> &C {
|
||||
&self.codec
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying codec wrapped by
|
||||
/// `UdpFramed`.
|
||||
///
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec_mut(&mut self) -> &mut C {
|
||||
&mut self.codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the read buffer.
|
||||
pub fn read_buffer(&self) -> &BytesMut {
|
||||
&self.rd
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the read buffer.
|
||||
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.rd
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! UDP framing
|
||||
|
||||
mod frame;
|
||||
pub use self::frame::UdpFramed;
|
||||
pub use frame::UdpFramed;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::{prelude::*, stream::StreamExt};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_test::assert_ok;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed, FramedParts};
|
||||
|
||||
@@ -51,7 +51,7 @@ impl Read for DontReadIntoThis {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for DontReadIntoThis {
|
||||
impl tokio::io::AsyncRead for DontReadIntoThis {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
/// produces at most `remaining` zeros, that returns error.
|
||||
/// each time it reads at most 31 byte.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use bytes::Bytes;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::stream::iter;
|
||||
use tokio_stream::iter;
|
||||
use tokio_util::io::StreamReader;
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use tokio::time::{self, sleep, Duration, Instant};
|
||||
use tokio::time::{self, sleep, sleep_until, Duration, Instant};
|
||||
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
|
||||
use tokio_util::time::DelayQueue;
|
||||
|
||||
@@ -107,9 +107,10 @@ async fn multi_delay_at_start() {
|
||||
assert_pending!(poll!(queue));
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
let start = Instant::now();
|
||||
for elapsed in 0..1200 {
|
||||
sleep(ms(1)).await;
|
||||
let elapsed = elapsed + 1;
|
||||
tokio::time::sleep_until(start + ms(elapsed)).await;
|
||||
|
||||
if delays.contains(&elapsed) {
|
||||
assert!(queue.is_woken());
|
||||
@@ -117,7 +118,12 @@ async fn multi_delay_at_start() {
|
||||
assert_pending!(poll!(queue));
|
||||
} else if queue.is_woken() {
|
||||
let cascade = &[192, 960];
|
||||
assert!(cascade.contains(&elapsed), "elapsed={}", elapsed);
|
||||
assert!(
|
||||
cascade.contains(&elapsed),
|
||||
"elapsed={} dt={:?}",
|
||||
elapsed,
|
||||
Instant::now() - start
|
||||
);
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
}
|
||||
@@ -205,7 +211,7 @@ async fn reset_much_later() {
|
||||
|
||||
sleep(ms(3)).await;
|
||||
|
||||
queue.reset_at(&key, now + ms(5));
|
||||
queue.reset_at(&key, now + ms(10));
|
||||
|
||||
sleep(ms(20)).await;
|
||||
|
||||
@@ -402,7 +408,7 @@ async fn insert_before_first_after_poll() {
|
||||
|
||||
sleep(ms(99)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
sleep(ms(1)).await;
|
||||
|
||||
@@ -457,7 +463,7 @@ async fn reset_later_after_slot_starts() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
sleep(ms(80)).await;
|
||||
sleep_until(now + Duration::from_millis(80)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
@@ -472,7 +478,7 @@ async fn reset_later_after_slot_starts() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
sleep(ms(39)).await;
|
||||
sleep_until(now + Duration::from_millis(119)).await;
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
sleep(ms(1)).await;
|
||||
@@ -515,7 +521,7 @@ async fn reset_earlier_after_slot_starts() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
sleep(ms(80)).await;
|
||||
sleep_until(now + Duration::from_millis(80)).await;
|
||||
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
@@ -530,7 +536,7 @@ async fn reset_earlier_after_slot_starts() {
|
||||
|
||||
assert_pending!(poll!(queue));
|
||||
|
||||
sleep(ms(39)).await;
|
||||
sleep_until(now + Duration::from_millis(119)).await;
|
||||
assert!(!queue.is_woken());
|
||||
|
||||
sleep(ms(1)).await;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user