mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
177
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00e3c29e48 | ||
|
|
bcba4aaa54 | ||
|
|
71c47fabf4 | ||
|
|
4996e27673 | ||
|
|
5bf06f2b5a | ||
|
|
12be90e3ff | ||
|
|
0d49e112b2 | ||
|
|
9eca96aa21 | ||
|
|
a16c9a5a01 | ||
|
|
f0bfebb7e1 | ||
|
|
968c143acd | ||
|
|
6fbaac91e0 | ||
|
|
e35038ed79 | ||
|
|
f9ddb93604 | ||
|
|
a70f7203a4 | ||
|
|
7079bcd609 | ||
|
|
f8714e9901 | ||
|
|
0545b349e1 | ||
|
|
8cf98d6946 | ||
|
|
f9ea576cca | ||
|
|
7f580071f3 | ||
|
|
5fe2df0fba | ||
|
|
176df2448a | ||
|
|
5bbf976268 | ||
|
|
90969420a2 | ||
|
|
bffbaab30d | ||
|
|
a5e774bb38 | ||
|
|
0bb17300f7 | ||
|
|
c7719a2d29 | ||
|
|
38bff0adda | ||
|
|
fbe143b142 | ||
|
|
9df805ff54 | ||
|
|
5d82ac2d1e | ||
|
|
5bf78d77ad | ||
|
|
3176d0a48a | ||
|
|
bb6c3839ef | ||
|
|
1475448bdf | ||
|
|
7eb8d447ad | ||
|
|
1222d81741 | ||
|
|
619d730d61 | ||
|
|
476bf0084a | ||
|
|
bd8971cd95 | ||
|
|
eb1a8e1792 | ||
|
|
5b091fa3f0 | ||
|
|
7c3f1cb4a3 | ||
|
|
64d2389911 | ||
|
|
8471e0a0ee | ||
|
|
0ba6e9abdb | ||
|
|
a939dc48b0 | ||
|
|
cfd9b36d89 | ||
|
|
f5c20cd228 | ||
|
|
c7c74a5a76 | ||
|
|
b34a849b79 | ||
|
|
a7a79f28a8 | ||
|
|
bd28a7a767 | ||
|
|
275769b5b9 | ||
|
|
b70615b299 | ||
|
|
6406328176 | ||
|
|
f28c9f0d17 | ||
|
|
7ee5542182 | ||
|
|
7fb54315f1 | ||
|
|
ffd4025fce | ||
|
|
8bf4696f31 | ||
|
|
10398b20c0 | ||
|
|
780d6f91a0 | ||
|
|
45da5f3510 | ||
|
|
855d39f849 | ||
|
|
798e86821f | ||
|
|
0193df3a59 | ||
|
|
5930acef73 | ||
|
|
3540c5b9ee | ||
|
|
188fc6e0d2 | ||
|
|
d45f61c183 | ||
|
|
dcfa895b51 | ||
|
|
f0006006ed | ||
|
|
84ff73e687 | ||
|
|
32e15b3a24 | ||
|
|
efcbf9613f | ||
|
|
3736467dbb | ||
|
|
e43f28f6a8 | ||
|
|
3cf91db4b6 | ||
|
|
e8fcf55881 | ||
|
|
a515f9c459 | ||
|
|
50b91c0247 | ||
|
|
67bf9c36f3 | ||
|
|
101f770af3 | ||
|
|
6ff4e349e2 | ||
|
|
adc5186ebd | ||
|
|
7b53b7b659 | ||
|
|
99fa93bf0e | ||
|
|
0133bc1883 | ||
|
|
a854094825 | ||
|
|
3d1b4b3058 | ||
|
|
8656b7b8eb | ||
|
|
f309b295bb | ||
|
|
b1266a48c4 | ||
|
|
de5ec6e1bc | ||
|
|
3dcd76a38f | ||
|
|
3b9c7b1715 | ||
|
|
3bff5a3ffe | ||
|
|
248bf2144f | ||
|
|
93ab70a9a0 | ||
|
|
58b5abdb99 | ||
|
|
2d78cfe56a | ||
|
|
4c645866ef | ||
|
|
b0836ece7a | ||
|
|
0c0f682010 | ||
|
|
b24ad9fe86 | ||
|
|
7c010ed030 | ||
|
|
42c942de14 | ||
|
|
5e8f7eb03c | ||
|
|
9211adbe01 | ||
|
|
e5b99b0f7a | ||
|
|
83cd754bc8 | ||
|
|
17e424112d | ||
|
|
41d15ea212 | ||
|
|
8add90210b | ||
|
|
efb4b67a54 | ||
|
|
74d33a1b2f | ||
|
|
69885e214c | ||
|
|
4b85565bd7 | ||
|
|
d593c5b051 | ||
|
|
91ecb4b4c2 | ||
|
|
8abaf89e5f | ||
|
|
b560df9e66 | ||
|
|
df8278acb6 | ||
|
|
5862b9a2e0 | ||
|
|
c0953d41a5 | ||
|
|
24cd6d67f7 | ||
|
|
975576952f | ||
|
|
5d5755dca4 | ||
|
|
41ffdbb7d9 | ||
|
|
2450b5bfc9 | ||
|
|
80abff0e57 | ||
|
|
c632337e6f | ||
|
|
a53f94ab61 | ||
|
|
98c9a77f18 | ||
|
|
e00c49611a | ||
|
|
9c9fabc44b | ||
|
|
c3461b3ef3 | ||
|
|
b7ecd35036 | ||
|
|
dbcd1f9a09 | ||
|
|
0e729aa341 | ||
|
|
cbe369a3ed | ||
|
|
8bcbe78dbe | ||
|
|
6efe07c3fb | ||
|
|
8a2160a913 | ||
|
|
38c361781f | ||
|
|
07451f8b94 | ||
|
|
e87df0557d | ||
|
|
a8a4a9f0fc | ||
|
|
8b60c5386a | ||
|
|
af07f5bee7 | ||
|
|
939a0dd7b0 | ||
|
|
1ea6733568 | ||
|
|
8ce408492a | ||
|
|
b559a0cd9a | ||
|
|
417460cf86 | ||
|
|
adaba1a0bc | ||
|
|
467b6ea783 | ||
|
|
a2cfc877a7 | ||
|
|
ec7f2ae306 | ||
|
|
4261ab6627 | ||
|
|
aef434c089 | ||
|
|
cd73951130 | ||
|
|
524e66314f | ||
|
|
34d751bf92 | ||
|
|
942feab040 | ||
|
|
dc356a4158 | ||
|
|
2cd1d74092 | ||
|
|
632ee507ba | ||
|
|
7f605ee27f | ||
|
|
38e602f4d8 | ||
|
|
8e83a9f2c3 | ||
|
|
c146f48f0b | ||
|
|
ebf5f37989 | ||
|
|
abfa857f09 |
+1
-1
@@ -15,7 +15,7 @@ It should be considered a map to help you navigate the process.
|
||||
The [dev channel][dev] is available for any concerns not covered in this guide, please join
|
||||
us!
|
||||
|
||||
[dev]: https://gitter.im/tokio-rs/dev
|
||||
[dev]: https://discord.gg/6yGkFeN
|
||||
|
||||
## Conduct
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ members = [
|
||||
"tokio-util",
|
||||
|
||||
# Internal
|
||||
"benches",
|
||||
"examples",
|
||||
"tests-build",
|
||||
"tests-integration",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Tokio
|
||||
|
||||
**NOTE**: Tokio's [`master`](https://github.com/tokio-rs/tokio) is currently undergoing heavy development. This branch and the alpha releases will see API breaking changes. Use the [`v0.1.x`](https://github.com/tokio-rs/tokio/tree/v0.1.x) branch for stable releases.
|
||||
|
||||
A runtime for writing reliable, asynchronous, and slim applications with
|
||||
the Rust programming language. It is:
|
||||
|
||||
@@ -17,7 +15,7 @@ the Rust programming language. It is:
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Gitter chat][gitter-badge]][gitter-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
@@ -25,13 +23,14 @@ the Rust programming language. It is:
|
||||
[mit-url]: 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
|
||||
[gitter-badge]: https://img.shields.io/gitter/room/tokio-rs/tokio.svg
|
||||
[gitter-url]: https://gitter.im/tokio-rs/tokio
|
||||
[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/docs/) |
|
||||
[API Docs](https://docs.rs/tokio/latest/tokio) |
|
||||
[Chat](https://gitter.im/tokio-rs/tokio)
|
||||
[Roadmap](https://github.com/tokio-rs/tokio/blob/master/ROADMAP.md) |
|
||||
[Chat](https://discord.gg/tokio)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -89,25 +88,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
More examples can be found [here](examples). Note that the `master` branch
|
||||
is currently being updated to use `async` / `await`. The examples are
|
||||
not fully ported. Examples for stable Tokio can be found
|
||||
[here](https://github.com/tokio-rs/tokio/tree/v0.1.x/tokio/examples).
|
||||
|
||||
More examples can be found [here](examples).
|
||||
|
||||
## Getting Help
|
||||
|
||||
First, see if the answer to your question can be found in the [Guides] or the
|
||||
[API documentation]. If the answer is not there, there is an active community in
|
||||
the [Tokio Gitter channel][chat]. We would be happy to try to answer your
|
||||
question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
the [Tokio Discord server][chat]. We would be happy to try to answer your
|
||||
question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
|
||||
[Guides]: https://tokio.rs/docs/
|
||||
[API documentation]: https://docs.rs/tokio/latest/tokio
|
||||
[chat]: https://gitter.im/tokio-rs/tokio
|
||||
[chat]: https://discord.gg/tokio
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues/new
|
||||
|
||||
## Contributing
|
||||
@@ -123,14 +117,32 @@ project.
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
|
||||
|
||||
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
|
||||
|
||||
* [`warp`]: A super-easy, composable, web server framework for warp speeds.
|
||||
|
||||
* [`tower`]: A library of modular and reusable components for building robust networking clients and servers.
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level
|
||||
tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
* [`loom`]: A testing tool for concurrent Rust code
|
||||
|
||||
[`warp`]: https://github.com/seanmonstar/warp
|
||||
[`hyper`]: https://github.com/hyperium/hyper
|
||||
[`tonic`]: https://github.com/hyperium/tonic
|
||||
[`tower`]: https://github.com/tower-rs/tower
|
||||
[`loom`]: https://github.com/tokio-rs/loom
|
||||
[`rdbc`]: https://github.com/tokio-rs/rdbc
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# Tokio Roadmap
|
||||
|
||||
## A Roadmap to 1.0
|
||||
|
||||
The question of "why not 1.0?" has come up a few times. After all, Tokio 0.1 has
|
||||
been stable for three years. The short answer: because it isn't time. There is
|
||||
nobody who would rather ship a Tokio 1.0 than us. It also isn't something to rush.
|
||||
|
||||
After all, `async / await` only landed in the stable Rust channel weeks ago.
|
||||
There has been no significant production validation yet, except maybe fuchsia
|
||||
and that seems like a fairly specialized use case. This release of Tokio
|
||||
includes significant new code and new strategies with feature flags. Also, there
|
||||
are still big open questions, such as the [proposed changes][pr-1744] to
|
||||
`AsyncRead` and `AsyncWrite`.
|
||||
|
||||
Tokio 1.0 will be released as soon as the APIs are proven to handle real-world
|
||||
production cases.
|
||||
|
||||
### Tokio 1.0 in Q3 2020 with LTS support
|
||||
|
||||
The Tokio 1.0 release will be **no later** than Q3 2020. It will also come with
|
||||
"long-term support" guarantees:
|
||||
|
||||
* A minimum of 5 years of maintenance.
|
||||
* A minimum of 3 years before a hypothetical 2.0 release.
|
||||
|
||||
When Tokio 1.0 is released in Q3 2020, on-going support, security fixes, and
|
||||
critical bug fixes are guaranteed until **at least** Q3 2025. Tokio 2.0 will not
|
||||
be released until **at least** Q3 2023 (though, ideally there will never been 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 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
|
||||
+8
-1
@@ -3,7 +3,7 @@ pr: ["master"]
|
||||
|
||||
variables:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
nightly: nightly-2019-11-16
|
||||
nightly: nightly-2020-01-25
|
||||
|
||||
jobs:
|
||||
# Test top level crate
|
||||
@@ -30,6 +30,13 @@ jobs:
|
||||
- tokio-util
|
||||
- examples
|
||||
|
||||
# Run integration tests
|
||||
- template: ci/azure-test-integration.yml
|
||||
parameters:
|
||||
name: test_integration
|
||||
displayName: Integration tests
|
||||
rust: stable
|
||||
|
||||
# Run tests from `tests-build`. This requires a different process
|
||||
- template: ci/azure-test-build.yml
|
||||
parameters:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
|
||||
[[bench]]
|
||||
name = "spawn"
|
||||
path = "spawn.rs"
|
||||
harness = false
|
||||
@@ -0,0 +1,70 @@
|
||||
//! Benchmark spawning a task onto the basic and threaded Tokio executors.
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
|
||||
async fn work() -> usize {
|
||||
let val = 1 + 1;
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
bench.iter(|| {
|
||||
let h = tokio::spawn(work());
|
||||
black_box(h);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
bench.iter(|| {
|
||||
let h = tokio::spawn(work());
|
||||
black_box(h);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn basic_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
benches,
|
||||
basic_scheduler_local_spawn,
|
||||
threaded_scheduler_local_spawn,
|
||||
basic_scheduler_remote_spawn,
|
||||
threaded_scheduler_remote_spawn
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(benches);
|
||||
+1
-1
@@ -12,5 +12,5 @@ jobs:
|
||||
cargo clippy --version
|
||||
displayName: Install clippy
|
||||
- script: |
|
||||
cargo clippy --all --all-features -- -A clippy::mutex-atomic
|
||||
cargo clippy --all --all-features
|
||||
displayName: cargo clippy --all
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ jobs:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- script: RUSTFLAGS="--cfg loom" cargo test --lib --release -- --test-threads=1 --nocapture
|
||||
- script: RUSTFLAGS="--cfg loom" cargo test --lib --release --features "full" -- --test-threads=1 --nocapture
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 1
|
||||
CI: 'True'
|
||||
|
||||
@@ -13,5 +13,6 @@ jobs:
|
||||
cargo fmt --version
|
||||
displayName: Install rustfmt
|
||||
- script: |
|
||||
cargo fmt --all -- --check
|
||||
# Workaround for rust-lang/cargo#7732
|
||||
rustfmt --check --edition 2018 $(find . -name '*.rs' -print)
|
||||
displayName: Check formatting
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: ubuntu-16.04
|
||||
MacOS:
|
||||
vmImage: macOS-10.13
|
||||
Windows:
|
||||
vmImage: vs2017-win2016
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: cargo install cargo-hack
|
||||
displayName: Install cargo-hack
|
||||
|
||||
# Run with all crate features
|
||||
- script: cargo hack test --each-feature
|
||||
env:
|
||||
CI: 'True'
|
||||
displayName: cargo hack test --each-feature
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests-integration
|
||||
@@ -30,6 +30,11 @@ jobs:
|
||||
displayName: ${{ crate }} - cargo test --all-features
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
|
||||
# Check benches
|
||||
- script: cargo check --all-features --benches
|
||||
displayName: ${{ crate }} - cargo check --benches
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
|
||||
+11
-2
@@ -7,9 +7,14 @@ edition = "2018"
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio-util = { version = "0.2.0", path = "../tokio-util", features = ["full"] }
|
||||
|
||||
bytes = "0.5.0"
|
||||
bytes = "0.5"
|
||||
futures = "0.3.0"
|
||||
http = "0.2"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
httparse = "1.0"
|
||||
time = "0.1"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
@@ -50,3 +55,7 @@ path = "udp-client.rs"
|
||||
[[example]]
|
||||
name = "udp-codec"
|
||||
path = "udp-codec.rs"
|
||||
|
||||
[[example]]
|
||||
name = "tinyhttp"
|
||||
path = "tinyhttp.rs"
|
||||
|
||||
+18
-4
@@ -1,6 +1,20 @@
|
||||
## Examples of how to use Tokio
|
||||
|
||||
The `master` branch is currently being updated to use `async` / `await`.
|
||||
The examples are not fully ported. Examples for stable Tokio can be
|
||||
found
|
||||
[here](https://github.com/tokio-rs/tokio/tree/v0.1.x/tokio/examples).
|
||||
This directory contains a number of examples showcasing various capabilities of
|
||||
the `tokio` crate.
|
||||
|
||||
All examples can be executed with:
|
||||
|
||||
```
|
||||
cargo run --example $name
|
||||
```
|
||||
|
||||
A good starting point for the examples would be [`hello_world`](hello_world.rs)
|
||||
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
|
||||
additional guides for some of the examples.
|
||||
|
||||
If you've got an example you'd like to see here, please feel free to open an
|
||||
issue. Otherwise if you've got an example you'd like to add, please feel free
|
||||
to make a PR!
|
||||
|
||||
[tokioweb]: https://tokio.rs/docs/overview/
|
||||
|
||||
+7
-4
@@ -27,10 +27,11 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::{Stream, StreamExt};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
|
||||
|
||||
use futures::{SinkExt, Stream, StreamExt};
|
||||
use futures::SinkExt;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
@@ -49,7 +50,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// client connection.
|
||||
let state = Arc::new(Mutex::new(Shared::new()));
|
||||
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:6142".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:6142".to_string());
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
@@ -161,12 +164,12 @@ impl Stream for Peer {
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// First poll the `UnboundedReceiver`.
|
||||
|
||||
if let Poll::Ready(Some(v)) = self.rx.poll_next_unpin(cx) {
|
||||
if let Poll::Ready(Some(v)) = Pin::new(&mut self.rx).poll_next(cx) {
|
||||
return Poll::Ready(Some(Ok(Message::Received(v))));
|
||||
}
|
||||
|
||||
// Secondly poll the `Framed` stream.
|
||||
let result: Option<_> = futures::ready!(self.lines.poll_next_unpin(cx));
|
||||
let result: Option<_> = futures::ready!(Pin::new(&mut self.lines).poll_next(cx));
|
||||
|
||||
Poll::Ready(match result {
|
||||
// We've received a message we should broadcast to others.
|
||||
|
||||
+27
-90
@@ -16,28 +16,16 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures::StreamExt;
|
||||
use tokio::io;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
run().await.unwrap();
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
rx.await.map_err(Into::into)
|
||||
}
|
||||
|
||||
// Currently, we need to spawn the initial future due to https://github.com/tokio-rs/tokio/issues/1356
|
||||
async fn run() -> Result<(), Box<dyn Error>> {
|
||||
// Determine if we're going to run in TCP or UDP mode
|
||||
let mut args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let tcp = match args.iter().position(|a| a == "--udp") {
|
||||
@@ -49,14 +37,14 @@ async fn run() -> Result<(), Box<dyn Error>> {
|
||||
};
|
||||
|
||||
// Parse what address we're going to connect to
|
||||
let addr = match args.first() {
|
||||
Some(addr) => addr,
|
||||
None => Err("this program requires at least one argument")?,
|
||||
};
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = stdin();
|
||||
let stdout = FramedWrite::new(io::stdout(), codec::Bytes);
|
||||
let stdin = FramedRead::new(io::stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(io::stdout(), BytesCodec::new());
|
||||
|
||||
if tcp {
|
||||
tcp::connect(&addr, stdin, stdout).await?;
|
||||
@@ -67,39 +55,27 @@ async fn run() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Temporary work around for stdin blocking the stream
|
||||
fn stdin() -> impl Stream<Item = Result<Vec<u8>, io::Error>> + Unpin {
|
||||
let mut stdin = FramedRead::new(io::stdin(), codec::Bytes);
|
||||
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(res) = stdin.next().await {
|
||||
let _ = tx.send(res);
|
||||
}
|
||||
});
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
mod tcp {
|
||||
use super::codec;
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::{error::Error, io, net::SocketAddr};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Vec<u8>, io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Vec<u8>, Error = io::Error> + Unpin,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let sink = FramedWrite::new(w, codec::Bytes);
|
||||
let mut stream = FramedRead::new(r, codec::Bytes)
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
Ok(i) => future::ready(Some(i)),
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={}", e);
|
||||
future::ready(None)
|
||||
@@ -107,7 +83,7 @@ mod tcp {
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
match future::join(stdin.forward(sink), stdout.send_all(&mut stream)).await {
|
||||
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
|
||||
(Err(e), _) | (_, Err(e)) => Err(e.into()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
@@ -115,18 +91,18 @@ mod tcp {
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use tokio::net::udp::{RecvHalf, SendHalf};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::udp::{RecvHalf, SendHalf};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Vec<u8>, io::Error>> + Unpin,
|
||||
stdout: impl Sink<Vec<u8>, Error = io::Error> + Unpin,
|
||||
stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
@@ -146,7 +122,7 @@ mod udp {
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Vec<u8>, io::Error>> + Unpin,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
writer: &mut SendHalf,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
@@ -158,7 +134,7 @@ mod udp {
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Vec<u8>, Error = io::Error> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
reader: &mut RecvHalf,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
@@ -166,47 +142,8 @@ mod udp {
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(buf).await?;
|
||||
stdout.send(Bytes::from(buf)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod codec {
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use std::io;
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
|
||||
/// A simple `Codec` implementation that just ships bytes around.
|
||||
///
|
||||
/// This type is used for "framing" a TCP/UDP stream of bytes but it's really
|
||||
/// just a convenient method for us to work with streams/sinks for now.
|
||||
/// This'll just take any data read and interpret it as a "frame" and
|
||||
/// conversely just shove data into the output location without looking at
|
||||
/// it.
|
||||
pub struct Bytes;
|
||||
|
||||
impl Decoder for Bytes {
|
||||
type Item = Vec<u8>;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<Vec<u8>>> {
|
||||
if buf.len() > 0 {
|
||||
let len = buf.len();
|
||||
Ok(Some(buf.split_to(len).into_iter().collect()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for Bytes {
|
||||
type Item = Vec<u8>;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, data: Vec<u8>, buf: &mut BytesMut) -> io::Result<()> {
|
||||
buf.put(&data[..]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ impl Server {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let socket = UdpSocket::bind(&addr).await?;
|
||||
println!("Listening on: {}", socket.local_addr()?);
|
||||
|
||||
+3
-1
@@ -33,7 +33,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
// program, but otherwise we'll just set up our TCP listener on
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
|
||||
@@ -55,9 +55,9 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{BytesCodec, Decoder};
|
||||
|
||||
use futures::StreamExt;
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
@@ -65,7 +65,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
// program, but otherwise we'll just set up our TCP listener on
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
|
||||
+6
-2
@@ -32,8 +32,12 @@ use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let listen_addr = env::args().nth(1).unwrap_or("127.0.0.1:8081".to_string());
|
||||
let server_addr = env::args().nth(2).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let listen_addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8081".to_string());
|
||||
let server_addr = env::args()
|
||||
.nth(2)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
+11
-11
@@ -42,9 +42,10 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{Framed, LinesCodec};
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use futures::SinkExt;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
@@ -84,7 +85,9 @@ enum Response {
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse the address we're going to run this server on
|
||||
// and set up our TCP listener to accept connections.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
@@ -175,15 +178,12 @@ fn handle_request(line: &str, db: &Arc<Database>) -> Response {
|
||||
|
||||
impl Request {
|
||||
fn parse(input: &str) -> Result<Request, String> {
|
||||
let mut parts = input.splitn(3, " ");
|
||||
let mut parts = input.splitn(3, ' ');
|
||||
match parts.next() {
|
||||
Some("GET") => {
|
||||
let key = match parts.next() {
|
||||
Some(key) => key,
|
||||
None => return Err(format!("GET must be followed by a key")),
|
||||
};
|
||||
let key = parts.next().ok_or("GET must be followed by a key")?;
|
||||
if parts.next().is_some() {
|
||||
return Err(format!("GET's key must not be followed by anything"));
|
||||
return Err("GET's key must not be followed by anything".into());
|
||||
}
|
||||
Ok(Request::Get {
|
||||
key: key.to_string(),
|
||||
@@ -192,11 +192,11 @@ impl Request {
|
||||
Some("SET") => {
|
||||
let key = match parts.next() {
|
||||
Some(key) => key,
|
||||
None => return Err(format!("SET must be followed by a key")),
|
||||
None => return Err("SET must be followed by a key".into()),
|
||||
};
|
||||
let value = match parts.next() {
|
||||
Some(value) => value,
|
||||
None => return Err(format!("SET needs a value")),
|
||||
None => return Err("SET needs a value".into()),
|
||||
};
|
||||
Ok(Request::Set {
|
||||
key: key.to_string(),
|
||||
@@ -204,7 +204,7 @@ impl Request {
|
||||
})
|
||||
}
|
||||
Some(cmd) => Err(format!("unknown command: {}", cmd)),
|
||||
None => Err(format!("empty input")),
|
||||
None => Err("empty input".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-17
@@ -14,22 +14,25 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use futures::SinkExt;
|
||||
use http::{header::HeaderValue, Request, Response, StatusCode};
|
||||
use serde::Serialize;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
use serde_json;
|
||||
use std::{env, error::Error, fmt, io};
|
||||
use tokio::{
|
||||
codec::{Decoder, Encoder, Framed},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse the arguments, bind the TCP socket we'll be listening to, spin up
|
||||
// our worker threads, and start shipping sockets to those worker threads.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
|
||||
let mut incoming = TcpListener::bind(&addr).await?.incoming();
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let mut server = TcpListener::bind(&addr).await?;
|
||||
let mut incoming = server.incoming();
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
while let Some(Ok(stream)) = incoming.next().await {
|
||||
@@ -63,11 +66,11 @@ async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
|
||||
let mut response = Response::builder();
|
||||
let body = match req.uri().path() {
|
||||
"/plaintext" => {
|
||||
response.header("Content-Type", "text/plain");
|
||||
response = response.header("Content-Type", "text/plain");
|
||||
"Hello, World!".to_string()
|
||||
}
|
||||
"/json" => {
|
||||
response.header("Content-Type", "application/json");
|
||||
response = response.header("Content-Type", "application/json");
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Message {
|
||||
@@ -78,7 +81,7 @@ async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
|
||||
})?
|
||||
}
|
||||
_ => {
|
||||
response.status(StatusCode::NOT_FOUND);
|
||||
response = response.status(StatusCode::NOT_FOUND);
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
@@ -196,16 +199,19 @@ impl Decoder for Http {
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
ret.method(&data[method.0..method.1]);
|
||||
ret.uri(data.slice(path.0, path.1));
|
||||
ret.version(http::Version::HTTP_11);
|
||||
ret = ret.method(&data[method.0..method.1]);
|
||||
let s = data.slice(path.0..path.1);
|
||||
let s = unsafe { String::from_utf8_unchecked(Vec::from(s.as_ref())) };
|
||||
ret = ret.uri(s);
|
||||
ret = ret.version(http::Version::HTTP_11);
|
||||
for header in headers.iter() {
|
||||
let (k, v) = match *header {
|
||||
Some((ref k, ref v)) => (k, v),
|
||||
None => break,
|
||||
};
|
||||
let value = unsafe { HeaderValue::from_shared_unchecked(data.slice(v.0, v.1)) };
|
||||
ret.header(&data[k.0..k.1], value);
|
||||
let value = HeaderValue::from_bytes(data.slice(v.0..v.1).as_ref())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "header decode error"))?;
|
||||
ret = ret.header(&data[k.0..k.1], value);
|
||||
}
|
||||
|
||||
let req = ret
|
||||
|
||||
@@ -44,7 +44,7 @@ fn get_stdin_data() -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let remote_addr: SocketAddr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or("127.0.0.1:8080".into())
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".into())
|
||||
.parse()?;
|
||||
|
||||
// We use port 0 to let the operating system allocate an available port for us.
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio::{io, time};
|
||||
use tokio_util::codec::BytesCodec;
|
||||
use tokio_util::udp::UdpFramed;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use futures::{FutureExt, SinkExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
@@ -22,7 +23,9 @@ use std::time::Duration;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:0".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:0".to_string());
|
||||
|
||||
// Bind both our sockets and then figure out what ports we got.
|
||||
let a = UdpSocket::bind(&addr).await?;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
edition = "2018"
|
||||
@@ -5,11 +5,23 @@ authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
full = [
|
||||
"macros",
|
||||
"rt-core",
|
||||
"rt-threaded",
|
||||
|
||||
"tokio/full",
|
||||
"tokio-test"
|
||||
]
|
||||
macros = ["tokio/macros"]
|
||||
rt-core = ["tokio/rt-core"]
|
||||
rt-threaded = ["rt-core", "tokio/rt-threaded"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", features = ["full"] }
|
||||
tokio = { path = "../tokio" }
|
||||
tokio-test = { path = "../tokio-test", optional = true }
|
||||
doc-comment = "0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
use doc_comment::doc_comment;
|
||||
|
||||
// #[doc = include_str!("../../README.md")]
|
||||
doc_comment!(include_str!("../../README.md"));
|
||||
#[cfg(feature = "full")]
|
||||
doc_comment::doc_comment!(include_str!("../../README.md"));
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
#[tokio::main]
|
||||
async fn basic_main() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn generic_fun<T: Default>() -> T {
|
||||
T::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "rt-core")]
|
||||
mod spawn {
|
||||
#[tokio::main]
|
||||
async fn spawning() -> usize {
|
||||
let join = tokio::spawn(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell() {
|
||||
assert_eq!(1, basic_main());
|
||||
assert_eq!(bool::default(), generic_fun::<bool>())
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use futures::executor::block_on;
|
||||
|
||||
async fn my_async_fn() {}
|
||||
|
||||
#[test]
|
||||
fn pin() {
|
||||
block_on(async {
|
||||
let future = my_async_fn();
|
||||
tokio::pin!(future);
|
||||
(&mut future).await
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use futures::executor::block_on;
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn join_with_select() {
|
||||
block_on(async {
|
||||
let (tx1, mut rx1) = oneshot::channel::<i32>();
|
||||
let (tx2, mut rx2) = oneshot::channel::<i32>();
|
||||
|
||||
thread::spawn(move || {
|
||||
tx1.send(123).unwrap();
|
||||
tx2.send(456).unwrap();
|
||||
});
|
||||
|
||||
let mut a = None;
|
||||
let mut b = None;
|
||||
|
||||
while a.is_none() || b.is_none() {
|
||||
tokio::select! {
|
||||
v1 = (&mut rx1), if a.is_none() => a = Some(v1.unwrap()),
|
||||
v2 = (&mut rx2), if b.is_none() => b = Some(v2.unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
let (a, b) = (a.unwrap(), b.unwrap());
|
||||
|
||||
assert_eq!(a, 123);
|
||||
assert_eq!(b, 456);
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::process::{Child, Command};
|
||||
@@ -25,8 +26,8 @@ fn cat() -> Command {
|
||||
}
|
||||
|
||||
async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
let mut stdin = cat.stdin().take().unwrap();
|
||||
let stdout = cat.stdout().take().unwrap();
|
||||
let mut stdin = cat.stdin.take().unwrap();
|
||||
let stdout = cat.stdout.take().unwrap();
|
||||
|
||||
// Produce n lines on the child's stdout.
|
||||
let write = async {
|
||||
@@ -97,7 +98,7 @@ async fn feed_a_lot() {
|
||||
#[tokio::test]
|
||||
async fn wait_with_output_captures() {
|
||||
let mut child = cat().spawn().unwrap();
|
||||
let mut stdin = child.stdin().take().unwrap();
|
||||
let mut stdin = child.stdin.take().unwrap();
|
||||
|
||||
let write_bytes = b"1234";
|
||||
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
# 0.2.4 (January 27, 2019)
|
||||
|
||||
### Fixed
|
||||
- generics on `#[tokio::main]` function (#2177).
|
||||
|
||||
### Added
|
||||
- support for `tokio::select!` (#2152).
|
||||
|
||||
# 0.2.3 (January 7, 2019)
|
||||
|
||||
### Fixed
|
||||
- Revert breaking change.
|
||||
|
||||
# 0.2.2 (January 7, 2019)
|
||||
|
||||
### Added
|
||||
- General refactoring and inclusion of additional runtime options (#2022 and #2038)
|
||||
|
||||
# 0.2.1 (December 18, 2019)
|
||||
|
||||
### Fixes
|
||||
- inherit visibility when wrapping async fn (#1954).
|
||||
|
||||
# 0.2.0 (November 26, 2019)
|
||||
|
||||
- Initial release
|
||||
|
||||
@@ -7,13 +7,13 @@ name = "tokio-macros"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.2.0"
|
||||
version = "0.2.4"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-macros/0.2.0/tokio_macros"
|
||||
documentation = "https://docs.rs/tokio-macros/0.2.3/tokio_macros"
|
||||
description = """
|
||||
Tokio's proc macros.
|
||||
"""
|
||||
@@ -25,6 +25,7 @@ proc-macro = true
|
||||
[features]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.7"
|
||||
quote = "1"
|
||||
syn = { version = "1.0.3", features = ["full"] }
|
||||
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Runtime {
|
||||
Basic,
|
||||
Threaded,
|
||||
}
|
||||
|
||||
fn parse_knobs(
|
||||
mut input: syn::ItemFn,
|
||||
args: syn::AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_threaded: bool,
|
||||
) -> Result<TokenStream, syn::Error> {
|
||||
let sig = &mut input.sig;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
if sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return Err(syn::Error::new_spanned(sig.fn_token, msg));
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let mut runtime = None;
|
||||
let mut core_threads = None;
|
||||
let mut max_threads = None;
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
|
||||
let ident = namevalue.path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"core_threads" => {
|
||||
if rt_threaded {
|
||||
match &namevalue.lit {
|
||||
syn::Lit::Int(expr) => {
|
||||
let num = expr.base10_parse::<NonZeroUsize>().unwrap();
|
||||
if num.get() > 1 {
|
||||
runtime = Some(Runtime::Threaded);
|
||||
} else {
|
||||
runtime = Some(Runtime::Basic);
|
||||
}
|
||||
|
||||
if let Some(v) = max_threads {
|
||||
if v < num {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads cannot be less than core_threads",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
core_threads = Some(num);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"core_threads argument must be an int",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"core_threads can only be set with rt-threaded feature flag enabled",
|
||||
));
|
||||
}
|
||||
}
|
||||
"max_threads" => match &namevalue.lit {
|
||||
syn::Lit::Int(expr) => {
|
||||
let num = expr.base10_parse::<NonZeroUsize>().unwrap();
|
||||
|
||||
if let Some(v) = core_threads {
|
||||
if num < v {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads cannot be less than core_threads",
|
||||
));
|
||||
}
|
||||
}
|
||||
max_threads = Some(num);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads argument must be an int",
|
||||
))
|
||||
}
|
||||
},
|
||||
name => {
|
||||
let msg = format!("Unknown attribute pair {} is specified; expected one of: `core_threads`, `max_threads`", name);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => {
|
||||
runtime = Some(runtime.unwrap_or_else(|| Runtime::Threaded))
|
||||
}
|
||||
"basic_scheduler" => runtime = Some(runtime.unwrap_or_else(|| Runtime::Basic)),
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
other,
|
||||
"Unknown attribute inside the macro",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut rt = quote! { tokio::runtime::Builder::new().basic_scheduler() };
|
||||
if rt_threaded && (runtime == Some(Runtime::Threaded) || (runtime.is_none() && !is_test)) {
|
||||
rt = quote! { #rt.threaded_scheduler() };
|
||||
}
|
||||
if let Some(v) = core_threads.map(|v| v.get()) {
|
||||
rt = quote! { #rt.core_threads(#v) };
|
||||
}
|
||||
if let Some(v) = max_threads.map(|v| v.get()) {
|
||||
rt = quote! { #rt.max_threads(#v) };
|
||||
}
|
||||
|
||||
let header = {
|
||||
if is_test {
|
||||
quote! {
|
||||
#[test]
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
};
|
||||
|
||||
let result = quote! {
|
||||
#header
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
#rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
};
|
||||
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, false, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
for attr in &input.attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
if !input.sig.inputs.is_empty() {
|
||||
let msg = "the test function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, true, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) mod old {
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
enum Runtime {
|
||||
Basic,
|
||||
Threaded,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let sig = &mut input.sig;
|
||||
let name = &sig.ident;
|
||||
let inputs = &sig.inputs;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
if sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if name == "main" && !inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded | Runtime::Auto => quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic => quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let ret = &input.sig.output;
|
||||
let name = &input.sig.ident;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
if input.sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(&input.sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if !input.sig.inputs.is_empty() {
|
||||
let msg = "the test function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded => quote! {
|
||||
#[test]
|
||||
#(#attrs)*
|
||||
#vis fn #name() #ret {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic | Runtime::Auto => quote! {
|
||||
#[test]
|
||||
#(#attrs)*
|
||||
#vis fn #name() #ret {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
}
|
||||
+129
-141
@@ -1,4 +1,5 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.2.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.2.3")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
@@ -13,15 +14,50 @@
|
||||
|
||||
//! Macros for use with Tokio
|
||||
|
||||
// This `extern` is required for older `rustc` versions but newer `rustc`
|
||||
// versions warn about the unused `extern crate`.
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
mod entry;
|
||||
mod select;
|
||||
|
||||
enum Runtime {
|
||||
Basic,
|
||||
Threaded,
|
||||
Auto,
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
/// Marks async function to be executed by selected runtime.
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `core_threads=n` - Sets core threads to `n`.
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Set number of core threads
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(core_threads = 1)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime.
|
||||
@@ -57,68 +93,33 @@ enum Runtime {
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
entry::old::main(args, item)
|
||||
}
|
||||
|
||||
let ret = &input.sig.output;
|
||||
let name = &input.sig.ident;
|
||||
let inputs = &input.sig.inputs;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
|
||||
if input.sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(input.sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if name == "main" && !inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded | Runtime::Auto => quote! {
|
||||
#(#attrs)*
|
||||
fn #name(#inputs) #ret {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic => quote! {
|
||||
#(#attrs)*
|
||||
fn #name(#inputs) #ret {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
/// Marks async function to be executed by selected runtime.
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, false)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test enviornment
|
||||
@@ -148,77 +149,64 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let ret = &input.sig.output;
|
||||
let name = &input.sig.ident;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
if input.sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(&input.sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if !input.sig.inputs.is_empty() {
|
||||
let msg = "the test function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded => quote! {
|
||||
#[test]
|
||||
#(#attrs)*
|
||||
fn #name() #ret {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic | Runtime::Auto => quote! {
|
||||
#[test]
|
||||
#(#attrs)*
|
||||
fn #name() #ret {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
pub fn test_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test enviornment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `core_threads=n` - Sets core threads to `n`.
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Select runtime
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(core_threads = 1)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::old::test(args, item)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test enviornment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
}
|
||||
|
||||
/// Implementation detail of the `select!` macro. This macro is **not** intended
|
||||
/// to be used as part of the public API and is permitted to change.
|
||||
#[proc_macro]
|
||||
#[doc(hidden)]
|
||||
pub fn select_priv_declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
select::declare_output_enum(input)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
// passed in is: `(_ _ _)` with one `_` per branch
|
||||
let branches = match input.into_iter().next() {
|
||||
Some(TokenTree::Group(group)) => group.stream().into_iter().count(),
|
||||
_ => panic!("unexpected macro input"),
|
||||
};
|
||||
|
||||
let variants = (0..branches)
|
||||
.map(|num| Ident::new(&format!("_{}", num), Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Use a bitfield to track which futures completed
|
||||
let mask = Ident::new(
|
||||
if branches <= 8 {
|
||||
"u8"
|
||||
} else if branches <= 16 {
|
||||
"u16"
|
||||
} else if branches <= 32 {
|
||||
"u32"
|
||||
} else if branches <= 64 {
|
||||
"u64"
|
||||
} else {
|
||||
panic!("up to 64 branches supported");
|
||||
},
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
TokenStream::from(quote! {
|
||||
pub(super) enum Out<#( #variants ),*> {
|
||||
#( #variants(#variants), )*
|
||||
// Include a `Disabled` variant signifying that all select branches
|
||||
// failed to resolve.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub(super) type Mask = #mask;
|
||||
})
|
||||
}
|
||||
@@ -20,7 +20,7 @@ Testing utilities for Tokio- and futures-based code
|
||||
categories = ["asynchronous", "testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["rt-core", "sync", "time", "test-util"] }
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["rt-core", "stream", "sync", "time", "test-util"] }
|
||||
|
||||
bytes = "0.5.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! A collection of useful macros for testing futures and tokio based code
|
||||
|
||||
/// Assert a `Poll` is ready, returning the value.
|
||||
/// Asserts a `Poll` is ready, returning the value.
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready` at
|
||||
/// runtime.
|
||||
@@ -39,7 +39,7 @@ macro_rules! assert_ready {
|
||||
}};
|
||||
}
|
||||
|
||||
/// Assert a `Poll<Result<...>>` is ready and `Ok`, returning the value.
|
||||
/// Asserts a `Poll<Result<...>>` is ready and `Ok`, returning the value.
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready(Ok(..))` at
|
||||
/// runtime.
|
||||
@@ -72,7 +72,7 @@ macro_rules! assert_ready_ok {
|
||||
}};
|
||||
}
|
||||
|
||||
/// Assert a `Poll<Result<...>>` is ready and `Err`, returning the error.
|
||||
/// Asserts a `Poll<Result<...>>` is ready and `Err`, returning the error.
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready(Err(..))` at
|
||||
/// runtime.
|
||||
@@ -105,7 +105,7 @@ macro_rules! assert_ready_err {
|
||||
}};
|
||||
}
|
||||
|
||||
/// Assert a `Poll` is pending.
|
||||
/// Asserts a `Poll` is pending.
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Pending` at
|
||||
/// runtime.
|
||||
@@ -144,7 +144,7 @@ macro_rules! assert_pending {
|
||||
}};
|
||||
}
|
||||
|
||||
/// Assert if a poll is ready and check for equality on the value
|
||||
/// Asserts if a poll is ready and check for equality on the value
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready` at
|
||||
/// runtime and the value produced does not partially equal the expected value.
|
||||
|
||||
+10
-8
@@ -1,6 +1,7 @@
|
||||
//! Futures task based helpers
|
||||
|
||||
use futures_core::Stream;
|
||||
#![allow(clippy::mutex_atomic)]
|
||||
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
@@ -8,6 +9,8 @@ use std::pin::Pin;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use tokio::stream::Stream;
|
||||
|
||||
/// TOOD: dox
|
||||
pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
Spawn {
|
||||
@@ -42,7 +45,7 @@ const WAKE: usize = 1;
|
||||
const SLEEP: usize = 2;
|
||||
|
||||
impl<T> Spawn<T> {
|
||||
/// Consume `self` returning the inner value
|
||||
/// Consumes `self` returning the inner value
|
||||
pub fn into_inner(mut self) -> T
|
||||
where
|
||||
T: Unpin,
|
||||
@@ -98,7 +101,7 @@ impl<T: Unpin> ops::DerefMut for Spawn<T> {
|
||||
}
|
||||
|
||||
impl<T: Future> Spawn<T> {
|
||||
/// Poll a future
|
||||
/// Polls a future
|
||||
pub fn poll(&mut self) -> Poll<T::Output> {
|
||||
let fut = self.future.as_mut();
|
||||
self.task.enter(|cx| fut.poll(cx))
|
||||
@@ -106,7 +109,7 @@ impl<T: Future> Spawn<T> {
|
||||
}
|
||||
|
||||
impl<T: Stream> Spawn<T> {
|
||||
/// Poll a stream
|
||||
/// Polls a stream
|
||||
pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
|
||||
let stream = self.future.as_mut();
|
||||
self.task.enter(|cx| stream.poll_next(cx))
|
||||
@@ -114,14 +117,14 @@ impl<T: Stream> Spawn<T> {
|
||||
}
|
||||
|
||||
impl MockTask {
|
||||
/// Create a new mock task
|
||||
/// Creates new mock task
|
||||
fn new() -> Self {
|
||||
MockTask {
|
||||
waker: Arc::new(ThreadWaker::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a closure from the context of the task.
|
||||
/// Runs a closure from the context of the task.
|
||||
///
|
||||
/// Any wake notifications resulting from the execution of the closure are
|
||||
/// tracked.
|
||||
@@ -187,8 +190,7 @@ impl ThreadWaker {
|
||||
}
|
||||
|
||||
fn wake(&self) {
|
||||
// First, try transitioning from IDLE -> NOTIFY, this does not require a
|
||||
// lock.
|
||||
// First, try transitioning from IDLE -> NOTIFY, this does not require a lock.
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let prev = *state;
|
||||
|
||||
|
||||
@@ -20,10 +20,8 @@ fn async_fn() {
|
||||
#[test]
|
||||
fn test_delay() {
|
||||
let deadline = Instant::now() + Duration::from_millis(100);
|
||||
assert_eq!(
|
||||
(),
|
||||
block_on(async {
|
||||
delay_until(deadline).await;
|
||||
})
|
||||
);
|
||||
|
||||
block_on(async {
|
||||
delay_until(deadline).await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ tokio = { version = "0.2.0", path = "../tokio" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["macros", "stream", "rt-core", "io-util", "net"] }
|
||||
tokio-util = { version = "0.2.0", path = "../tokio-util", features = ["full"] }
|
||||
|
||||
cfg-if = "0.1"
|
||||
env_logger = { version = "0.6", default-features = false }
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
// A tiny async TLS echo server with Tokio
|
||||
use native_tls;
|
||||
use native_tls::Identity;
|
||||
use tokio;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_tls;
|
||||
|
||||
/**
|
||||
an example to setup a tls server.
|
||||
how to test:
|
||||
wget https://127.0.0.1:12345 --no-check-certificate
|
||||
*/
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Bind the server's socket
|
||||
let addr = "127.0.0.1:12345".to_string();
|
||||
let mut tcp: TcpListener = TcpListener::bind(&addr).await?;
|
||||
|
||||
// Create the TLS acceptor.
|
||||
let der = include_bytes!("identity.p12");
|
||||
let cert = Identity::from_pkcs12(der, "mypass")?;
|
||||
let tls_acceptor =
|
||||
tokio_tls::TlsAcceptor::from(native_tls::TlsAcceptor::builder(cert).build()?);
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
let (socket, remote_addr) = tcp.accept().await?;
|
||||
let tls_acceptor = tls_acceptor.clone();
|
||||
println!("accept connection from {}", remote_addr);
|
||||
tokio::spawn(async move {
|
||||
// Accept the TLS connection.
|
||||
let mut tls_stream = tls_acceptor.accept(socket).await.expect("accept error");
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
|
||||
let mut buf = [0; 1024];
|
||||
let n = tls_stream
|
||||
.read(&mut buf)
|
||||
.await
|
||||
.expect("failed to read data from socket");
|
||||
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
println!("read={}", unsafe {
|
||||
String::from_utf8_unchecked(buf[0..n].into())
|
||||
});
|
||||
tls_stream
|
||||
.write_all(&buf[0..n])
|
||||
.await
|
||||
.expect("failed to write data to socket");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
// A tiny async TLS echo server with Tokio
|
||||
use native_tls;
|
||||
use native_tls::Identity;
|
||||
use tokio;
|
||||
use tokio::io;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use tokio_tls;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Bind the server's socket
|
||||
let addr = "127.0.0.1:12345".parse()?;
|
||||
let tcp = TcpListener::bind(&addr)?;
|
||||
|
||||
// Create the TLS acceptor.
|
||||
let der = include_bytes!("identity.p12");
|
||||
let cert = Identity::from_pkcs12(der, "mypass")?;
|
||||
let tls_acceptor =
|
||||
tokio_tls::TlsAcceptor::from(native_tls::TlsAcceptor::builder(cert).build()?);
|
||||
|
||||
// Iterate incoming connections
|
||||
let server = tcp
|
||||
.incoming()
|
||||
.for_each(move |tcp| {
|
||||
// Accept the TLS connection.
|
||||
let tls_accept = tls_acceptor
|
||||
.accept(tcp)
|
||||
.and_then(move |tls| {
|
||||
// Split up the read and write halves
|
||||
let (reader, writer) = tls.split();
|
||||
|
||||
// Copy the data back to the client
|
||||
let conn = io::copy(reader, writer)
|
||||
// print what happened
|
||||
.map(|(n, _, _)| println!("wrote {} bytes", n))
|
||||
// Handle any errors
|
||||
.map_err(|err| println!("IO error {:?}", err));
|
||||
|
||||
// Spawn the future as a concurrent task
|
||||
tokio::spawn(conn);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
println!("TLS accept error: {:?}", err);
|
||||
});
|
||||
tokio::spawn(tls_accept);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
println!("server error {:?}", err);
|
||||
});
|
||||
|
||||
// Start the runtime and spin up the server
|
||||
tokio::run(server);
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
use cfg_if::cfg_if;
|
||||
use env_logger;
|
||||
use futures::join;
|
||||
use futures::stream::StreamExt;
|
||||
use native_tls;
|
||||
use native_tls::{Identity, TlsAcceptor, TlsConnector};
|
||||
use std::io::Write;
|
||||
@@ -12,6 +11,7 @@ use std::process::Command;
|
||||
use std::ptr;
|
||||
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt, Error, ErrorKind};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_tls;
|
||||
|
||||
macro_rules! t {
|
||||
@@ -280,7 +280,7 @@ cfg_if! {
|
||||
use winapi::um::timezoneapi::*;
|
||||
use winapi::um::wincrypt::*;
|
||||
|
||||
const FRIENDLY_NAME: &'static str = "tokio-tls localhost testing cert";
|
||||
const FRIENDLY_NAME: &str = "tokio-tls localhost testing cert";
|
||||
|
||||
fn contexts() -> (tokio_tls::TlsAcceptor, tokio_tls::TlsConnector) {
|
||||
let cert = localhost_cert();
|
||||
@@ -433,7 +433,7 @@ description should mention "tokio-tls".
|
||||
let mut expiration_date: SYSTEMTIME = mem::zeroed();
|
||||
GetSystemTime(&mut expiration_date);
|
||||
let mut file_time: FILETIME = mem::zeroed();
|
||||
let res = SystemTimeToFileTime(&mut expiration_date,
|
||||
let res = SystemTimeToFileTime(&expiration_date,
|
||||
&mut file_time);
|
||||
if res != TRUE {
|
||||
return Err(Error::last_os_error());
|
||||
|
||||
@@ -26,7 +26,7 @@ default = []
|
||||
# Shorthand for enabling everything
|
||||
full = ["codec", "udp"]
|
||||
|
||||
codec = []
|
||||
codec = ["tokio/stream"]
|
||||
udp = ["tokio/udp"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -38,7 +38,7 @@ pub trait Decoder {
|
||||
/// Attempts to decode a frame from the provided buffer of bytes.
|
||||
///
|
||||
/// This method is called by `FramedRead` whenever bytes are ready to be
|
||||
/// parsed. The provided buffer of bytes is what's been read so far, and
|
||||
/// parsed. The provided buffer of bytes is what's been read so far, and
|
||||
/// this instance of `Decode` can determine whether an entire frame is in
|
||||
/// the buffer and is ready to be returned.
|
||||
///
|
||||
|
||||
@@ -3,10 +3,12 @@ use crate::codec::encoder::Encoder;
|
||||
use crate::codec::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2};
|
||||
use crate::codec::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2};
|
||||
|
||||
use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite};
|
||||
use tokio::{
|
||||
io::{AsyncBufRead, AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_core::Stream;
|
||||
use futures_sink::Sink;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::codec::framed::{Fuse, ProjectFuse};
|
||||
use crate::codec::Decoder;
|
||||
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::{io::AsyncRead, stream::Stream};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_core::Stream;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
@@ -2,10 +2,13 @@ use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
use crate::codec::framed::{Fuse, ProjectFuse};
|
||||
|
||||
use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite};
|
||||
use tokio::{
|
||||
io::{AsyncBufRead, AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
//!
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio/*/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/*/tokio/io/trait.AsyncWrite.html
|
||||
//! [`Stream`]: https://docs.rs/tokio/*/tokio/stream/trait.Stream.html
|
||||
//! [`Sink`]: https://docs.rs/futures-sink/*/futures_sink/trait.Sink.html
|
||||
//! [`Stream`]: https://docs.rs/futures-core/*/futures_core/stream/trait.Stream.html
|
||||
|
||||
mod bytes_codec;
|
||||
pub use self::bytes_codec::BytesCodec;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-util/0.2.0")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::codec::{Decoder, Encoder};
|
||||
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::{net::UdpSocket, stream::Stream};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures_core::{ready, Stream};
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use std::io;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
@@ -27,7 +27,7 @@ use std::task::{Context, Poll};
|
||||
/// calling `split` on the `UdpFramed` returned by this method, which will break
|
||||
/// them into separate objects, allowing them to interact more easily.
|
||||
#[must_use = "sinks do nothing unless polled"]
|
||||
#[cfg_attr(docsrs, doc(feature = "codec-udp"))]
|
||||
#[cfg_attr(docsrs, doc(all(feature = "codec", feature = "udp")))]
|
||||
#[derive(Debug)]
|
||||
pub struct UdpFramed<C> {
|
||||
socket: UdpSocket,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::prelude::*;
|
||||
use tokio::{prelude::*, stream::StreamExt};
|
||||
use tokio_test::assert_ok;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed, FramedParts};
|
||||
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use futures::StreamExt;
|
||||
use std::io::{self, Read};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
@@ -203,7 +203,7 @@ fn huge_size() {
|
||||
if buf.len() < 32 * 1024 {
|
||||
return Ok(None);
|
||||
}
|
||||
buf.split_to(32 * 1024);
|
||||
buf.advance(32 * 1024);
|
||||
Ok(Some(0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ fn write_hits_backpressure() {
|
||||
|
||||
// Append to the end
|
||||
match mock.calls.back_mut().unwrap() {
|
||||
&mut Ok(ref mut data) => {
|
||||
Ok(ref mut data) => {
|
||||
// Write in 2kb chunks
|
||||
if data.len() < ITER {
|
||||
data.extend_from_slice(&b[..]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::{net::UdpSocket, stream::StreamExt};
|
||||
use tokio_util::codec::{Decoder, Encoder};
|
||||
use tokio_util::udp::UdpFramed;
|
||||
|
||||
@@ -6,7 +6,6 @@ use bytes::{BufMut, BytesMut};
|
||||
use futures::future::try_join;
|
||||
use futures::future::FutureExt;
|
||||
use futures::sink::SinkExt;
|
||||
use futures::stream::StreamExt;
|
||||
use std::io;
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,3 +1,166 @@
|
||||
# 0.2.11 (January 27, 2019)
|
||||
|
||||
### Fixes
|
||||
- docs: misc fixes and tweaks (#2155, #2103, #2027, #2167, #2175).
|
||||
- macros: handle generics in `#[tokio::main]` method (#2177).
|
||||
- sync: `broadcast` potential lost notifications (#2135).
|
||||
- rt: improve "no runtime" panic messages (#2145).
|
||||
|
||||
### Added
|
||||
- optional support for using `parking_lot` internally (#2164).
|
||||
- fs: `fs::copy`, an async version of `std::fs::copy` (#2079).
|
||||
- macros: `select!` waits for the first branch to complete (#2152).
|
||||
- macros: `join!` waits for all branches to complete (#2158).
|
||||
- macros: `try_join!` waits for all branches to complete or the first error (#2169).
|
||||
- macros: `pin!` pins a value to the stack (#2163).
|
||||
- net: `ReadHalf::poll()` and `ReadHalf::poll_peak` (#2151)
|
||||
- stream: `StreamExt::timeout()` sets a per-item max duration (#2149).
|
||||
- stream: `StreamExt::fold()` applies a function, producing a single value. (#2122).
|
||||
- sync: impl `Eq`, `PartialEq` for `oneshot::RecvError` (#2168).
|
||||
- task: methods for inspecting the `JoinError` cause (#2051).
|
||||
|
||||
# 0.2.10 (January 21, 2019)
|
||||
|
||||
### Fixes
|
||||
- `#[tokio::main]` when `rt-core` feature flag is not enabled (#2139).
|
||||
- remove `AsyncBufRead` from `BufStream` impl block (#2108).
|
||||
- potential undefined behavior when implementing `AsyncRead` incorrectly (#2030).
|
||||
|
||||
### Added
|
||||
- `BufStream::with_capacity` (#2125).
|
||||
- impl `From` and `Default` for `RwLock` (#2089).
|
||||
- `io::ReadHalf::is_pair_of` checks if provided `WriteHalf` is for the same
|
||||
underlying object (#1762, #2144).
|
||||
- `runtime::Handle::try_current()` returns a handle to the current runtime (#2118).
|
||||
- `stream::empty()` returns an immediately ready empty stream (#2092).
|
||||
- `stream::once(val)` returns a stream that yields a single value: `val` (#2094).
|
||||
- `stream::pending()` returns a stream that never becomes ready (#2092).
|
||||
- `StreamExt::chain()` sequences a second stream after the first completes (#2093).
|
||||
- `StreamExt::collect()` transform a stream into a collection (#2109).
|
||||
- `StreamExt::fuse` ends the stream after the first `None` (#2085).
|
||||
- `StreamExt::merge` combines two streams, yielding values as they become ready (#2091).
|
||||
- Task-local storage (#2126).
|
||||
|
||||
# 0.2.9 (January 9, 2019)
|
||||
|
||||
### Fixes
|
||||
- `AsyncSeek` impl for `File` (#1986).
|
||||
- rt: shutdown deadlock in `threaded_scheduler` (#2074, #2082).
|
||||
- rt: memory ordering when dropping `JoinHandle` (#2044).
|
||||
- docs: misc API documentation fixes and improvements.
|
||||
|
||||
# 0.2.8 (January 7, 2019)
|
||||
|
||||
### Fixes
|
||||
- depend on new version of `tokio-macros`.
|
||||
|
||||
# 0.2.7 (January 7, 2019)
|
||||
|
||||
### Fixes
|
||||
- potential deadlock when dropping `basic_scheduler` Runtime.
|
||||
- calling `spawn_blocking` from within a `spawn_blocking` (#2006).
|
||||
- storing a `Runtime` instance in a thread-local (#2011).
|
||||
- miscellaneous documentation fixes.
|
||||
- rt: fix `Waker::will_wake` to return true when tasks match (#2045).
|
||||
- test-util: `time::advance` runs pending tasks before changing the time (#2059).
|
||||
|
||||
### Added
|
||||
- `net::lookup_host` maps a `T: ToSocketAddrs` to a stream of `SocketAddrs` (#1870).
|
||||
- `process::Child` fields are made public to match `std` (#2014).
|
||||
- impl `Stream` for `sync::broadcast::Receiver` (#2012).
|
||||
- `sync::RwLock` provides an asynchonous read-write lock (#1699).
|
||||
- `runtime::Handle::current` returns the handle for the current runtime (#2040).
|
||||
- `StreamExt::filter` filters stream values according to a predicate (#2001).
|
||||
- `StreamExt::filter_map` simultaneously filter and map stream values (#2001).
|
||||
- `StreamExt::try_next` convenience for streams of `Result<T, E>` (#2005).
|
||||
- `StreamExt::take` limits a stream to a specified number of values (#2025).
|
||||
- `StreamExt::take_while` limits a stream based on a predicate (#2029).
|
||||
- `StreamExt::all` tests if every element of the stream matches a predicate (#2035).
|
||||
- `StreamExt::any` tests if any element of the stream matches a predicate (#2034).
|
||||
- `task::LocalSet.await` runs spawned tasks until the set is idle (#1971).
|
||||
- `time::DelayQueue::len` returns the number entries in the queue (#1755).
|
||||
- expose runtime options from the `#[tokio::main]` and `#[tokio::test]` (#2022).
|
||||
|
||||
# 0.2.6 (December 19, 2019)
|
||||
|
||||
### Fixes
|
||||
- `fs::File::seek` API regression (#1991).
|
||||
|
||||
# 0.2.5 (December 18, 2019)
|
||||
|
||||
### Added
|
||||
- `io::AsyncSeek` trait (#1924).
|
||||
- `Mutex::try_lock` (#1939)
|
||||
- `mpsc::Receiver::try_recv` and `mpsc::UnboundedReceiver::try_recv` (#1939).
|
||||
- `writev` support for `TcpStream` (#1956).
|
||||
- `time::throttle` for throttling streams (#1949).
|
||||
- implement `Stream` for `time::DelayQueue` (#1975).
|
||||
- `sync::broadcast` provides a fan-out channel (#1943).
|
||||
- `sync::Semaphore` provides an async semaphore (#1973).
|
||||
- `stream::StreamExt` provides stream utilities (#1962).
|
||||
|
||||
### Fixes
|
||||
- deadlock risk while shutting down the runtime (#1972).
|
||||
- panic while shutting down the runtime (#1978).
|
||||
- `sync::MutexGuard` debug output (#1961).
|
||||
- misc doc improvements (#1933, #1934, #1940, #1942).
|
||||
|
||||
### Changes
|
||||
- runtime threads are configured with `runtime::Builder::core_threads` and
|
||||
`runtime::Builder::max_threads`. `runtime::Builder::num_threads` is
|
||||
deprecated (#1977).
|
||||
|
||||
# 0.2.4 (December 6, 2019)
|
||||
|
||||
### Fixes
|
||||
- `sync::Mutex` deadlock when `lock()` future is dropped early (#1898).
|
||||
|
||||
# 0.2.3 (December 6, 2019)
|
||||
|
||||
### Added
|
||||
- read / write integers using `AsyncReadExt` and `AsyncWriteExt` (#1863).
|
||||
- `read_buf` / `write_buf` for reading / writing `Buf` / `BufMut` (#1881).
|
||||
- `TcpStream::poll_peek` - pollable API for performing TCP peek (#1864).
|
||||
- `sync::oneshot::error::TryRecvError` provides variants to detect the error
|
||||
kind (#1874).
|
||||
- `LocalSet::block_on` accepts `!'static` task (#1882).
|
||||
- `task::JoinError` is now `Sync` (#1888).
|
||||
- impl conversions between `tokio::time::Instant` and
|
||||
`std::time::Instant` (#1904).
|
||||
|
||||
### Fixes
|
||||
- calling `spawn_blocking` after runtime shutdown (#1875).
|
||||
- `LocalSet` drop inifinite loop (#1892).
|
||||
- `LocalSet` hang under load (#1905).
|
||||
- improved documentation (#1865, #1866, #1868, #1874, #1876, #1911).
|
||||
|
||||
# 0.2.2 (November 29, 2019)
|
||||
|
||||
### Fixes
|
||||
- scheduling with `basic_scheduler` (#1861).
|
||||
- update `spawn` panic message to specify that a task scheduler is required (#1839).
|
||||
- API docs example for `runtime::Builder` to include a task scheduler (#1841).
|
||||
- general documentation (#1834).
|
||||
- building on illumos/solaris (#1772).
|
||||
- panic when dropping `LocalSet` (#1843).
|
||||
- API docs mention the required Cargo features for `Builder::{basic, threaded}_scheduler` (#1858).
|
||||
|
||||
### Added
|
||||
- impl `Stream` for `signal::unix::Signal` (#1849).
|
||||
- API docs for platform specific behavior of `signal::ctrl_c` and `signal::unix::Signal` (#1854).
|
||||
- API docs for `signal::unix::Signal::{recv, poll_recv}` and `signal::windows::CtrlBreak::{recv, poll_recv}` (#1854).
|
||||
- `File::into_std` and `File::try_into_std` methods (#1856).
|
||||
|
||||
# 0.2.1 (November 26, 2019)
|
||||
|
||||
### Fixes
|
||||
- API docs for `TcpListener::incoming`, `UnixListener::incoming` (#1831).
|
||||
|
||||
### Added
|
||||
- `tokio::task::LocalSet` provides a strategy for spawning `!Send` tasks (#1733).
|
||||
- export `tokio::time::Elapsed` (#1826).
|
||||
- impl `AsRawFd`, `AsRawHandle` for `tokio::fs::File` (#1827).
|
||||
|
||||
# 0.2.0 (November 26, 2019)
|
||||
|
||||
A major breaking change. Most implementation and APIs have changed one way or
|
||||
|
||||
+19
-10
@@ -8,12 +8,12 @@ name = "tokio"
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0"
|
||||
version = "0.2.11"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/tokio/0.2.0/tokio/"
|
||||
documentation = "https://docs.rs/tokio/0.2.11/tokio/"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
description = """
|
||||
@@ -39,6 +39,7 @@ full = [
|
||||
"net",
|
||||
"process",
|
||||
"rt-core",
|
||||
"rt-util",
|
||||
"rt-threaded",
|
||||
"signal",
|
||||
"stream",
|
||||
@@ -48,7 +49,7 @@ full = [
|
||||
|
||||
blocking = ["rt-core"]
|
||||
dns = ["rt-core"]
|
||||
fs = ["rt-core"]
|
||||
fs = ["rt-core", "io-util"]
|
||||
io-driver = ["mio", "lazy_static"]
|
||||
io-util = ["memchr"]
|
||||
# stdin, stdout, stderr
|
||||
@@ -67,6 +68,7 @@ process = [
|
||||
]
|
||||
# Includes basic task execution capabilities
|
||||
rt-core = []
|
||||
rt-util = []
|
||||
rt-threaded = [
|
||||
"num_cpus",
|
||||
"rt-core",
|
||||
@@ -83,14 +85,13 @@ signal = [
|
||||
stream = ["futures-core"]
|
||||
sync = ["fnv"]
|
||||
test-util = []
|
||||
tcp = ["io-driver"]
|
||||
tcp = ["io-driver", "iovec"]
|
||||
time = ["slab"]
|
||||
udp = ["io-driver"]
|
||||
uds = ["io-driver", "mio-uds", "libc"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
tokio-macros = { version = "0.2.0", optional = true, path = "../tokio-macros" }
|
||||
tokio-macros = { version = "0.2.4", path = "../tokio-macros", optional = true }
|
||||
|
||||
bytes = "0.5.0"
|
||||
pin-project-lite = "0.1.1"
|
||||
@@ -101,9 +102,10 @@ futures-core = { version = "0.3.0", optional = true }
|
||||
lazy_static = { version = "1.0.2", optional = true }
|
||||
memchr = { version = "2.2", optional = true }
|
||||
mio = { version = "0.6.20", optional = true }
|
||||
iovec = { version = "0.1.4", optional = true }
|
||||
num_cpus = { version = "1.8.0", optional = true }
|
||||
# Backs `DelayQueue`
|
||||
slab = { version = "0.4.1", optional = true }
|
||||
parking_lot = { version = "0.10.0", optional = true } # Not in full
|
||||
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
mio-uds = { version = "0.6.5", optional = true }
|
||||
@@ -119,12 +121,19 @@ default-features = false
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = { version = "0.2.0", path = "../tokio-test" }
|
||||
tokio-test = { version = "0.2.0" }
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
loom = { version = "0.2.13", features = ["futures", "checkpoint"] }
|
||||
proptest = "0.9.4"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
# loom is currently not compiling on windows.
|
||||
# See: https://github.com/Xudong-Huang/generator-rs/issues/19
|
||||
[target.'cfg(not(windows))'.dev-dependencies]
|
||||
loom = { version = "0.2.13", features = ["futures", "checkpoint"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.playground]
|
||||
features = ["full"]
|
||||
|
||||
+15
-23
@@ -1,7 +1,5 @@
|
||||
# Tokio
|
||||
|
||||
_NOTE_: Tokio's [`master`](https://github.com/tokio-rs/tokio) branch is currently in the process of moving to [`std::future::Future`](https://doc.rust-lang.org/std/future/trait.Future.html), for `v0.1.x` based tokio releases please check out the [`v0.1.x`](https://github.com/tokio-rs/tokio/tree/v0.1.x) branch.
|
||||
|
||||
A runtime for writing reliable, asynchronous, and slim applications with
|
||||
the Rust programming language. It is:
|
||||
|
||||
@@ -17,7 +15,7 @@ the Rust programming language. It is:
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Gitter chat][gitter-badge]][gitter-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
@@ -25,13 +23,13 @@ the Rust programming language. It is:
|
||||
[mit-url]: 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
|
||||
[gitter-badge]: https://img.shields.io/gitter/room/tokio-rs/tokio.svg
|
||||
[gitter-url]: https://gitter.im/tokio-rs/tokio
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/6yGkFeN
|
||||
|
||||
[Website](https://tokio.rs) |
|
||||
[Guides](https://tokio.rs/docs/) |
|
||||
[API Docs](https://docs.rs/tokio/0.2.0/tokio) |
|
||||
[Chat](https://gitter.im/tokio-rs/tokio)
|
||||
[API Docs](https://docs.rs/tokio/0.2/tokio) |
|
||||
[Chat](https://discord.gg/6yGkFeN)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -47,8 +45,8 @@ level, it provides a few major components:
|
||||
These components provide the runtime components necessary for building
|
||||
an asynchronous application.
|
||||
|
||||
[net]: https://docs.rs/tokio/0.2.0/tokio/net/index.html
|
||||
[scheduler]: https://docs.rs/tokio/0.2.0/tokio/runtime/index.html
|
||||
[net]: https://docs.rs/tokio/0.2/tokio/net/index.html
|
||||
[scheduler]: https://docs.rs/tokio/0.2/tokio/runtime/index.html
|
||||
|
||||
## Example
|
||||
|
||||
@@ -63,15 +61,13 @@ shorthand, the `full` feature enables all components.
|
||||
|
||||
A basic TCP echo server with Tokio:
|
||||
|
||||
```rust
|
||||
|
||||
```rust,no_run
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "127.0.0.1:8080".parse()?;
|
||||
let mut listener = TcpListener::bind(&addr).unwrap();
|
||||
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
@@ -86,14 +82,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; err = {:?}", e);
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Write the data back
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
println!("failed to write to socket; err = {:?}", e);
|
||||
eprintln!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -102,22 +98,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
More examples can be found [here](../examples). Note that the `master` branch
|
||||
is currently being updated to use `async` / `await`. The examples are
|
||||
not fully ported. Examples for stable Tokio can be found
|
||||
[here](https://github.com/tokio-rs/tokio/tree/v0.1.x/tokio/examples).
|
||||
|
||||
More examples can be found [here](../examples).
|
||||
|
||||
## Getting Help
|
||||
|
||||
First, see if the answer to your question can be found in the [Guides] or the
|
||||
[API documentation]. If the answer is not there, there is an active community in
|
||||
the [Tokio Gitter channel][chat]. We would be happy to try to answer your
|
||||
question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
the [Tokio Discord server][chat]. We would be happy to try to answer your
|
||||
question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
|
||||
[Guides]: https://tokio.rs/docs/
|
||||
[API documentation]: https://docs.rs/tokio/0.2
|
||||
[chat]: https://gitter.im/tokio-rs/tokio
|
||||
[chat]: https://discord.gg/6yGkFeN
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues/new
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::thread;
|
||||
|
||||
use futures::sync::mpsc;
|
||||
use futures::sync::oneshot;
|
||||
use futures::try_ready;
|
||||
use futures::{Future, Poll, Sink, Stream};
|
||||
use test::Bencher;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
/// UDP echo server
|
||||
struct EchoServer {
|
||||
socket: UdpSocket,
|
||||
buf: Vec<u8>,
|
||||
to_send: Option<(usize, SocketAddr)>,
|
||||
}
|
||||
|
||||
impl EchoServer {
|
||||
fn new(s: UdpSocket) -> Self {
|
||||
EchoServer {
|
||||
socket: s,
|
||||
to_send: None,
|
||||
buf: vec![0u8; 1600],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for EchoServer {
|
||||
type Item = ();
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<(), io::Error> {
|
||||
loop {
|
||||
if let Some(&(size, peer)) = self.to_send.as_ref() {
|
||||
try_ready!(self.socket.poll_send_to(&self.buf[..size], &peer));
|
||||
self.to_send = None;
|
||||
}
|
||||
self.to_send = Some(try_ready!(self.socket.poll_recv_from(&mut self.buf)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn udp_echo_latency(b: &mut Bencher) {
|
||||
let any_addr = "127.0.0.1:0".to_string();
|
||||
let any_addr = any_addr.parse::<SocketAddr>().unwrap();
|
||||
|
||||
let (stop_c, stop_p) = oneshot::channel::<()>();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let child = thread::spawn(move || {
|
||||
let socket = tokio::net::UdpSocket::bind(&any_addr).unwrap();
|
||||
tx.send(socket.local_addr().unwrap()).unwrap();
|
||||
|
||||
let server = EchoServer::new(socket);
|
||||
let server = server.select(stop_p.map_err(|_| panic!()));
|
||||
let server = server.map_err(|_| ());
|
||||
server.wait().unwrap();
|
||||
});
|
||||
|
||||
let client = std::net::UdpSocket::bind(&any_addr).unwrap();
|
||||
|
||||
let server_addr = rx.wait().unwrap();
|
||||
let mut buf = [0u8; 1000];
|
||||
|
||||
// warmup phase; for some reason initial couple of
|
||||
// runs are much slower
|
||||
//
|
||||
// TODO: Describe the exact reasons; caching? branch predictor? lazy closures?
|
||||
for _ in 0..8 {
|
||||
client.send_to(&buf, &server_addr).unwrap();
|
||||
let _ = client.recv_from(&mut buf).unwrap();
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
client.send_to(&buf, &server_addr).unwrap();
|
||||
let _ = client.recv_from(&mut buf).unwrap();
|
||||
});
|
||||
|
||||
stop_c.send(()).unwrap();
|
||||
child.join().unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn futures_channel_latency(b: &mut Bencher) {
|
||||
let (mut in_tx, in_rx) = mpsc::channel(32);
|
||||
let (out_tx, out_rx) = mpsc::channel::<_>(32);
|
||||
|
||||
let child = thread::spawn(|| out_tx.send_all(in_rx.then(|r| r.unwrap())).wait());
|
||||
let mut rx_iter = out_rx.wait();
|
||||
|
||||
// warmup phase; for some reason initial couple of runs are much slower
|
||||
//
|
||||
// TODO: Describe the exact reasons; caching? branch predictor? lazy closures?
|
||||
for _ in 0..8 {
|
||||
in_tx.start_send(Ok(1usize)).unwrap();
|
||||
let _ = rx_iter.next();
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
in_tx.start_send(Ok(1usize)).unwrap();
|
||||
let _ = rx_iter.next();
|
||||
});
|
||||
|
||||
drop(in_tx);
|
||||
child.join().unwrap().unwrap();
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Measure cost of different operations
|
||||
// to get a sense of performance tradeoffs
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use test::Bencher;
|
||||
|
||||
use mio::tcp::TcpListener;
|
||||
use mio::{PollOpt, Ready, Token};
|
||||
|
||||
#[bench]
|
||||
fn mio_register_deregister(b: &mut Bencher) {
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
// Setup the server socket
|
||||
let sock = TcpListener::bind(&addr).unwrap();
|
||||
let poll = mio::Poll::new().unwrap();
|
||||
|
||||
const CLIENT: Token = Token(1);
|
||||
|
||||
b.iter(|| {
|
||||
poll.register(&sock, CLIENT, Ready::readable(), PollOpt::edge())
|
||||
.unwrap();
|
||||
poll.deregister(&sock).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn mio_reregister(b: &mut Bencher) {
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
// Setup the server socket
|
||||
let sock = TcpListener::bind(&addr).unwrap();
|
||||
let poll = mio::Poll::new().unwrap();
|
||||
|
||||
const CLIENT: Token = Token(1);
|
||||
poll.register(&sock, CLIENT, Ready::readable(), PollOpt::edge())
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
poll.reregister(&sock, CLIENT, Ready::readable(), PollOpt::edge())
|
||||
.unwrap();
|
||||
});
|
||||
poll.deregister(&sock).unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn mio_poll(b: &mut Bencher) {
|
||||
let poll = mio::Poll::new().unwrap();
|
||||
let timeout = std::time::Duration::new(0, 0);
|
||||
let mut events = mio::Events::with_capacity(1024);
|
||||
|
||||
b.iter(|| {
|
||||
poll.poll(&mut events, Some(timeout)).unwrap();
|
||||
});
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use tokio::sync::mpsc::*;
|
||||
|
||||
use futures::{future, Async, Future, Sink, Stream};
|
||||
use std::thread;
|
||||
use test::Bencher;
|
||||
|
||||
type Medium = [usize; 64];
|
||||
type Large = [Medium; 64];
|
||||
|
||||
#[bench]
|
||||
fn bounded_new_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let _ = test::black_box(&channel::<Medium>(1_000));
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn unbounded_new_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let _ = test::black_box(&unbounded_channel::<Medium>());
|
||||
})
|
||||
}
|
||||
#[bench]
|
||||
fn bounded_new_large(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let _ = test::black_box(&channel::<Large>(1_000));
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn unbounded_new_large(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let _ = test::black_box(&unbounded_channel::<Large>());
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn send_one_message(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = channel(1_000);
|
||||
|
||||
// Send
|
||||
tx.try_send(1).unwrap();
|
||||
|
||||
// Receive
|
||||
assert_eq!(Async::Ready(Some(1)), rx.poll().unwrap());
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn send_one_message_large(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = channel::<Large>(1_000);
|
||||
|
||||
// Send
|
||||
let _ = tx.try_send([[0; 64]; 64]);
|
||||
|
||||
// Receive
|
||||
let _ = test::black_box(&rx.poll());
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bounded_rx_not_ready(b: &mut Bencher) {
|
||||
let (_tx, mut rx) = channel::<i32>(1_000);
|
||||
b.iter(|| {
|
||||
future::lazy(|| {
|
||||
assert!(rx.poll().unwrap().is_not_ready());
|
||||
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bounded_tx_poll_ready(b: &mut Bencher) {
|
||||
let (mut tx, _rx) = channel::<i32>(1);
|
||||
b.iter(|| {
|
||||
future::lazy(|| {
|
||||
assert!(tx.poll_ready().unwrap().is_ready());
|
||||
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bounded_tx_poll_not_ready(b: &mut Bencher) {
|
||||
let (mut tx, _rx) = channel::<i32>(1);
|
||||
tx.try_send(1).unwrap();
|
||||
b.iter(|| {
|
||||
future::lazy(|| {
|
||||
assert!(tx.poll_ready().unwrap().is_not_ready());
|
||||
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn unbounded_rx_not_ready(b: &mut Bencher) {
|
||||
let (_tx, mut rx) = unbounded_channel::<i32>();
|
||||
b.iter(|| {
|
||||
future::lazy(|| {
|
||||
assert!(rx.poll().unwrap().is_not_ready());
|
||||
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn unbounded_rx_not_ready_x5(b: &mut Bencher) {
|
||||
let (_tx, mut rx) = unbounded_channel::<i32>();
|
||||
b.iter(|| {
|
||||
future::lazy(|| {
|
||||
assert!(rx.poll().unwrap().is_not_ready());
|
||||
assert!(rx.poll().unwrap().is_not_ready());
|
||||
assert!(rx.poll().unwrap().is_not_ready());
|
||||
assert!(rx.poll().unwrap().is_not_ready());
|
||||
assert!(rx.poll().unwrap().is_not_ready());
|
||||
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bounded_uncontended_1(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = channel(1_000);
|
||||
|
||||
for i in 0..1000 {
|
||||
tx.try_send(i).unwrap();
|
||||
// No need to create a task, because poll is not going to park.
|
||||
assert_eq!(Async::Ready(Some(i)), rx.poll().unwrap());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bounded_uncontended_1_large(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = channel::<Large>(1_000);
|
||||
|
||||
for i in 0..1000 {
|
||||
let _ = tx.try_send([[i; 64]; 64]);
|
||||
// No need to create a task, because poll is not going to park.
|
||||
let _ = test::black_box(&rx.poll());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bounded_uncontended_2(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = channel(1000);
|
||||
|
||||
for i in 0..1000 {
|
||||
tx.try_send(i).unwrap();
|
||||
}
|
||||
|
||||
for i in 0..1000 {
|
||||
// No need to create a task, because poll is not going to park.
|
||||
assert_eq!(Async::Ready(Some(i)), rx.poll().unwrap());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn contended_unbounded_tx(b: &mut Bencher) {
|
||||
let mut threads = vec![];
|
||||
let mut txs = vec![];
|
||||
|
||||
for _ in 0..4 {
|
||||
let (tx, rx) = ::std::sync::mpsc::channel::<Sender<i32>>();
|
||||
txs.push(tx);
|
||||
|
||||
threads.push(thread::spawn(move || {
|
||||
for mut tx in rx.iter() {
|
||||
for i in 0..1_000 {
|
||||
tx.try_send(i).unwrap();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
// TODO make unbounded
|
||||
let (tx, rx) = channel::<i32>(1_000_000);
|
||||
|
||||
for th in &txs {
|
||||
th.send(tx.clone()).unwrap();
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
|
||||
let rx = rx.wait().take(4 * 1_000);
|
||||
|
||||
for v in rx {
|
||||
let _ = test::black_box(v);
|
||||
}
|
||||
});
|
||||
|
||||
drop(txs);
|
||||
|
||||
for th in threads {
|
||||
th.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn contended_bounded_tx(b: &mut Bencher) {
|
||||
const THREADS: usize = 4;
|
||||
const ITERS: usize = 100;
|
||||
|
||||
let mut threads = vec![];
|
||||
let mut txs = vec![];
|
||||
|
||||
for _ in 0..THREADS {
|
||||
let (tx, rx) = ::std::sync::mpsc::channel::<Sender<i32>>();
|
||||
txs.push(tx);
|
||||
|
||||
threads.push(thread::spawn(move || {
|
||||
for tx in rx.iter() {
|
||||
let mut tx = tx.wait();
|
||||
for i in 0..ITERS {
|
||||
tx.send(i as i32).unwrap();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let (tx, rx) = channel::<i32>(1);
|
||||
|
||||
for th in &txs {
|
||||
th.send(tx.clone()).unwrap();
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
|
||||
let rx = rx.wait().take(THREADS * ITERS);
|
||||
|
||||
for v in rx {
|
||||
let _ = test::black_box(v);
|
||||
}
|
||||
});
|
||||
|
||||
drop(txs);
|
||||
|
||||
for th in threads {
|
||||
th.join().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use futures::{future, Async, Future};
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn new(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let _ = ::test::black_box(&oneshot::channel::<i32>());
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn same_thread_send_recv(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
let _ = tx.send(1);
|
||||
|
||||
assert_eq!(Async::Ready(1), rx.poll().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn same_thread_recv_multi_send_recv(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
future::lazy(|| {
|
||||
let _ = rx.poll();
|
||||
let _ = rx.poll();
|
||||
let _ = rx.poll();
|
||||
let _ = rx.poll();
|
||||
|
||||
let _ = tx.send(1);
|
||||
assert_eq!(Async::Ready(1), rx.poll().unwrap());
|
||||
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn multi_thread_send_recv(b: &mut Bencher) {
|
||||
const MAX: usize = 10_000_000;
|
||||
|
||||
use std::thread;
|
||||
|
||||
fn spin<F: Future>(mut f: F) -> Result<F::Item, F::Error> {
|
||||
use futures::Async::Ready;
|
||||
loop {
|
||||
match f.poll() {
|
||||
Ok(Ready(v)) => return Ok(v),
|
||||
Ok(_) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut ping_txs = vec![];
|
||||
let mut ping_rxs = vec![];
|
||||
let mut pong_txs = vec![];
|
||||
let mut pong_rxs = vec![];
|
||||
|
||||
for _ in 0..MAX {
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
|
||||
ping_txs.push(Some(tx));
|
||||
ping_rxs.push(Some(rx));
|
||||
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
|
||||
pong_txs.push(Some(tx));
|
||||
pong_rxs.push(Some(rx));
|
||||
}
|
||||
|
||||
thread::spawn(move || {
|
||||
future::lazy(|| {
|
||||
for i in 0..MAX {
|
||||
let ping_rx = ping_rxs[i].take().unwrap();
|
||||
let pong_tx = pong_txs[i].take().unwrap();
|
||||
|
||||
if spin(ping_rx).is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pong_tx.send(()).unwrap();
|
||||
}
|
||||
|
||||
Ok::<(), ()>(())
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
future::lazy(|| {
|
||||
let mut i = 0;
|
||||
|
||||
b.iter(|| {
|
||||
let ping_tx = ping_txs[i].take().unwrap();
|
||||
let pong_rx = pong_rxs[i].take().unwrap();
|
||||
|
||||
ping_tx.send(()).unwrap();
|
||||
spin(pong_rx).unwrap();
|
||||
|
||||
i += 1;
|
||||
});
|
||||
|
||||
Ok::<(), ()>(())
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
pub extern crate test;
|
||||
|
||||
mod prelude {
|
||||
pub use futures::*;
|
||||
pub use tokio::net::{TcpListener, TcpStream};
|
||||
pub use tokio::reactor::Reactor;
|
||||
pub use tokio_io::io::read_to_end;
|
||||
|
||||
pub use std::io::{self, Read, Write};
|
||||
pub use std::thread;
|
||||
pub use std::time::Duration;
|
||||
pub use test::{self, Bencher};
|
||||
}
|
||||
|
||||
mod connect_churn {
|
||||
use crate::prelude::*;
|
||||
|
||||
const NUM: usize = 300;
|
||||
const CONCURRENT: usize = 8;
|
||||
|
||||
#[bench]
|
||||
fn one_thread(b: &mut Bencher) {
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
|
||||
b.iter(move || {
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
// Spawn a single future that accepts & drops connections
|
||||
let serve_incomings = listener
|
||||
.incoming()
|
||||
.map_err(|e| panic!("server err: {:?}", e))
|
||||
.for_each(|_| Ok(()));
|
||||
|
||||
let connects = stream::iter_result((0..NUM).map(|_| {
|
||||
Ok(TcpStream::connect(&addr).and_then(|sock| {
|
||||
sock.set_linger(Some(Duration::from_secs(0))).unwrap();
|
||||
read_to_end(sock, vec![])
|
||||
}))
|
||||
}));
|
||||
|
||||
let connects_concurrent = connects
|
||||
.buffer_unordered(CONCURRENT)
|
||||
.map_err(|e| panic!("client err: {:?}", e))
|
||||
.for_each(|_| Ok(()));
|
||||
|
||||
serve_incomings
|
||||
.select(connects_concurrent)
|
||||
.map(|_| ())
|
||||
.map_err(|_| ())
|
||||
.wait()
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn n_workers(n: usize, b: &mut Bencher) {
|
||||
let (shutdown_tx, shutdown_rx) = sync::oneshot::channel();
|
||||
let (addr_tx, addr_rx) = sync::oneshot::channel();
|
||||
|
||||
// Spawn reactor thread
|
||||
let server_thread = thread::spawn(move || {
|
||||
// Bind the TCP listener
|
||||
let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
|
||||
// Get the address being listened on.
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
// Send the remote & address back to the main thread
|
||||
addr_tx.send(addr).unwrap();
|
||||
|
||||
// Spawn a single future that accepts & drops connections
|
||||
let serve_incomings = listener
|
||||
.incoming()
|
||||
.map_err(|e| panic!("server err: {:?}", e))
|
||||
.for_each(|_| Ok(()));
|
||||
|
||||
// Run server
|
||||
serve_incomings
|
||||
.select(shutdown_rx)
|
||||
.map(|_| ())
|
||||
.map_err(|_| ())
|
||||
.wait()
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// Get the bind addr of the server
|
||||
let addr = addr_rx.wait().unwrap();
|
||||
|
||||
b.iter(move || {
|
||||
use std::sync::{Arc, Barrier};
|
||||
|
||||
// Create a barrier to coordinate threads
|
||||
let barrier = Arc::new(Barrier::new(n + 1));
|
||||
|
||||
// Spawn worker threads
|
||||
let threads: Vec<_> = (0..n)
|
||||
.map(|_| {
|
||||
let barrier = barrier.clone();
|
||||
let addr = addr.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let connects = stream::iter_result((0..(NUM / n)).map(|_| {
|
||||
Ok(TcpStream::connect(&addr)
|
||||
.map_err(|e| panic!("connect err: {:?}", e))
|
||||
.and_then(|sock| {
|
||||
sock.set_linger(Some(Duration::from_secs(0))).unwrap();
|
||||
read_to_end(sock, vec![])
|
||||
}))
|
||||
}));
|
||||
|
||||
barrier.wait();
|
||||
|
||||
connects
|
||||
.buffer_unordered(CONCURRENT)
|
||||
.map_err(|e| panic!("client err: {:?}", e))
|
||||
.for_each(|_| Ok(()))
|
||||
.wait()
|
||||
.unwrap();
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
barrier.wait();
|
||||
|
||||
for th in threads {
|
||||
th.join().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// Shutdown the server
|
||||
shutdown_tx.send(()).unwrap();
|
||||
server_thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn two_threads(b: &mut Bencher) {
|
||||
n_workers(1, b);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn multi_threads(b: &mut Bencher) {
|
||||
n_workers(4, b);
|
||||
}
|
||||
}
|
||||
|
||||
mod transfer {
|
||||
use crate::prelude::*;
|
||||
use std::{cmp, mem};
|
||||
use tokio_io::try_nb;
|
||||
|
||||
const MB: usize = 3 * 1024 * 1024;
|
||||
|
||||
struct Drain {
|
||||
sock: TcpStream,
|
||||
chunk: usize,
|
||||
}
|
||||
|
||||
impl Future for Drain {
|
||||
type Item = ();
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<(), io::Error> {
|
||||
let mut buf: [u8; 1024] = unsafe { mem::uninitialized() };
|
||||
|
||||
loop {
|
||||
match try_nb!(self.sock.read(&mut buf[..self.chunk])) {
|
||||
0 => return Ok(Async::Ready(())),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Transfer {
|
||||
sock: TcpStream,
|
||||
rem: usize,
|
||||
chunk: usize,
|
||||
}
|
||||
|
||||
impl Future for Transfer {
|
||||
type Item = ();
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<(), io::Error> {
|
||||
while self.rem > 0 {
|
||||
let len = cmp::min(self.rem, self.chunk);
|
||||
let buf = &DATA[..len];
|
||||
|
||||
let n = try_nb!(self.sock.write(&buf));
|
||||
self.rem -= n;
|
||||
}
|
||||
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
|
||||
static DATA: [u8; 1024] = [0; 1024];
|
||||
|
||||
fn one_thread(b: &mut Bencher, read_size: usize, write_size: usize) {
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
|
||||
b.iter(move || {
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
// Spawn a single future that accepts 1 connection, Drain it and drops
|
||||
let server = listener
|
||||
.incoming()
|
||||
.into_future() // take the first connection
|
||||
.map_err(|(e, _other_incomings)| e)
|
||||
.map(|(connection, _other_incomings)| connection.unwrap())
|
||||
.and_then(|sock| {
|
||||
sock.set_linger(Some(Duration::from_secs(0))).unwrap();
|
||||
let drain = Drain {
|
||||
sock,
|
||||
chunk: read_size,
|
||||
};
|
||||
drain
|
||||
.map(|_| ())
|
||||
.map_err(|e| panic!("server error: {:?}", e))
|
||||
})
|
||||
.map_err(|e| panic!("server err: {:?}", e));
|
||||
|
||||
let client = TcpStream::connect(&addr)
|
||||
.and_then(move |sock| Transfer {
|
||||
sock,
|
||||
rem: MB,
|
||||
chunk: write_size,
|
||||
})
|
||||
.map_err(|e| panic!("client err: {:?}", e));
|
||||
|
||||
server.join(client).wait().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
mod small_chunks {
|
||||
use crate::prelude::*;
|
||||
|
||||
#[bench]
|
||||
fn one_thread(b: &mut Bencher) {
|
||||
super::one_thread(b, 32, 32);
|
||||
}
|
||||
}
|
||||
|
||||
mod big_chunks {
|
||||
use crate::prelude::*;
|
||||
|
||||
#[bench]
|
||||
fn one_thread(b: &mut Bencher) {
|
||||
super::one_thread(b, 1_024, 1_024);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use tokio::executor::thread_pool::{Builder, Spawner};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
struct Backoff(usize);
|
||||
|
||||
impl Future for Backoff {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||
if self.0 == 0 {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
self.0 -= 1;
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const NUM_THREADS: usize = 6;
|
||||
|
||||
#[bench]
|
||||
fn spawn_many(b: &mut test::Bencher) {
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
|
||||
let threadpool = Builder::new().num_threads(NUM_THREADS).build();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(|| {
|
||||
rem.store(NUM_SPAWN, Relaxed);
|
||||
|
||||
for _ in 0..NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
|
||||
threadpool.spawn(async move {
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn yield_many(b: &mut test::Bencher) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS_PER_CPU: usize = 50;
|
||||
|
||||
let threadpool = Builder::new().num_threads(NUM_THREADS).build();
|
||||
|
||||
let tasks = TASKS_PER_CPU * num_cpus::get_physical();
|
||||
let (tx, rx) = mpsc::sync_channel(tasks);
|
||||
|
||||
b.iter(move || {
|
||||
for _ in 0..tasks {
|
||||
let tx = tx.clone();
|
||||
|
||||
threadpool.spawn(async move {
|
||||
let backoff = Backoff(NUM_YIELD);
|
||||
backoff.await;
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..tasks {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn ping_pong(b: &mut test::Bencher) {
|
||||
const NUM_PINGS: usize = 1_000;
|
||||
|
||||
let threadpool = Builder::new().num_threads(NUM_THREADS).build();
|
||||
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(|| {
|
||||
let done_tx = done_tx.clone();
|
||||
let rem = rem.clone();
|
||||
rem.store(NUM_PINGS, Relaxed);
|
||||
|
||||
let spawner = threadpool.spawner().clone();
|
||||
|
||||
threadpool.spawn(async move {
|
||||
for _ in 0..NUM_PINGS {
|
||||
let rem = rem.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
let spawner2 = spawner.clone();
|
||||
|
||||
spawner.spawn(async move {
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
|
||||
spawner2.spawn(async move {
|
||||
rx1.await.unwrap();
|
||||
tx2.send(()).unwrap();
|
||||
});
|
||||
|
||||
tx1.send(()).unwrap();
|
||||
rx2.await.unwrap();
|
||||
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
done_tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn chained_spawn(b: &mut test::Bencher) {
|
||||
const ITER: usize = 1_000;
|
||||
|
||||
let threadpool = Builder::new().num_threads(NUM_THREADS).build();
|
||||
|
||||
fn iter(spawner: Spawner, done_tx: mpsc::SyncSender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
done_tx.send(()).unwrap();
|
||||
} else {
|
||||
let s2 = spawner.clone();
|
||||
spawner.spawn(async move {
|
||||
iter(s2, done_tx, n - 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
|
||||
b.iter(move || {
|
||||
let done_tx = done_tx.clone();
|
||||
let spawner = threadpool.spawner().clone();
|
||||
threadpool.spawn(async move {
|
||||
iter(spawner, done_tx, ITER);
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use crate::fs::asyncify;
|
||||
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Returns the canonical, absolute form of a path with all intermediate
|
||||
/// components normalized and symbolic links resolved.
|
||||
///
|
||||
/// This is an async version of [`std::fs::canonicalize`][std]
|
||||
///
|
||||
/// [std]: std::fs::canonicalize
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// This function currently corresponds to the `realpath` function on Unix
|
||||
/// and the `CreateFile` and `GetFinalPathNameByHandle` functions on Windows.
|
||||
/// Note that, this [may change in the future][changes].
|
||||
///
|
||||
/// On Windows, this converts the path to use [extended length path][path]
|
||||
/// syntax, which allows your program to use longer path names, but means you
|
||||
/// can only join backslash-delimited paths to it, and it may be incompatible
|
||||
/// with other applications (if passed to the application on the command-line,
|
||||
/// or written to a file another application may read).
|
||||
///
|
||||
/// [changes]: https://doc.rust-lang.org/std/io/index.html#platform-specific-behavior
|
||||
/// [path]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error in the following situations, but is not
|
||||
/// limited to just these cases:
|
||||
///
|
||||
/// * `path` does not exist.
|
||||
/// * A non-final component in path is not a directory.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let path = fs::canonicalize("../a/../foo.txt").await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref().to_owned();
|
||||
asyncify(move || std::fs::canonicalize(path)).await
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use crate::fs::File;
|
||||
use crate::io;
|
||||
use std::path::Path;
|
||||
|
||||
/// Copies the contents of one file to another. This function will also copy the permission bits of the original file to the destination file.
|
||||
/// This function will overwrite the contents of to.
|
||||
///
|
||||
/// This is the async equivalent of `std::fs::copy`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs;
|
||||
///
|
||||
/// # async fn dox() -> std::io::Result<()> {
|
||||
/// fs::copy("foo.txt", "bar.txt").await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
||||
pub async fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64, std::io::Error> {
|
||||
let from = File::open(from).await?;
|
||||
let to = File::create(to).await?;
|
||||
let (mut from, mut to) = (io::BufReader::new(from), io::BufWriter::new(to));
|
||||
io::copy(&mut from, &mut to).await
|
||||
}
|
||||
@@ -7,7 +7,45 @@ use std::path::Path;
|
||||
///
|
||||
/// This is an async version of [`std::fs::create_dir`][std]
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/fs/fn.create_dir.html
|
||||
/// [std]: std::fs::create_dir
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// This function currently corresponds to the `mkdir` function on Unix
|
||||
/// and the `CreateDirectory` function on Windows.
|
||||
/// Note that, this [may change in the future][changes].
|
||||
///
|
||||
/// [changes]: https://doc.rust-lang.org/std/io/index.html#platform-specific-behavior
|
||||
///
|
||||
/// **NOTE**: If a parent of the given path doesn't exist, this function will
|
||||
/// return an error. To create a directory and all its missing parents at the
|
||||
/// same time, use the [`create_dir_all`] function.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error in the following situations, but is not
|
||||
/// limited to just these cases:
|
||||
///
|
||||
/// * User lacks permissions to create directory at `path`.
|
||||
/// * A parent of the given path doesn't exist. (To create a directory and all
|
||||
/// its missing parents at the same time, use the [`create_dir_all`]
|
||||
/// function.)
|
||||
/// * `path` already exists.
|
||||
///
|
||||
/// [`create_dir_all`]: super::create_dir_all()
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// fs::create_dir("/some/dir").await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
asyncify(move || std::fs::create_dir(path)).await
|
||||
|
||||
@@ -3,12 +3,50 @@ use crate::fs::asyncify;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
/// Recursively create a directory and all of its parent components if they
|
||||
/// Recursively creates a directory and all of its parent components if they
|
||||
/// are missing.
|
||||
///
|
||||
/// This is an async version of [`std::fs::create_dir_all`][std]
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/fs/fn.create_dir_all.html
|
||||
/// [std]: std::fs::create_dir_all
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// This function currently corresponds to the `mkdir` function on Unix
|
||||
/// and the `CreateDirectory` function on Windows.
|
||||
/// Note that, this [may change in the future][changes].
|
||||
///
|
||||
/// [changes]: https://doc.rust-lang.org/std/io/index.html#platform-specific-behavior
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error in the following situations, but is not
|
||||
/// limited to just these cases:
|
||||
///
|
||||
/// * If any directory in the path specified by `path` does not already exist
|
||||
/// and it could not be created otherwise. The specific error conditions for
|
||||
/// when a directory is being created (after it is determined to not exist) are
|
||||
/// outlined by [`fs::create_dir`].
|
||||
///
|
||||
/// Notable exception is made for situations where any of the directories
|
||||
/// specified in the `path` could not be created as it was being created concurrently.
|
||||
/// Such cases are considered to be successful. That is, calling `create_dir_all`
|
||||
/// concurrently from multiple threads or processes is guaranteed not to fail
|
||||
/// due to a race condition with itself.
|
||||
///
|
||||
/// [`fs::create_dir`]: std::fs::create_dir
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
/// fs::create_dir_all("/some/dir").await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn create_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
asyncify(move || std::fs::create_dir_all(path)).await
|
||||
|
||||
+147
-10
@@ -1,11 +1,11 @@
|
||||
//! Types for working with [`File`].
|
||||
//!
|
||||
//! [`File`]: file/struct.File.html
|
||||
//! [`File`]: File
|
||||
|
||||
use self::State::*;
|
||||
use crate::fs::{asyncify, sys};
|
||||
use crate::io::blocking::Buf;
|
||||
use crate::io::{AsyncRead, AsyncWrite};
|
||||
use crate::io::{AsyncRead, AsyncSeek, AsyncWrite};
|
||||
|
||||
use std::fmt;
|
||||
use std::fs::{Metadata, Permissions};
|
||||
@@ -29,7 +29,7 @@ use std::task::Poll::*;
|
||||
///
|
||||
/// Files are automatically closed when they go out of scope.
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/fs/struct.File.html
|
||||
/// [std]: std::fs::File
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -90,7 +90,7 @@ impl File {
|
||||
///
|
||||
/// See [`OpenOptions`] for more details.
|
||||
///
|
||||
/// [`OpenOptions`]: struct.OpenOptions.html
|
||||
/// [`OpenOptions`]: super::OpenOptions
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -128,14 +128,14 @@ impl File {
|
||||
///
|
||||
/// See [`OpenOptions`] for more details.
|
||||
///
|
||||
/// [`OpenOptions`]: struct.OpenOptions.html
|
||||
/// [`OpenOptions`]: super::OpenOptions
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Results in an error if called from outside of the Tokio runtime or if
|
||||
/// the underlying [`create`] call results in an error.
|
||||
///
|
||||
/// [`create`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.create
|
||||
/// [`create`]: std::fs::File::create
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -155,10 +155,10 @@ impl File {
|
||||
Ok(File::from_std(std_file))
|
||||
}
|
||||
|
||||
/// Convert a [`std::fs::File`][std] to a [`tokio_fs::File`][file].
|
||||
/// Converts a [`std::fs::File`][std] to a [`tokio::fs::File`][file].
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/fs/struct.File.html
|
||||
/// [file]: struct.File.html
|
||||
/// [std]: std::fs::File
|
||||
/// [file]: File
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -176,7 +176,7 @@ impl File {
|
||||
}
|
||||
}
|
||||
|
||||
/// Seek to an offset, in bytes, in a stream.
|
||||
/// Seeks to an offset, in bytes, in a stream.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -394,6 +394,59 @@ impl File {
|
||||
Ok(File::from_std(std_file))
|
||||
}
|
||||
|
||||
/// Destructures `File` into a [`std::fs::File`][std]. This function is
|
||||
/// async to allow any in-flight operations to complete.
|
||||
///
|
||||
/// Use `File::try_into_std` to attempt conversion immediately.
|
||||
///
|
||||
/// [std]: std::fs::File
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::File;
|
||||
///
|
||||
/// # async fn dox() -> std::io::Result<()> {
|
||||
/// let tokio_file = File::open("foo.txt").await?;
|
||||
/// let std_file = tokio_file.into_std().await;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn into_std(mut self) -> sys::File {
|
||||
self.complete_inflight().await;
|
||||
Arc::try_unwrap(self.std).expect("Arc::try_unwrap failed")
|
||||
}
|
||||
|
||||
/// Tries to immediately destructure `File` into a [`std::fs::File`][std].
|
||||
///
|
||||
/// [std]: std::fs::File
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error containing the file if some
|
||||
/// operation is in-flight.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::File;
|
||||
///
|
||||
/// # async fn dox() -> std::io::Result<()> {
|
||||
/// let tokio_file = File::open("foo.txt").await?;
|
||||
/// let std_file = tokio_file.try_into_std().unwrap();
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn try_into_std(mut self) -> Result<sys::File, Self> {
|
||||
match Arc::try_unwrap(self.std) {
|
||||
Ok(file) => Ok(file),
|
||||
Err(std_file_arc) => {
|
||||
self.std = std_file_arc;
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes the permissions on the underlying file.
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
@@ -499,6 +552,76 @@ impl AsyncRead for File {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncSeek for File {
|
||||
fn start_seek(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
mut pos: SeekFrom,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
match self.state {
|
||||
Idle(ref mut buf_cell) => {
|
||||
let mut buf = buf_cell.take().unwrap();
|
||||
|
||||
// Factor in any unread data from the buf
|
||||
if !buf.is_empty() {
|
||||
let n = buf.discard_read();
|
||||
|
||||
if let SeekFrom::Current(ref mut offset) = pos {
|
||||
*offset += n;
|
||||
}
|
||||
}
|
||||
|
||||
let std = self.std.clone();
|
||||
|
||||
self.state = Busy(sys::run(move || {
|
||||
let res = (&*std).seek(pos);
|
||||
(Operation::Seek(res), buf)
|
||||
}));
|
||||
|
||||
return Ready(Ok(()));
|
||||
}
|
||||
Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
self.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {}
|
||||
Operation::Write(Err(e)) => {
|
||||
assert!(self.last_write_err.is_none());
|
||||
self.last_write_err = Some(e.kind());
|
||||
}
|
||||
Operation::Write(_) => {}
|
||||
Operation::Seek(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
loop {
|
||||
match self.state {
|
||||
Idle(_) => panic!("must call start_seek before calling poll_complete"),
|
||||
Busy(ref mut rx) => {
|
||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
||||
self.state = Idle(Some(buf));
|
||||
|
||||
match op {
|
||||
Operation::Read(_) => {}
|
||||
Operation::Write(Err(e)) => {
|
||||
assert!(self.last_write_err.is_none());
|
||||
self.last_write_err = Some(e.kind());
|
||||
}
|
||||
Operation::Write(_) => {}
|
||||
Operation::Seek(res) => return Ready(res),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for File {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
@@ -600,3 +723,17 @@ impl fmt::Debug for File {
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::os::unix::io::AsRawFd for File {
|
||||
fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
|
||||
self.std.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl std::os::windows::io::AsRawHandle for File {
|
||||
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
|
||||
self.std.as_raw_handle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,39 @@ use std::path::Path;
|
||||
|
||||
/// Creates a new hard link on the filesystem.
|
||||
///
|
||||
/// This is an async version of [`std::fs::hard_link`][std]
|
||||
///
|
||||
/// [std]: std::fs::hard_link
|
||||
///
|
||||
/// The `dst` path will be a link pointing to the `src` path. Note that systems
|
||||
/// often require these two paths to both be located on the same filesystem.
|
||||
///
|
||||
/// This is an async version of [`std::fs::hard_link`][std]
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/fs/fn.hard_link.html
|
||||
/// This function currently corresponds to the `link` function on Unix
|
||||
/// and the `CreateHardLink` function on Windows.
|
||||
/// Note that, this [may change in the future][changes].
|
||||
///
|
||||
/// [changes]: https://doc.rust-lang.org/std/io/index.html#platform-specific-behavior
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error in the following situations, but is not
|
||||
/// limited to just these cases:
|
||||
///
|
||||
/// * The `src` path is not a file or doesn't exist.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
/// fs::hard_link("a.txt", "b.txt").await?; // Hard link a.txt to b.txt
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
|
||||
@@ -4,7 +4,43 @@ use std::fs::Metadata;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
/// Queries the file system metadata for a path.
|
||||
/// Given a path, queries the file system to get information about a file,
|
||||
/// directory, etc.
|
||||
///
|
||||
/// This is an async version of [`std::fs::metadata`][std]
|
||||
///
|
||||
/// This function will traverse symbolic links to query information about the
|
||||
/// destination file.
|
||||
///
|
||||
/// # Platform-specific behavior
|
||||
///
|
||||
/// This function currently corresponds to the `stat` function on Unix and the
|
||||
/// `GetFileAttributesEx` function on Windows. Note that, this [may change in
|
||||
/// the future][changes].
|
||||
///
|
||||
/// [std]: std::fs::metadata
|
||||
/// [changes]: https://doc.rust-lang.org/std/io/index.html#platform-specific-behavior
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error in the following situations, but is not
|
||||
/// limited to just these cases:
|
||||
///
|
||||
/// * The user lacks permissions to perform `metadata` call on `path`.
|
||||
/// * `path` does not exist.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use tokio::fs;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
/// let attr = fs::metadata("/some/file/path.txt").await?;
|
||||
/// // inspect attr ...
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {
|
||||
let path = path.as_ref().to_owned();
|
||||
asyncify(|| std::fs::metadata(path)).await
|
||||
|
||||
+7
-1
@@ -18,12 +18,15 @@
|
||||
//! Where possible, users should prefer the provided asynchronous-specific
|
||||
//! traits such as [`AsyncRead`], or methods returning a `Future` or `Poll`
|
||||
//! type. Adaptions also extend to traits like `std::io::Read` where methods
|
||||
//! return `std::io::Result`. Be warned that these adapted methods may return
|
||||
//! return `std::io::Result`. Be warned that these adapted methods may return
|
||||
//! `std::io::ErrorKind::WouldBlock` if a *worker* thread can not be converted
|
||||
//! to a *backup* thread immediately.
|
||||
//!
|
||||
//! [`AsyncRead`]: https://docs.rs/tokio-io/0.1/tokio_io/trait.AsyncRead.html
|
||||
|
||||
mod canonicalize;
|
||||
pub use self::canonicalize::canonicalize;
|
||||
|
||||
mod create_dir;
|
||||
pub use self::create_dir::create_dir;
|
||||
|
||||
@@ -77,6 +80,9 @@ pub use self::symlink_metadata::symlink_metadata;
|
||||
mod write;
|
||||
pub use self::write::write;
|
||||
|
||||
mod copy;
|
||||
pub use self::copy::copy;
|
||||
|
||||
use std::io;
|
||||
|
||||
pub(crate) async fn asyncify<F, T>(f: F) -> io::Result<T>
|
||||
|
||||
+304
-17
@@ -5,13 +5,69 @@ use std::path::Path;
|
||||
|
||||
/// Options and flags which can be used to configure how a file is opened.
|
||||
///
|
||||
/// This builder exposes the ability to configure how a [`File`] is opened and
|
||||
/// what operations are permitted on the open file. The [`File::open`] and
|
||||
/// [`File::create`] methods are aliases for commonly used options using this
|
||||
/// builder.
|
||||
///
|
||||
/// Generally speaking, when using `OpenOptions`, you'll first call [`new`],
|
||||
/// then chain calls to methods to set each option, then call [`open`], passing
|
||||
/// the path of the file you're trying to open. This will give you a
|
||||
/// [`io::Result`][result] with a [`File`] inside that you can further operate
|
||||
/// on.
|
||||
///
|
||||
/// This is a specialized version of [`std::fs::OpenOptions`] for usage from
|
||||
/// the Tokio runtime.
|
||||
///
|
||||
/// `From<std::fs::OpenOptions>` is implemented for more advanced configuration
|
||||
/// than the methods provided here.
|
||||
///
|
||||
/// [`std::fs::OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
|
||||
/// [`new`]: OpenOptions::new
|
||||
/// [`open`]: OpenOptions::open
|
||||
/// [result]: std::io::Result
|
||||
/// [`File`]: File
|
||||
/// [`File::open`]: File::open
|
||||
/// [`File::create`]: File::create
|
||||
/// [`std::fs::OpenOptions`]: std::fs::OpenOptions
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Opening a file to read:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .read(true)
|
||||
/// .open("foo.txt")
|
||||
/// .await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Opening a file for both reading and writing, as well as creating it if it
|
||||
/// doesn't exist:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .read(true)
|
||||
/// .write(true)
|
||||
/// .create(true)
|
||||
/// .open("foo.txt")
|
||||
/// .await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OpenOptions(std::fs::OpenOptions);
|
||||
|
||||
@@ -20,9 +76,13 @@ impl OpenOptions {
|
||||
///
|
||||
/// All options are initially set to `false`.
|
||||
///
|
||||
/// This is an async version of [`std::fs::OpenOptions::new`][std]
|
||||
///
|
||||
/// [std]: std::fs::OpenOptions::new
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
///
|
||||
/// let mut options = OpenOptions::new();
|
||||
@@ -32,49 +92,232 @@ impl OpenOptions {
|
||||
OpenOptions(std::fs::OpenOptions::new())
|
||||
}
|
||||
|
||||
/// See the underlying [`read`] call for details.
|
||||
/// Sets the option for read access.
|
||||
///
|
||||
/// [`read`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.read
|
||||
/// This option, when true, will indicate that the file should be
|
||||
/// `read`-able if opened.
|
||||
///
|
||||
/// This is an async version of [`std::fs::OpenOptions::read`][std]
|
||||
///
|
||||
/// [std]: std::fs::OpenOptions::read
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .read(true)
|
||||
/// .open("foo.txt")
|
||||
/// .await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn read(&mut self, read: bool) -> &mut OpenOptions {
|
||||
self.0.read(read);
|
||||
self
|
||||
}
|
||||
|
||||
/// See the underlying [`write`] call for details.
|
||||
/// Sets the option for write access.
|
||||
///
|
||||
/// [`write`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.write
|
||||
/// This option, when true, will indicate that the file should be
|
||||
/// `write`-able if opened.
|
||||
///
|
||||
/// This is an async version of [`std::fs::OpenOptions::write`][std]
|
||||
///
|
||||
/// [std]: std::fs::OpenOptions::write
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .write(true)
|
||||
/// .open("foo.txt")
|
||||
/// .await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn write(&mut self, write: bool) -> &mut OpenOptions {
|
||||
self.0.write(write);
|
||||
self
|
||||
}
|
||||
|
||||
/// See the underlying [`append`] call for details.
|
||||
/// Sets the option for the append mode.
|
||||
///
|
||||
/// [`append`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.append
|
||||
/// This option, when true, means that writes will append to a file instead
|
||||
/// of overwriting previous contents. Note that setting
|
||||
/// `.write(true).append(true)` has the same effect as setting only
|
||||
/// `.append(true)`.
|
||||
///
|
||||
/// For most filesystems, the operating system guarantees that all writes are
|
||||
/// atomic: no writes get mangled because another process writes at the same
|
||||
/// time.
|
||||
///
|
||||
/// One maybe obvious note when using append-mode: make sure that all data
|
||||
/// that belongs together is written to the file in one operation. This
|
||||
/// can be done by concatenating strings before passing them to [`write()`],
|
||||
/// or using a buffered writer (with a buffer of adequate size),
|
||||
/// and calling [`flush()`] when the message is complete.
|
||||
///
|
||||
/// If a file is opened with both read and append access, beware that after
|
||||
/// opening, and after every write, the position for reading may be set at the
|
||||
/// end of the file. So, before writing, save the current position (using
|
||||
/// [`seek`]`(`[`SeekFrom`]`::`[`Current`]`(0))`), and restore it before the next read.
|
||||
///
|
||||
/// This is an async version of [`std::fs::OpenOptions::append`][std]
|
||||
///
|
||||
/// [std]: std::fs::OpenOptions::append
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// This function doesn't create the file if it doesn't exist. Use the [`create`]
|
||||
/// method to do so.
|
||||
///
|
||||
/// [`write()`]: crate::io::AsyncWriteExt::write
|
||||
/// [`flush()`]: crate::io::AsyncWriteExt::flush
|
||||
/// [`seek`]: crate::io::AsyncSeekExt::seek
|
||||
/// [`SeekFrom`]: std::io::SeekFrom
|
||||
/// [`Current`]: std::io::SeekFrom::Current
|
||||
/// [`create`]: OpenOptions::create
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .append(true)
|
||||
/// .open("foo.txt")
|
||||
/// .await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn append(&mut self, append: bool) -> &mut OpenOptions {
|
||||
self.0.append(append);
|
||||
self
|
||||
}
|
||||
|
||||
/// See the underlying [`truncate`] call for details.
|
||||
/// Sets the option for truncating a previous file.
|
||||
///
|
||||
/// [`truncate`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.truncate
|
||||
/// If a file is successfully opened with this option set it will truncate
|
||||
/// the file to 0 length if it already exists.
|
||||
///
|
||||
/// The file must be opened with write access for truncate to work.
|
||||
///
|
||||
/// This is an async version of [`std::fs::OpenOptions::truncate`][std]
|
||||
///
|
||||
/// [std]: std::fs::OpenOptions::truncate
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .write(true)
|
||||
/// .truncate(true)
|
||||
/// .open("foo.txt")
|
||||
/// .await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions {
|
||||
self.0.truncate(truncate);
|
||||
self
|
||||
}
|
||||
|
||||
/// See the underlying [`create`] call for details.
|
||||
/// Sets the option for creating a new file.
|
||||
///
|
||||
/// [`create`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create
|
||||
/// This option indicates whether a new file will be created if the file
|
||||
/// does not yet already exist.
|
||||
///
|
||||
/// In order for the file to be created, [`write`] or [`append`] access must
|
||||
/// be used.
|
||||
///
|
||||
/// This is an async version of [`std::fs::OpenOptions::create`][std]
|
||||
///
|
||||
/// [std]: std::fs::OpenOptions::create
|
||||
/// [`write`]: OpenOptions::write
|
||||
/// [`append`]: OpenOptions::append
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .write(true)
|
||||
/// .create(true)
|
||||
/// .open("foo.txt")
|
||||
/// .await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn create(&mut self, create: bool) -> &mut OpenOptions {
|
||||
self.0.create(create);
|
||||
self
|
||||
}
|
||||
|
||||
/// See the underlying [`create_new`] call for details.
|
||||
/// Sets the option to always create a new file.
|
||||
///
|
||||
/// [`create_new`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new
|
||||
/// This option indicates whether a new file will be created. No file is
|
||||
/// allowed to exist at the target location, also no (dangling) symlink.
|
||||
///
|
||||
/// This option is useful because it is atomic. Otherwise between checking
|
||||
/// whether a file exists and creating a new one, the file may have been
|
||||
/// created by another process (a TOCTOU race condition / attack).
|
||||
///
|
||||
/// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are
|
||||
/// ignored.
|
||||
///
|
||||
/// The file must be opened with write or append access in order to create a
|
||||
/// new file.
|
||||
///
|
||||
/// This is an async version of [`std::fs::OpenOptions::create_new`][std]
|
||||
///
|
||||
/// [std]: std::fs::OpenOptions::create_new
|
||||
/// [`.create()`]: OpenOptions::create
|
||||
/// [`.truncate()`]: OpenOptions::truncate
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new()
|
||||
/// .write(true)
|
||||
/// .create_new(true)
|
||||
/// .open("foo.txt")
|
||||
/// .await?;
|
||||
///
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {
|
||||
self.0.create_new(create_new);
|
||||
self
|
||||
@@ -82,12 +325,56 @@ impl OpenOptions {
|
||||
|
||||
/// Opens a file at `path` with the options specified by `self`.
|
||||
///
|
||||
/// This is an async version of [`std::fs::OpenOptions::open`][std]
|
||||
///
|
||||
/// [std]: std::fs::OpenOptions::open
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `OpenOptionsFuture` results in an error if called from outside of the
|
||||
/// Tokio runtime or if the underlying [`open`] call results in an error.
|
||||
/// This function will return an error under a number of different
|
||||
/// circumstances. Some of these error conditions are listed here, together
|
||||
/// with their [`ErrorKind`]. The mapping to [`ErrorKind`]s is not part of
|
||||
/// the compatibility contract of the function, especially the `Other` kind
|
||||
/// might change to more specific kinds in the future.
|
||||
///
|
||||
/// [`open`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.open
|
||||
/// * [`NotFound`]: The specified file does not exist and neither `create`
|
||||
/// or `create_new` is set.
|
||||
/// * [`NotFound`]: One of the directory components of the file path does
|
||||
/// not exist.
|
||||
/// * [`PermissionDenied`]: The user lacks permission to get the specified
|
||||
/// access rights for the file.
|
||||
/// * [`PermissionDenied`]: The user lacks permission to open one of the
|
||||
/// directory components of the specified path.
|
||||
/// * [`AlreadyExists`]: `create_new` was specified and the file already
|
||||
/// exists.
|
||||
/// * [`InvalidInput`]: Invalid combinations of open options (truncate
|
||||
/// without write access, no access mode set, etc.).
|
||||
/// * [`Other`]: One of the directory components of the specified file path
|
||||
/// was not, in fact, a directory.
|
||||
/// * [`Other`]: Filesystem-level errors: full disk, write permission
|
||||
/// requested on a read-only file system, exceeded disk quota, too many
|
||||
/// open files, too long filename, too many symbolic links in the
|
||||
/// specified path (Unix-like systems only), etc.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs::OpenOptions;
|
||||
/// use std::io;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> io::Result<()> {
|
||||
/// let file = OpenOptions::new().open("foo.txt").await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`ErrorKind`]: std::io::ErrorKind
|
||||
/// [`AlreadyExists`]: std::io::ErrorKind::AlreadyExists
|
||||
/// [`InvalidInput`]: std::io::ErrorKind::InvalidInput
|
||||
/// [`NotFound`]: std::io::ErrorKind::NotFound
|
||||
/// [`Other`]: std::io::ErrorKind::Other
|
||||
/// [`PermissionDenied`]: std::io::ErrorKind::PermissionDenied
|
||||
pub async fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
|
||||
let path = path.as_ref().to_owned();
|
||||
let opts = self.0.clone();
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::path::Path;
|
||||
///
|
||||
/// This is an async version of [`std::os::unix::fs::symlink`][std]
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html
|
||||
/// [std]: std::os::unix::fs::symlink
|
||||
pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::path::Path;
|
||||
///
|
||||
/// This is an async version of [`std::os::windows::fs::symlink_dir`][std]
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html
|
||||
/// [std]: std::os::windows::fs::symlink_dir
|
||||
pub async fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::path::Path;
|
||||
///
|
||||
/// This is an async version of [`std::os::windows::fs::symlink_file`][std]
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html
|
||||
/// [std]: std::os::windows::fs::symlink_file
|
||||
pub async fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
||||
let src = src.as_ref().to_owned();
|
||||
let dst = dst.as_ref().to_owned();
|
||||
|
||||
+31
-8
@@ -2,21 +2,44 @@ use crate::fs::asyncify;
|
||||
|
||||
use std::{io, path::Path};
|
||||
|
||||
/// Creates a future which will open a file for reading and read the entire
|
||||
/// contents into a buffer and return said buffer.
|
||||
/// Reads the entire contents of a file into a bytes vector.
|
||||
///
|
||||
/// This is the async equivalent of `std::fs::read`.
|
||||
/// This is an async version of [`std::fs::read`][std]
|
||||
///
|
||||
/// [std]: std::fs::read
|
||||
///
|
||||
/// This is a convenience function for using [`File::open`] and [`read_to_end`]
|
||||
/// with fewer imports and without an intermediate variable. It pre-allocates a
|
||||
/// buffer based on the file size when available, so it is generally faster than
|
||||
/// reading into a vector created with `Vec::new()`.
|
||||
///
|
||||
/// [`File::open`]: super::File::open
|
||||
/// [`read_to_end`]: crate::io::AsyncReadExt::read_to_end
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if `path` does not already exist.
|
||||
/// Other errors may also be returned according to [`OpenOptions::open`].
|
||||
///
|
||||
/// [`OpenOptions::open`]: super::OpenOptions::open
|
||||
///
|
||||
/// It will also return an error if it encounters while reading an error
|
||||
/// of a kind other than [`ErrorKind::Interrupted`].
|
||||
///
|
||||
/// [`ErrorKind::Interrupted`]: std::io::ErrorKind::Interrupted
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio::fs;
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
/// # async fn dox() -> std::io::Result<()> {
|
||||
/// let contents = fs::read("foo.txt").await?;
|
||||
/// println!("foo.txt contains {} bytes", contents.len());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
|
||||
/// let contents = fs::read("address.txt").await?;
|
||||
/// let foo: SocketAddr = String::from_utf8_lossy(&contents).parse()?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
|
||||
let path = path.as_ref().to_owned();
|
||||
|
||||
@@ -36,7 +36,7 @@ pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
|
||||
///
|
||||
/// [`read_dir`]: read_dir
|
||||
/// [`DirEntry`]: DirEntry
|
||||
/// [`Stream`]: futures_core::Stream
|
||||
/// [`Stream`]: crate::stream::Stream
|
||||
/// [`Err`]: std::result::Result::Err
|
||||
#[derive(Debug)]
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
@@ -85,7 +85,7 @@ impl ReadDir {
|
||||
}
|
||||
|
||||
#[cfg(feature = "stream")]
|
||||
impl futures_core::Stream for ReadDir {
|
||||
impl crate::stream::Stream for ReadDir {
|
||||
type Item = io::Result<DirEntry>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
@@ -165,7 +165,7 @@ impl DirEntry {
|
||||
self.0.file_name()
|
||||
}
|
||||
|
||||
/// Return the metadata for the file that this entry points at.
|
||||
/// Returns the metadata for the file that this entry points at.
|
||||
///
|
||||
/// This function will not traverse symlinks if this entry points at a
|
||||
/// symlink.
|
||||
@@ -200,7 +200,7 @@ impl DirEntry {
|
||||
asyncify(move || std.metadata()).await
|
||||
}
|
||||
|
||||
/// Return the file type for the file that this entry points at.
|
||||
/// Returns the file type for the file that this entry points at.
|
||||
///
|
||||
/// This function will not traverse symlinks if this entry points at a
|
||||
/// symlink.
|
||||
|
||||
@@ -5,13 +5,13 @@ use std::path::Path;
|
||||
|
||||
/// Removes a file from the filesystem.
|
||||
///
|
||||
/// Note that there is no
|
||||
/// guarantee that the file is immediately deleted (e.g. depending on
|
||||
/// platform, other open file descriptors may prevent immediate removal).
|
||||
/// Note that there is no guarantee that the file is immediately deleted (e.g.
|
||||
/// depending on platform, other open file descriptors may prevent immediate
|
||||
/// removal).
|
||||
///
|
||||
/// This is an async version of [`std::fs::remove_file`][std]
|
||||
///
|
||||
/// [std]: https://doc.rust-lang.org/std/fs/fn.remove_file.html
|
||||
/// [std]: std::fs::remove_file
|
||||
pub async fn remove_file(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
asyncify(move || std::fs::remove_file(path)).await
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::fs::asyncify;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
/// Rename a file or directory to a new name, replacing the original file if
|
||||
/// Renames a file or directory to a new name, replacing the original file if
|
||||
/// `to` already exists.
|
||||
///
|
||||
/// This will not work if the new name is on a different mount point.
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::task::{Context, Poll};
|
||||
|
||||
/// A future that may have completed.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum MaybeDone<Fut: Future> {
|
||||
pub enum MaybeDone<Fut: Future> {
|
||||
/// A not-yet-completed future
|
||||
Future(Fut),
|
||||
/// The output of the completed future
|
||||
@@ -21,7 +21,7 @@ pub(crate) enum MaybeDone<Fut: Future> {
|
||||
impl<Fut: Future + Unpin> Unpin for MaybeDone<Fut> {}
|
||||
|
||||
/// Wraps a future into a `MaybeDone`
|
||||
pub(crate) fn maybe_done<Fut: Future>(future: Fut) -> MaybeDone<Fut> {
|
||||
pub fn maybe_done<Fut: Future>(future: Fut) -> MaybeDone<Fut> {
|
||||
MaybeDone::Future(future)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ impl<Fut: Future> MaybeDone<Fut> {
|
||||
/// The output of this method will be [`Some`] if and only if the inner
|
||||
/// future has been completed and [`take_output`](MaybeDone::take_output)
|
||||
/// has not yet been called.
|
||||
pub(crate) fn output_mut(self: Pin<&mut Self>) -> Option<&mut Fut::Output> {
|
||||
pub fn output_mut(self: Pin<&mut Self>) -> Option<&mut Fut::Output> {
|
||||
unsafe {
|
||||
let this = self.get_unchecked_mut();
|
||||
match this {
|
||||
@@ -40,10 +40,10 @@ impl<Fut: Future> MaybeDone<Fut> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to take the output of a `MaybeDone` without driving it
|
||||
/// Attempts to take the output of a `MaybeDone` without driving it
|
||||
/// towards completion.
|
||||
#[inline]
|
||||
pub(crate) fn take_output(self: Pin<&mut Self>) -> Option<Fut::Output> {
|
||||
pub fn take_output(self: Pin<&mut Self>) -> Option<Fut::Output> {
|
||||
unsafe {
|
||||
let this = self.get_unchecked_mut();
|
||||
match this {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
//! Asynchronous values.
|
||||
|
||||
mod maybe_done;
|
||||
pub(crate) use maybe_done::{maybe_done, MaybeDone};
|
||||
pub use maybe_done::{maybe_done, MaybeDone};
|
||||
|
||||
mod poll_fn;
|
||||
pub(crate) use poll_fn::poll_fn;
|
||||
pub use poll_fn::poll_fn;
|
||||
|
||||
mod ready;
|
||||
pub(crate) use ready::{ok, Ready};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use sdt::pin::Pin;
|
||||
use std::future::Future;
|
||||
use std::marker;
|
||||
use sdt::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Future for the [`pending()`] function.
|
||||
@@ -29,7 +29,8 @@ struct Pending<T> {
|
||||
pub async fn pending() -> ! {
|
||||
Pending {
|
||||
_data: marker::PhantomData,
|
||||
}.await
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
impl<T> Future for Pending<T> {
|
||||
@@ -40,5 +41,4 @@ impl<T> Future for Pending<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Unpin for Pending<T> {
|
||||
}
|
||||
impl<T> Unpin for Pending<T> {}
|
||||
|
||||
@@ -6,14 +6,14 @@ use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Future for the [`poll_fn`] function.
|
||||
pub(crate) struct PollFn<F> {
|
||||
pub struct PollFn<F> {
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<F> Unpin for PollFn<F> {}
|
||||
|
||||
/// Creates a new future wrapping around a function returning [`Poll`].
|
||||
pub(crate) fn poll_fn<T, F>(f: F) -> PollFn<F>
|
||||
pub fn poll_fn<T, F>(f: F) -> PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ impl<T> Future for Ready<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a future that is immediately ready with a success value.
|
||||
/// Creates a future that is immediately ready with a success value.
|
||||
pub(crate) fn ok<T, E>(t: T) -> Ready<Result<T, E>> {
|
||||
Ready(Some(Ok(t)))
|
||||
}
|
||||
|
||||
@@ -5,13 +5,19 @@ use std::ops::DerefMut;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Read bytes asynchronously.
|
||||
/// Reads bytes asynchronously.
|
||||
///
|
||||
/// This trait inherits from `std::io::BufRead` and indicates that an I/O object is
|
||||
/// This trait inherits from [`std::io::BufRead`] and indicates that an I/O object is
|
||||
/// **non-blocking**. All non-blocking I/O objects must return an error when
|
||||
/// bytes are unavailable instead of blocking the current thread.
|
||||
///
|
||||
/// Utilities for working with `AsyncBufRead` values are provided by
|
||||
/// [`AsyncBufReadExt`].
|
||||
///
|
||||
/// [`std::io::BufRead`]: std::io::BufRead
|
||||
/// [`AsyncBufReadExt`]: crate::io::AsyncBufReadExt
|
||||
pub trait AsyncBufRead: AsyncRead {
|
||||
/// Attempt to return the contents of the internal buffer, filling it with more data
|
||||
/// Attempts to return the contents of the internal buffer, filling it with more data
|
||||
/// from the inner reader if it is empty.
|
||||
///
|
||||
/// On success, returns `Poll::Ready(Ok(buf))`.
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::ops::DerefMut;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Read bytes from a source.
|
||||
/// Reads bytes from a source.
|
||||
///
|
||||
/// This trait is analogous to the [`std::io::Read`] trait, but integrates with
|
||||
/// the asynchronous task system. In particular, the [`poll_read`] method,
|
||||
@@ -82,7 +82,7 @@ pub trait AsyncRead {
|
||||
true
|
||||
}
|
||||
|
||||
/// Attempt to read from the `AsyncRead` into `buf`.
|
||||
/// Attempts to read from the `AsyncRead` into `buf`.
|
||||
///
|
||||
/// On success, returns `Poll::Ready(Ok(num_bytes_read))`.
|
||||
///
|
||||
@@ -96,7 +96,7 @@ pub trait AsyncRead {
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>>;
|
||||
|
||||
/// Pull some bytes from this source into the specified `BufMut`, returning
|
||||
/// Pulls some bytes from this source into the specified `BufMut`, returning
|
||||
/// how many bytes were read.
|
||||
///
|
||||
/// The `buf` provided will have bytes read into it and the internal cursor
|
||||
@@ -123,7 +123,9 @@ pub trait AsyncRead {
|
||||
// Convert to `&mut [u8]`
|
||||
let b = &mut *(b as *mut [MaybeUninit<u8>] as *mut [u8]);
|
||||
|
||||
ready!(self.poll_read(cx, b))?
|
||||
let n = ready!(self.poll_read(cx, b))?;
|
||||
assert!(n <= b.len(), "Bad AsyncRead implementation, more bytes were reported as read than the buffer can hold");
|
||||
n
|
||||
};
|
||||
|
||||
buf.advance_mut(n);
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
use std::io::{self, SeekFrom};
|
||||
use std::ops::DerefMut;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Seek bytes asynchronously.
|
||||
///
|
||||
/// This trait is analogous to the [`std::io::Seek`] trait, but integrates
|
||||
/// with the asynchronous task system. In particular, the `start_seek`
|
||||
/// method, unlike [`Seek::seek`], will not block the calling thread.
|
||||
///
|
||||
/// Utilities for working with `AsyncSeek` values are provided by
|
||||
/// [`AsyncSeekExt`].
|
||||
///
|
||||
/// [`std::io::Seek`]: std::io::Seek
|
||||
/// [`Seek::seek`]: std::io::Seek::seek()
|
||||
/// [`AsyncSeekExt`]: crate::io::AsyncSeekExt
|
||||
pub trait AsyncSeek {
|
||||
/// Attempts to seek to an offset, in bytes, in a stream.
|
||||
///
|
||||
/// A seek beyond the end of a stream is allowed, but behavior is defined
|
||||
/// by the implementation.
|
||||
///
|
||||
/// If this function returns successfully, then the job has been submitted.
|
||||
/// To find out when it completes, call `poll_complete`.
|
||||
fn start_seek(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
position: SeekFrom,
|
||||
) -> Poll<io::Result<()>>;
|
||||
|
||||
/// Waits for a seek operation to complete.
|
||||
///
|
||||
/// If the seek operation completed successfully,
|
||||
/// this method returns the new position from the start of the stream.
|
||||
/// That position can be used later with [`SeekFrom::Start`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Seeking to a negative offset is considered an error.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Calling this method without calling `start_seek` first is an error.
|
||||
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>>;
|
||||
}
|
||||
|
||||
macro_rules! deref_async_seek {
|
||||
() => {
|
||||
fn start_seek(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
pos: SeekFrom,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut **self).start_seek(cx, pos)
|
||||
}
|
||||
|
||||
fn poll_complete(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<io::Result<u64>> {
|
||||
Pin::new(&mut **self).poll_complete(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + AsyncSeek + Unpin> AsyncSeek for Box<T> {
|
||||
deref_async_seek!();
|
||||
}
|
||||
|
||||
impl<T: ?Sized + AsyncSeek + Unpin> AsyncSeek for &mut T {
|
||||
deref_async_seek!();
|
||||
}
|
||||
|
||||
impl<P> AsyncSeek for Pin<P>
|
||||
where
|
||||
P: DerefMut + Unpin,
|
||||
P::Target: AsyncSeek,
|
||||
{
|
||||
fn start_seek(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
pos: SeekFrom,
|
||||
) -> Poll<io::Result<()>> {
|
||||
self.get_mut().as_mut().start_seek(cx, pos)
|
||||
}
|
||||
|
||||
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
self.get_mut().as_mut().poll_complete(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + Unpin> AsyncSeek for io::Cursor<T> {
|
||||
fn start_seek(
|
||||
mut self: Pin<&mut Self>,
|
||||
_: &mut Context<'_>,
|
||||
pos: SeekFrom,
|
||||
) -> Poll<io::Result<()>> {
|
||||
Poll::Ready(io::Seek::seek(&mut *self, pos).map(drop))
|
||||
}
|
||||
fn poll_complete(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
||||
Poll::Ready(Ok(self.get_mut().position()))
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@ use std::task::{Context, Poll};
|
||||
|
||||
/// Writes bytes asynchronously.
|
||||
///
|
||||
/// The trait inherits from `std::io::Write` and indicates that an I/O object is
|
||||
/// The trait inherits from [`std::io::Write`] and indicates that an I/O object is
|
||||
/// **nonblocking**. All non-blocking I/O objects must return an error when
|
||||
/// bytes cannot be written instead of blocking the current thread.
|
||||
///
|
||||
/// Specifically, this means that the `poll_write` function will return one of
|
||||
/// Specifically, this means that the [`poll_write`] function will return one of
|
||||
/// the following:
|
||||
///
|
||||
/// * `Poll::Ready(Ok(n))` means that `n` bytes of data was immediately
|
||||
@@ -26,14 +26,23 @@ use std::task::{Context, Poll};
|
||||
/// * `Poll::Ready(Err(e))` for other errors are standard I/O errors coming from the
|
||||
/// underlying object.
|
||||
///
|
||||
/// This trait importantly means that the `write` method only works in the
|
||||
/// context of a future's task. The object may panic if used outside of a task.
|
||||
/// This trait importantly means that the [`write`][stdwrite] method only works in
|
||||
/// the context of a future's task. The object may panic if used outside of a task.
|
||||
///
|
||||
/// Note that this trait also represents that the `Write::flush` method works
|
||||
/// very similarly to the `write` method, notably that `Ok(())` means that the
|
||||
/// Note that this trait also represents that the [`Write::flush`][stdflush] method
|
||||
/// works very similarly to the `write` method, notably that `Ok(())` means that the
|
||||
/// writer has successfully been flushed, a "would block" error means that the
|
||||
/// current task is ready to receive a notification when flushing can make more
|
||||
/// progress, and otherwise normal errors can happen as well.
|
||||
///
|
||||
/// Utilities for working with `AsyncWrite` values are provided by
|
||||
/// [`AsyncWriteExt`].
|
||||
///
|
||||
/// [`std::io::Write`]: std::io::Write
|
||||
/// [`poll_write`]: AsyncWrite::poll_write()
|
||||
/// [stdwrite]: std::io::Write::write()
|
||||
/// [stdflush]: std::io::Write::flush()
|
||||
/// [`AsyncWriteExt`]: crate::io::AsyncWriteExt
|
||||
pub trait AsyncWrite {
|
||||
/// Attempt to write bytes from `buf` into the object.
|
||||
///
|
||||
@@ -49,7 +58,7 @@ pub trait AsyncWrite {
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>>;
|
||||
|
||||
/// Attempt to flush the object, ensuring that any buffered data reach
|
||||
/// Attempts to flush the object, ensuring that any buffered data reach
|
||||
/// their destination.
|
||||
///
|
||||
/// On success, returns `Poll::Ready(Ok(()))`.
|
||||
@@ -120,7 +129,7 @@ pub trait AsyncWrite {
|
||||
/// task.
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>>;
|
||||
|
||||
/// Write a `Buf` into this value, returning how many bytes were written.
|
||||
/// Writes a `Buf` into this value, returning how many bytes were written.
|
||||
///
|
||||
/// Note that this method will advance the `buf` provided automatically by
|
||||
/// the number of bytes written.
|
||||
|
||||
@@ -16,7 +16,7 @@ use self::State::*;
|
||||
pub(crate) struct Blocking<T> {
|
||||
inner: Option<T>,
|
||||
state: State<T>,
|
||||
/// true if the lower IO layer needs flushing
|
||||
/// `true` if the lower IO layer needs flushing
|
||||
need_flush: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,14 @@ pub(crate) use scheduled_io::ScheduledIo; // pub(crate) for tests
|
||||
|
||||
use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::park::{Park, Unpark};
|
||||
use crate::runtime::context;
|
||||
use crate::util::slab::{Address, Slab};
|
||||
|
||||
use mio::event::Evented;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::task::Waker;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -54,11 +53,6 @@ pub(super) enum Direction {
|
||||
Write,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Tracks the reactor for the current execution context.
|
||||
static CURRENT_REACTOR: RefCell<Option<Handle>> = RefCell::new(None)
|
||||
}
|
||||
|
||||
const TOKEN_WAKEUP: mio::Token = mio::Token(Address::NULL);
|
||||
|
||||
fn _assert_kinds() {
|
||||
@@ -69,40 +63,6 @@ fn _assert_kinds() {
|
||||
|
||||
// ===== impl Driver =====
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Guard that resets current reactor on drop.
|
||||
pub(crate) struct DefaultGuard<'a> {
|
||||
_lifetime: PhantomData<&'a u8>,
|
||||
}
|
||||
|
||||
impl Drop for DefaultGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
CURRENT_REACTOR.with(|current| {
|
||||
let mut current = current.borrow_mut();
|
||||
*current = None;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets handle for a default reactor, returning guard that unsets it on drop.
|
||||
pub(crate) fn set_default(handle: &Handle) -> DefaultGuard<'_> {
|
||||
CURRENT_REACTOR.with(|current| {
|
||||
let mut current = current.borrow_mut();
|
||||
|
||||
assert!(
|
||||
current.is_none(),
|
||||
"default Tokio reactor already set \
|
||||
for execution context"
|
||||
);
|
||||
|
||||
*current = Some(handle.clone());
|
||||
});
|
||||
|
||||
DefaultGuard {
|
||||
_lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
/// Creates a new event loop, returning any error that happened during the
|
||||
/// creation.
|
||||
@@ -238,10 +198,8 @@ impl Handle {
|
||||
///
|
||||
/// This function panics if there is no current reactor set.
|
||||
pub(super) fn current() -> Self {
|
||||
CURRENT_REACTOR.with(|current| match *current.borrow() {
|
||||
Some(ref handle) => handle.clone(),
|
||||
None => panic!("no current reactor"),
|
||||
})
|
||||
context::io_handle()
|
||||
.expect("there is no reactor running, must be called from the context of Tokio runtime")
|
||||
}
|
||||
|
||||
/// Forces a reactor blocked in a call to `turn` to wakeup, or otherwise
|
||||
@@ -279,7 +237,7 @@ impl fmt::Debug for Handle {
|
||||
// ===== impl Inner =====
|
||||
|
||||
impl Inner {
|
||||
/// Register an I/O resource with the reactor.
|
||||
/// Registers an I/O resource with the reactor.
|
||||
///
|
||||
/// The registration token is returned.
|
||||
pub(super) fn add_source(&self, source: &dyn Evented) -> io::Result<Address> {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::loom::sync::atomic::AtomicUsize;
|
||||
use crate::util::bit;
|
||||
use crate::util::slab::{Address, Entry, Generation};
|
||||
|
||||
use std::sync::atomic::Ordering::{Acquire, AcqRel, SeqCst};
|
||||
use std::sync::atomic::Ordering::{AcqRel, Acquire, SeqCst};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ScheduledIo {
|
||||
@@ -29,12 +29,10 @@ impl Entry for ScheduledIo {
|
||||
|
||||
let next = PACK.pack(generation.next().to_usize(), 0);
|
||||
|
||||
match self.readiness.compare_exchange(
|
||||
current,
|
||||
next,
|
||||
AcqRel,
|
||||
Acquire,
|
||||
) {
|
||||
match self
|
||||
.readiness
|
||||
.compare_exchange(current, next, AcqRel, Acquire)
|
||||
{
|
||||
Ok(_) => break,
|
||||
Err(actual) => current = actual,
|
||||
}
|
||||
|
||||
+8
-2
@@ -164,6 +164,9 @@ pub use self::async_buf_read::AsyncBufRead;
|
||||
mod async_read;
|
||||
pub use self::async_read::AsyncRead;
|
||||
|
||||
mod async_seek;
|
||||
pub use self::async_seek::AsyncSeek;
|
||||
|
||||
mod async_write;
|
||||
pub use self::async_write::AsyncWrite;
|
||||
|
||||
@@ -192,10 +195,13 @@ cfg_io_util! {
|
||||
mod split;
|
||||
pub use split::{split, ReadHalf, WriteHalf};
|
||||
|
||||
pub(crate) mod seek;
|
||||
pub use self::seek::Seek;
|
||||
|
||||
pub(crate) mod util;
|
||||
pub use util::{
|
||||
copy, empty, repeat, sink, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufStream,
|
||||
BufWriter, Copy, Empty, Lines, Repeat, Sink, Split, Take,
|
||||
copy, empty, repeat, sink, AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader,
|
||||
BufStream, BufWriter, Copy, Empty, Lines, Repeat, Sink, Split, Take,
|
||||
};
|
||||
|
||||
// Re-export io::Error so that users don't have to deal with conflicts when
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::io::driver::platform;
|
||||
use crate::io::{AsyncRead, AsyncWrite, Registration};
|
||||
use crate::io::driver::{platform};
|
||||
|
||||
use mio::event::Evented;
|
||||
use std::fmt;
|
||||
@@ -27,7 +27,7 @@ cfg_io_driver! {
|
||||
/// **Note**: While `PollEvented` is `Sync` (if the underlying I/O type is
|
||||
/// `Sync`), the caller must ensure that there are at most two tasks that
|
||||
/// use a `PollEvented` instance concurrently. One for reading and one for
|
||||
/// writing. While violating this requirement is "safe" from a Rust memory
|
||||
/// writing. While violating this requirement is "safe" from a Rust memory
|
||||
/// model point of view, it will result in unexpected behavior in the form
|
||||
/// of lost notifications and tasks hanging.
|
||||
///
|
||||
@@ -166,6 +166,14 @@ where
|
||||
E: Evented,
|
||||
{
|
||||
/// Creates a new `PollEvented` associated with the default reactor.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if thread-local runtime is not set.
|
||||
///
|
||||
/// The runtime is usually set implicitly when this function is called
|
||||
/// from a future driven by a tokio runtime, otherwise runtime can be set
|
||||
/// explicitly with [`Handle::enter`](crate::runtime::Handle::enter) function.
|
||||
pub fn new(io: E) -> io::Result<Self> {
|
||||
let registration = Registration::new(&io)?;
|
||||
Ok(Self {
|
||||
@@ -204,7 +212,7 @@ where
|
||||
Ok(io)
|
||||
}
|
||||
|
||||
/// Check the I/O resource's read readiness state.
|
||||
/// Checks the I/O resource's read readiness state.
|
||||
///
|
||||
/// The mask argument allows specifying what readiness to notify on. This
|
||||
/// can be any value, including platform specific readiness, **except**
|
||||
@@ -272,12 +280,12 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check the I/O resource's write readiness state.
|
||||
/// Checks the I/O resource's write readiness state.
|
||||
///
|
||||
/// This always checks for writable readiness and also checks for HUP
|
||||
/// readiness on platforms that support it.
|
||||
///
|
||||
/// If the resource is not ready for a write then `Async::NotReady` is
|
||||
/// If the resource is not ready for a write then `Poll::Pending` is
|
||||
/// returned and the current task is notified once a new event is received.
|
||||
///
|
||||
/// The I/O resource will remain in a write-ready state until readiness is
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::io::driver::{Direction, Handle, platform};
|
||||
use crate::io::driver::{platform, Direction, Handle};
|
||||
use crate::util::slab::Address;
|
||||
|
||||
use mio::{self, Evented};
|
||||
use std::task::{Context, Poll};
|
||||
use std::io;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
cfg_io_driver! {
|
||||
/// Associates an I/O resource with the reactor instance that drives it.
|
||||
@@ -30,7 +30,7 @@ cfg_io_driver! {
|
||||
/// ## Platform-specific events
|
||||
///
|
||||
/// `Registration` also allows receiving platform-specific `mio::Ready`
|
||||
/// events. These events are included as part of the read readiness event
|
||||
/// events. These events are included as part of the read readiness event
|
||||
/// stream. The write readiness event stream is only for `Ready::writable()`
|
||||
/// events.
|
||||
///
|
||||
@@ -47,12 +47,21 @@ cfg_io_driver! {
|
||||
// ===== impl Registration =====
|
||||
|
||||
impl Registration {
|
||||
/// Register the I/O resource with the default reactor.
|
||||
/// Registers the I/O resource with the default reactor.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// - `Ok` if the registration happened successfully
|
||||
/// - `Err` if an error was encountered during registration
|
||||
///
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if thread-local runtime is not set.
|
||||
///
|
||||
/// The runtime is usually set implicitly when this function is called
|
||||
/// from a future driven by a tokio runtime, otherwise runtime can be set
|
||||
/// explicitly with [`Handle::enter`](crate::runtime::Handle::enter) function.
|
||||
pub fn new<T>(io: &T) -> io::Result<Registration>
|
||||
where
|
||||
T: Evented,
|
||||
@@ -70,7 +79,7 @@ impl Registration {
|
||||
Ok(Registration { handle, address })
|
||||
}
|
||||
|
||||
/// Deregister the I/O resource from the reactor it is associated with.
|
||||
/// Deregisters the I/O resource from the reactor it is associated with.
|
||||
///
|
||||
/// This function must be called before the I/O resource associated with the
|
||||
/// registration is dropped.
|
||||
@@ -97,7 +106,7 @@ impl Registration {
|
||||
inner.deregister_source(io)
|
||||
}
|
||||
|
||||
/// Poll for events on the I/O resource's read readiness stream.
|
||||
/// Polls for events on the I/O resource's read readiness stream.
|
||||
///
|
||||
/// If the I/O resource receives a new read readiness event since the last
|
||||
/// call to `poll_read_ready`, it is returned. If it has not, the current
|
||||
@@ -148,7 +157,7 @@ impl Registration {
|
||||
self.poll_ready(Direction::Read, None)
|
||||
}
|
||||
|
||||
/// Poll for events on the I/O resource's write readiness stream.
|
||||
/// Polls for events on the I/O resource's write readiness stream.
|
||||
///
|
||||
/// If the I/O resource receives a new write readiness event since the last
|
||||
/// call to `poll_write_ready`, it is returned. If it has not, the current
|
||||
@@ -188,7 +197,7 @@ impl Registration {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume any pending write readiness event.
|
||||
/// Consumes any pending write readiness event.
|
||||
///
|
||||
/// This function is identical to [`poll_write_ready`] **except** that it
|
||||
/// will not notify the current task when a new event is received. As such,
|
||||
@@ -199,7 +208,7 @@ impl Registration {
|
||||
self.poll_ready(Direction::Write, None)
|
||||
}
|
||||
|
||||
/// Poll for events on the I/O resource's `direction` readiness stream.
|
||||
/// Polls for events on the I/O resource's `direction` readiness stream.
|
||||
///
|
||||
/// If called with a task context, notify the task when a new event is
|
||||
/// received.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use crate::io::AsyncSeek;
|
||||
use std::future::Future;
|
||||
use std::io::{self, SeekFrom};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Future for the [`seek`](crate::io::AsyncSeekExt::seek) method.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Seek<'a, S: ?Sized> {
|
||||
seek: &'a mut S,
|
||||
pos: Option<SeekFrom>,
|
||||
}
|
||||
|
||||
pub(crate) fn seek<S>(seek: &mut S, pos: SeekFrom) -> Seek<'_, S>
|
||||
where
|
||||
S: AsyncSeek + ?Sized + Unpin,
|
||||
{
|
||||
Seek {
|
||||
seek,
|
||||
pos: Some(pos),
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Future for Seek<'_, S>
|
||||
where
|
||||
S: AsyncSeek + ?Sized + Unpin,
|
||||
{
|
||||
type Output = io::Result<u64>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = &mut *self;
|
||||
match me.pos {
|
||||
Some(pos) => match Pin::new(&mut me.seek).start_seek(cx, pos) {
|
||||
Poll::Ready(Ok(())) => {
|
||||
me.pos = None;
|
||||
Pin::new(&mut me.seek).poll_complete(cx)
|
||||
}
|
||||
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
|
||||
Poll::Pending => Poll::Pending,
|
||||
},
|
||||
None => Pin::new(&mut me.seek).poll_complete(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn assert_unpin() {
|
||||
use std::marker::PhantomPinned;
|
||||
crate::is_unpin::<Seek<'_, PhantomPinned>>();
|
||||
}
|
||||
}
|
||||
+25
-9
@@ -17,21 +17,21 @@ use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
cfg_io_util! {
|
||||
/// The readable half of a value returned from `split`.
|
||||
/// The readable half of a value returned from [`split`](split()).
|
||||
pub struct ReadHalf<T> {
|
||||
inner: Arc<Inner<T>>,
|
||||
}
|
||||
|
||||
/// The writable half of a value returned from `split`.
|
||||
/// The writable half of a value returned from [`split`](split()).
|
||||
pub struct WriteHalf<T> {
|
||||
inner: Arc<Inner<T>>,
|
||||
}
|
||||
|
||||
/// Split a single value implementing `AsyncRead + AsyncWrite` into separate
|
||||
/// Splits a single value implementing `AsyncRead + AsyncWrite` into separate
|
||||
/// `AsyncRead` and `AsyncWrite` handles.
|
||||
///
|
||||
/// To restore this read/write object from its `split::ReadHalf` and
|
||||
/// `split::WriteHalf` use `unsplit`.
|
||||
/// To restore this read/write object from its `ReadHalf` and
|
||||
/// `WriteHalf` use [`unsplit`](ReadHalf::unsplit()).
|
||||
pub fn split<T>(stream: T) -> (ReadHalf<T>, WriteHalf<T>)
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
@@ -61,19 +61,27 @@ struct Guard<'a, T> {
|
||||
}
|
||||
|
||||
impl<T> ReadHalf<T> {
|
||||
/// Reunite with a previously split `WriteHalf`.
|
||||
/// Checks if this `ReadHalf` and some `WriteHalf` were split from the same
|
||||
/// stream.
|
||||
pub fn is_pair_of(&self, other: &WriteHalf<T>) -> bool {
|
||||
other.is_pair_of(&self)
|
||||
}
|
||||
|
||||
/// Reunites with a previously split `WriteHalf`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If this `ReadHalf` and the given `WriteHalf` do not originate from the
|
||||
/// same `split` operation this method will panic.
|
||||
/// This can be checked ahead of time by comparing the stream ID
|
||||
/// of the two halves.
|
||||
pub fn unsplit(self, wr: WriteHalf<T>) -> T {
|
||||
if Arc::ptr_eq(&self.inner, &wr.inner) {
|
||||
if self.is_pair_of(&wr) {
|
||||
drop(wr);
|
||||
|
||||
let inner = Arc::try_unwrap(self.inner)
|
||||
.ok()
|
||||
.expect("Arc::try_unwrap failed");
|
||||
.expect("`Arc::try_unwrap` failed");
|
||||
|
||||
inner.stream.into_inner()
|
||||
} else {
|
||||
@@ -82,6 +90,14 @@ impl<T> ReadHalf<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WriteHalf<T> {
|
||||
/// Check if this `WriteHalf` and some `ReadHalf` were split from the same
|
||||
/// stream.
|
||||
pub fn is_pair_of(&self, other: &ReadHalf<T>) -> bool {
|
||||
Arc::ptr_eq(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead> AsyncRead for ReadHalf<T> {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
@@ -139,7 +155,7 @@ impl<T> Inner<T> {
|
||||
} else {
|
||||
// Spin... but investigate a better strategy
|
||||
|
||||
::std::thread::yield_now();
|
||||
std::thread::yield_now();
|
||||
cx.waker().wake_by_ref();
|
||||
|
||||
Poll::Pending
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user