mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97565c0e75 | ||
|
|
96b014c12a | ||
|
|
b0a90d88cd | ||
|
|
9e91b8d87e | ||
|
|
22b7bd2f51 | ||
|
|
23ecc2b5eb | ||
|
|
da186a7859 | ||
|
|
2117ce7bac | ||
|
|
39f369f686 | ||
|
|
83e8fff090 | ||
|
|
f545d1276b | ||
|
|
59fb5b9a7d | ||
|
|
57ba3a7fbc | ||
|
|
c3c3481d74 | ||
|
|
7b39388415 | ||
|
|
11a1ce2721 | ||
|
|
c9532e49d7 | ||
|
|
b4cb3226ab | ||
|
|
4446eb4db8 | ||
|
|
cad0c35623 |
+11
-12
@@ -8,18 +8,14 @@ freebsd_instance:
|
||||
task:
|
||||
name: FreeBSD 12.0
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: -Dwarnings
|
||||
LOOM_MAX_DURATION: 10
|
||||
setup_script:
|
||||
- pkg install -y curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
- sh rustup.sh -y
|
||||
- . $HOME/.cargo/env
|
||||
- rustup target add i686-unknown-freebsd
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
|
||||
# Remove any existing patch statements
|
||||
mv Cargo.toml Cargo.toml.bck
|
||||
sed -n '/\[patch.crates-io\]/q;p' Cargo.toml.bck > Cargo.toml
|
||||
@@ -31,12 +27,15 @@ task:
|
||||
echo "~~~~ Cargo.toml ~~~~"
|
||||
cat Cargo.toml
|
||||
echo "~~~~~~~~~~~~~~~~~~~~"
|
||||
cargo_cache:
|
||||
folder: $HOME/.cargo/registry
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all
|
||||
- cargo doc --all --no-deps
|
||||
# TODO: Re-enable
|
||||
# i686_test_script:
|
||||
# - . $HOME/.cargo/env
|
||||
# - |
|
||||
# cargo test --all --exclude tokio-tls --exclude tokio-macros --target i686-unknown-freebsd
|
||||
- cargo doc --all
|
||||
i686_test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
cargo test --all --exclude tokio-tls --exclude tokio-macros --target i686-unknown-freebsd
|
||||
before_cache_script:
|
||||
- rm -rf $HOME/.cargo/registry/index
|
||||
|
||||
+6
-62
@@ -12,10 +12,10 @@ use your help.
|
||||
This guide will help you get started. **Do not let this guide intimidate you**.
|
||||
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
|
||||
You may also get help with contributing in the [dev channel][dev], please join
|
||||
us!
|
||||
|
||||
[dev]: https://discord.gg/6yGkFeN
|
||||
[dev]: https://gitter.im/tokio-rs/dev
|
||||
|
||||
## Conduct
|
||||
|
||||
@@ -153,6 +153,8 @@ The type level example for `tokio_timer::Timeout` provides a good example of a
|
||||
documentation test:
|
||||
|
||||
```
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// // import the `timeout` function, usually this is done
|
||||
/// // with `use tokio::prelude::*`
|
||||
/// use tokio::prelude::FutureExt;
|
||||
@@ -190,6 +192,8 @@ If this were a documentation test for the `Timeout::new` function, then the
|
||||
example would explicitly use `Timeout::new`. For example:
|
||||
|
||||
```
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// use tokio::timer::Timeout;
|
||||
/// use futures::Future;
|
||||
/// use futures::sync::oneshot;
|
||||
@@ -381,63 +385,3 @@ _Adapted from the [Node.js contributing guide][node]_.
|
||||
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
|
||||
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
|
||||
[documentation test]: https://doc.rust-lang.org/rustdoc/documentation-tests.html
|
||||
|
||||
## Releasing
|
||||
|
||||
Since the Tokio project consists of a number of crates, many of which depend on
|
||||
each other, releasing new versions to crates.io can involve some complexities.
|
||||
When releasing a new version of a crate, follow these steps:
|
||||
|
||||
1. **Ensure that the release crate has no path dependencies.** When the HEAD
|
||||
version of a Tokio crate requires unreleased changes in another Tokio crate,
|
||||
the crates.io dependency on the second crate will be replaced with a path
|
||||
dependency. Crates with path dependencies cannot be published, so before
|
||||
publishing the dependent crate, any path dependencies must also be published.
|
||||
This should be done through a form of depth-first tree traversal:
|
||||
|
||||
1. Starting with the first path dependency in the crate to be released,
|
||||
inspect the `Cargo.toml` for the dependency. If the dependency has any
|
||||
path dependencies of its own, repeat this step with the first such
|
||||
dependency.
|
||||
2. Begin the release process for the path dependency.
|
||||
3. Once the path dependency has been published to crates.io, update the
|
||||
dependent crate to depend on the crates.io version.
|
||||
4. When all path dependencies have been published, the dependent crate may
|
||||
be published.
|
||||
|
||||
To verify that a crate is ready to publish, run:
|
||||
|
||||
```bash
|
||||
bin/publish --dry-run <CRATE NAME> <CRATE VERSION>
|
||||
```
|
||||
|
||||
2. **Update Cargo metadata.** After releasing any path dependencies, update the
|
||||
`version` field in `Cargo.toml` to the new version, and the `documentation`
|
||||
field to the docs.rs URL of the new version.
|
||||
3. **Update other documentation links.** Update the `#![doc(html_root_url)]`
|
||||
attribute in the crate's `lib.rs` and the "Documentation" link in the crate's
|
||||
`README.md` to point to the docs.rs URL of the new version.
|
||||
4. **Update the changelog for the crate.** Each crate in the Tokio repository
|
||||
has its own `CHANGELOG.md` in that crate's subdirectory. Any changes to that
|
||||
crate since the last release should be added to the changelog. Change
|
||||
descriptions may be taken from the Git history, but should be edited to
|
||||
ensure a consistent format, based on [Keep A Changelog][keep-a-changelog].
|
||||
Other entries in that crate's changelog may also be used for reference.
|
||||
5. **Perform a final audit for breaking changes.** Compare the HEAD version of
|
||||
crate with the Git tag for the most recent release version. If there are any
|
||||
breaking API changes, determine if those changes can be made without breaking
|
||||
existing APIs. If so, resolve those issues. Otherwise, if it is necessary to
|
||||
make a breaking release, update the version numbers to reflect this.
|
||||
6. **Open a pull request with your changes.** Once that pull request has been
|
||||
approved by a maintainer and the pull request has been merged, continue to
|
||||
the next step.
|
||||
7. **Release the crate.** Run the following command:
|
||||
|
||||
```bash
|
||||
bin/publish <NAME OF CRATE> <VERSION>
|
||||
```
|
||||
|
||||
Your editor and prompt you to edit a message for the tag. Copy the changelog
|
||||
entry for that release version into your editor and close the window.
|
||||
|
||||
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog/blob/master/CHANGELOG.md
|
||||
|
||||
+15
-7
@@ -2,13 +2,21 @@
|
||||
|
||||
members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
"tokio-buf",
|
||||
"tokio-codec",
|
||||
"tokio-current-thread",
|
||||
"tokio-executor",
|
||||
"tokio-fs",
|
||||
"tokio-futures",
|
||||
"tokio-io",
|
||||
"tokio-reactor",
|
||||
"tokio-signal",
|
||||
"tokio-sync",
|
||||
"tokio-test",
|
||||
"tokio-threadpool",
|
||||
"tokio-timer",
|
||||
"tokio-tcp",
|
||||
"tokio-tls",
|
||||
"tokio-util",
|
||||
|
||||
# Internal
|
||||
"examples",
|
||||
"tests-build",
|
||||
"tests-integration",
|
||||
"tokio-udp",
|
||||
"tokio-uds",
|
||||
]
|
||||
|
||||
@@ -15,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]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
[![Gitter chat][gitter-badge]][gitter-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
@@ -23,13 +23,17 @@ 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
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/6yGkFeN
|
||||
[gitter-badge]: https://img.shields.io/gitter/room/tokio-rs/tokio.svg
|
||||
[gitter-url]: https://gitter.im/tokio-rs/tokio
|
||||
|
||||
[Website](https://tokio.rs) |
|
||||
[Guides](https://tokio.rs/docs/) |
|
||||
[API Docs](https://docs.rs/tokio/latest/tokio) |
|
||||
[Chat](https://discord.gg/6yGkFeN)
|
||||
[Guides](https://tokio.rs/docs/getting-started/hello-world/) |
|
||||
[API Docs](https://docs.rs/tokio/0.1.20/tokio) |
|
||||
[Chat](https://gitter.im/tokio-rs/tokio)
|
||||
|
||||
The API docs for the master branch are published [here][master-dox].
|
||||
|
||||
[master-dox]: https://tokio-rs.github.io/tokio/doc/tokio/
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -38,74 +42,72 @@ asynchronous applications with the Rust programming language. At a high
|
||||
level, it provides a few major components:
|
||||
|
||||
* A multithreaded, work-stealing based task [scheduler].
|
||||
* A reactor backed by the operating system's event queue (epoll, kqueue,
|
||||
* A [reactor] backed by the operating system's event queue (epoll, kqueue,
|
||||
IOCP, etc...).
|
||||
* Asynchronous [TCP and UDP][net] sockets.
|
||||
|
||||
These components provide the runtime components necessary for building
|
||||
an asynchronous application.
|
||||
|
||||
[net]: https://docs.rs/tokio/latest/tokio/net/index.html
|
||||
[scheduler]: https://docs.rs/tokio/latest/tokio/runtime/index.html
|
||||
[net]: https://docs.rs/tokio/0.1.20/tokio/net/index.html
|
||||
[reactor]: https://docs.rs/tokio/0.1.20/tokio/reactor/index.html
|
||||
[scheduler]: https://docs.rs/tokio/0.1.20/tokio/runtime/index.html
|
||||
|
||||
## Example
|
||||
|
||||
A basic TCP echo server with Tokio:
|
||||
|
||||
```rust,no_run
|
||||
use tokio::net::TcpListener;
|
||||
```rust
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::prelude::*;
|
||||
use tokio::io::copy;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
fn main() {
|
||||
// Bind the server's socket.
|
||||
let addr = "127.0.0.1:12345".parse().unwrap();
|
||||
let listener = TcpListener::bind(&addr)
|
||||
.expect("unable to bind TCP listener");
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
// Pull out a stream of sockets for incoming connections
|
||||
let server = listener.incoming()
|
||||
.map_err(|e| eprintln!("accept failed = {:?}", e))
|
||||
.for_each(|sock| {
|
||||
// Split up the reading and writing parts of the
|
||||
// socket.
|
||||
let (reader, writer) = sock.split();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
// A future that echos the data and returns how
|
||||
// many bytes were copied...
|
||||
let bytes_copied = copy(reader, writer);
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// ... after which we'll print what happened.
|
||||
let handle_conn = bytes_copied.map(|amt| {
|
||||
println!("wrote {:?} bytes", amt)
|
||||
}).map_err(|err| {
|
||||
eprintln!("IO error {:?}", err)
|
||||
});
|
||||
|
||||
// Write the data back
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
eprintln!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Spawn the future as a concurrent task.
|
||||
tokio::spawn(handle_conn)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Start the Tokio runtime
|
||||
tokio::run(server);
|
||||
}
|
||||
```
|
||||
|
||||
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](tokio/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 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.
|
||||
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.
|
||||
|
||||
[Guides]: https://tokio.rs/docs/
|
||||
[API documentation]: https://docs.rs/tokio/latest/tokio
|
||||
[chat]: https://discord.gg/6yGkFeN
|
||||
[chat]: https://gitter.im/tokio-rs/tokio
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues/new
|
||||
|
||||
## Contributing
|
||||
@@ -116,6 +118,58 @@ project.
|
||||
|
||||
[guide]: CONTRIBUTING.md
|
||||
|
||||
## Project layout
|
||||
|
||||
The `tokio` crate, found at the root, is primarily intended for use by
|
||||
application developers. Library authors should depend on the sub crates, which
|
||||
have greater guarantees of stability.
|
||||
|
||||
The crates included as part of Tokio are:
|
||||
|
||||
* [`tokio-current-thread`]: Schedule the execution of futures on the current
|
||||
thread.
|
||||
|
||||
* [`tokio-executor`]: Task execution related traits and utilities.
|
||||
|
||||
* [`tokio-fs`]: Filesystem (and standard in / out) APIs.
|
||||
|
||||
* [`tokio-futures`]: Experimental `std::future::Future` and `async` / `await` support.
|
||||
|
||||
* [`tokio-codec`]: Utilities for encoding and decoding protocol frames.
|
||||
|
||||
* [`tokio-io`]: Asynchronous I/O related traits and utilities.
|
||||
|
||||
* [`tokio-macros`]: Macros for usage with Tokio.
|
||||
|
||||
* [`tokio-reactor`]: Event loop that drives I/O resources (like TCP and UDP
|
||||
sockets).
|
||||
|
||||
* [`tokio-tcp`]: TCP bindings for use with `tokio-io` and `tokio-reactor`.
|
||||
|
||||
* [`tokio-threadpool`]: Schedules the execution of futures across a pool of
|
||||
threads.
|
||||
|
||||
* [ `tokio-timer`]: Time related APIs.
|
||||
|
||||
* [`tokio-udp`]: UDP bindings for use with `tokio-io` and `tokio-reactor`.
|
||||
|
||||
* [`tokio-uds`]: Unix Domain Socket bindings for use with `tokio-io` and
|
||||
`tokio-reactor`.
|
||||
|
||||
[`tokio-codec`]: tokio-codec
|
||||
[`tokio-current-thread`]: tokio-current-thread
|
||||
[`tokio-executor`]: tokio-executor
|
||||
[`tokio-fs`]: tokio-fs
|
||||
[`tokio-futures`]: tokio-futures
|
||||
[`tokio-io`]: tokio-io
|
||||
[`tokio-macros`]: tokio-macros
|
||||
[`tokio-reactor`]: tokio-reactor
|
||||
[`tokio-tcp`]: tokio-tcp
|
||||
[`tokio-threadpool`]: tokio-threadpool
|
||||
[`tokio-timer`]: tokio-timer
|
||||
[`tokio-udp`]: tokio-udp
|
||||
[`tokio-uds`]: tokio-uds
|
||||
|
||||
## Related Projects
|
||||
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
|
||||
+57
-62
@@ -1,61 +1,78 @@
|
||||
trigger: ["master"]
|
||||
pr: ["master"]
|
||||
|
||||
variables:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
nightly: nightly-2019-11-16
|
||||
trigger: ["master", "v0.1.x"]
|
||||
pr: ["master", "v0.1.x"]
|
||||
|
||||
jobs:
|
||||
# Check formatting
|
||||
- template: ci/azure-rustfmt.yml
|
||||
parameters:
|
||||
name: rustfmt
|
||||
|
||||
# Test top level crate
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_tokio
|
||||
rust: stable
|
||||
displayName: Test tokio
|
||||
cross: true
|
||||
crates:
|
||||
- tokio
|
||||
- tests-integration
|
||||
|
||||
# Test sub crates
|
||||
# Test crates that are platform specific
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_sub_cross
|
||||
displayName: Test sub crates -
|
||||
cross: true
|
||||
crates:
|
||||
- tokio-fs
|
||||
- tokio-reactor
|
||||
- tokio-signal
|
||||
- tokio-tcp
|
||||
- tokio-tls
|
||||
- tokio-udp
|
||||
- tokio-uds
|
||||
|
||||
# Test crates that are NOT platform specific
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_linux
|
||||
displayName: Test sub crates -
|
||||
rust: stable
|
||||
crates:
|
||||
- tokio-macros
|
||||
- tokio-buf
|
||||
- tokio-codec
|
||||
- tokio-current-thread
|
||||
- tokio-executor
|
||||
- tokio-io
|
||||
- tokio-sync
|
||||
- tokio-threadpool
|
||||
- tokio-timer
|
||||
- tokio-test
|
||||
- tokio-tls
|
||||
- tokio-util
|
||||
- examples
|
||||
|
||||
# Run tests from `tests-build`. This requires a different process
|
||||
- template: ci/azure-test-build.yml
|
||||
- template: ci/azure-cargo-check.yml
|
||||
parameters:
|
||||
name: test_build
|
||||
displayName: Test build permutations
|
||||
rust: stable
|
||||
|
||||
# Run loom tests
|
||||
- template: ci/azure-loom.yml
|
||||
parameters:
|
||||
name: loom
|
||||
name: features
|
||||
displayName: Check feature permtuations
|
||||
rust: stable
|
||||
crates:
|
||||
- tokio
|
||||
tokio:
|
||||
- codec
|
||||
- fs
|
||||
- io
|
||||
- reactor
|
||||
- rt-full
|
||||
- tcp
|
||||
- timer
|
||||
- udp
|
||||
- uds
|
||||
- sync
|
||||
- experimental-tracing
|
||||
tokio-buf:
|
||||
- util
|
||||
|
||||
# Try cross compiling
|
||||
- template: ci/azure-cross-compile.yml
|
||||
parameters:
|
||||
name: cross
|
||||
rust: stable
|
||||
|
||||
# Check each feature works properly
|
||||
- template: ci/azure-check-features.yml
|
||||
parameters:
|
||||
rust: $(nightly)
|
||||
name: check_features
|
||||
name: cross_32bit_linux
|
||||
target: i686-unknown-linux-gnu
|
||||
|
||||
# This represents the minimum Rust version supported by
|
||||
# Tokio. Updating this should be done in a dedicated PR and
|
||||
@@ -67,42 +84,20 @@ jobs:
|
||||
- template: ci/azure-check-minrust.yml
|
||||
parameters:
|
||||
name: minrust
|
||||
rust: 1.39.0
|
||||
rust_version: 1.31.0
|
||||
|
||||
# Check formatting
|
||||
- template: ci/azure-rustfmt.yml
|
||||
- template: ci/azure-tsan.yml
|
||||
parameters:
|
||||
rust: stable
|
||||
name: rustfmt
|
||||
|
||||
# Apply clippy lints to all crates
|
||||
- template: ci/azure-clippy.yml
|
||||
parameters:
|
||||
rust: stable
|
||||
name: clippy
|
||||
|
||||
# Check doc generation
|
||||
- template: ci/azure-check-docs.yml
|
||||
parameters:
|
||||
rust: $(nightly)
|
||||
name: docs
|
||||
|
||||
# - template: ci/azure-tsan.yml
|
||||
# parameters:
|
||||
# name: tsan
|
||||
# rust: stable
|
||||
name: tsan
|
||||
|
||||
- template: ci/azure-deploy-docs.yml
|
||||
parameters:
|
||||
rust: stable
|
||||
dependsOn:
|
||||
- rustfmt
|
||||
- clippy
|
||||
- test_tokio
|
||||
- test_sub_cross
|
||||
- test_linux
|
||||
- test_build
|
||||
- loom
|
||||
- cross
|
||||
- features
|
||||
- cross_32bit_linux
|
||||
- minrust
|
||||
- check_features
|
||||
# - tsan
|
||||
- tsan
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
@@ -10,7 +11,6 @@ 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;
|
||||
@@ -1,9 +1,8 @@
|
||||
// Measure cost of different operations
|
||||
// to get a sense of performance tradeoffs
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate mio;
|
||||
extern crate test;
|
||||
|
||||
use test::Bencher;
|
||||
@@ -1,6 +1,10 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
|
||||
#[macro_use]
|
||||
extern crate tokio_io;
|
||||
|
||||
pub extern crate test;
|
||||
|
||||
@@ -17,7 +21,7 @@ mod prelude {
|
||||
}
|
||||
|
||||
mod connect_churn {
|
||||
use crate::prelude::*;
|
||||
use prelude::*;
|
||||
|
||||
const NUM: usize = 300;
|
||||
const CONCURRENT: usize = 8;
|
||||
@@ -148,9 +152,8 @@ mod connect_churn {
|
||||
}
|
||||
|
||||
mod transfer {
|
||||
use crate::prelude::*;
|
||||
use prelude::*;
|
||||
use std::{cmp, mem};
|
||||
use tokio_io::try_nb;
|
||||
|
||||
const MB: usize = 3 * 1024 * 1024;
|
||||
|
||||
@@ -216,7 +219,7 @@ mod transfer {
|
||||
.and_then(|sock| {
|
||||
sock.set_linger(Some(Duration::from_secs(0))).unwrap();
|
||||
let drain = Drain {
|
||||
sock,
|
||||
sock: sock,
|
||||
chunk: read_size,
|
||||
};
|
||||
drain
|
||||
@@ -227,7 +230,7 @@ mod transfer {
|
||||
|
||||
let client = TcpStream::connect(&addr)
|
||||
.and_then(move |sock| Transfer {
|
||||
sock,
|
||||
sock: sock,
|
||||
rem: MB,
|
||||
chunk: write_size,
|
||||
})
|
||||
@@ -238,7 +241,7 @@ mod transfer {
|
||||
}
|
||||
|
||||
mod small_chunks {
|
||||
use crate::prelude::*;
|
||||
use prelude::*;
|
||||
|
||||
#[bench]
|
||||
fn one_thread(b: &mut Bencher) {
|
||||
@@ -247,7 +250,7 @@ mod transfer {
|
||||
}
|
||||
|
||||
mod big_chunks {
|
||||
use crate::prelude::*;
|
||||
use prelude::*;
|
||||
|
||||
#[bench]
|
||||
fn one_thread(b: &mut Bencher) {
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Publish a new release of a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-v, --verbose Use verbose Cargo output
|
||||
-d, --dry-run Perform a dry run (do not publish or tag the release)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
DRY_RUN=""
|
||||
VERBOSE=""
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
verify() {
|
||||
status "Verifying" "if $CRATE v$VERSION can be released"
|
||||
ACTUAL=$(cargo pkgid | sed -n 's/.*#\(.*\)/\1/p')
|
||||
|
||||
if [ "$ACTUAL" != "$VERSION" ]; then
|
||||
err "expected to release version $VERSION, but Cargo.toml contained $ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git tag -l | grep -Fxq "$TAG" ; then
|
||||
err "git tag \`$TAG\` already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATH_DEPS=$(grep -F "path = \"" Cargo.toml | sed -e 's/^/ /')
|
||||
if [ -n "$PATH_DEPS" ]; then
|
||||
err "crate \`$CRATE\` contained path dependencies:\n$PATH_DEPS"
|
||||
echo "path dependencies must be removed prior to release"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
release() {
|
||||
status "Releasing" "$CRATE v$VERSION"
|
||||
cargo package $VERBOSE
|
||||
cargo publish $VERBOSE $DRY_RUN
|
||||
|
||||
status "Tagging" "$TAG"
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
echo "# git tag $TAG && git push --tags"
|
||||
else
|
||||
git tag "$TAG" && git push --tags
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE="--verbose"
|
||||
set +x
|
||||
shift
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
(cd "$CRATE" && verify && release )
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Update links to docs.rs in a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-d, --dry-run Perform a dry run (do not modify any file)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
c1grep() { grep "$@" || test $? = 1; }
|
||||
|
||||
update_versions_in_doc() {
|
||||
# Print what is being/would be done
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
local MSG="Would change:"
|
||||
else
|
||||
local MSG="Updating:"
|
||||
fi
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed --quiet \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|gp" \
|
||||
| sed -e "s/^/$MSG /"
|
||||
|
||||
# Apply changes if not in dry run
|
||||
if [ -z "$DRY_RUN" ]; then
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed -i \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|g"
|
||||
fi
|
||||
}
|
||||
|
||||
update() {
|
||||
update_versions_in_doc
|
||||
}
|
||||
|
||||
show_outdated() {
|
||||
OUTDATED=$(git grep -rn "docs.rs/$CRATE/" \
|
||||
| c1grep -v "$VERSION" \
|
||||
| sed -e 's/^/ - /')
|
||||
if [[ -n "$OUTDATED" ]]; then
|
||||
echo "Found the following links to docs.rs with an outdated version:"
|
||||
echo "$OUTDATED"
|
||||
echo
|
||||
else
|
||||
echo "Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
# Does not cd in order to update everywhere
|
||||
show_outdated && update
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
jobs:
|
||||
# Check docs
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Check docs
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: |
|
||||
RUSTDOCFLAGS="--cfg docsrs" cargo doc --lib --no-deps --all-features
|
||||
displayName: Check docs
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Check features
|
||||
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 }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo install cargo-hack
|
||||
displayName: Install cargo-hack
|
||||
|
||||
# Check each feature works properly
|
||||
# * --each-feature
|
||||
# run for each feature which includes --no-default-features and default features of package
|
||||
# * -Z avoid-dev-deps
|
||||
# build without dev-dependencies to avoid https://github.com/rust-lang/cargo/issues/4866
|
||||
# tracking-issue: https://github.com/rust-lang/cargo/issues/5133
|
||||
- script: cargo hack check --all --each-feature -Z avoid-dev-deps
|
||||
displayName: cargo hack check --all --each-feature
|
||||
@@ -6,7 +6,7 @@ jobs:
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
rust_version: ${{ parameters.rust_version }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Clippy
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
- script: |
|
||||
rustup component add clippy
|
||||
cargo clippy --version
|
||||
displayName: Install clippy
|
||||
- script: |
|
||||
cargo clippy --all --all-features -- -A clippy::mutex-atomic
|
||||
displayName: cargo clippy --all
|
||||
@@ -1,44 +1,27 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
strategy:
|
||||
matrix:
|
||||
i686:
|
||||
vmImage: ubuntu-16.04
|
||||
target: i686-unknown-linux-gnu
|
||||
powerpc:
|
||||
vmImage: ubuntu-16.04
|
||||
target: powerpc-unknown-linux-gnu
|
||||
powerpc64:
|
||||
vmImage: ubuntu-16.04
|
||||
target: powerpc64-unknown-linux-gnu
|
||||
mips:
|
||||
vmImage: ubuntu-16.04
|
||||
target: mips-unknown-linux-gnu
|
||||
arm:
|
||||
vmImage: ubuntu-16.04
|
||||
target: arm-linux-androideabi
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
rust_version: stable
|
||||
|
||||
- script: sudo apt-get update
|
||||
displayName: apt-get update
|
||||
displayName: "apt-get update"
|
||||
|
||||
- script: sudo apt-get install gcc-multilib
|
||||
displayName: Install gcc-multilib
|
||||
displayName: "Install gcc-multilib"
|
||||
|
||||
- script: cargo install cross
|
||||
displayName: Install cross
|
||||
- script: rustup target add ${{ parameters.target }}
|
||||
displayName: "Add target"
|
||||
|
||||
# Always patch
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cross check --all --exclude tokio-tls --target $(target)
|
||||
- script: cargo check --all --exclude tokio-tls --target ${{ parameters.target }}
|
||||
displayName: Check source
|
||||
|
||||
# - script: cross check --tests --all --exclude tokio-tls --target $(target)
|
||||
# displayName: Check tests
|
||||
- script: cargo check --tests --all --exclude tokio-tls --target ${{ parameters.target }}
|
||||
displayName: Check tests
|
||||
|
||||
@@ -12,10 +12,9 @@ jobs:
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
# rust_version: stable
|
||||
rust_version: ${{ parameters.rust }}
|
||||
rust_version: stable
|
||||
- script: |
|
||||
cargo doc --all --no-deps --all-features
|
||||
cargo doc --all --no-deps
|
||||
cp -R target/doc '$(Build.BinariesDirectory)'
|
||||
displayName: 'Generate Documentation'
|
||||
- script: |
|
||||
|
||||
@@ -2,7 +2,7 @@ steps:
|
||||
# Linux and macOS.
|
||||
- script: |
|
||||
set -e
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain none
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none
|
||||
export PATH=$PATH:$HOME/.cargo/bin
|
||||
rustup toolchain install $RUSTUP_TOOLCHAIN
|
||||
rustup default $RUSTUP_TOOLCHAIN
|
||||
@@ -14,20 +14,20 @@ steps:
|
||||
|
||||
# Windows.
|
||||
- script: |
|
||||
echo "windows"
|
||||
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||
rustup-init.exe -y --profile minimal --default-toolchain none
|
||||
rustup-init.exe -y --default-toolchain none
|
||||
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
rustup toolchain install %RUSTUP_TOOLCHAIN%
|
||||
rustup default %RUSTUP_TOOLCHAIN%
|
||||
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: ${{parameters.rust_version}}
|
||||
displayName: "Install rust (windows)"
|
||||
displayName: Install rust (windows)
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
|
||||
# All platforms.
|
||||
- script: |
|
||||
rustup toolchain list
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
displayName: Query rust and cargo versions
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Loom tests
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- script: RUSTFLAGS="--cfg loom" cargo test --lib --release --features "full" -- --test-threads=1 --nocapture
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 1
|
||||
CI: 'True'
|
||||
displayName: test ${{ crate }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
@@ -7,10 +7,9 @@ jobs:
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
rust_version: stable
|
||||
- script: |
|
||||
rustup component add rustfmt
|
||||
cargo fmt --version
|
||||
displayName: Install rustfmt
|
||||
- script: |
|
||||
cargo fmt --all -- --check
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: cargo install cargo-hack
|
||||
displayName: Install cargo-hack
|
||||
|
||||
- script: cargo hack test --each-feature
|
||||
displayName: cargo hack test --each-feature
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests-build
|
||||
@@ -1,19 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo check --all
|
||||
displayName: cargo check --all
|
||||
|
||||
# Check benches
|
||||
- script: cargo check --benches --all
|
||||
displayName: Check benchmarks
|
||||
@@ -17,26 +17,25 @@ jobs:
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
rust_version: stable
|
||||
|
||||
- template: azure-is-release.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
# Run with all crate features
|
||||
- script: cargo test --all-features
|
||||
- script: cargo test
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
LOOM_MAX_DURATION: 10
|
||||
CI: 'True'
|
||||
displayName: ${{ crate }} - cargo test --all-features
|
||||
displayName: cargo test -p ${{ crate }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
condition: and(succeeded(), ne(variables['isRelease'], 'true'))
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
# Run with all crate features
|
||||
- script: cargo test --all-features
|
||||
- script: cargo test
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
LOOM_MAX_DURATION: 10
|
||||
CI: 'True'
|
||||
displayName: ${{ crate }} - cargo test --all-features
|
||||
displayName: cargo test -p ${{ crate }} (PATCHED)
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
|
||||
+3
-1
@@ -5,12 +5,14 @@ jobs:
|
||||
matrix:
|
||||
Timer:
|
||||
cmd: cargo test -p tokio-timer --test hammer
|
||||
Threadpool:
|
||||
cmd: cargo test -p tokio-threadpool --tests
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
rust_version: nightly-2019-07-17
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
- script: |
|
||||
|
||||
+15
-3
@@ -2,7 +2,19 @@
|
||||
# repository.
|
||||
[patch.crates-io]
|
||||
tokio = { path = "tokio" }
|
||||
tokio-macros = { path = "tokio-macros" }
|
||||
tokio-test = { path = "tokio-test" }
|
||||
tokio-buf = { path = "tokio-buf" }
|
||||
tokio-codec = { path = "tokio-codec" }
|
||||
tokio-current-thread = { path = "tokio-current-thread" }
|
||||
tokio-executor = { path = "tokio-executor" }
|
||||
tokio-fs = { path = "tokio-fs" }
|
||||
tokio-futures = { path = "tokio-futures" }
|
||||
tokio-io = { path = "tokio-io" }
|
||||
tokio-reactor = { path = "tokio-reactor" }
|
||||
tokio-signal = { path = "tokio-signal" }
|
||||
tokio-sync = { path = "tokio-sync" }
|
||||
tokio-threadpool = { path = "tokio-threadpool" }
|
||||
tokio-timer = { path = "tokio-timer" }
|
||||
tokio-tcp = { path = "tokio-tcp" }
|
||||
tokio-tls = { path = "tokio-tls" }
|
||||
tokio-util = { path = "tokio-util" }
|
||||
tokio-udp = { path = "tokio-udp" }
|
||||
tokio-uds = { path = "tokio-uds" }
|
||||
|
||||
@@ -8,8 +8,6 @@ race:Weak*drop
|
||||
# `std` mpsc is not used in any Tokio code base. This race is triggered by some
|
||||
# rust runtime logic.
|
||||
race:std*mpsc_queue
|
||||
race:std*lang_start
|
||||
race:drop*std::thread*
|
||||
|
||||
# Probably more fences in std.
|
||||
race:__call_tls_dtors
|
||||
@@ -37,3 +35,10 @@ race:WorkerEntry::set_next_sleeper
|
||||
# This ignores a false positive caused by `thread::park()`/`thread::unpark()`.
|
||||
# See: https://github.com/rust-lang/rust/pull/54806#issuecomment-436193353
|
||||
race:pthread_cond_destroy
|
||||
|
||||
# Recent rand dependency updates and seeding changes have introduced
|
||||
# lazy_static's and other racy code. See:
|
||||
# https://github.com/tokio-rs/tokio/pull/1358#issuecomment-516172383
|
||||
race:RandomState*::build_hasher
|
||||
race:lazy_static::
|
||||
race:c2_chacha::guts
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
[package]
|
||||
name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
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"
|
||||
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"
|
||||
path = "chat.rs"
|
||||
|
||||
[[example]]
|
||||
name = "connect"
|
||||
path = "connect.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-udp"
|
||||
path = "echo-udp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo"
|
||||
path = "echo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "hello_world.rs"
|
||||
|
||||
[[example]]
|
||||
name = "print_each_packet"
|
||||
path = "print_each_packet.rs"
|
||||
|
||||
[[example]]
|
||||
name = "proxy"
|
||||
path = "proxy.rs"
|
||||
|
||||
[[example]]
|
||||
name = "tinydb"
|
||||
path = "tinydb.rs"
|
||||
|
||||
[[example]]
|
||||
name = "udp-client"
|
||||
path = "udp-client.rs"
|
||||
|
||||
[[example]]
|
||||
name = "udp-codec"
|
||||
path = "udp-codec.rs"
|
||||
|
||||
[[example]]
|
||||
name = "tinyhttp"
|
||||
path = "tinyhttp.rs"
|
||||
@@ -1,6 +0,0 @@
|
||||
## 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).
|
||||
@@ -1,255 +0,0 @@
|
||||
//! A chat server that broadcasts a message to all connections.
|
||||
//!
|
||||
//! This example is explicitly more verbose than it has to be. This is to
|
||||
//! illustrate more concepts.
|
||||
//!
|
||||
//! A chat server for telnet clients. After a telnet client connects, the first
|
||||
//! line should contain the client's name. After that, all lines sent by a
|
||||
//! client are broadcasted to all other connected clients.
|
||||
//!
|
||||
//! Because the client is telnet, lines are delimited by "\r\n".
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! cargo run --example chat
|
||||
//!
|
||||
//! And then in another terminal run:
|
||||
//!
|
||||
//! telnet localhost 6142
|
||||
//!
|
||||
//! You can run the `telnet` command in any number of additional windows.
|
||||
//!
|
||||
//! You can run the second command in multiple windows and then chat between the
|
||||
//! two, seeing the messages from the other client as they're received. For all
|
||||
//! connected clients they'll all join the same room and see everyone else's
|
||||
//! messages.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
|
||||
|
||||
use futures::{SinkExt, Stream, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Create the shared state. This is how all the peers communicate.
|
||||
//
|
||||
// The server task will hold a handle to this. For every new client, the
|
||||
// `state` handle is cloned and passed into the task that processes the
|
||||
// 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());
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
println!("server running on {}", addr);
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound TcpStream.
|
||||
let (stream, addr) = listener.accept().await?;
|
||||
|
||||
// Clone a handle to the `Shared` state for the new connection.
|
||||
let state = Arc::clone(&state);
|
||||
|
||||
// Spawn our handler to be run asynchronously.
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process(state, stream, addr).await {
|
||||
println!("an error occured; error = {:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorthand for the transmit half of the message channel.
|
||||
type Tx = mpsc::UnboundedSender<String>;
|
||||
|
||||
/// Shorthand for the receive half of the message channel.
|
||||
type Rx = mpsc::UnboundedReceiver<String>;
|
||||
|
||||
/// Data that is shared between all peers in the chat server.
|
||||
///
|
||||
/// This is the set of `Tx` handles for all connected clients. Whenever a
|
||||
/// message is received from a client, it is broadcasted to all peers by
|
||||
/// iterating over the `peers` entries and sending a copy of the message on each
|
||||
/// `Tx`.
|
||||
struct Shared {
|
||||
peers: HashMap<SocketAddr, Tx>,
|
||||
}
|
||||
|
||||
/// The state for each connected client.
|
||||
struct Peer {
|
||||
/// The TCP socket wrapped with the `Lines` codec, defined below.
|
||||
///
|
||||
/// This handles sending and receiving data on the socket. When using
|
||||
/// `Lines`, we can work at the line level instead of having to manage the
|
||||
/// raw byte operations.
|
||||
lines: Framed<TcpStream, LinesCodec>,
|
||||
|
||||
/// Receive half of the message channel.
|
||||
///
|
||||
/// This is used to receive messages from peers. When a message is received
|
||||
/// off of this `Rx`, it will be written to the socket.
|
||||
rx: Rx,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
/// Create a new, empty, instance of `Shared`.
|
||||
fn new() -> Self {
|
||||
Shared {
|
||||
peers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a `LineCodec` encoded message to every peer, except
|
||||
/// for the sender.
|
||||
async fn broadcast(&mut self, sender: SocketAddr, message: &str) {
|
||||
for peer in self.peers.iter_mut() {
|
||||
if *peer.0 != sender {
|
||||
let _ = peer.1.send(message.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Peer {
|
||||
/// Create a new instance of `Peer`.
|
||||
async fn new(
|
||||
state: Arc<Mutex<Shared>>,
|
||||
lines: Framed<TcpStream, LinesCodec>,
|
||||
) -> io::Result<Peer> {
|
||||
// Get the client socket address
|
||||
let addr = lines.get_ref().peer_addr()?;
|
||||
|
||||
// Create a channel for this peer
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
// Add an entry for this `Peer` in the shared state map.
|
||||
state.lock().await.peers.insert(addr, tx);
|
||||
|
||||
Ok(Peer { lines, rx })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Message {
|
||||
/// A message that should be broadcasted to others.
|
||||
Broadcast(String),
|
||||
|
||||
/// A message that should be received by a client
|
||||
Received(String),
|
||||
}
|
||||
|
||||
// Peer implements `Stream` in a way that polls both the `Rx`, and `Framed` types.
|
||||
// A message is produced whenever an event is ready until the `Framed` stream returns `None`.
|
||||
impl Stream for Peer {
|
||||
type Item = Result<Message, LinesCodecError>;
|
||||
|
||||
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) {
|
||||
return Poll::Ready(Some(Ok(Message::Received(v))));
|
||||
}
|
||||
|
||||
// Secondly poll the `Framed` stream.
|
||||
let result: Option<_> = futures::ready!(self.lines.poll_next_unpin(cx));
|
||||
|
||||
Poll::Ready(match result {
|
||||
// We've received a message we should broadcast to others.
|
||||
Some(Ok(message)) => Some(Ok(Message::Broadcast(message))),
|
||||
|
||||
// An error occured.
|
||||
Some(Err(e)) => Some(Err(e)),
|
||||
|
||||
// The stream has been exhausted.
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an individual chat client
|
||||
async fn process(
|
||||
state: Arc<Mutex<Shared>>,
|
||||
stream: TcpStream,
|
||||
addr: SocketAddr,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut lines = Framed::new(stream, LinesCodec::new());
|
||||
|
||||
// Send a prompt to the client to enter their username.
|
||||
lines
|
||||
.send(String::from("Please enter your username:"))
|
||||
.await?;
|
||||
|
||||
// Read the first line from the `LineCodec` stream to get the username.
|
||||
let username = match lines.next().await {
|
||||
Some(Ok(line)) => line,
|
||||
// We didn't get a line so we return early here.
|
||||
_ => {
|
||||
println!("Failed to get username from {}. Client disconnected.", addr);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// Register our peer with state which internally sets up some channels.
|
||||
let mut peer = Peer::new(state.clone(), lines).await?;
|
||||
|
||||
// A client has connected, let's let everyone know.
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{} has joined the chat", username);
|
||||
println!("{}", msg);
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
// Process incoming messages until our stream is exhausted by a disconnect.
|
||||
while let Some(result) = peer.next().await {
|
||||
match result {
|
||||
// A message was received from the current user, we should
|
||||
// broadcast this message to the other users.
|
||||
Ok(Message::Broadcast(msg)) => {
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{}: {}", username, msg);
|
||||
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
// A message was received from a peer. Send it to the
|
||||
// current user.
|
||||
Ok(Message::Received(msg)) => {
|
||||
peer.lines.send(msg).await?;
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"an error occured while processing messages for {}; error = {:?}",
|
||||
username, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this section is reached it means that the client was disconnected!
|
||||
// Let's let everyone still connected know about it.
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
state.peers.remove(&addr);
|
||||
|
||||
let msg = format!("{} has left the chat", username);
|
||||
println!("{}", msg);
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
//! An example of hooking up stdin/stdout to either a TCP or UDP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. An optional `--udp` argument can be passed to specify
|
||||
//! that the connection should be made over UDP instead of TCP, translating each
|
||||
//! line entered on stdin to a UDP packet to be sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> 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") {
|
||||
Some(i) => {
|
||||
args.remove(i);
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
// 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 = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(io::stdin(), codec::Bytes);
|
||||
let stdout = FramedWrite::new(io::stdout(), codec::Bytes);
|
||||
|
||||
if tcp {
|
||||
tcp::connect(&addr, stdin, stdout).await?;
|
||||
} else {
|
||||
udp::connect(&addr, stdin, stdout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod tcp {
|
||||
use super::codec;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::{error::Error, io, net::SocketAddr};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{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,
|
||||
) -> 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)
|
||||
.filter_map(|i| match i {
|
||||
Ok(i) => future::ready(Some(i)),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={}", e);
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
match future::join(stdin.forward(sink), stdout.send_all(&mut stream)).await {
|
||||
(Err(e), _) | (_, Err(e)) => Err(e.into()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use tokio::net::udp::{RecvHalf, SendHalf};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
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,
|
||||
) -> 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.
|
||||
let bind_addr = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
};
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
let (mut r, mut w) = socket.split();
|
||||
|
||||
future::try_join(send(stdin, &mut w), recv(stdout, &mut r)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Vec<u8>, io::Error>> + Unpin,
|
||||
writer: &mut SendHalf,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
writer.send(&buf[..]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Vec<u8>, Error = io::Error> + Unpin,
|
||||
reader: &mut RecvHalf,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
//! A "hello world" echo server with Tokio
|
||||
//!
|
||||
//! This server will create a TCP listener, accept connections in a loop, and
|
||||
//! write back everything that's read off of each TCP connection.
|
||||
//!
|
||||
//! Because the Tokio runtime uses a thread pool, each TCP connection is
|
||||
//! processed concurrently with all other TCP connections across multiple
|
||||
//! threads.
|
||||
//!
|
||||
//! To see this server in action, you can run this in one terminal:
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be echo'd back to
|
||||
//! you! If you open up multiple terminals running the `connect` example you
|
||||
//! should be able to see them all make progress simultaneously.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
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());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
// And this is where much of the magic of this server happens. We
|
||||
// crucially want all clients to make progress concurrently, rather than
|
||||
// blocking one on completion of another. To achieve this we use the
|
||||
// `tokio::spawn` function to execute the work in the background.
|
||||
//
|
||||
// Essentially here we're executing a new task to run concurrently,
|
||||
// which will allow all of our clients to be processed concurrently.
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
let n = socket
|
||||
.read(&mut buf)
|
||||
.await
|
||||
.expect("failed to read data from socket");
|
||||
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
socket
|
||||
.write_all(&buf[0..n])
|
||||
.await
|
||||
.expect("failed to write data to socket");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//! Hello world server.
|
||||
//!
|
||||
//! A simple client that opens a TCP stream, writes "hello world\n", and closes
|
||||
//! the connection.
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! ncat -l 6142
|
||||
//!
|
||||
//! And then in another terminal run:
|
||||
//!
|
||||
//! cargo run --example hello_world
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Open a TCP stream to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpStream, which is fully async.
|
||||
let mut stream = TcpStream::connect("127.0.0.1:6142").await?;
|
||||
println!("created stream");
|
||||
|
||||
let result = stream.write(b"hello world\n").await;
|
||||
println!("wrote to stream; success={:?}", result.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
//! A "print-each-packet" server with Tokio
|
||||
//!
|
||||
//! This server will create a TCP listener, accept connections in a loop, and
|
||||
//! put down in the stdout everything that's read off of each TCP connection.
|
||||
//!
|
||||
//! Because the Tokio runtime uses a thread pool, each TCP connection is
|
||||
//! processed concurrently with all other TCP connections across multiple
|
||||
//! threads.
|
||||
//!
|
||||
//! To see this server in action, you can run this in one terminal:
|
||||
//!
|
||||
//! cargo run --example print\_each\_packet
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be written to terminal!
|
||||
//!
|
||||
//! Minimal js example:
|
||||
//!
|
||||
//! ```js
|
||||
//! var net = require("net");
|
||||
//!
|
||||
//! var listenPort = 8080;
|
||||
//!
|
||||
//! var server = net.createServer(function (socket) {
|
||||
//! socket.on("data", function (bytes) {
|
||||
//! console.log("bytes", bytes);
|
||||
//! });
|
||||
//!
|
||||
//! socket.on("end", function() {
|
||||
//! console.log("Socket received FIN packet and closed connection");
|
||||
//! });
|
||||
//! socket.on("error", function (error) {
|
||||
//! console.log("Socket closed with error", error);
|
||||
//! });
|
||||
//!
|
||||
//! socket.on("close", function (with_error) {
|
||||
//! if (with_error) {
|
||||
//! console.log("Socket closed with result: Err(SomeError)");
|
||||
//! } else {
|
||||
//! console.log("Socket closed with result: Ok(())");
|
||||
//! }
|
||||
//! });
|
||||
//!
|
||||
//! });
|
||||
//!
|
||||
//! server.listen(listenPort);
|
||||
//!
|
||||
//! console.log("Listening on:", listenPort);
|
||||
//! ```
|
||||
//!
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_util::codec::{BytesCodec, Decoder};
|
||||
|
||||
use futures::StreamExt;
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
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());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop, so we pass in a handle
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
let (socket, _) = listener.accept().await?;
|
||||
|
||||
// And this is where much of the magic of this server happens. We
|
||||
// crucially want all clients to make progress concurrently, rather than
|
||||
// blocking one on completion of another. To achieve this we use the
|
||||
// `tokio::spawn` function to execute the work in the background.
|
||||
//
|
||||
// Essentially here we're executing a new task to run concurrently,
|
||||
// which will allow all of our clients to be processed concurrently.
|
||||
tokio::spawn(async move {
|
||||
// We're parsing each socket with the `BytesCodec` included in `tokio::codec`.
|
||||
let mut framed = BytesCodec::new().framed(socket);
|
||||
|
||||
// We loop while there are messages coming from the Stream `framed`.
|
||||
// The stream will return None once the client disconnects.
|
||||
while let Some(message) = framed.next().await {
|
||||
match message {
|
||||
Ok(bytes) => println!("bytes: {:?}", bytes),
|
||||
Err(err) => println!("Socket closed with error: {:?}", err),
|
||||
}
|
||||
}
|
||||
println!("Socket received FIN packet and closed connection");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
//! A proxy that forwards data to another server and forwards that server's
|
||||
//! responses back to clients.
|
||||
//!
|
||||
//! Because the Tokio runtime uses a thread pool, each TCP connection is
|
||||
//! processed concurrently with all other TCP connections across multiple
|
||||
//! threads.
|
||||
//!
|
||||
//! You can showcase this by running this in one terminal:
|
||||
//!
|
||||
//! cargo run --example proxy
|
||||
//!
|
||||
//! This in another terminal
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//!
|
||||
//! And finally this in another terminal
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8081
|
||||
//!
|
||||
//! This final terminal will connect to our proxy, which will in turn connect to
|
||||
//! the echo server, and you'll be able to see data flowing between them.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use futures::future::try_join;
|
||||
use futures::FutureExt;
|
||||
use std::env;
|
||||
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());
|
||||
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
let mut listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(transfer);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<dyn Error>> {
|
||||
let mut outbound = TcpStream::connect(proxy_addr).await?;
|
||||
|
||||
let (mut ri, mut wi) = inbound.split();
|
||||
let (mut ro, mut wo) = outbound.split();
|
||||
|
||||
let client_to_server = io::copy(&mut ri, &mut wo);
|
||||
let server_to_client = io::copy(&mut ro, &mut wi);
|
||||
|
||||
try_join(client_to_server, server_to_client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
//! This example leverages `BytesCodec` to create a UDP client and server which
|
||||
//! speak a custom protocol.
|
||||
//!
|
||||
//! Here we're using the codec from `tokio-codec` to convert a UDP socket to a stream of
|
||||
//! client messages. These messages are then processed and returned back as a
|
||||
//! new message with a new destination. Overall, we then use this to construct a
|
||||
//! "ping pong" pair where two sockets are sending messages back and forth.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::{io, time};
|
||||
use tokio_util::codec::BytesCodec;
|
||||
use tokio_util::udp::UdpFramed;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
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());
|
||||
|
||||
// Bind both our sockets and then figure out what ports we got.
|
||||
let a = UdpSocket::bind(&addr).await?;
|
||||
let b = UdpSocket::bind(&addr).await?;
|
||||
|
||||
let b_addr = b.local_addr()?;
|
||||
|
||||
let mut a = UdpFramed::new(a, BytesCodec::new());
|
||||
let mut b = UdpFramed::new(b, BytesCodec::new());
|
||||
|
||||
// Start off by sending a ping from a to b, afterwards we just print out
|
||||
// what they send us and continually send pings
|
||||
let a = ping(&mut a, b_addr);
|
||||
|
||||
// The second client we have will receive the pings from `a` and then send
|
||||
// back pongs.
|
||||
let b = pong(&mut b);
|
||||
|
||||
// Run both futures simultaneously of `a` and `b` sending messages back and forth.
|
||||
match futures::future::try_join(a, b).await {
|
||||
Err(e) => println!("an error occured; error = {:?}", e),
|
||||
_ => println!("done!"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn ping(socket: &mut UdpFramed<BytesCodec>, b_addr: SocketAddr) -> Result<(), io::Error> {
|
||||
socket.send((Bytes::from(&b"PING"[..]), b_addr)).await?;
|
||||
|
||||
for _ in 0..4usize {
|
||||
let (bytes, addr) = socket.next().map(|e| e.unwrap()).await?;
|
||||
|
||||
println!("[a] recv: {}", String::from_utf8_lossy(&bytes));
|
||||
|
||||
socket.send((Bytes::from(&b"PING"[..]), addr)).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn pong(socket: &mut UdpFramed<BytesCodec>) -> Result<(), io::Error> {
|
||||
let timeout = Duration::from_millis(200);
|
||||
|
||||
while let Ok(Some(Ok((bytes, addr)))) = time::timeout(timeout, socket.next()).await {
|
||||
println!("[b] recv: {}", String::from_utf8_lossy(&bytes));
|
||||
|
||||
socket.send((Bytes::from(&b"PONG"[..]), addr)).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
edition = "2018"
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "tests-build"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
full = ["tokio/full"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
@@ -1,2 +0,0 @@
|
||||
Tests the various combination of feature flags. This is broken out to a separate
|
||||
crate to work around limitations with cargo features.
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "tokio")]
|
||||
pub use tokio;
|
||||
@@ -1,25 +0,0 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
fn main_is_not_async() {}
|
||||
|
||||
#[tokio::main(foo)]
|
||||
async fn main_attr_has_unknown_args() {}
|
||||
|
||||
#[tokio::main(threadpool::bar)]
|
||||
async fn main_attr_has_path_args() {}
|
||||
|
||||
#[tokio::test]
|
||||
fn test_is_not_async() {}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_fn_has_args(_x: u8) {}
|
||||
|
||||
#[tokio::test(foo)]
|
||||
async fn test_attr_has_args() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[test]
|
||||
async fn test_has_second_test_attr() {}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,41 +0,0 @@
|
||||
error: the async keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:4:1
|
||||
|
|
||||
4 | fn main_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
--> $DIR/macros_invalid_input.rs:6:15
|
||||
|
|
||||
6 | #[tokio::main(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Must have specified ident
|
||||
--> $DIR/macros_invalid_input.rs:9:15
|
||||
|
|
||||
9 | #[tokio::main(threadpool::bar)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: the async keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:13:1
|
||||
|
|
||||
13 | fn test_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: the test function cannot accept arguments
|
||||
--> $DIR/macros_invalid_input.rs:16:27
|
||||
|
|
||||
16 | async fn test_fn_has_args(_x: u8) {}
|
||||
| ^^^^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
--> $DIR/macros_invalid_input.rs:18:15
|
||||
|
|
||||
18 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
|
||||
error: second test attribute is supplied
|
||||
--> $DIR/macros_invalid_input.rs:22:1
|
||||
|
|
||||
22 | #[test]
|
||||
| ^^^^^^^
|
||||
@@ -1,9 +0,0 @@
|
||||
#[test]
|
||||
fn compile_fail() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
drop(t);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", features = ["full"] }
|
||||
doc-comment = "0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = { path = "../tokio-test" }
|
||||
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
@@ -1 +0,0 @@
|
||||
Tests that require additional components than just the `tokio` crate.
|
||||
@@ -1,20 +0,0 @@
|
||||
//! A cat-like utility that can be used as a subprocess to test I/O
|
||||
//! stream communication.
|
||||
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
fn main() {
|
||||
let stdin = io::stdin();
|
||||
let mut stdout = io::stdout();
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
line.clear();
|
||||
stdin.read_line(&mut line).unwrap();
|
||||
if line.is_empty() {
|
||||
break;
|
||||
}
|
||||
stdout.write_all(line.as_bytes()).unwrap();
|
||||
}
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
use doc_comment::doc_comment;
|
||||
|
||||
// #[doc = include_str!("../../README.md")]
|
||||
doc_comment!(include_str!("../../README.md"));
|
||||
@@ -1,126 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio_test::assert_ok;
|
||||
|
||||
use futures::future::{self, FutureExt};
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
|
||||
fn cat() -> Command {
|
||||
let mut me = env::current_exe().unwrap();
|
||||
me.pop();
|
||||
|
||||
if me.ends_with("deps") {
|
||||
me.pop();
|
||||
}
|
||||
|
||||
me.push("test-cat");
|
||||
|
||||
let mut cmd = Command::new(me);
|
||||
cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
cmd
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Produce n lines on the child's stdout.
|
||||
let write = async {
|
||||
for i in 0..n {
|
||||
let bytes = format!("line {}\n", i).into_bytes();
|
||||
stdin.write_all(&bytes).await.unwrap();
|
||||
}
|
||||
|
||||
drop(stdin);
|
||||
};
|
||||
|
||||
let read = async {
|
||||
let mut reader = BufReader::new(stdout).lines();
|
||||
let mut num_lines = 0;
|
||||
|
||||
// Try to read `n + 1` lines, ensuring the last one is empty
|
||||
// (i.e. EOF is reached after `n` lines.
|
||||
loop {
|
||||
let data = reader
|
||||
.next_line()
|
||||
.await
|
||||
.unwrap_or_else(|_| Some(String::new()))
|
||||
.expect("failed to read line");
|
||||
|
||||
let num_read = data.len();
|
||||
let done = num_lines >= n;
|
||||
|
||||
match (done, num_read) {
|
||||
(false, 0) => panic!("broken pipe"),
|
||||
(true, n) if n != 0 => panic!("extraneous data"),
|
||||
_ => {
|
||||
let expected = format!("line {}", num_lines);
|
||||
assert_eq!(expected, data);
|
||||
}
|
||||
};
|
||||
|
||||
num_lines += 1;
|
||||
if num_lines >= n {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Compose reading and writing concurrently.
|
||||
future::join3(write, read, cat)
|
||||
.map(|(_, _, status)| status)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Check for the following properties when feeding stdin and
|
||||
/// consuming stdout of a cat-like process:
|
||||
///
|
||||
/// - A number of lines that amounts to a number of bytes exceeding a
|
||||
/// typical OS buffer size can be fed to the child without
|
||||
/// deadlock. This tests that we also consume the stdout
|
||||
/// concurrently; otherwise this would deadlock.
|
||||
///
|
||||
/// - We read the same lines from the child that we fed it.
|
||||
///
|
||||
/// - The child does produce EOF on stdout after the last line.
|
||||
#[tokio::test]
|
||||
async fn feed_a_lot() {
|
||||
let child = cat().spawn().unwrap();
|
||||
let status = feed_cat(child, 10000).await.unwrap();
|
||||
assert_eq!(status.code(), Some(0));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn wait_with_output_captures() {
|
||||
let mut child = cat().spawn().unwrap();
|
||||
let mut stdin = child.stdin().take().unwrap();
|
||||
|
||||
let write_bytes = b"1234";
|
||||
|
||||
let future = async {
|
||||
stdin.write_all(write_bytes).await?;
|
||||
drop(stdin);
|
||||
let out = child.wait_with_output();
|
||||
out.await
|
||||
};
|
||||
|
||||
let output = future.await.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
assert_eq!(output.stdout, write_bytes);
|
||||
assert_eq!(output.stderr.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_closes_any_pipes() {
|
||||
// Cat will open a pipe between the parent and child.
|
||||
// If `status_async` doesn't ensure the handles are closed,
|
||||
// we would end up blocking forever (and time out).
|
||||
let child = cat().status();
|
||||
|
||||
assert_ok!(child.await);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# 0.1.1 (April 22, 2019)
|
||||
|
||||
### Added
|
||||
- Utilities for creating a `BufStream` from iterators and streams (#1011).
|
||||
- Add `BufStream::into_stream` (#1048).
|
||||
- Implement `FromBufStream` for `Bytes` (#1009).
|
||||
- Implement `Error` for `CollectVecError` (#1010).
|
||||
|
||||
### Fixed
|
||||
- Implement `size_hint` for string types (#1012).
|
||||
|
||||
# 0.1.0 (February 23, 2019)
|
||||
|
||||
* Initial release
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "tokio-buf"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.1"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-buf/0.1.1/tokio_buf"
|
||||
description = """
|
||||
Asynchronous stream of byte buffers
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
bytes = "0.4.10"
|
||||
either = { version = "1.5", optional = true}
|
||||
futures = "0.1.23"
|
||||
|
||||
[features]
|
||||
default = ["util"]
|
||||
util = ["bytes/either", "either"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-mock-task = "0.1.1"
|
||||
@@ -0,0 +1,35 @@
|
||||
# tokio-buf
|
||||
|
||||
Asynchronous stream of byte buffers
|
||||
|
||||
[Documenation](https://docs.rs/tokio-buf)
|
||||
|
||||
## Usage
|
||||
|
||||
First, add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio-buf = "0.1.1"
|
||||
```
|
||||
|
||||
Next, add this to your crate:
|
||||
|
||||
```rust
|
||||
extern crate tokio_buf;
|
||||
```
|
||||
|
||||
You can find extensive documentation and examples about how to use this crate
|
||||
online at [https://tokio.rs](https://tokio.rs). The [API
|
||||
documentation](https://docs.rs/tokio-buf) is also a great place to get started
|
||||
for the nitty-gritty.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -0,0 +1,98 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-buf/0.1.1")]
|
||||
#![deny(missing_docs, missing_debug_implementations, unreachable_pub)]
|
||||
|
||||
//! Asynchronous stream of bytes.
|
||||
//!
|
||||
//! This crate contains the `BufStream` trait and a number of combinators for
|
||||
//! this trait. The trait is similar to `Stream` in the `futures` library, but
|
||||
//! instead of yielding arbitrary values, it only yields types that implement
|
||||
//! `Buf` (i.e, byte collections).
|
||||
|
||||
extern crate bytes;
|
||||
#[cfg(feature = "util")]
|
||||
extern crate either;
|
||||
#[allow(unused)]
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
|
||||
mod never;
|
||||
mod size_hint;
|
||||
mod str;
|
||||
mod u8;
|
||||
#[cfg(feature = "util")]
|
||||
pub mod util;
|
||||
|
||||
pub use self::size_hint::SizeHint;
|
||||
#[doc(inline)]
|
||||
#[cfg(feature = "util")]
|
||||
pub use util::BufStreamExt;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::Poll;
|
||||
|
||||
/// An asynchronous stream of bytes.
|
||||
///
|
||||
/// `BufStream` asynchronously yields values implementing `Buf`, i.e. byte
|
||||
/// buffers.
|
||||
pub trait BufStream {
|
||||
/// Values yielded by the `BufStream`.
|
||||
///
|
||||
/// Each item is a sequence of bytes representing a chunk of the total
|
||||
/// `ByteStream`.
|
||||
type Item: Buf;
|
||||
|
||||
/// The error type this `BufStream` might generate.
|
||||
type Error;
|
||||
|
||||
/// Attempt to pull out the next buffer of this stream, registering the
|
||||
/// current task for wakeup if the value is not yet available, and returning
|
||||
/// `None` if the stream is exhausted.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// There are several possible return values, each indicating a distinct
|
||||
/// stream state:
|
||||
///
|
||||
/// - `Ok(Async::NotReady)` means that this stream's next value is not ready
|
||||
/// yet. Implementations will ensure that the current task will be notified
|
||||
/// when the next value may be ready.
|
||||
///
|
||||
/// - `Ok(Async::Ready(Some(buf)))` means that the stream has successfully
|
||||
/// produced a value, `buf`, and may produce further values on subsequent
|
||||
/// `poll_buf` calls.
|
||||
///
|
||||
/// - `Ok(Async::Ready(None))` means that the stream has terminated, and
|
||||
/// `poll_buf` should not be invoked again.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Once a stream is finished, i.e. `Ready(None)` has been returned, further
|
||||
/// calls to `poll_buf` may result in a panic or other "bad behavior".
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error>;
|
||||
|
||||
/// Returns the bounds on the remaining length of the stream.
|
||||
///
|
||||
/// The size hint allows the caller to perform certain optimizations that
|
||||
/// are dependent on the byte stream size. For example, `collect` uses the
|
||||
/// size hint to pre-allocate enough capacity to store the entirety of the
|
||||
/// data received from the byte stream.
|
||||
///
|
||||
/// When `SizeHint::upper()` returns `Some` with a value equal to
|
||||
/// `SizeHint::lower()`, this represents the exact number of bytes that will
|
||||
/// be yielded by the `BufStream`.
|
||||
///
|
||||
/// # Implementation notes
|
||||
///
|
||||
/// While not enforced, implementations are expected to respect the values
|
||||
/// returned from `SizeHint`. Any deviation is considered an implementation
|
||||
/// bug. Consumers may rely on correctness in order to use the value as part
|
||||
/// of protocol impelmentations. For example, an HTTP library may use the
|
||||
/// size hint to set the `content-length` header.
|
||||
///
|
||||
/// However, `size_hint` must not be trusted to omit bounds checks in unsafe
|
||||
/// code. An incorrect implementation of `size_hint()` must not lead to
|
||||
/// memory safety violations.
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
SizeHint::default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use std::{error, fmt};
|
||||
|
||||
/// An error that can never occur
|
||||
pub enum Never {}
|
||||
|
||||
impl fmt::Debug for Never {
|
||||
fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Never {
|
||||
fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Never {
|
||||
fn description(&self) -> &str {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::u64;
|
||||
|
||||
/// A `BufStream` size hint
|
||||
///
|
||||
/// The default implementation returns:
|
||||
///
|
||||
/// * 0 for `available`
|
||||
/// * 0 for `lower`
|
||||
/// * `None` for `upper`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SizeHint {
|
||||
lower: u64,
|
||||
upper: Option<u64>,
|
||||
}
|
||||
|
||||
impl SizeHint {
|
||||
/// Returns a new `SizeHint` with default values
|
||||
pub fn new() -> SizeHint {
|
||||
SizeHint::default()
|
||||
}
|
||||
|
||||
/// Returns the lower bound of data that the `BufStream` will yield before
|
||||
/// completing.
|
||||
pub fn lower(&self) -> u64 {
|
||||
self.lower
|
||||
}
|
||||
|
||||
/// Set the value of the `lower` hint.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The function panics if `value` is greater than `upper`.
|
||||
pub fn set_lower(&mut self, value: u64) {
|
||||
assert!(value <= self.upper.unwrap_or(u64::MAX));
|
||||
self.lower = value;
|
||||
}
|
||||
|
||||
/// Returns the upper bound of data the `BufStream` will yield before
|
||||
/// completing, or `None` if the value is unknown.
|
||||
pub fn upper(&self) -> Option<u64> {
|
||||
self.upper
|
||||
}
|
||||
|
||||
/// Set the value of the `upper` hint value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `value` is less than `lower`.
|
||||
pub fn set_upper(&mut self, value: u64) {
|
||||
// There is no need to check `available` as that is guaranteed to be
|
||||
// less than or equal to `lower`.
|
||||
assert!(value >= self.lower, "`value` is less than than `lower`");
|
||||
|
||||
self.upper = Some(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use never::Never;
|
||||
use BufStream;
|
||||
use SizeHint;
|
||||
|
||||
use futures::Poll;
|
||||
|
||||
use std::io;
|
||||
use std::mem;
|
||||
|
||||
impl BufStream for String {
|
||||
type Item = io::Cursor<Vec<u8>>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
let bytes = mem::replace(self, Default::default()).into_bytes();
|
||||
let buf = io::Cursor::new(bytes);
|
||||
|
||||
Ok(Some(buf).into())
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
size_hint(&self[..])
|
||||
}
|
||||
}
|
||||
|
||||
impl BufStream for &'static str {
|
||||
type Item = io::Cursor<&'static [u8]>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
let bytes = mem::replace(self, Default::default()).as_bytes();
|
||||
let buf = io::Cursor::new(bytes);
|
||||
|
||||
Ok(Some(buf).into())
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
size_hint(&self[..])
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(s: &str) -> SizeHint {
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_lower(s.len() as u64);
|
||||
hint.set_upper(s.len() as u64);
|
||||
hint
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::Poll;
|
||||
use never::Never;
|
||||
use std::io;
|
||||
use BufStream;
|
||||
|
||||
impl BufStream for Vec<u8> {
|
||||
type Item = io::Cursor<Vec<u8>>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
poll_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufStream for &'static [u8] {
|
||||
type Item = io::Cursor<&'static [u8]>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
poll_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufStream for Bytes {
|
||||
type Item = io::Cursor<Bytes>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
poll_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufStream for BytesMut {
|
||||
type Item = io::Cursor<BytesMut>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
poll_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_bytes<T: Default>(buf: &mut T) -> Poll<Option<io::Cursor<T>>, Never> {
|
||||
use std::mem;
|
||||
|
||||
let bytes = mem::replace(buf, Default::default());
|
||||
let buf = io::Cursor::new(bytes);
|
||||
|
||||
Ok(Some(buf).into())
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use BufStream;
|
||||
|
||||
use either::Either;
|
||||
use futures::Poll;
|
||||
|
||||
/// A buf stream that sequences two buf streams together.
|
||||
///
|
||||
/// `Chain` values are produced by the `chain` function on `BufStream`.
|
||||
#[derive(Debug)]
|
||||
pub struct Chain<T, U> {
|
||||
left: Option<T>,
|
||||
right: U,
|
||||
}
|
||||
|
||||
impl<T, U> Chain<T, U> {
|
||||
pub(crate) fn new(left: T, right: U) -> Chain<T, U> {
|
||||
Chain {
|
||||
left: Some(left),
|
||||
right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> BufStream for Chain<T, U>
|
||||
where
|
||||
T: BufStream,
|
||||
U: BufStream<Error = T::Error>,
|
||||
{
|
||||
type Item = Either<T::Item, U::Item>;
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if let Some(ref mut stream) = self.left {
|
||||
let res = try_ready!(stream.poll_buf());
|
||||
|
||||
if res.is_some() {
|
||||
return Ok(res.map(Either::Left).into());
|
||||
}
|
||||
}
|
||||
|
||||
self.left = None;
|
||||
|
||||
let res = try_ready!(self.right.poll_buf());
|
||||
Ok(res.map(Either::Right).into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use super::FromBufStream;
|
||||
use BufStream;
|
||||
|
||||
use futures::{Future, Poll};
|
||||
|
||||
/// Consumes a buf stream, collecting the data into a single byte container.
|
||||
///
|
||||
/// `Collect` values are produced by `BufStream::collect`.
|
||||
#[derive(Debug)]
|
||||
pub struct Collect<T, U>
|
||||
where
|
||||
T: BufStream,
|
||||
U: FromBufStream<T::Item>,
|
||||
{
|
||||
stream: T,
|
||||
builder: Option<U::Builder>,
|
||||
}
|
||||
|
||||
/// Errors returned from `Collect` future.
|
||||
#[derive(Debug)]
|
||||
pub struct CollectError<T, U> {
|
||||
inner: Error<T, U>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error<T, U> {
|
||||
Stream(T),
|
||||
Collect(U),
|
||||
}
|
||||
|
||||
impl<T, U> Collect<T, U>
|
||||
where
|
||||
T: BufStream,
|
||||
U: FromBufStream<T::Item>,
|
||||
{
|
||||
pub(crate) fn new(stream: T) -> Collect<T, U> {
|
||||
let builder = U::builder(&stream.size_hint());
|
||||
|
||||
Collect {
|
||||
stream,
|
||||
builder: Some(builder),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Future for Collect<T, U>
|
||||
where
|
||||
T: BufStream,
|
||||
U: FromBufStream<T::Item>,
|
||||
{
|
||||
type Item = U;
|
||||
type Error = CollectError<T::Error, U::Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
let res = self.stream.poll_buf().map_err(|err| {
|
||||
let inner = Error::Stream(err);
|
||||
CollectError { inner }
|
||||
});
|
||||
|
||||
match try_ready!(res) {
|
||||
Some(mut buf) => {
|
||||
let builder = self.builder.as_mut().expect("cannot poll after done");
|
||||
|
||||
U::extend(builder, &mut buf, &self.stream.size_hint()).map_err(|err| {
|
||||
let inner = Error::Collect(err);
|
||||
CollectError { inner }
|
||||
})?;
|
||||
}
|
||||
None => {
|
||||
let builder = self.builder.take().expect("cannot poll after done");
|
||||
let value = U::build(builder).map_err(|err| {
|
||||
let inner = Error::Collect(err);
|
||||
CollectError { inner }
|
||||
})?;
|
||||
return Ok(value.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl CollectError =====
|
||||
|
||||
impl<T, U> CollectError<T, U> {
|
||||
/// Returns `true` if the error was caused by polling the stream.
|
||||
pub fn is_stream_err(&self) -> bool {
|
||||
match self.inner {
|
||||
Error::Stream(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the error happened while collecting the data.
|
||||
pub fn is_collect_err(&self) -> bool {
|
||||
match self.inner {
|
||||
Error::Collect(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
use SizeHint;
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes};
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::usize;
|
||||
|
||||
/// Conversion from a `BufStream`.
|
||||
///
|
||||
/// By implementing `FromBufStream` for a type, you define how it will be
|
||||
/// created from a buf stream. This is common for types which describe byte
|
||||
/// storage of some kind.
|
||||
///
|
||||
/// `FromBufStream` is rarely called explicitly, and it is instead used through
|
||||
/// `BufStream`'s `collect` method.
|
||||
pub trait FromBufStream<T: Buf>: Sized {
|
||||
/// Type that is used to build `Self` while the `BufStream` is being
|
||||
/// consumed.
|
||||
type Builder;
|
||||
|
||||
/// Error that might happen on conversion.
|
||||
type Error;
|
||||
|
||||
/// Create a new, empty, builder. The provided `hint` can be used to inform
|
||||
/// reserving capacity.
|
||||
fn builder(hint: &SizeHint) -> Self::Builder;
|
||||
|
||||
/// Extend the builder with the `Buf`.
|
||||
///
|
||||
/// This method is called whenever a new `Buf` value is obtained from the
|
||||
/// buf stream.
|
||||
///
|
||||
/// The provided size hint represents the state of the stream **after**
|
||||
/// `buf` has been yielded. The lower bound represents the minimum amount of
|
||||
/// data that will be provided after this call to `extend` returns.
|
||||
fn extend(builder: &mut Self::Builder, buf: &mut T, hint: &SizeHint)
|
||||
-> Result<(), Self::Error>;
|
||||
|
||||
/// Finalize the building of `Self`.
|
||||
///
|
||||
/// Called once the buf stream is fully consumed.
|
||||
fn build(builder: Self::Builder) -> Result<Self, Self::Error>;
|
||||
}
|
||||
|
||||
/// Error returned from collecting into a `Vec<u8>`
|
||||
#[derive(Debug)]
|
||||
pub struct CollectVecError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
/// Error returned from collecting into a `Bytes`
|
||||
#[derive(Debug)]
|
||||
pub struct CollectBytesError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
impl<T: Buf> FromBufStream<T> for Vec<u8> {
|
||||
type Builder = Vec<u8>;
|
||||
type Error = CollectVecError;
|
||||
|
||||
fn builder(hint: &SizeHint) -> Vec<u8> {
|
||||
Vec::with_capacity(hint.lower() as usize)
|
||||
}
|
||||
|
||||
fn extend(builder: &mut Self, buf: &mut T, hint: &SizeHint) -> Result<(), Self::Error> {
|
||||
let lower = hint.lower();
|
||||
|
||||
// If the lower bound is greater than `usize::MAX` then we have a
|
||||
// problem
|
||||
if lower > usize::MAX as u64 {
|
||||
return Err(CollectVecError { _p: () });
|
||||
}
|
||||
|
||||
let mut reserve = lower as usize;
|
||||
|
||||
// If `upper` is set, use this value if it is less than or equal to 64.
|
||||
// This only really impacts the first iteration.
|
||||
match hint.upper() {
|
||||
Some(upper) if upper <= 64 => {
|
||||
reserve = upper as usize;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// hint.lower() represents the minimum amount of data that will be
|
||||
// received *after* this function call. We reserve this amount on top of
|
||||
// the amount of data in `buf`.
|
||||
reserve = match reserve.checked_add(buf.remaining()) {
|
||||
Some(n) => n,
|
||||
None => return Err(CollectVecError { _p: () }),
|
||||
};
|
||||
|
||||
// Always reserve 64 bytes the first time, unless `upper` is set and is
|
||||
// less than 64.
|
||||
if builder.is_empty() {
|
||||
reserve = reserve.max(match hint.upper() {
|
||||
Some(upper) if upper < 64 => upper as usize,
|
||||
_ => 64,
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure overflow won't happen when reserving
|
||||
if reserve.checked_add(builder.len()).is_none() {
|
||||
return Err(CollectVecError { _p: () });
|
||||
}
|
||||
|
||||
// Reserve space
|
||||
builder.reserve(reserve);
|
||||
|
||||
// Copy the data
|
||||
builder.put(buf);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(builder: Self) -> Result<Self, Self::Error> {
|
||||
Ok(builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Buf> FromBufStream<T> for Bytes {
|
||||
type Builder = Vec<u8>;
|
||||
type Error = CollectBytesError;
|
||||
|
||||
fn builder(hint: &SizeHint) -> Vec<u8> {
|
||||
<Vec<u8> as FromBufStream<T>>::builder(hint)
|
||||
}
|
||||
|
||||
fn extend(builder: &mut Vec<u8>, buf: &mut T, hint: &SizeHint) -> Result<(), Self::Error> {
|
||||
<Vec<u8> as FromBufStream<T>>::extend(builder, buf, hint)
|
||||
.map_err(|_| CollectBytesError { _p: () })
|
||||
}
|
||||
|
||||
fn build(builder: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
Ok(builder.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CollectVecError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "BufStream is too big")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CollectVecError {
|
||||
fn description(&self) -> &str {
|
||||
"BufStream too big"
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CollectBytesError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "BufStream too big")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CollectBytesError {
|
||||
fn description(&self) -> &str {
|
||||
"BufStream too big"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use bytes::Buf;
|
||||
use futures::Poll;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use BufStream;
|
||||
|
||||
/// Converts an `Iterator` into a `BufStream` which is always ready to yield the
|
||||
/// next value.
|
||||
///
|
||||
/// Iterators in Rust don't express the ability to block, so this adapter
|
||||
/// simply always calls `iter.next()` and returns that.
|
||||
pub fn iter<I>(i: I) -> Iter<I::IntoIter>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Buf,
|
||||
{
|
||||
Iter {
|
||||
iter: i.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// `BufStream` returned by the [`iter`] function.
|
||||
#[derive(Debug)]
|
||||
pub struct Iter<I> {
|
||||
iter: I,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Never {}
|
||||
|
||||
impl<I> BufStream for Iter<I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: Buf,
|
||||
{
|
||||
type Item = I::Item;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
Ok(self.iter.next().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Never {
|
||||
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Never {
|
||||
fn description(&self) -> &str {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use BufStream;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::Poll;
|
||||
|
||||
/// Limits the stream to a maximum amount of data.
|
||||
#[derive(Debug)]
|
||||
pub struct Limit<T> {
|
||||
stream: T,
|
||||
remaining: u64,
|
||||
}
|
||||
|
||||
/// Errors returned from `Limit`.
|
||||
#[derive(Debug)]
|
||||
pub struct LimitError<T> {
|
||||
/// When `None`, limit was reached
|
||||
inner: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Limit<T> {
|
||||
pub(crate) fn new(stream: T, amount: u64) -> Limit<T> {
|
||||
Limit {
|
||||
stream,
|
||||
remaining: amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BufStream for Limit<T>
|
||||
where
|
||||
T: BufStream,
|
||||
{
|
||||
type Item = T::Item;
|
||||
type Error = LimitError<T::Error>;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
use futures::Async::Ready;
|
||||
|
||||
if self.stream.size_hint().lower() > self.remaining {
|
||||
return Err(LimitError { inner: None });
|
||||
}
|
||||
|
||||
let res = self
|
||||
.stream
|
||||
.poll_buf()
|
||||
.map_err(|err| LimitError { inner: Some(err) });
|
||||
|
||||
match res {
|
||||
Ok(Ready(Some(ref buf))) => {
|
||||
if buf.remaining() as u64 > self.remaining {
|
||||
self.remaining = 0;
|
||||
return Err(LimitError { inner: None });
|
||||
}
|
||||
|
||||
self.remaining -= buf.remaining() as u64;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl LimitError =====
|
||||
|
||||
impl<T> LimitError<T> {
|
||||
/// Returns `true` if the error was caused by polling the stream.
|
||||
pub fn is_stream_err(&self) -> bool {
|
||||
self.inner.is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if the stream reached its limit.
|
||||
pub fn is_limit_err(&self) -> bool {
|
||||
self.inner.is_none()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//! Types and utilities for working with `BufStream`.
|
||||
|
||||
mod chain;
|
||||
mod collect;
|
||||
mod from;
|
||||
mod iter;
|
||||
mod limit;
|
||||
mod stream;
|
||||
|
||||
pub use self::chain::Chain;
|
||||
pub use self::collect::Collect;
|
||||
pub use self::from::FromBufStream;
|
||||
pub use self::iter::iter;
|
||||
pub use self::limit::Limit;
|
||||
pub use self::stream::{stream, IntoStream};
|
||||
|
||||
pub mod error {
|
||||
//! Error types
|
||||
|
||||
pub use super::collect::CollectError;
|
||||
pub use super::from::{CollectBytesError, CollectVecError};
|
||||
pub use super::limit::LimitError;
|
||||
}
|
||||
|
||||
use BufStream;
|
||||
|
||||
impl<T> BufStreamExt for T where T: BufStream {}
|
||||
|
||||
/// An extension trait for `BufStream`'s that provides a variety of convenient
|
||||
/// adapters.
|
||||
pub trait BufStreamExt: BufStream {
|
||||
/// Takes two buf streams and creates a new buf stream over both in
|
||||
/// sequence.
|
||||
///
|
||||
/// `chain()` returns a new `BufStream` value which will first yield all
|
||||
/// data from `self` then all data from `other`.
|
||||
///
|
||||
/// In other words, it links two buf streams together, in a chain.
|
||||
fn chain<T>(self, other: T) -> Chain<Self, T>
|
||||
where
|
||||
Self: Sized,
|
||||
T: BufStream<Error = Self::Error>,
|
||||
{
|
||||
Chain::new(self, other)
|
||||
}
|
||||
|
||||
/// Consumes all data from `self`, storing it in byte storage of type `T`.
|
||||
///
|
||||
/// `collect()` returns a future that buffers all data yielded from `self`
|
||||
/// into storage of type of `T`. The future completes once `self` yield
|
||||
/// `None`, returning the buffered data.
|
||||
///
|
||||
/// The collect future will yield an error if `self` yields an error or if
|
||||
/// the collect operation errors. The collect error cases are dependent on
|
||||
/// the target storage type.
|
||||
fn collect<T>(self) -> Collect<Self, T>
|
||||
where
|
||||
Self: Sized,
|
||||
T: FromBufStream<Self::Item>,
|
||||
{
|
||||
Collect::new(self)
|
||||
}
|
||||
|
||||
/// Limit the number of bytes that the stream can yield.
|
||||
///
|
||||
/// `limit()` returns a new `BufStream` value which yields all the data from
|
||||
/// `self` while ensuring that at most `amount` bytes are yielded.
|
||||
///
|
||||
/// If `self` can yield greater than `amount` bytes, the returned stream
|
||||
/// will yield an error.
|
||||
fn limit(self, amount: u64) -> Limit<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Limit::new(self, amount)
|
||||
}
|
||||
|
||||
/// Creates a `Stream` from a `BufStream`.
|
||||
///
|
||||
/// This produces a `Stream` of `BufStream::Items`.
|
||||
fn into_stream(self) -> IntoStream<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
IntoStream::new(self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use bytes::Buf;
|
||||
use futures::{Async, Poll, Stream};
|
||||
use BufStream;
|
||||
|
||||
/// Converts a `Stream` of `Buf` types into a `BufStream`.
|
||||
///
|
||||
/// While `Stream` and `BufStream` are very similar, they are not identical. The
|
||||
/// `stream` function returns a `BufStream` that is backed by the provided
|
||||
/// `Stream` type.
|
||||
pub fn stream<T>(stream: T) -> FromStream<T>
|
||||
where
|
||||
T: Stream,
|
||||
T::Item: Buf,
|
||||
{
|
||||
FromStream { stream }
|
||||
}
|
||||
|
||||
/// `BufStream` returned by the [`stream`] function.
|
||||
#[derive(Debug)]
|
||||
pub struct FromStream<T> {
|
||||
stream: T,
|
||||
}
|
||||
|
||||
impl<T> BufStream for FromStream<T>
|
||||
where
|
||||
T: Stream,
|
||||
T::Item: Buf,
|
||||
{
|
||||
type Item = T::Item;
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
self.stream.poll()
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `BufStream` into a `Stream`.
|
||||
#[derive(Debug)]
|
||||
pub struct IntoStream<T> {
|
||||
buf: T,
|
||||
}
|
||||
|
||||
impl<T> IntoStream<T> {
|
||||
/// Create a new `Stream` from the provided `BufStream`.
|
||||
pub fn new(buf: T) -> Self {
|
||||
IntoStream { buf }
|
||||
}
|
||||
|
||||
/// Get a reference to the inner `BufStream`.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.buf
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the inner `BufStream`
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.buf
|
||||
}
|
||||
|
||||
/// Get the inner `BufStream`.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BufStream> Stream for IntoStream<T> {
|
||||
type Item = T::Item;
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
match self.buf.poll_buf()? {
|
||||
Async::Ready(Some(buf)) => Ok(Async::Ready(Some(buf))),
|
||||
Async::Ready(None) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
extern crate tokio_buf;
|
||||
|
||||
use tokio_buf::BufStream;
|
||||
|
||||
// Ensures that `BufStream` can be a trait object
|
||||
#[allow(dead_code)]
|
||||
fn obj(_: &mut dyn BufStream<Item = u32, Error = ()>) {}
|
||||
@@ -0,0 +1,43 @@
|
||||
#![cfg(feature = "util")]
|
||||
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use futures::Async::*;
|
||||
use tokio_buf::{BufStream, BufStreamExt};
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
use support::*;
|
||||
|
||||
#[test]
|
||||
fn chain() {
|
||||
// Chain one with one
|
||||
//
|
||||
let mut bs = one("hello").chain(one("world"));
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "hello");
|
||||
assert_buf_eq!(bs.poll_buf(), "world");
|
||||
assert_none!(bs.poll_buf());
|
||||
|
||||
// Chain multi with multi
|
||||
let mut bs = list(&["foo", "bar"]).chain(list(&["baz", "bok"]));
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "foo");
|
||||
assert_buf_eq!(bs.poll_buf(), "bar");
|
||||
assert_buf_eq!(bs.poll_buf(), "baz");
|
||||
assert_buf_eq!(bs.poll_buf(), "bok");
|
||||
assert_none!(bs.poll_buf());
|
||||
|
||||
// Chain includes a not ready call
|
||||
//
|
||||
let mut bs = new_mock(&[Ok(Ready("foo")), Ok(NotReady), Ok(Ready("bar"))]).chain(one("baz"));
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "foo");
|
||||
assert_not_ready!(bs.poll_buf());
|
||||
assert_buf_eq!(bs.poll_buf(), "bar");
|
||||
assert_buf_eq!(bs.poll_buf(), "baz");
|
||||
assert_none!(bs.poll_buf());
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#![cfg(feature = "util")]
|
||||
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::Future;
|
||||
use tokio_buf::BufStreamExt;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
use support::*;
|
||||
|
||||
macro_rules! test_collect_impl {
|
||||
($t:ty $(, $capacity:ident)*) => {
|
||||
// While unfortunate, this test makes some assumptions on vec's resizing
|
||||
// behavior.
|
||||
//
|
||||
// Collect one
|
||||
//
|
||||
let bs = one("hello world");
|
||||
|
||||
let vec: $t = bs.collect().wait().unwrap();
|
||||
|
||||
assert_eq!(vec, &b"hello world"[..]);
|
||||
$( assert_eq!(vec.$capacity(), 64); )*
|
||||
|
||||
// Collect one, with size hint
|
||||
//
|
||||
let mut bs = one("hello world");
|
||||
bs.size_hint.set_lower(11);
|
||||
|
||||
let vec: $t = bs.collect().wait().unwrap();
|
||||
|
||||
assert_eq!(vec, &b"hello world"[..]);
|
||||
$( assert_eq!(vec.$capacity(), 64); )*
|
||||
|
||||
// Collect one, with size hint
|
||||
//
|
||||
let mut bs = one("hello world");
|
||||
bs.size_hint.set_lower(10);
|
||||
|
||||
let vec: $t = bs.collect().wait().unwrap();
|
||||
|
||||
assert_eq!(vec, &b"hello world"[..]);
|
||||
$( assert_eq!(vec.$capacity(), 64); )*
|
||||
|
||||
// Collect many
|
||||
//
|
||||
let bs = list(&["hello", " ", "world", ", one two three"]);
|
||||
|
||||
let vec: $t = bs.collect().wait().unwrap();
|
||||
|
||||
assert_eq!(vec, &b"hello world, one two three"[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_vec() {
|
||||
test_collect_impl!(Vec<u8>, capacity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_bytes() {
|
||||
test_collect_impl!(Bytes);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use futures::Async::*;
|
||||
use std::io::Cursor;
|
||||
use tokio_buf::{util, BufStream};
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
type Buf = Cursor<&'static [u8]>;
|
||||
|
||||
#[test]
|
||||
fn empty_iter() {
|
||||
let mut bs = util::iter(Vec::<Buf>::new());
|
||||
assert_none!(bs.poll_buf());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_iter() {
|
||||
let bufs = vec![buf(b"one"), buf(b"two"), buf(b"three")];
|
||||
|
||||
let mut bs = util::iter(bufs);
|
||||
assert_buf_eq!(bs.poll_buf(), "one");
|
||||
assert_buf_eq!(bs.poll_buf(), "two");
|
||||
assert_buf_eq!(bs.poll_buf(), "three");
|
||||
assert_none!(bs.poll_buf());
|
||||
}
|
||||
|
||||
fn buf(data: &'static [u8]) -> Buf {
|
||||
Cursor::new(data)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#![cfg(feature = "util")]
|
||||
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use futures::Async::*;
|
||||
use futures::Future;
|
||||
use tokio_buf::{BufStream, BufStreamExt};
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
use support::*;
|
||||
|
||||
#[test]
|
||||
fn limit() {
|
||||
// Not limited
|
||||
|
||||
let res = one("hello world")
|
||||
.limit(100)
|
||||
.collect::<Vec<_>>()
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res, b"hello world");
|
||||
|
||||
let res = list(&["hello", " ", "world"])
|
||||
.limit(100)
|
||||
.collect::<Vec<_>>()
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res, b"hello world");
|
||||
|
||||
let res = list(&["hello", " ", "world"])
|
||||
.limit(11)
|
||||
.collect::<Vec<_>>()
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res, b"hello world");
|
||||
|
||||
// Limited
|
||||
|
||||
let res = one("hello world").limit(5).collect::<Vec<_>>().wait();
|
||||
|
||||
assert!(res.is_err());
|
||||
|
||||
let res = one("hello world").limit(10).collect::<Vec<_>>().wait();
|
||||
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut bs = list(&["hello", " ", "world"]).limit(9);
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "hello");
|
||||
assert_buf_eq!(bs.poll_buf(), " ");
|
||||
assert!(bs.poll_buf().is_err());
|
||||
|
||||
let mut bs = list(&["hello", " ", "world"]);
|
||||
bs.size_hint.set_lower(11);
|
||||
let mut bs = bs.limit(9);
|
||||
|
||||
assert!(bs.poll_buf().is_err());
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
extern crate tokio_buf;
|
||||
|
||||
use tokio_buf::SizeHint;
|
||||
|
||||
#[test]
|
||||
fn size_hint() {
|
||||
let hint = SizeHint::new();
|
||||
assert_eq!(hint.lower(), 0);
|
||||
assert!(hint.upper().is_none());
|
||||
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_lower(100);
|
||||
assert_eq!(hint.lower(), 100);
|
||||
assert!(hint.upper().is_none());
|
||||
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_upper(200);
|
||||
assert_eq!(hint.lower(), 0);
|
||||
assert_eq!(hint.upper(), Some(200));
|
||||
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_lower(100);
|
||||
hint.set_upper(100);
|
||||
assert_eq!(hint.lower(), 100);
|
||||
assert_eq!(hint.upper(), Some(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn size_hint_lower_bigger_than_upper() {
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_upper(100);
|
||||
hint.set_lower(200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn size_hint_upper_less_than_lower() {
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_lower(200);
|
||||
hint.set_upper(100);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
extern crate tokio_mock_task;
|
||||
|
||||
use futures::sync::mpsc;
|
||||
use futures::Async::*;
|
||||
use std::io::Cursor;
|
||||
use tokio_buf::{util, BufStream};
|
||||
use tokio_mock_task::MockTask;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
type Buf = Cursor<&'static [u8]>;
|
||||
|
||||
#[test]
|
||||
fn empty_stream() {
|
||||
let (_, rx) = mpsc::unbounded::<Buf>();
|
||||
let mut bs = util::stream(rx);
|
||||
assert_none!(bs.poll_buf());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_stream() {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let mut bs = util::stream(rx);
|
||||
let mut task = MockTask::new();
|
||||
|
||||
tx.unbounded_send(buf(b"one")).unwrap();
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "one");
|
||||
task.enter(|| assert_not_ready!(bs.poll_buf()));
|
||||
|
||||
tx.unbounded_send(buf(b"two")).unwrap();
|
||||
|
||||
assert!(task.is_notified());
|
||||
assert_buf_eq!(bs.poll_buf(), "two");
|
||||
task.enter(|| assert_not_ready!(bs.poll_buf()));
|
||||
|
||||
drop(tx);
|
||||
|
||||
assert!(task.is_notified());
|
||||
assert_none!(bs.poll_buf());
|
||||
}
|
||||
|
||||
fn buf(data: &'static [u8]) -> Buf {
|
||||
Cursor::new(data)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use futures::Async::*;
|
||||
use std::fmt;
|
||||
use tokio_buf::BufStream;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
fn test_hello_world<B>(mut bs: B)
|
||||
where
|
||||
B: BufStream + fmt::Debug,
|
||||
B::Item: fmt::Debug,
|
||||
B::Error: fmt::Debug,
|
||||
{
|
||||
let hint = bs.size_hint();
|
||||
assert_eq!(hint.lower(), 11);
|
||||
assert_eq!(hint.upper(), Some(11));
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "hello world");
|
||||
|
||||
let hint = bs.size_hint();
|
||||
assert_eq!(hint.lower(), 0);
|
||||
assert_eq!(hint.upper(), Some(0));
|
||||
assert_none!(bs.poll_buf());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string() {
|
||||
test_hello_world("hello world".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str() {
|
||||
test_hello_world("hello world");
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#![allow(unused)]
|
||||
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::Async::*;
|
||||
use futures::Poll;
|
||||
use tokio_buf::{BufStream, SizeHint};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::Cursor;
|
||||
|
||||
macro_rules! assert_buf_eq {
|
||||
($actual:expr, $expect:expr) => {{
|
||||
use bytes::Buf;
|
||||
match $actual {
|
||||
Ok(Ready(Some(val))) => {
|
||||
assert_eq!(val.remaining(), val.bytes().len());
|
||||
assert_eq!(val.bytes(), $expect.as_bytes());
|
||||
}
|
||||
Ok(Ready(None)) => panic!("expected value; BufStream yielded None"),
|
||||
Ok(NotReady) => panic!("expected value; BufStream is not ready"),
|
||||
Err(e) => panic!("expected value; got error = {:?}", e),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_none {
|
||||
($actual:expr) => {
|
||||
match $actual {
|
||||
Ok(Ready(None)) => {}
|
||||
actual => panic!("expected None; actual = {:?}", actual),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_not_ready {
|
||||
($actual:expr) => {
|
||||
match $actual {
|
||||
Ok(NotReady) => {}
|
||||
actual => panic!("expected NotReady; actual = {:?}", actual),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ===== Test utils =====
|
||||
|
||||
pub fn one(buf: &'static str) -> Mock {
|
||||
list(&[buf])
|
||||
}
|
||||
|
||||
pub fn list(bufs: &[&'static str]) -> Mock {
|
||||
let mut polls = VecDeque::new();
|
||||
|
||||
for &buf in bufs {
|
||||
polls.push_back(Ok(Ready(buf.as_bytes())));
|
||||
}
|
||||
|
||||
Mock {
|
||||
polls,
|
||||
size_hint: SizeHint::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mock(values: &[Poll<&'static str, ()>]) -> Mock {
|
||||
let mut polls = VecDeque::new();
|
||||
|
||||
for &v in values {
|
||||
polls.push_back(match v {
|
||||
Ok(Ready(v)) => Ok(Ready(v.as_bytes())),
|
||||
Ok(NotReady) => Ok(NotReady),
|
||||
Err(e) => Err(e),
|
||||
});
|
||||
}
|
||||
|
||||
Mock {
|
||||
polls,
|
||||
size_hint: SizeHint::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mock {
|
||||
pub polls: VecDeque<Poll<&'static [u8], ()>>,
|
||||
pub size_hint: SizeHint,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockBuf {
|
||||
pub data: Cursor<&'static [u8]>,
|
||||
}
|
||||
|
||||
impl BufStream for Mock {
|
||||
type Item = MockBuf;
|
||||
type Error = ();
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
match self.polls.pop_front() {
|
||||
Some(Ok(Ready(value))) => Ok(Ready(Some(MockBuf::new(value)))),
|
||||
Some(Ok(NotReady)) => Ok(NotReady),
|
||||
Some(Err(e)) => Err(e),
|
||||
None => Ok(Ready(None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
self.size_hint.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl MockBuf {
|
||||
fn new(data: &'static [u8]) -> MockBuf {
|
||||
MockBuf {
|
||||
data: Cursor::new(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Buf for MockBuf {
|
||||
fn remaining(&self) -> usize {
|
||||
self.data.remaining()
|
||||
}
|
||||
|
||||
fn bytes(&self) -> &[u8] {
|
||||
self.data.bytes()
|
||||
}
|
||||
|
||||
fn advance(&mut self, cnt: usize) {
|
||||
self.data.advance(cnt)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# 0.1.1 (September 26, 2018)
|
||||
|
||||
* Allow setting max line length with `LinesCodec` (#632)
|
||||
|
||||
# 0.1.0 (June 13, 2018)
|
||||
|
||||
* Initial release (#353)
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "tokio-codec"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.1"
|
||||
authors = ["Carl Lerche <[email protected]>", "Bryan Burgers <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-codec/0.1.1/tokio_codec"
|
||||
description = """
|
||||
Utilities for encoding and decoding frames.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
tokio-io = "0.1.7"
|
||||
bytes = "0.4.7"
|
||||
futures = "0.1.18"
|
||||
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,35 @@
|
||||
# tokio-codec
|
||||
|
||||
Utilities for encoding and decoding frames.
|
||||
|
||||
[Documentation](https://docs.rs/tokio-codec)
|
||||
|
||||
## Usage
|
||||
|
||||
First, add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio-codec = "0.1"
|
||||
```
|
||||
|
||||
Next, add this to your crate:
|
||||
|
||||
```rust
|
||||
extern crate tokio_codec;
|
||||
```
|
||||
|
||||
You can find extensive documentation and examples about how to use this crate
|
||||
online at [https://tokio.rs](https://tokio.rs). The [API
|
||||
documentation](https://docs.rs/tokio-codec) is also a great place to get started
|
||||
for the nitty-gritty.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -1,11 +1,9 @@
|
||||
use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use std::io;
|
||||
use tokio_io::_tokio_codec::{Decoder, Encoder};
|
||||
|
||||
/// A simple `Codec` implementation that just ships bytes around.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct BytesCodec(());
|
||||
|
||||
impl BytesCodec {
|
||||
@@ -20,7 +18,7 @@ impl Decoder for BytesCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, io::Error> {
|
||||
if !buf.is_empty() {
|
||||
if buf.len() > 0 {
|
||||
let len = buf.len();
|
||||
Ok(Some(buf.split_to(len)))
|
||||
} else {
|
||||
@@ -0,0 +1,25 @@
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-codec/0.1.1")]
|
||||
|
||||
//! Utilities for encoding and decoding frames.
|
||||
//!
|
||||
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and
|
||||
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
|
||||
//! Framed streams are also known as [transports].
|
||||
//!
|
||||
//! [`AsyncRead`]: #
|
||||
//! [`AsyncWrite`]: #
|
||||
//! [`Sink`]: #
|
||||
//! [`Stream`]: #
|
||||
//! [transports]: #
|
||||
|
||||
extern crate bytes;
|
||||
extern crate tokio_io;
|
||||
|
||||
mod bytes_codec;
|
||||
mod lines_codec;
|
||||
|
||||
pub use tokio_io::_tokio_codec::{Decoder, Encoder, Framed, FramedParts, FramedRead, FramedWrite};
|
||||
|
||||
pub use bytes_codec::BytesCodec;
|
||||
pub use lines_codec::LinesCodec;
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use std::{cmp, fmt, io, str, usize};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use std::{cmp, io, str, usize};
|
||||
use tokio_io::_tokio_codec::{Decoder, Encoder};
|
||||
|
||||
/// A simple `Codec` implementation that splits up data into lines.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
@@ -71,13 +69,13 @@ impl LinesCodec {
|
||||
///
|
||||
/// ```
|
||||
/// use std::usize;
|
||||
/// use tokio_util::codec::LinesCodec;
|
||||
/// use tokio_codec::LinesCodec;
|
||||
///
|
||||
/// let codec = LinesCodec::new();
|
||||
/// assert_eq!(codec.max_length(), usize::MAX);
|
||||
/// ```
|
||||
/// ```
|
||||
/// use tokio_util::codec::LinesCodec;
|
||||
/// use tokio_codec::LinesCodec;
|
||||
///
|
||||
/// let codec = LinesCodec::new_with_max_length(256);
|
||||
/// assert_eq!(codec.max_length(), 256);
|
||||
@@ -85,6 +83,23 @@ impl LinesCodec {
|
||||
pub fn max_length(&self) -> usize {
|
||||
self.max_length
|
||||
}
|
||||
|
||||
fn discard(&mut self, newline_offset: Option<usize>, read_to: usize, buf: &mut BytesMut) {
|
||||
let discard_to = if let Some(offset) = newline_offset {
|
||||
// If we found a newline, discard up to that offset and
|
||||
// then stop discarding. On the next iteration, we'll try
|
||||
// to read a line normally.
|
||||
self.is_discarding = false;
|
||||
offset + self.next_index + 1
|
||||
} else {
|
||||
// Otherwise, we didn't find a newline, so we'll discard
|
||||
// everything we read. On the next iteration, we'll continue
|
||||
// discarding up to max_len bytes unless we find a newline.
|
||||
read_to
|
||||
};
|
||||
buf.advance(discard_to);
|
||||
self.next_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn utf8(buf: &[u8]) -> Result<&str, io::Error> {
|
||||
@@ -102,9 +117,11 @@ fn without_carriage_return(s: &[u8]) -> &[u8] {
|
||||
|
||||
impl Decoder for LinesCodec {
|
||||
type Item = String;
|
||||
type Error = LinesCodecError;
|
||||
// TODO: in the next breaking change, this should be changed to a custom
|
||||
// error type that indicates the "max length exceeded" condition better.
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<String>, LinesCodecError> {
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<String>, io::Error> {
|
||||
loop {
|
||||
// Determine how far into the buffer we'll search for a newline. If
|
||||
// there's no max_length set, we'll read to the end of the buffer.
|
||||
@@ -114,26 +131,10 @@ impl Decoder for LinesCodec {
|
||||
.iter()
|
||||
.position(|b| *b == b'\n');
|
||||
|
||||
match (self.is_discarding, newline_offset) {
|
||||
(true, Some(offset)) => {
|
||||
// If we found a newline, discard up to that offset and
|
||||
// then stop discarding. On the next iteration, we'll try
|
||||
// to read a line normally.
|
||||
buf.advance(offset + self.next_index + 1);
|
||||
self.is_discarding = false;
|
||||
self.next_index = 0;
|
||||
}
|
||||
(true, None) => {
|
||||
// Otherwise, we didn't find a newline, so we'll discard
|
||||
// everything we read. On the next iteration, we'll continue
|
||||
// discarding up to max_len bytes unless we find a newline.
|
||||
buf.advance(read_to);
|
||||
self.next_index = 0;
|
||||
if buf.is_empty() {
|
||||
return Err(LinesCodecError::MaxLineLengthExceeded);
|
||||
}
|
||||
}
|
||||
(false, Some(offset)) => {
|
||||
if self.is_discarding {
|
||||
self.discard(newline_offset, read_to, buf);
|
||||
} else {
|
||||
return if let Some(offset) = newline_offset {
|
||||
// Found a line!
|
||||
let newline_index = offset + self.next_index;
|
||||
self.next_index = 0;
|
||||
@@ -141,26 +142,28 @@ impl Decoder for LinesCodec {
|
||||
let line = &line[..line.len() - 1];
|
||||
let line = without_carriage_return(line);
|
||||
let line = utf8(line)?;
|
||||
return Ok(Some(line.to_string()));
|
||||
}
|
||||
(false, None) if buf.len() > self.max_length => {
|
||||
|
||||
Ok(Some(line.to_string()))
|
||||
} else if buf.len() > self.max_length {
|
||||
// Reached the maximum length without finding a
|
||||
// newline, return an error and start discarding on the
|
||||
// next call.
|
||||
self.is_discarding = true;
|
||||
return Err(LinesCodecError::MaxLineLengthExceeded);
|
||||
}
|
||||
(false, None) => {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"line length limit exceeded",
|
||||
))
|
||||
} else {
|
||||
// We didn't find a line or reach the length limit, so the next
|
||||
// call will resume searching at the current offset.
|
||||
self.next_index = read_to;
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(None)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<String>, LinesCodecError> {
|
||||
fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<String>, io::Error> {
|
||||
Ok(match self.decode(buf)? {
|
||||
Some(frame) => Some(frame),
|
||||
None => {
|
||||
@@ -168,7 +171,7 @@ impl Decoder for LinesCodec {
|
||||
if buf.is_empty() || buf == &b"\r"[..] {
|
||||
None
|
||||
} else {
|
||||
let line = buf.split_to(buf.len());
|
||||
let line = buf.take();
|
||||
let line = without_carriage_return(&line);
|
||||
let line = utf8(line)?;
|
||||
self.next_index = 0;
|
||||
@@ -181,44 +184,12 @@ impl Decoder for LinesCodec {
|
||||
|
||||
impl Encoder for LinesCodec {
|
||||
type Item = String;
|
||||
type Error = LinesCodecError;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, line: String, buf: &mut BytesMut) -> Result<(), LinesCodecError> {
|
||||
fn encode(&mut self, line: String, buf: &mut BytesMut) -> Result<(), io::Error> {
|
||||
buf.reserve(line.len() + 1);
|
||||
buf.put(line.as_bytes());
|
||||
buf.put(line);
|
||||
buf.put_u8(b'\n');
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LinesCodec {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occured while encoding or decoding a line.
|
||||
#[derive(Debug)]
|
||||
pub enum LinesCodecError {
|
||||
/// The maximum line length was exceeded.
|
||||
MaxLineLengthExceeded,
|
||||
/// An IO error occured.
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for LinesCodecError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
LinesCodecError::MaxLineLengthExceeded => write!(f, "max line length exceeded"),
|
||||
LinesCodecError::Io(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for LinesCodecError {
|
||||
fn from(e: io::Error) -> LinesCodecError {
|
||||
LinesCodecError::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LinesCodecError {}
|
||||
@@ -1,8 +1,8 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio_util::codec::{BytesCodec, Decoder, Encoder, LinesCodec};
|
||||
extern crate bytes;
|
||||
extern crate tokio_codec;
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use tokio_codec::{BytesCodec, Decoder, Encoder, LinesCodec};
|
||||
|
||||
#[test]
|
||||
fn bytes_decoder() {
|
||||
@@ -45,14 +45,14 @@ fn lines_decoder() {
|
||||
let mut codec = LinesCodec::new();
|
||||
let buf = &mut BytesMut::new();
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"line 1\nline 2\r\nline 3\n\r\n\r");
|
||||
buf.put("line 1\nline 2\r\nline 3\n\r\n\r");
|
||||
assert_eq!("line 1", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("line 2", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("line 3", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!("", codec.decode(buf).unwrap().unwrap());
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
buf.put_slice(b"k");
|
||||
buf.put("k");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!("\rk", codec.decode_eof(buf).unwrap().unwrap());
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
@@ -67,7 +67,7 @@ fn lines_decoder_max_length() {
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"line 1 is too long\nline 2\nline 3\r\nline 4\n\r\n\r");
|
||||
buf.put("line 1 is too long\nline 2\nline 3\r\nline 4\n\r\n\r");
|
||||
|
||||
assert!(codec.decode(buf).is_err());
|
||||
|
||||
@@ -102,7 +102,7 @@ fn lines_decoder_max_length() {
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
buf.put_slice(b"k");
|
||||
buf.put("k");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
let line = codec.decode_eof(buf).unwrap().unwrap();
|
||||
@@ -119,8 +119,8 @@ fn lines_decoder_max_length() {
|
||||
|
||||
// Line that's one character too long. This could cause an out of bounds
|
||||
// error if we peek at the next characters using slice indexing.
|
||||
buf.put_slice(b"aaabbbc");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
// buf.put("aaabbbc");
|
||||
// assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -131,16 +131,16 @@ fn lines_decoder_max_length_underrun() {
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"line ");
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"too l");
|
||||
buf.put("too l");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put_slice(b"ong\n");
|
||||
buf.put("ong\n");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
buf.put_slice(b"line 2");
|
||||
buf.put("line 2");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"\n");
|
||||
buf.put("\n");
|
||||
assert_eq!("line 2", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
@@ -152,11 +152,11 @@ fn lines_decoder_max_length_bursts() {
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"line ");
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"too l");
|
||||
buf.put("too l");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"ong\n");
|
||||
buf.put("ong\n");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
@@ -168,9 +168,9 @@ fn lines_decoder_max_length_big_burst() {
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"line ");
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put_slice(b"too long!\n");
|
||||
buf.put("too long!\n");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
@@ -182,28 +182,13 @@ fn lines_decoder_max_length_newline_between_decodes() {
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"hello");
|
||||
buf.put("hello");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
buf.put_slice(b"\nworld");
|
||||
buf.put("\nworld");
|
||||
assert_eq!("hello", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
// Regression test for [infinite loop bug](https://github.com/tokio-rs/tokio/issues/1483)
|
||||
#[test]
|
||||
fn lines_decoder_discard_repeat() {
|
||||
const MAX_LENGTH: usize = 1;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put_slice(b"aa");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put_slice(b"a");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_encoder() {
|
||||
let mut codec = LinesCodec::new();
|
||||
@@ -1,14 +1,13 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_io;
|
||||
|
||||
use tokio::prelude::*;
|
||||
use tokio_test::assert_ok;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed, FramedParts};
|
||||
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use futures::StreamExt;
|
||||
use bytes::{Buf, BufMut, BytesMut, IntoBuf};
|
||||
use futures::{Future, Stream};
|
||||
use std::io::{self, Read};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio_codec::{Decoder, Encoder, Framed, FramedParts};
|
||||
use tokio_io::AsyncRead;
|
||||
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
@@ -24,7 +23,7 @@ impl Decoder for U32Codec {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let n = buf.split_to(4).get_u32();
|
||||
let n = buf.split_to(4).into_buf().get_u32_be();
|
||||
Ok(Some(n))
|
||||
}
|
||||
}
|
||||
@@ -36,7 +35,7 @@ impl Encoder for U32Codec {
|
||||
fn encode(&mut self, item: u32, dst: &mut BytesMut) -> io::Result<()> {
|
||||
// Reserve space
|
||||
dst.reserve(4);
|
||||
dst.put_u32(item);
|
||||
dst.put_u32_be(item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -53,23 +52,21 @@ impl Read for DontReadIntoThis {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for DontReadIntoThis {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
_buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
impl AsyncRead for DontReadIntoThis {}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_read_from_existing_buf() {
|
||||
#[test]
|
||||
fn can_read_from_existing_buf() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
parts.read_buf = BytesMut::from(&[0, 0, 0, 42][..]);
|
||||
parts.read_buf = vec![0, 0, 0, 42].into();
|
||||
|
||||
let mut framed = Framed::from_parts(parts);
|
||||
let num = assert_ok!(framed.next().await.unwrap());
|
||||
let framed = Framed::from_parts(parts);
|
||||
|
||||
let num = framed
|
||||
.into_future()
|
||||
.map(|(first_num, _)| first_num.unwrap())
|
||||
.wait()
|
||||
.map_err(|e| e.0)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(num, 42);
|
||||
}
|
||||
@@ -77,7 +74,7 @@ async fn can_read_from_existing_buf() {
|
||||
#[test]
|
||||
fn external_buf_grows_to_init() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
parts.read_buf = BytesMut::from(&[0, 0, 0, 42][..]);
|
||||
parts.read_buf = vec![0, 0, 0, 42].into();
|
||||
|
||||
let framed = Framed::from_parts(parts);
|
||||
let FramedParts { read_buf, .. } = framed.into_parts();
|
||||
@@ -88,7 +85,7 @@ fn external_buf_grows_to_init() {
|
||||
#[test]
|
||||
fn external_buf_does_not_shrink() {
|
||||
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
|
||||
parts.read_buf = BytesMut::from(&vec![0; INITIAL_CAPACITY * 2][..]);
|
||||
parts.read_buf = vec![0; INITIAL_CAPACITY * 2].into();
|
||||
|
||||
let framed = Framed::from_parts(parts);
|
||||
let FramedParts { read_buf, .. } = framed.into_parts();
|
||||
@@ -0,0 +1,215 @@
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_io;
|
||||
|
||||
use tokio_codec::{Decoder, FramedRead};
|
||||
use tokio_io::AsyncRead;
|
||||
|
||||
use bytes::{Buf, BytesMut, IntoBuf};
|
||||
use futures::Async::{NotReady, Ready};
|
||||
use futures::Stream;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{self, Read};
|
||||
|
||||
macro_rules! mock {
|
||||
($($x:expr,)*) => {{
|
||||
let mut v = VecDeque::new();
|
||||
v.extend(vec![$($x),*]);
|
||||
Mock { calls: v }
|
||||
}};
|
||||
}
|
||||
|
||||
struct U32Decoder;
|
||||
|
||||
impl Decoder for U32Decoder {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
if buf.len() < 4 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let n = buf.split_to(4).into_buf().get_u32_be();
|
||||
Ok(Some(n))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_multi_frame_in_packet() {
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02".to_vec()),
|
||||
};
|
||||
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
assert_eq!(Ready(Some(0)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(1)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(2)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(None), framed.poll().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_multi_frame_across_packets() {
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x00".to_vec()),
|
||||
Ok(b"\x00\x00\x00\x01".to_vec()),
|
||||
Ok(b"\x00\x00\x00\x02".to_vec()),
|
||||
};
|
||||
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
assert_eq!(Ready(Some(0)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(1)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(2)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(None), framed.poll().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_not_ready() {
|
||||
let mock = mock! {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "")),
|
||||
Ok(b"\x00\x00\x00\x00".to_vec()),
|
||||
Ok(b"\x00\x00\x00\x01".to_vec()),
|
||||
};
|
||||
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
assert_eq!(NotReady, framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(0)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(1)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(None), framed.poll().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_partial_then_not_ready() {
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00".to_vec()),
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "")),
|
||||
Ok(b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02".to_vec()),
|
||||
};
|
||||
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
assert_eq!(NotReady, framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(0)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(1)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(2)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(None), framed.poll().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_err() {
|
||||
let mock = mock! {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "")),
|
||||
};
|
||||
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
assert_eq!(io::ErrorKind::Other, framed.poll().unwrap_err().kind());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_partial_then_err() {
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00".to_vec()),
|
||||
Err(io::Error::new(io::ErrorKind::Other, "")),
|
||||
};
|
||||
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
assert_eq!(io::ErrorKind::Other, framed.poll().unwrap_err().kind());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_partial_would_block_then_err() {
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00".to_vec()),
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "")),
|
||||
Err(io::Error::new(io::ErrorKind::Other, "")),
|
||||
};
|
||||
|
||||
let mut framed = FramedRead::new(mock, U32Decoder);
|
||||
assert_eq!(NotReady, framed.poll().unwrap());
|
||||
assert_eq!(io::ErrorKind::Other, framed.poll().unwrap_err().kind());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_size() {
|
||||
let data = [0; 32 * 1024];
|
||||
|
||||
let mut framed = FramedRead::new(&data[..], BigDecoder);
|
||||
assert_eq!(Ready(Some(0)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(None), framed.poll().unwrap());
|
||||
|
||||
struct BigDecoder;
|
||||
|
||||
impl Decoder for BigDecoder {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
if buf.len() < 32 * 1024 {
|
||||
return Ok(None);
|
||||
}
|
||||
buf.split_to(32 * 1024);
|
||||
Ok(Some(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_remaining_is_error() {
|
||||
let data = [0; 5];
|
||||
|
||||
let mut framed = FramedRead::new(&data[..], U32Decoder);
|
||||
assert_eq!(Ready(Some(0)), framed.poll().unwrap());
|
||||
assert!(framed.poll().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_frames_on_eof() {
|
||||
struct MyDecoder(Vec<u32>);
|
||||
|
||||
impl Decoder for MyDecoder {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, _buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, _buf: &mut BytesMut) -> io::Result<Option<u32>> {
|
||||
if self.0.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(self.0.remove(0)))
|
||||
}
|
||||
}
|
||||
|
||||
let mut framed = FramedRead::new(mock!(), MyDecoder(vec![0, 1, 2, 3]));
|
||||
assert_eq!(Ready(Some(0)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(1)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(2)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(Some(3)), framed.poll().unwrap());
|
||||
assert_eq!(Ready(None), framed.poll().unwrap());
|
||||
}
|
||||
|
||||
// ===== Mock ======
|
||||
|
||||
struct Mock {
|
||||
calls: VecDeque<io::Result<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Read for Mock {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ok(data)) => {
|
||||
debug_assert!(dst.len() >= data.len());
|
||||
dst[..data.len()].copy_from_slice(&data[..]);
|
||||
Ok(data.len())
|
||||
}
|
||||
Some(Err(e)) => Err(e),
|
||||
None => Ok(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Mock {}
|
||||
@@ -0,0 +1,134 @@
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_io;
|
||||
|
||||
use tokio_codec::{Encoder, FramedWrite};
|
||||
use tokio_io::AsyncWrite;
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::{Poll, Sink};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{self, Write};
|
||||
|
||||
macro_rules! mock {
|
||||
($($x:expr,)*) => {{
|
||||
let mut v = VecDeque::new();
|
||||
v.extend(vec![$($x),*]);
|
||||
Mock { calls: v }
|
||||
}};
|
||||
}
|
||||
|
||||
struct U32Encoder;
|
||||
|
||||
impl Encoder for U32Encoder {
|
||||
type Item = u32;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: u32, dst: &mut BytesMut) -> io::Result<()> {
|
||||
// Reserve space
|
||||
dst.reserve(4);
|
||||
dst.put_u32_be(item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_multi_frame_in_packet() {
|
||||
let mock = mock! {
|
||||
Ok(b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02".to_vec()),
|
||||
};
|
||||
|
||||
let mut framed = FramedWrite::new(mock, U32Encoder);
|
||||
assert!(framed.start_send(0).unwrap().is_ready());
|
||||
assert!(framed.start_send(1).unwrap().is_ready());
|
||||
assert!(framed.start_send(2).unwrap().is_ready());
|
||||
|
||||
// Nothing written yet
|
||||
assert_eq!(1, framed.get_ref().calls.len());
|
||||
|
||||
// Flush the writes
|
||||
assert!(framed.poll_complete().unwrap().is_ready());
|
||||
|
||||
assert_eq!(0, framed.get_ref().calls.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_hits_backpressure() {
|
||||
const ITER: usize = 2 * 1024;
|
||||
|
||||
let mut mock = mock! {
|
||||
// Block the `ITER`th write
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, "not ready")),
|
||||
Ok(b"".to_vec()),
|
||||
};
|
||||
|
||||
for i in 0..(ITER + 1) {
|
||||
let mut b = BytesMut::with_capacity(4);
|
||||
b.put_u32_be(i as u32);
|
||||
|
||||
// Append to the end
|
||||
match mock.calls.back_mut().unwrap() {
|
||||
&mut Ok(ref mut data) => {
|
||||
// Write in 2kb chunks
|
||||
if data.len() < ITER {
|
||||
data.extend_from_slice(&b[..]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Push a new new chunk
|
||||
mock.calls.push_back(Ok(b[..].to_vec()));
|
||||
}
|
||||
|
||||
let mut framed = FramedWrite::new(mock, U32Encoder);
|
||||
|
||||
for i in 0..ITER {
|
||||
assert!(framed.start_send(i as u32).unwrap().is_ready());
|
||||
}
|
||||
|
||||
// This should reject
|
||||
assert!(!framed.start_send(ITER as u32).unwrap().is_ready());
|
||||
|
||||
// This should succeed and start flushing the buffer.
|
||||
assert!(framed.start_send(ITER as u32).unwrap().is_ready());
|
||||
|
||||
// Flush the rest of the buffer
|
||||
assert!(framed.poll_complete().unwrap().is_ready());
|
||||
|
||||
// Ensure the mock is empty
|
||||
assert_eq!(0, framed.get_ref().calls.len());
|
||||
}
|
||||
|
||||
// ===== Mock ======
|
||||
|
||||
struct Mock {
|
||||
calls: VecDeque<io::Result<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Write for Mock {
|
||||
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
|
||||
match self.calls.pop_front() {
|
||||
Some(Ok(data)) => {
|
||||
assert!(src.len() >= data.len());
|
||||
assert_eq!(&data[..], &src[..data.len()]);
|
||||
Ok(data.len())
|
||||
}
|
||||
Some(Err(e)) => Err(e),
|
||||
None => panic!("unexpected write; {:?}", src),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Mock {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# 0.1.6 (March 22, 2019)
|
||||
|
||||
### Added
|
||||
- implement `TypedExecutor` (#993).
|
||||
|
||||
# 0.1.5 (March 1, 2019)
|
||||
|
||||
### Fixed
|
||||
- Documentation typos (#882).
|
||||
|
||||
# 0.1.4 (November 21, 2018)
|
||||
|
||||
* Fix shutdown on idle (#763).
|
||||
|
||||
# 0.1.3 (September 27, 2018)
|
||||
|
||||
* Fix minimal versions
|
||||
|
||||
# 0.1.2 (September 26, 2018)
|
||||
|
||||
* Implement `futures::Executor` for executor types (#563)
|
||||
* Spawning performance improvements (#565)
|
||||
|
||||
# 0.1.1 (August 6, 2018)
|
||||
|
||||
* Implement `std::Error` for misc error types (#501)
|
||||
* bugfix: Track tasks pending in spawn queue (#478)
|
||||
|
||||
# 0.1.0 (June 13, 2018)
|
||||
|
||||
* Extract `tokio::executor::current_thread` to a tokio-current-thread crate (#356)
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "tokio-current-thread"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.6"
|
||||
documentation = "https://docs.rs/tokio-current-thread/0.1.6/tokio_current_thread"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://github.com/tokio-rs/tokio"
|
||||
license = "MIT"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
description = """
|
||||
Single threaded executor which manage many tasks concurrently on the current thread.
|
||||
"""
|
||||
keywords = ["futures", "tokio"]
|
||||
categories = ["concurrency", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
tokio-executor = "0.1.7"
|
||||
futures = "0.1.19"
|
||||
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,19 @@
|
||||
# tokio-current-thread
|
||||
|
||||
Single threaded executor for Tokio.
|
||||
|
||||
[Documentation](https://docs.rs/tokio-current-thread/0.1.6/tokio_current_thread/)
|
||||
|
||||
## Overview
|
||||
|
||||
This crate provides the single threaded executor which execute many tasks concurrently.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -0,0 +1,874 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-current-thread/0.1.6")]
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
|
||||
//! A single-threaded executor which executes tasks on the same thread from which
|
||||
//! they are spawned.
|
||||
//!
|
||||
//!
|
||||
//! The crate provides:
|
||||
//!
|
||||
//! * [`CurrentThread`] is the main type of this crate. It executes tasks on the current thread.
|
||||
//! The easiest way to start a new [`CurrentThread`] executor is to call
|
||||
//! [`block_on_all`] with an initial task to seed the executor.
|
||||
//! All tasks that are being managed by a [`CurrentThread`] executor are able to
|
||||
//! spawn additional tasks by calling [`spawn`].
|
||||
//!
|
||||
//!
|
||||
//! Application authors will not use this crate directly. Instead, they will use the
|
||||
//! `tokio` crate. Library authors should only depend on `tokio-current-thread` if they
|
||||
//! are building a custom task executor.
|
||||
//!
|
||||
//! For more details, see [executor module] documentation in the Tokio crate.
|
||||
//!
|
||||
//! [`CurrentThread`]: struct.CurrentThread.html
|
||||
//! [`spawn`]: fn.spawn.html
|
||||
//! [`block_on_all`]: fn.block_on_all.html
|
||||
//! [executor module]: https://docs.rs/tokio/0.1/tokio/executor/index.html
|
||||
|
||||
extern crate futures;
|
||||
extern crate tokio_executor;
|
||||
|
||||
mod scheduler;
|
||||
|
||||
use self::scheduler::Scheduler;
|
||||
|
||||
use tokio_executor::park::{Park, ParkThread, Unpark};
|
||||
use tokio_executor::{Enter, SpawnError};
|
||||
|
||||
use futures::future::{ExecuteError, ExecuteErrorKind, Executor};
|
||||
use futures::{executor, Async, Future};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{atomic, mpsc, Arc};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Executes tasks on the current thread
|
||||
pub struct CurrentThread<P: Park = ParkThread> {
|
||||
/// Execute futures and receive unpark notifications.
|
||||
scheduler: Scheduler<P::Unpark>,
|
||||
|
||||
/// Current number of futures being executed.
|
||||
///
|
||||
/// The LSB is used to indicate that the runtime is preparing to shut down.
|
||||
/// Thus, to get the actual number of pending futures, `>>1`.
|
||||
num_futures: Arc<atomic::AtomicUsize>,
|
||||
|
||||
/// Thread park handle
|
||||
park: P,
|
||||
|
||||
/// Handle for spawning new futures from other threads
|
||||
spawn_handle: Handle,
|
||||
|
||||
/// Receiver for futures spawned from other threads
|
||||
spawn_receiver: mpsc::Receiver<Box<dyn Future<Item = (), Error = ()> + Send + 'static>>,
|
||||
|
||||
/// The thread-local ID assigned to this executor.
|
||||
id: u64,
|
||||
}
|
||||
|
||||
/// Executes futures on the current thread.
|
||||
///
|
||||
/// All futures executed using this executor will be executed on the current
|
||||
/// thread. As such, `run` will wait for these futures to complete before
|
||||
/// returning.
|
||||
///
|
||||
/// For more details, see the [module level](index.html) documentation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TaskExecutor {
|
||||
// Prevent the handle from moving across threads.
|
||||
_p: ::std::marker::PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
/// Returned by the `turn` function.
|
||||
#[derive(Debug)]
|
||||
pub struct Turn {
|
||||
polled: bool,
|
||||
}
|
||||
|
||||
impl Turn {
|
||||
/// `true` if any futures were polled at all and `false` otherwise.
|
||||
pub fn has_polled(&self) -> bool {
|
||||
self.polled
|
||||
}
|
||||
}
|
||||
|
||||
/// A `CurrentThread` instance bound to a supplied execution context.
|
||||
pub struct Entered<'a, P: Park + 'a> {
|
||||
executor: &'a mut CurrentThread<P>,
|
||||
enter: &'a mut Enter,
|
||||
}
|
||||
|
||||
/// Error returned by the `run` function.
|
||||
#[derive(Debug)]
|
||||
pub struct RunError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
impl fmt::Display for RunError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for RunError {
|
||||
fn description(&self) -> &str {
|
||||
"Run error"
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by the `run_timeout` function.
|
||||
#[derive(Debug)]
|
||||
pub struct RunTimeoutError {
|
||||
timeout: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for RunTimeoutError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for RunTimeoutError {
|
||||
fn description(&self) -> &str {
|
||||
if self.timeout {
|
||||
"Run timeout error (timeout)"
|
||||
} else {
|
||||
"Run timeout error (not timeout)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by the `turn` function.
|
||||
#[derive(Debug)]
|
||||
pub struct TurnError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
impl fmt::Display for TurnError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TurnError {
|
||||
fn description(&self) -> &str {
|
||||
"Turn error"
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by the `block_on` function.
|
||||
#[derive(Debug)]
|
||||
pub struct BlockError<T> {
|
||||
inner: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> fmt::Display for BlockError<T> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "Block error")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> Error for BlockError<T> {
|
||||
fn description(&self) -> &str {
|
||||
"Block error"
|
||||
}
|
||||
}
|
||||
|
||||
/// This is mostly split out to make the borrow checker happy.
|
||||
struct Borrow<'a, U: 'a> {
|
||||
id: u64,
|
||||
scheduler: &'a mut Scheduler<U>,
|
||||
num_futures: &'a atomic::AtomicUsize,
|
||||
}
|
||||
|
||||
trait SpawnLocal {
|
||||
fn spawn_local(
|
||||
&mut self,
|
||||
future: Box<dyn Future<Item = (), Error = ()>>,
|
||||
already_counted: bool,
|
||||
);
|
||||
}
|
||||
|
||||
struct CurrentRunner {
|
||||
spawn: Cell<Option<*mut dyn SpawnLocal>>,
|
||||
id: Cell<Option<u64>>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Current thread's task runner. This is set in `TaskRunner::with`
|
||||
static CURRENT: CurrentRunner = CurrentRunner {
|
||||
spawn: Cell::new(None),
|
||||
id: Cell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Unique ID to assign to each new executor launched on this thread.
|
||||
///
|
||||
/// The unique ID is used to determine if the currently running executor matches the one
|
||||
/// referred to by a `Handle` so that direct task dispatch can be used.
|
||||
static EXECUTOR_ID: Cell<u64> = Cell::new(0)
|
||||
}
|
||||
|
||||
/// Run the executor bootstrapping the execution with the provided future.
|
||||
///
|
||||
/// This creates a new [`CurrentThread`] executor, spawns the provided future,
|
||||
/// and blocks the current thread until the provided future and **all**
|
||||
/// subsequently spawned futures complete. In other words:
|
||||
///
|
||||
/// * If the provided bootstrap future does **not** spawn any additional tasks,
|
||||
/// `block_on_all` returns once `future` completes.
|
||||
/// * If the provided bootstrap future **does** spawn additional tasks, then
|
||||
/// `block_on_all` returns once **all** spawned futures complete.
|
||||
///
|
||||
/// See [module level][mod] documentation for more details.
|
||||
///
|
||||
/// [`CurrentThread`]: struct.CurrentThread.html
|
||||
/// [mod]: index.html
|
||||
pub fn block_on_all<F>(future: F) -> Result<F::Item, F::Error>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let ret = current_thread.block_on(future);
|
||||
current_thread.run().unwrap();
|
||||
|
||||
ret.map_err(|e| e.into_inner().expect("unexpected execution error"))
|
||||
}
|
||||
|
||||
/// Executes a future on the current thread.
|
||||
///
|
||||
/// The provided future must complete or be canceled before `run` will return.
|
||||
///
|
||||
/// Unlike [`tokio::spawn`], this function will always spawn on a
|
||||
/// `CurrentThread` executor and is able to spawn futures that are not `Send`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function can only be invoked from the context of a `run` call; any
|
||||
/// other use will result in a panic.
|
||||
///
|
||||
/// [`tokio::spawn`]: ../fn.spawn.html
|
||||
pub fn spawn<F>(future: F)
|
||||
where
|
||||
F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
TaskExecutor::current()
|
||||
.spawn_local(Box::new(future))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// ===== impl CurrentThread =====
|
||||
|
||||
impl CurrentThread<ParkThread> {
|
||||
/// Create a new instance of `CurrentThread`.
|
||||
pub fn new() -> Self {
|
||||
CurrentThread::new_with_park(ParkThread::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Park> CurrentThread<P> {
|
||||
/// Create a new instance of `CurrentThread` backed by the given park
|
||||
/// handle.
|
||||
pub fn new_with_park(park: P) -> Self {
|
||||
let unpark = park.unpark();
|
||||
|
||||
let (spawn_sender, spawn_receiver) = mpsc::channel();
|
||||
let thread = thread::current().id();
|
||||
let id = EXECUTOR_ID.with(|idc| {
|
||||
let id = idc.get();
|
||||
idc.set(id + 1);
|
||||
id
|
||||
});
|
||||
|
||||
let scheduler = Scheduler::new(unpark);
|
||||
let notify = scheduler.notify();
|
||||
|
||||
let num_futures = Arc::new(atomic::AtomicUsize::new(0));
|
||||
|
||||
CurrentThread {
|
||||
scheduler: scheduler,
|
||||
num_futures: num_futures.clone(),
|
||||
park,
|
||||
id,
|
||||
spawn_handle: Handle {
|
||||
sender: spawn_sender,
|
||||
num_futures: num_futures,
|
||||
notify: notify,
|
||||
shut_down: Cell::new(false),
|
||||
thread: thread,
|
||||
id,
|
||||
},
|
||||
spawn_receiver: spawn_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the executor is currently idle.
|
||||
///
|
||||
/// An idle executor is defined by not currently having any spawned tasks.
|
||||
///
|
||||
/// Note that this method is inherently racy -- if a future is spawned from a remote `Handle`,
|
||||
/// this method may return `true` even though there are more futures to be executed.
|
||||
pub fn is_idle(&self) -> bool {
|
||||
self.num_futures.load(atomic::Ordering::SeqCst) <= 1
|
||||
}
|
||||
|
||||
/// Spawn the future on the executor.
|
||||
///
|
||||
/// This internally queues the future to be executed once `run` is called.
|
||||
pub fn spawn<F>(&mut self, future: F) -> &mut Self
|
||||
where
|
||||
F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
self.borrow().spawn_local(Box::new(future), false);
|
||||
self
|
||||
}
|
||||
|
||||
/// Synchronously waits for the provided `future` to complete.
|
||||
///
|
||||
/// This function can be used to synchronously block the current thread
|
||||
/// until the provided `future` has resolved either successfully or with an
|
||||
/// error. The result of the future is then returned from this function
|
||||
/// call.
|
||||
///
|
||||
/// Note that this function will **also** execute any spawned futures on the
|
||||
/// current thread, but will **not** block until these other spawned futures
|
||||
/// have completed.
|
||||
///
|
||||
/// The caller is responsible for ensuring that other spawned futures
|
||||
/// complete execution.
|
||||
pub fn block_on<F>(&mut self, future: F) -> Result<F::Item, BlockError<F::Error>>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
let mut enter = tokio_executor::enter().expect("failed to start `current_thread::Runtime`");
|
||||
self.enter(&mut enter).block_on(future)
|
||||
}
|
||||
|
||||
/// Run the executor to completion, blocking the thread until **all**
|
||||
/// spawned futures have completed.
|
||||
pub fn run(&mut self) -> Result<(), RunError> {
|
||||
let mut enter = tokio_executor::enter().expect("failed to start `current_thread::Runtime`");
|
||||
self.enter(&mut enter).run()
|
||||
}
|
||||
|
||||
/// Run the executor to completion, blocking the thread until all
|
||||
/// spawned futures have completed **or** `duration` time has elapsed.
|
||||
pub fn run_timeout(&mut self, duration: Duration) -> Result<(), RunTimeoutError> {
|
||||
let mut enter = tokio_executor::enter().expect("failed to start `current_thread::Runtime`");
|
||||
self.enter(&mut enter).run_timeout(duration)
|
||||
}
|
||||
|
||||
/// Perform a single iteration of the event loop.
|
||||
///
|
||||
/// This function blocks the current thread even if the executor is idle.
|
||||
pub fn turn(&mut self, duration: Option<Duration>) -> Result<Turn, TurnError> {
|
||||
let mut enter = tokio_executor::enter().expect("failed to start `current_thread::Runtime`");
|
||||
self.enter(&mut enter).turn(duration)
|
||||
}
|
||||
|
||||
/// Bind `CurrentThread` instance with an execution context.
|
||||
pub fn enter<'a>(&'a mut self, enter: &'a mut Enter) -> Entered<'a, P> {
|
||||
Entered {
|
||||
executor: self,
|
||||
enter,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying `Park` instance.
|
||||
pub fn get_park(&self) -> &P {
|
||||
&self.park
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying `Park` instance.
|
||||
pub fn get_park_mut(&mut self) -> &mut P {
|
||||
&mut self.park
|
||||
}
|
||||
|
||||
fn borrow(&mut self) -> Borrow<P::Unpark> {
|
||||
Borrow {
|
||||
id: self.id,
|
||||
scheduler: &mut self.scheduler,
|
||||
num_futures: &*self.num_futures,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a new handle to spawn futures on the executor
|
||||
///
|
||||
/// Different to the executor itself, the handle can be sent to different
|
||||
/// threads and can be used to spawn futures on the executor.
|
||||
pub fn handle(&self) -> Handle {
|
||||
self.spawn_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Park> Drop for CurrentThread<P> {
|
||||
fn drop(&mut self) {
|
||||
// Signal to Handles that no more futures can be spawned by setting LSB.
|
||||
//
|
||||
// NOTE: this isn't technically necessary since the send on the mpsc will fail once the
|
||||
// receiver is dropped, but it's useful to illustrate how clean shutdown will be
|
||||
// implemented (e.g., by setting the LSB).
|
||||
let pending = self.num_futures.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
|
||||
// TODO: We currently ignore any pending futures at the time we shut down.
|
||||
//
|
||||
// The "proper" fix for this is to have an explicit shutdown phase (`shutdown_on_idle`)
|
||||
// which sets LSB (as above) do make Handle::spawn stop working, and then runs until
|
||||
// num_futures.load() == 1.
|
||||
let _ = pending;
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_executor::Executor for CurrentThread {
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Box<dyn Future<Item = (), Error = ()> + Send>,
|
||||
) -> Result<(), SpawnError> {
|
||||
self.borrow().spawn_local(future, false);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> tokio_executor::TypedExecutor<T> for CurrentThread
|
||||
where
|
||||
T: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
fn spawn(&mut self, future: T) -> Result<(), SpawnError> {
|
||||
self.borrow().spawn_local(Box::new(future), false);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Park> fmt::Debug for CurrentThread<P> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("CurrentThread")
|
||||
.field("scheduler", &self.scheduler)
|
||||
.field(
|
||||
"num_futures",
|
||||
&self.num_futures.load(atomic::Ordering::SeqCst),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Entered =====
|
||||
|
||||
impl<'a, P: Park> Entered<'a, P> {
|
||||
/// Spawn the future on the executor.
|
||||
///
|
||||
/// This internally queues the future to be executed once `run` is called.
|
||||
pub fn spawn<F>(&mut self, future: F) -> &mut Self
|
||||
where
|
||||
F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
self.executor.borrow().spawn_local(Box::new(future), false);
|
||||
self
|
||||
}
|
||||
|
||||
/// Synchronously waits for the provided `future` to complete.
|
||||
///
|
||||
/// This function can be used to synchronously block the current thread
|
||||
/// until the provided `future` has resolved either successfully or with an
|
||||
/// error. The result of the future is then returned from this function
|
||||
/// call.
|
||||
///
|
||||
/// Note that this function will **also** execute any spawned futures on the
|
||||
/// current thread, but will **not** block until these other spawned futures
|
||||
/// have completed.
|
||||
///
|
||||
/// The caller is responsible for ensuring that other spawned futures
|
||||
/// complete execution.
|
||||
pub fn block_on<F>(&mut self, future: F) -> Result<F::Item, BlockError<F::Error>>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
let mut future = executor::spawn(future);
|
||||
let notify = self.executor.scheduler.notify();
|
||||
|
||||
loop {
|
||||
let res = self
|
||||
.executor
|
||||
.borrow()
|
||||
.enter(self.enter, || future.poll_future_notify(¬ify, 0));
|
||||
|
||||
match res {
|
||||
Ok(Async::Ready(e)) => return Ok(e),
|
||||
Err(e) => return Err(BlockError { inner: Some(e) }),
|
||||
Ok(Async::NotReady) => {}
|
||||
}
|
||||
|
||||
self.tick();
|
||||
|
||||
if let Err(_) = self.executor.park.park() {
|
||||
return Err(BlockError { inner: None });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor to completion, blocking the thread until **all**
|
||||
/// spawned futures have completed.
|
||||
pub fn run(&mut self) -> Result<(), RunError> {
|
||||
self.run_timeout2(None).map_err(|_| RunError { _p: () })
|
||||
}
|
||||
|
||||
/// Run the executor to completion, blocking the thread until all
|
||||
/// spawned futures have completed **or** `duration` time has elapsed.
|
||||
pub fn run_timeout(&mut self, duration: Duration) -> Result<(), RunTimeoutError> {
|
||||
self.run_timeout2(Some(duration))
|
||||
}
|
||||
|
||||
/// Perform a single iteration of the event loop.
|
||||
///
|
||||
/// This function blocks the current thread even if the executor is idle.
|
||||
pub fn turn(&mut self, duration: Option<Duration>) -> Result<Turn, TurnError> {
|
||||
let res = if self.executor.scheduler.has_pending_futures() {
|
||||
self.executor.park.park_timeout(Duration::from_millis(0))
|
||||
} else {
|
||||
match duration {
|
||||
Some(duration) => self.executor.park.park_timeout(duration),
|
||||
None => self.executor.park.park(),
|
||||
}
|
||||
};
|
||||
|
||||
if res.is_err() {
|
||||
return Err(TurnError { _p: () });
|
||||
}
|
||||
|
||||
let polled = self.tick();
|
||||
|
||||
Ok(Turn { polled })
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying `Park` instance.
|
||||
pub fn get_park(&self) -> &P {
|
||||
&self.executor.park
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying `Park` instance.
|
||||
pub fn get_park_mut(&mut self) -> &mut P {
|
||||
&mut self.executor.park
|
||||
}
|
||||
|
||||
fn run_timeout2(&mut self, dur: Option<Duration>) -> Result<(), RunTimeoutError> {
|
||||
if self.executor.is_idle() {
|
||||
// Nothing to do
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut time = dur.map(|dur| (Instant::now() + dur, dur));
|
||||
|
||||
loop {
|
||||
self.tick();
|
||||
|
||||
if self.executor.is_idle() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match time {
|
||||
Some((until, rem)) => {
|
||||
if let Err(_) = self.executor.park.park_timeout(rem) {
|
||||
return Err(RunTimeoutError::new(false));
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
if now >= until {
|
||||
return Err(RunTimeoutError::new(true));
|
||||
}
|
||||
|
||||
time = Some((until, until - now));
|
||||
}
|
||||
None => {
|
||||
if let Err(_) = self.executor.park.park() {
|
||||
return Err(RunTimeoutError::new(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if any futures were processed
|
||||
fn tick(&mut self) -> bool {
|
||||
// Spawn any futures that were spawned from other threads by manually
|
||||
// looping over the receiver stream
|
||||
|
||||
// FIXME: Slightly ugly but needed to make the borrow checker happy
|
||||
let (mut borrow, spawn_receiver) = (
|
||||
Borrow {
|
||||
id: self.executor.id,
|
||||
scheduler: &mut self.executor.scheduler,
|
||||
num_futures: &*self.executor.num_futures,
|
||||
},
|
||||
&mut self.executor.spawn_receiver,
|
||||
);
|
||||
|
||||
while let Ok(future) = spawn_receiver.try_recv() {
|
||||
borrow.spawn_local(future, true);
|
||||
}
|
||||
|
||||
// After any pending futures were scheduled, do the actual tick
|
||||
borrow
|
||||
.scheduler
|
||||
.tick(borrow.id, &mut *self.enter, borrow.num_futures)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: Park> fmt::Debug for Entered<'a, P> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Entered")
|
||||
.field("executor", &self.executor)
|
||||
.field("enter", &self.enter)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Handle =====
|
||||
|
||||
/// Handle to spawn a future on the corresponding `CurrentThread` instance
|
||||
#[derive(Clone)]
|
||||
pub struct Handle {
|
||||
sender: mpsc::Sender<Box<dyn Future<Item = (), Error = ()> + Send + 'static>>,
|
||||
num_futures: Arc<atomic::AtomicUsize>,
|
||||
shut_down: Cell<bool>,
|
||||
notify: executor::NotifyHandle,
|
||||
thread: thread::ThreadId,
|
||||
|
||||
/// The thread-local ID assigned to this Handle's executor.
|
||||
id: u64,
|
||||
}
|
||||
|
||||
// Manual implementation because the Sender does not implement Debug
|
||||
impl fmt::Debug for Handle {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Handle")
|
||||
.field("shut_down", &self.shut_down.get())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Spawn a future onto the `CurrentThread` instance corresponding to this handle
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the spawn fails. Failure occurs if the `CurrentThread`
|
||||
/// instance of the `Handle` does not exist anymore.
|
||||
pub fn spawn<F>(&self, future: F) -> Result<(), SpawnError>
|
||||
where
|
||||
F: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
if thread::current().id() == self.thread {
|
||||
let mut e = TaskExecutor::current();
|
||||
if e.id() == Some(self.id) {
|
||||
return e.spawn_local(Box::new(future));
|
||||
}
|
||||
}
|
||||
|
||||
if self.shut_down.get() {
|
||||
return Err(SpawnError::shutdown());
|
||||
}
|
||||
|
||||
// NOTE: += 2 since LSB is the shutdown bit
|
||||
let pending = self.num_futures.fetch_add(2, atomic::Ordering::SeqCst);
|
||||
if pending % 2 == 1 {
|
||||
// Bring the count back so we still know when the Runtime is idle.
|
||||
self.num_futures.fetch_sub(2, atomic::Ordering::SeqCst);
|
||||
|
||||
// Once the Runtime is shutting down, we know it won't come back.
|
||||
self.shut_down.set(true);
|
||||
|
||||
return Err(SpawnError::shutdown());
|
||||
}
|
||||
|
||||
self.sender
|
||||
.send(Box::new(future))
|
||||
.expect("CurrentThread does not exist anymore");
|
||||
// use 0 for the id, CurrentThread does not make use of it
|
||||
self.notify.notify(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Provides a best effort **hint** to whether or not `spawn` will succeed.
|
||||
///
|
||||
/// This function may return both false positives **and** false negatives.
|
||||
/// If `status` returns `Ok`, then a call to `spawn` will *probably*
|
||||
/// succeed, but may fail. If `status` returns `Err`, a call to `spawn` will
|
||||
/// *probably* fail, but may succeed.
|
||||
///
|
||||
/// This allows a caller to avoid creating the task if the call to `spawn`
|
||||
/// has a high likelihood of failing.
|
||||
pub fn status(&self) -> Result<(), SpawnError> {
|
||||
if self.shut_down.get() {
|
||||
return Err(SpawnError::shutdown());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl TaskExecutor =====
|
||||
|
||||
impl TaskExecutor {
|
||||
/// Returns an executor that executes futures on the current thread.
|
||||
///
|
||||
/// The user of `TaskExecutor` must ensure that when a future is submitted,
|
||||
/// that it is done within the context of a call to `run`.
|
||||
///
|
||||
/// For more details, see the [module level](index.html) documentation.
|
||||
pub fn current() -> TaskExecutor {
|
||||
TaskExecutor {
|
||||
_p: ::std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current executor's thread-local ID.
|
||||
fn id(&self) -> Option<u64> {
|
||||
CURRENT.with(|current| current.id.get())
|
||||
}
|
||||
|
||||
/// Spawn a future onto the current `CurrentThread` instance.
|
||||
pub fn spawn_local(
|
||||
&mut self,
|
||||
future: Box<dyn Future<Item = (), Error = ()>>,
|
||||
) -> Result<(), SpawnError> {
|
||||
CURRENT.with(|current| match current.spawn.get() {
|
||||
Some(spawn) => {
|
||||
unsafe { (*spawn).spawn_local(future, false) };
|
||||
Ok(())
|
||||
}
|
||||
None => Err(SpawnError::shutdown()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_executor::Executor for TaskExecutor {
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Box<dyn Future<Item = (), Error = ()> + Send>,
|
||||
) -> Result<(), SpawnError> {
|
||||
self.spawn_local(future)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> tokio_executor::TypedExecutor<F> for TaskExecutor
|
||||
where
|
||||
F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
fn spawn(&mut self, future: F) -> Result<(), SpawnError> {
|
||||
self.spawn_local(Box::new(future))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Executor<F> for TaskExecutor
|
||||
where
|
||||
F: Future<Item = (), Error = ()> + 'static,
|
||||
{
|
||||
fn execute(&self, future: F) -> Result<(), ExecuteError<F>> {
|
||||
CURRENT.with(|current| match current.spawn.get() {
|
||||
Some(spawn) => {
|
||||
unsafe { (*spawn).spawn_local(Box::new(future), false) };
|
||||
Ok(())
|
||||
}
|
||||
None => Err(ExecuteError::new(ExecuteErrorKind::Shutdown, future)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Borrow =====
|
||||
|
||||
impl<'a, U: Unpark> Borrow<'a, U> {
|
||||
fn enter<F, R>(&mut self, _: &mut Enter, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
CURRENT.with(|current| {
|
||||
current.id.set(Some(self.id));
|
||||
current.set_spawn(self, || f())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, U: Unpark> SpawnLocal for Borrow<'a, U> {
|
||||
fn spawn_local(
|
||||
&mut self,
|
||||
future: Box<dyn Future<Item = (), Error = ()>>,
|
||||
already_counted: bool,
|
||||
) {
|
||||
if !already_counted {
|
||||
// NOTE: we have a borrow of the Runtime, so we know that it isn't shut down.
|
||||
// NOTE: += 2 since LSB is the shutdown bit
|
||||
self.num_futures.fetch_add(2, atomic::Ordering::SeqCst);
|
||||
}
|
||||
self.scheduler.schedule(future);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl CurrentRunner =====
|
||||
|
||||
impl CurrentRunner {
|
||||
fn set_spawn<F, R>(&self, spawn: &mut dyn SpawnLocal, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
struct Reset<'a>(&'a CurrentRunner);
|
||||
|
||||
impl<'a> Drop for Reset<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.0.spawn.set(None);
|
||||
self.0.id.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
let _reset = Reset(self);
|
||||
|
||||
let spawn = unsafe { hide_lt(spawn as *mut dyn SpawnLocal) };
|
||||
self.spawn.set(Some(spawn));
|
||||
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn hide_lt<'a>(p: *mut (dyn SpawnLocal + 'a)) -> *mut (dyn SpawnLocal + 'static) {
|
||||
use std::mem;
|
||||
mem::transmute(p)
|
||||
}
|
||||
|
||||
// ===== impl RunTimeoutError =====
|
||||
|
||||
impl RunTimeoutError {
|
||||
fn new(timeout: bool) -> Self {
|
||||
RunTimeoutError { timeout }
|
||||
}
|
||||
|
||||
/// Returns `true` if the error was caused by the operation timing out.
|
||||
pub fn is_timeout(&self) -> bool {
|
||||
self.timeout
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tokio_executor::EnterError> for RunTimeoutError {
|
||||
fn from(_: tokio_executor::EnterError) -> Self {
|
||||
RunTimeoutError::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl BlockError =====
|
||||
|
||||
impl<T> BlockError<T> {
|
||||
/// Returns the error yielded by the future being blocked on
|
||||
pub fn into_inner(self) -> Option<T> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<tokio_executor::EnterError> for BlockError<T> {
|
||||
fn from(_: tokio_executor::EnterError) -> Self {
|
||||
BlockError { inner: None }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,770 @@
|
||||
use super::Borrow;
|
||||
use tokio_executor::park::Unpark;
|
||||
use tokio_executor::Enter;
|
||||
|
||||
use futures::executor::{self, NotifyHandle, Spawn, UnsafeNotify};
|
||||
use futures::{Async, Future};
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release, SeqCst};
|
||||
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::thread;
|
||||
use std::usize;
|
||||
|
||||
/// A generic task-aware scheduler.
|
||||
///
|
||||
/// This is used both by `FuturesUnordered` and the current-thread executor.
|
||||
pub struct Scheduler<U> {
|
||||
inner: Arc<Inner<U>>,
|
||||
nodes: List<U>,
|
||||
}
|
||||
|
||||
pub struct Notify<'a, U: 'a>(&'a Arc<Node<U>>);
|
||||
|
||||
// A linked-list of nodes
|
||||
struct List<U> {
|
||||
len: usize,
|
||||
head: *const Node<U>,
|
||||
tail: *const Node<U>,
|
||||
}
|
||||
|
||||
// Scheduler is implemented using two linked lists. The first linked list tracks
|
||||
// all items managed by a `Scheduler`. This list is stored on the `Scheduler`
|
||||
// struct and is **not** thread safe. The second linked list is an
|
||||
// implementation of the intrusive MPSC queue algorithm described by
|
||||
// 1024cores.net and is stored on `Inner`. This linked list can push items to
|
||||
// the back concurrently but only one consumer may pop from the front. To
|
||||
// enforce this requirement, all popping will be performed via fns on
|
||||
// `Scheduler` that take `&mut self`.
|
||||
//
|
||||
// When a item is submitted to the set a node is allocated and inserted in
|
||||
// both linked lists. This means that all insertion operations **must** be
|
||||
// originated from `Scheduler` with `&mut self` The next call to `tick` will
|
||||
// (eventually) see this node and call `poll` on the item.
|
||||
//
|
||||
// Nodes are wrapped in `Arc` cells which manage the lifetime of the node.
|
||||
// However, `Arc` handles are sometimes cast to `*const Node` pointers.
|
||||
// Specifically, when a node is stored in at least one of the two lists
|
||||
// described above, this represents a logical `Arc` handle. This is how
|
||||
// `Scheduler` maintains its reference to all nodes it manages. Each
|
||||
// `NotifyHandle` instance is an `Arc<Node>` as well.
|
||||
//
|
||||
// When `Scheduler` drops, it clears the linked list of all nodes that it
|
||||
// manages. When doing so, it must attempt to decrement the reference count (by
|
||||
// dropping an Arc handle). However, it can **only** decrement the reference
|
||||
// count if the node is not currently stored in the mpsc channel. If the node
|
||||
// **is** "queued" in the mpsc channel, then the arc reference count cannot be
|
||||
// decremented. Once the node is popped from the mpsc channel, then the final
|
||||
// arc reference count can be decremented, thus freeing the node.
|
||||
|
||||
struct Inner<U> {
|
||||
// Thread unpark handle
|
||||
unpark: U,
|
||||
|
||||
// Tick number
|
||||
tick_num: AtomicUsize,
|
||||
|
||||
// Head/tail of the readiness queue
|
||||
head_readiness: AtomicPtr<Node<U>>,
|
||||
tail_readiness: UnsafeCell<*const Node<U>>,
|
||||
|
||||
// Used as part of the mpsc queue algorithm
|
||||
stub: Arc<Node<U>>,
|
||||
}
|
||||
|
||||
unsafe impl<U: Sync + Send> Send for Inner<U> {}
|
||||
unsafe impl<U: Sync + Send> Sync for Inner<U> {}
|
||||
|
||||
impl<U: Unpark> executor::Notify for Inner<U> {
|
||||
fn notify(&self, _: usize) {
|
||||
self.unpark.unpark();
|
||||
}
|
||||
}
|
||||
|
||||
struct Node<U> {
|
||||
// The item
|
||||
item: UnsafeCell<Option<Task>>,
|
||||
|
||||
// The tick at which this node was notified
|
||||
notified_at: AtomicUsize,
|
||||
|
||||
// Next pointer for linked list tracking all active nodes
|
||||
next_all: UnsafeCell<*const Node<U>>,
|
||||
|
||||
// Previous node in linked list tracking all active nodes
|
||||
prev_all: UnsafeCell<*const Node<U>>,
|
||||
|
||||
// Next pointer in readiness queue
|
||||
next_readiness: AtomicPtr<Node<U>>,
|
||||
|
||||
// Whether or not this node is currently in the mpsc queue.
|
||||
queued: AtomicBool,
|
||||
|
||||
// Queue that we'll be enqueued to when notified
|
||||
queue: Weak<Inner<U>>,
|
||||
}
|
||||
|
||||
/// Returned by `Inner::dequeue`, representing either a dequeue success (with
|
||||
/// the dequeued node), an empty list, or an inconsistent state.
|
||||
///
|
||||
/// The inconsistent state is described in more detail at [1024cores], but
|
||||
/// roughly indicates that a node will be ready to dequeue sometime shortly in
|
||||
/// the future and the caller should try again soon.
|
||||
///
|
||||
/// [1024cores]: http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
|
||||
enum Dequeue<U> {
|
||||
Data(*const Node<U>),
|
||||
Empty,
|
||||
Yield,
|
||||
Inconsistent,
|
||||
}
|
||||
|
||||
/// Wraps a spawned boxed future
|
||||
struct Task(Spawn<Box<dyn Future<Item = (), Error = ()>>>);
|
||||
|
||||
/// A task that is scheduled. `turn` must be called
|
||||
pub struct Scheduled<'a, U: 'a> {
|
||||
task: &'a mut Task,
|
||||
notify: &'a Notify<'a, U>,
|
||||
done: &'a mut bool,
|
||||
}
|
||||
|
||||
impl<U> Scheduler<U>
|
||||
where
|
||||
U: Unpark,
|
||||
{
|
||||
/// Constructs a new, empty `Scheduler`
|
||||
///
|
||||
/// The returned `Scheduler` does not contain any items and, in this
|
||||
/// state, `Scheduler::poll` will return `Ok(Async::Ready(None))`.
|
||||
pub fn new(unpark: U) -> Self {
|
||||
let stub = Arc::new(Node {
|
||||
item: UnsafeCell::new(None),
|
||||
notified_at: AtomicUsize::new(0),
|
||||
next_all: UnsafeCell::new(ptr::null()),
|
||||
prev_all: UnsafeCell::new(ptr::null()),
|
||||
next_readiness: AtomicPtr::new(ptr::null_mut()),
|
||||
queued: AtomicBool::new(true),
|
||||
queue: Weak::new(),
|
||||
});
|
||||
let stub_ptr = &*stub as *const Node<U>;
|
||||
let inner = Arc::new(Inner {
|
||||
unpark,
|
||||
tick_num: AtomicUsize::new(0),
|
||||
head_readiness: AtomicPtr::new(stub_ptr as *mut _),
|
||||
tail_readiness: UnsafeCell::new(stub_ptr),
|
||||
stub: stub,
|
||||
});
|
||||
|
||||
Scheduler {
|
||||
inner: inner,
|
||||
nodes: List::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(&self) -> NotifyHandle {
|
||||
self.inner.clone().into()
|
||||
}
|
||||
|
||||
pub fn schedule(&mut self, item: Box<dyn Future<Item = (), Error = ()>>) {
|
||||
// Get the current scheduler tick
|
||||
let tick_num = self.inner.tick_num.load(SeqCst);
|
||||
|
||||
let node = Arc::new(Node {
|
||||
item: UnsafeCell::new(Some(Task::new(item))),
|
||||
notified_at: AtomicUsize::new(tick_num),
|
||||
next_all: UnsafeCell::new(ptr::null_mut()),
|
||||
prev_all: UnsafeCell::new(ptr::null_mut()),
|
||||
next_readiness: AtomicPtr::new(ptr::null_mut()),
|
||||
queued: AtomicBool::new(true),
|
||||
queue: Arc::downgrade(&self.inner),
|
||||
});
|
||||
|
||||
// Right now our node has a strong reference count of 1. We transfer
|
||||
// ownership of this reference count to our internal linked list
|
||||
// and we'll reclaim ownership through the `unlink` function below.
|
||||
let ptr = self.nodes.push_back(node);
|
||||
|
||||
// We'll need to get the item "into the system" to start tracking it,
|
||||
// e.g. getting its unpark notifications going to us tracking which
|
||||
// items are ready. To do that we unconditionally enqueue it for
|
||||
// polling here.
|
||||
self.inner.enqueue(ptr);
|
||||
}
|
||||
|
||||
/// Returns `true` if there are currently any pending futures
|
||||
pub fn has_pending_futures(&mut self) -> bool {
|
||||
// See function definition for why the unsafe is needed and
|
||||
// correctly used here
|
||||
unsafe { self.inner.has_pending_futures() }
|
||||
}
|
||||
|
||||
/// Advance the scheduler state, returning `true` if any futures were
|
||||
/// processed.
|
||||
///
|
||||
/// This function should be called whenever the caller is notified via a
|
||||
/// wakeup.
|
||||
pub fn tick(&mut self, eid: u64, enter: &mut Enter, num_futures: &AtomicUsize) -> bool {
|
||||
let mut ret = false;
|
||||
let tick = self.inner.tick_num.fetch_add(1, SeqCst).wrapping_add(1);
|
||||
|
||||
loop {
|
||||
let node = match unsafe { self.inner.dequeue(Some(tick)) } {
|
||||
Dequeue::Empty => {
|
||||
return ret;
|
||||
}
|
||||
Dequeue::Yield => {
|
||||
self.inner.unpark.unpark();
|
||||
return ret;
|
||||
}
|
||||
Dequeue::Inconsistent => {
|
||||
thread::yield_now();
|
||||
continue;
|
||||
}
|
||||
Dequeue::Data(node) => node,
|
||||
};
|
||||
|
||||
ret = true;
|
||||
|
||||
debug_assert!(node != self.inner.stub());
|
||||
|
||||
unsafe {
|
||||
if (*(*node).item.get()).is_none() {
|
||||
// The node has already been released. However, while it was
|
||||
// being released, another thread notified it, which
|
||||
// resulted in it getting pushed into the mpsc channel.
|
||||
//
|
||||
// In this case, we just decrement the ref count.
|
||||
let node = ptr2arc(node);
|
||||
assert!((*node.next_all.get()).is_null());
|
||||
assert!((*node.prev_all.get()).is_null());
|
||||
continue;
|
||||
};
|
||||
|
||||
// We're going to need to be very careful if the `poll`
|
||||
// function below panics. We need to (a) not leak memory and
|
||||
// (b) ensure that we still don't have any use-after-frees. To
|
||||
// manage this we do a few things:
|
||||
//
|
||||
// * This "bomb" here will call `release_node` if dropped
|
||||
// abnormally. That way we'll be sure the memory management
|
||||
// of the `node` is managed correctly.
|
||||
//
|
||||
// * We unlink the node from our internal queue to preemptively
|
||||
// assume is is complete (will return Ready or panic), in
|
||||
// which case we'll want to discard it regardless.
|
||||
//
|
||||
struct Bomb<'a, U: Unpark + 'a> {
|
||||
borrow: &'a mut Borrow<'a, U>,
|
||||
enter: &'a mut Enter,
|
||||
node: Option<Arc<Node<U>>>,
|
||||
}
|
||||
|
||||
impl<'a, U: Unpark> Drop for Bomb<'a, U> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(node) = self.node.take() {
|
||||
self.borrow.enter(self.enter, || release_node(node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let node = self.nodes.remove(node);
|
||||
|
||||
let mut borrow = Borrow {
|
||||
id: eid,
|
||||
scheduler: self,
|
||||
num_futures,
|
||||
};
|
||||
|
||||
let mut bomb = Bomb {
|
||||
node: Some(node),
|
||||
enter: enter,
|
||||
borrow: &mut borrow,
|
||||
};
|
||||
|
||||
let mut done = false;
|
||||
|
||||
// Now that the bomb holds the node, create a new scope. This
|
||||
// scope ensures that the borrow will go out of scope before we
|
||||
// mutate the node pointer in `bomb` again
|
||||
{
|
||||
let node = bomb.node.as_ref().unwrap();
|
||||
|
||||
// Get a reference to the inner future. We already ensured
|
||||
// that the item `is_some`.
|
||||
let item = (*node.item.get()).as_mut().unwrap();
|
||||
|
||||
// Unset queued flag... this must be done before
|
||||
// polling. This ensures that the item gets
|
||||
// rescheduled if it is notified **during** a call
|
||||
// to `poll`.
|
||||
let prev = (*node).queued.swap(false, SeqCst);
|
||||
assert!(prev);
|
||||
|
||||
// Poll the underlying item with the appropriate `notify`
|
||||
// implementation. This is where a large bit of the unsafety
|
||||
// starts to stem from internally. The `notify` instance itself
|
||||
// is basically just our `Arc<Node>` and tracks the mpsc
|
||||
// queue of ready items.
|
||||
//
|
||||
// Critically though `Node` won't actually access `Task`, the
|
||||
// item, while it's floating around inside of `Task`
|
||||
// instances. These structs will basically just use `T` to size
|
||||
// the internal allocation, appropriately accessing fields and
|
||||
// deallocating the node if need be.
|
||||
let borrow = &mut *bomb.borrow;
|
||||
let enter = &mut *bomb.enter;
|
||||
let notify = Notify(bomb.node.as_ref().unwrap());
|
||||
|
||||
let mut scheduled = Scheduled {
|
||||
task: item,
|
||||
notify: ¬ify,
|
||||
done: &mut done,
|
||||
};
|
||||
|
||||
if borrow.enter(enter, || scheduled.tick()) {
|
||||
// we have a borrow of the Runtime, so we know it's not shut down
|
||||
borrow.num_futures.fetch_sub(2, SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// The future is not done, push it back into the "all
|
||||
// node" list.
|
||||
let node = bomb.node.take().unwrap();
|
||||
bomb.borrow.scheduler.nodes.push_back(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, U: Unpark> Scheduled<'a, U> {
|
||||
/// Polls the task, returns `true` if the task has completed.
|
||||
pub fn tick(&mut self) -> bool {
|
||||
// Tick the future
|
||||
let ret = match self.task.0.poll_future_notify(self.notify, 0) {
|
||||
Ok(Async::Ready(_)) | Err(_) => true,
|
||||
Ok(Async::NotReady) => false,
|
||||
};
|
||||
|
||||
*self.done = ret;
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn new(future: Box<dyn Future<Item = (), Error = ()> + 'static>) -> Self {
|
||||
Task(executor::spawn(future))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Task {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Task").finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn release_node<U>(node: Arc<Node<U>>) {
|
||||
// The item is done, try to reset the queued flag. This will prevent
|
||||
// `notify` from doing any work in the item
|
||||
let prev = node.queued.swap(true, SeqCst);
|
||||
|
||||
// Drop the item, even if it hasn't finished yet. This is safe
|
||||
// because we're dropping the item on the thread that owns
|
||||
// `Scheduler`, which correctly tracks T's lifetimes and such.
|
||||
unsafe {
|
||||
drop((*node.item.get()).take());
|
||||
}
|
||||
|
||||
// If the queued flag was previously set then it means that this node
|
||||
// is still in our internal mpsc queue. We then transfer ownership
|
||||
// of our reference count to the mpsc queue, and it'll come along and
|
||||
// free it later, noticing that the item is `None`.
|
||||
//
|
||||
// If, however, the queued flag was *not* set then we're safe to
|
||||
// release our reference count on the internal node. The queued flag
|
||||
// was set above so all item `enqueue` operations will not actually
|
||||
// enqueue the node, so our node will never see the mpsc queue again.
|
||||
// The node itself will be deallocated once all reference counts have
|
||||
// been dropped by the various owning tasks elsewhere.
|
||||
if prev {
|
||||
mem::forget(node);
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> Debug for Scheduler<U> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "Scheduler {{ ... }}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> Drop for Scheduler<U> {
|
||||
fn drop(&mut self) {
|
||||
// When a `Scheduler` is dropped we want to drop all items associated
|
||||
// with it. At the same time though there may be tons of `Task` handles
|
||||
// flying around which contain `Node` references inside them. We'll
|
||||
// let those naturally get deallocated when the `Task` itself goes out
|
||||
// of scope or gets notified.
|
||||
while let Some(node) = self.nodes.pop_front() {
|
||||
release_node(node);
|
||||
}
|
||||
|
||||
// Note that at this point we could still have a bunch of nodes in the
|
||||
// mpsc queue. None of those nodes, however, have items associated
|
||||
// with them so they're safe to destroy on any thread. At this point
|
||||
// the `Scheduler` struct, the owner of the one strong reference
|
||||
// to `Inner` will drop the strong reference. At that point
|
||||
// whichever thread releases the strong refcount last (be it this
|
||||
// thread or some other thread as part of an `upgrade`) will clear out
|
||||
// the mpsc queue and free all remaining nodes.
|
||||
//
|
||||
// While that freeing operation isn't guaranteed to happen here, it's
|
||||
// guaranteed to happen "promptly" as no more "blocking work" will
|
||||
// happen while there's a strong refcount held.
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> Inner<U> {
|
||||
/// The enqueue function from the 1024cores intrusive MPSC queue algorithm.
|
||||
fn enqueue(&self, node: *const Node<U>) {
|
||||
unsafe {
|
||||
debug_assert!((*node).queued.load(Relaxed));
|
||||
|
||||
// This action does not require any coordination
|
||||
(*node).next_readiness.store(ptr::null_mut(), Relaxed);
|
||||
|
||||
// Note that these atomic orderings come from 1024cores
|
||||
let node = node as *mut _;
|
||||
let prev = self.head_readiness.swap(node, AcqRel);
|
||||
(*prev).next_readiness.store(node, Release);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if there are currently any pending futures
|
||||
///
|
||||
/// See `dequeue` for an explanation why this function is unsafe.
|
||||
unsafe fn has_pending_futures(&self) -> bool {
|
||||
let tail = *self.tail_readiness.get();
|
||||
let next = (*tail).next_readiness.load(Acquire);
|
||||
|
||||
if tail == self.stub() {
|
||||
if next.is_null() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// The dequeue function from the 1024cores intrusive MPSC queue algorithm
|
||||
///
|
||||
/// Note that this unsafe as it required mutual exclusion (only one thread
|
||||
/// can call this) to be guaranteed elsewhere.
|
||||
unsafe fn dequeue(&self, tick: Option<usize>) -> Dequeue<U> {
|
||||
let mut tail = *self.tail_readiness.get();
|
||||
let mut next = (*tail).next_readiness.load(Acquire);
|
||||
|
||||
if tail == self.stub() {
|
||||
if next.is_null() {
|
||||
return Dequeue::Empty;
|
||||
}
|
||||
|
||||
*self.tail_readiness.get() = next;
|
||||
tail = next;
|
||||
next = (*next).next_readiness.load(Acquire);
|
||||
}
|
||||
|
||||
if let Some(tick) = tick {
|
||||
let actual = (*tail).notified_at.load(SeqCst);
|
||||
|
||||
// Only dequeue if the node was not scheduled during the current
|
||||
// tick.
|
||||
if actual == tick {
|
||||
// Only doing the check above **should** be enough in
|
||||
// practice. However, technically there is a potential for
|
||||
// deadlocking if there are `usize::MAX` ticks while the thread
|
||||
// scheduling the task is frozen.
|
||||
//
|
||||
// If, for some reason, this is not enough, calling `unpark`
|
||||
// here will resolve the issue.
|
||||
return Dequeue::Yield;
|
||||
}
|
||||
}
|
||||
|
||||
if !next.is_null() {
|
||||
*self.tail_readiness.get() = next;
|
||||
debug_assert!(tail != self.stub());
|
||||
return Dequeue::Data(tail);
|
||||
}
|
||||
|
||||
if self.head_readiness.load(Acquire) as *const _ != tail {
|
||||
return Dequeue::Inconsistent;
|
||||
}
|
||||
|
||||
self.enqueue(self.stub());
|
||||
|
||||
next = (*tail).next_readiness.load(Acquire);
|
||||
|
||||
if !next.is_null() {
|
||||
*self.tail_readiness.get() = next;
|
||||
return Dequeue::Data(tail);
|
||||
}
|
||||
|
||||
Dequeue::Inconsistent
|
||||
}
|
||||
|
||||
fn stub(&self) -> *const Node<U> {
|
||||
&*self.stub
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> Drop for Inner<U> {
|
||||
fn drop(&mut self) {
|
||||
// Once we're in the destructor for `Inner` we need to clear out the
|
||||
// mpsc queue of nodes if there's anything left in there.
|
||||
//
|
||||
// Note that each node has a strong reference count associated with it
|
||||
// which is owned by the mpsc queue. All nodes should have had their
|
||||
// items dropped already by the `Scheduler` destructor above,
|
||||
// so we're just pulling out nodes and dropping their refcounts.
|
||||
unsafe {
|
||||
loop {
|
||||
match self.dequeue(None) {
|
||||
Dequeue::Empty => break,
|
||||
Dequeue::Yield => unreachable!(),
|
||||
Dequeue::Inconsistent => abort("inconsistent in drop"),
|
||||
Dequeue::Data(ptr) => drop(ptr2arc(ptr)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> List<U> {
|
||||
fn new() -> Self {
|
||||
List {
|
||||
len: 0,
|
||||
head: ptr::null_mut(),
|
||||
tail: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends an element to the back of the list
|
||||
fn push_back(&mut self, node: Arc<Node<U>>) -> *const Node<U> {
|
||||
let ptr = arc2ptr(node);
|
||||
|
||||
unsafe {
|
||||
// Point to the current last node in the list
|
||||
*(*ptr).prev_all.get() = self.tail;
|
||||
*(*ptr).next_all.get() = ptr::null_mut();
|
||||
|
||||
if !self.tail.is_null() {
|
||||
*(*self.tail).next_all.get() = ptr;
|
||||
self.tail = ptr;
|
||||
} else {
|
||||
// This is the first node
|
||||
self.tail = ptr;
|
||||
self.head = ptr;
|
||||
}
|
||||
}
|
||||
|
||||
self.len += 1;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/// Pop an element from the front of the list
|
||||
fn pop_front(&mut self) -> Option<Arc<Node<U>>> {
|
||||
if self.head.is_null() {
|
||||
// The list is empty
|
||||
return None;
|
||||
}
|
||||
|
||||
self.len -= 1;
|
||||
|
||||
unsafe {
|
||||
// Convert the ptr to Arc<_>
|
||||
let node = ptr2arc(self.head);
|
||||
|
||||
// Update the head pointer
|
||||
self.head = *node.next_all.get();
|
||||
|
||||
// If the pointer is null, then the list is empty
|
||||
if self.head.is_null() {
|
||||
self.tail = ptr::null_mut();
|
||||
} else {
|
||||
*(*self.head).prev_all.get() = ptr::null_mut();
|
||||
}
|
||||
|
||||
Some(node)
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a specific node
|
||||
unsafe fn remove(&mut self, node: *const Node<U>) -> Arc<Node<U>> {
|
||||
let node = ptr2arc(node);
|
||||
let next = *node.next_all.get();
|
||||
let prev = *node.prev_all.get();
|
||||
*node.next_all.get() = ptr::null_mut();
|
||||
*node.prev_all.get() = ptr::null_mut();
|
||||
|
||||
if !next.is_null() {
|
||||
*(*next).prev_all.get() = prev;
|
||||
} else {
|
||||
self.tail = prev;
|
||||
}
|
||||
|
||||
if !prev.is_null() {
|
||||
*(*prev).next_all.get() = next;
|
||||
} else {
|
||||
self.head = next;
|
||||
}
|
||||
|
||||
self.len -= 1;
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, U> Clone for Notify<'a, U> {
|
||||
fn clone(&self) -> Self {
|
||||
Notify(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, U> fmt::Debug for Notify<'a, U> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Notify").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, U: Unpark> From<Notify<'a, U>> for NotifyHandle {
|
||||
fn from(handle: Notify<'a, U>) -> NotifyHandle {
|
||||
unsafe {
|
||||
let ptr = handle.0.clone();
|
||||
let ptr = mem::transmute::<Arc<Node<U>>, *mut ArcNode<U>>(ptr);
|
||||
NotifyHandle::new(hide_lt(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ArcNode<U>(PhantomData<U>);
|
||||
|
||||
// We should never touch `Task` on any thread other than the one owning
|
||||
// `Scheduler`, so this should be a safe operation.
|
||||
unsafe impl<U: Sync + Send> Send for ArcNode<U> {}
|
||||
unsafe impl<U: Sync + Send> Sync for ArcNode<U> {}
|
||||
|
||||
impl<U: Unpark> executor::Notify for ArcNode<U> {
|
||||
fn notify(&self, _id: usize) {
|
||||
unsafe {
|
||||
let me: *const ArcNode<U> = self;
|
||||
let me: *const *const ArcNode<U> = &me;
|
||||
let me = me as *const Arc<Node<U>>;
|
||||
Node::notify(&*me)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<U: Unpark> UnsafeNotify for ArcNode<U> {
|
||||
unsafe fn clone_raw(&self) -> NotifyHandle {
|
||||
let me: *const ArcNode<U> = self;
|
||||
let me: *const *const ArcNode<U> = &me;
|
||||
let me = &*(me as *const Arc<Node<U>>);
|
||||
Notify(me).into()
|
||||
}
|
||||
|
||||
unsafe fn drop_raw(&self) {
|
||||
let mut me: *const ArcNode<U> = self;
|
||||
let me = &mut me as *mut *const ArcNode<U> as *mut Arc<Node<U>>;
|
||||
ptr::drop_in_place(me);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn hide_lt<U: Unpark>(p: *mut ArcNode<U>) -> *mut dyn UnsafeNotify {
|
||||
mem::transmute(p as *mut dyn UnsafeNotify)
|
||||
}
|
||||
|
||||
impl<U: Unpark> Node<U> {
|
||||
fn notify(me: &Arc<Node<U>>) {
|
||||
let inner = match me.queue.upgrade() {
|
||||
Some(inner) => inner,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// It's our job to notify the node that it's ready to get polled,
|
||||
// meaning that we need to enqueue it into the readiness queue. To
|
||||
// do this we flag that we're ready to be queued, and if successful
|
||||
// we then do the literal queueing operation, ensuring that we're
|
||||
// only queued once.
|
||||
//
|
||||
// Once the node is inserted we be sure to notify the parent task,
|
||||
// as it'll want to come along and pick up our node now.
|
||||
//
|
||||
// Note that we don't change the reference count of the node here,
|
||||
// we're just enqueueing the raw pointer. The `Scheduler`
|
||||
// implementation guarantees that if we set the `queued` flag true that
|
||||
// there's a reference count held by the main `Scheduler` queue
|
||||
// still.
|
||||
let prev = me.queued.swap(true, SeqCst);
|
||||
if !prev {
|
||||
// Get the current scheduler tick
|
||||
let tick_num = inner.tick_num.load(SeqCst);
|
||||
me.notified_at.store(tick_num, SeqCst);
|
||||
|
||||
inner.enqueue(&**me);
|
||||
inner.unpark.unpark();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> Drop for Node<U> {
|
||||
fn drop(&mut self) {
|
||||
// Currently a `Node` is sent across all threads for any lifetime,
|
||||
// regardless of `T`. This means that for memory safety we can't
|
||||
// actually touch `T` at any time except when we have a reference to the
|
||||
// `Scheduler` itself.
|
||||
//
|
||||
// Consequently it *should* be the case that we always drop items from
|
||||
// the `Scheduler` instance, but this is a bomb in place to catch
|
||||
// any bugs in that logic.
|
||||
unsafe {
|
||||
if (*self.item.get()).is_some() {
|
||||
abort("item still here when dropping");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn arc2ptr<T>(ptr: Arc<T>) -> *const T {
|
||||
let addr = &*ptr as *const T;
|
||||
mem::forget(ptr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
unsafe fn ptr2arc<T>(ptr: *const T) -> Arc<T> {
|
||||
let anchor = mem::transmute::<usize, Arc<T>>(0x10);
|
||||
let addr = &*anchor as *const T;
|
||||
mem::forget(anchor);
|
||||
let offset = addr as isize - 0x10;
|
||||
mem::transmute::<isize, Arc<T>>(ptr as isize - offset)
|
||||
}
|
||||
|
||||
fn abort(s: &str) -> ! {
|
||||
struct DoublePanic;
|
||||
|
||||
impl Drop for DoublePanic {
|
||||
fn drop(&mut self) {
|
||||
panic!("panicking twice to abort the program");
|
||||
}
|
||||
}
|
||||
|
||||
let _bomb = DoublePanic;
|
||||
panic!("{}", s);
|
||||
}
|
||||
@@ -0,0 +1,835 @@
|
||||
extern crate futures;
|
||||
extern crate tokio_current_thread;
|
||||
extern crate tokio_executor;
|
||||
|
||||
use tokio_current_thread::{block_on_all, CurrentThread};
|
||||
|
||||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::future::{self, lazy};
|
||||
use futures::task;
|
||||
// This is not actually unused --- we need this trait to be in scope for
|
||||
// the tests that sue TaskExecutor::current().execute(). The compiler
|
||||
// doesn't realise that.
|
||||
#[allow(unused_imports)]
|
||||
use futures::future::Executor as _futures_Executor;
|
||||
use futures::prelude::*;
|
||||
use futures::sync::oneshot;
|
||||
|
||||
mod from_block_on_all {
|
||||
use super::*;
|
||||
fn test<F: Fn(Box<dyn Future<Item = (), Error = ()>>) + 'static>(spawn: F) {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
let msg = tokio_current_thread::block_on_all(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
|
||||
// Spawn!
|
||||
spawn(Box::new(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
Ok::<(), ()>(())
|
||||
})));
|
||||
|
||||
Ok::<_, ()>("hello")
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(2, cnt.get());
|
||||
assert_eq!(msg, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn() {
|
||||
test(tokio_current_thread::spawn)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
test(|f| {
|
||||
tokio_current_thread::TaskExecutor::current()
|
||||
.execute(f)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_waits() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
thread::spawn(|| {
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let cnt2 = cnt.clone();
|
||||
|
||||
block_on_all(rx.then(move |_| {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok::<_, ()>(())
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, cnt2.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_many() {
|
||||
const ITER: usize = 200;
|
||||
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let mut tokio_current_thread = CurrentThread::new();
|
||||
|
||||
for _ in 0..ITER {
|
||||
let cnt = cnt.clone();
|
||||
tokio_current_thread.spawn(lazy(move || {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
}
|
||||
|
||||
tokio_current_thread.run().unwrap();
|
||||
|
||||
assert_eq!(cnt.get(), ITER);
|
||||
}
|
||||
|
||||
mod does_not_set_global_executor_by_default {
|
||||
use super::*;
|
||||
|
||||
fn test<F: Fn(Box<dyn Future<Item = (), Error = ()> + Send>) -> Result<(), E> + 'static, E>(
|
||||
spawn: F,
|
||||
) {
|
||||
block_on_all(lazy(|| {
|
||||
spawn(Box::new(lazy(|| ok()))).unwrap_err();
|
||||
ok()
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn() {
|
||||
use tokio_executor::Executor;
|
||||
test(|f| tokio_executor::DefaultExecutor::current().spawn(f))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
test(|f| tokio_executor::DefaultExecutor::current().execute(f))
|
||||
}
|
||||
}
|
||||
|
||||
mod from_block_on_future {
|
||||
use super::*;
|
||||
|
||||
fn test<F: Fn(Box<dyn Future<Item = (), Error = ()>>)>(spawn: F) {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
|
||||
let mut tokio_current_thread = CurrentThread::new();
|
||||
|
||||
tokio_current_thread
|
||||
.block_on(lazy(|| {
|
||||
let cnt = cnt.clone();
|
||||
|
||||
spawn(Box::new(lazy(move || {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok(())
|
||||
})));
|
||||
|
||||
Ok::<_, ()>(())
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
tokio_current_thread.run().unwrap();
|
||||
|
||||
assert_eq!(1, cnt.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn() {
|
||||
test(tokio_current_thread::spawn);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
test(|f| {
|
||||
tokio_current_thread::TaskExecutor::current()
|
||||
.execute(f)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct Never(Rc<()>);
|
||||
|
||||
impl Future for Never {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
mod outstanding_tasks_are_dropped_when_executor_is_dropped {
|
||||
use super::*;
|
||||
|
||||
fn test<F, G>(spawn: F, dotspawn: G)
|
||||
where
|
||||
F: Fn(Box<dyn Future<Item = (), Error = ()>>) + 'static,
|
||||
G: Fn(&mut CurrentThread, Box<dyn Future<Item = (), Error = ()>>),
|
||||
{
|
||||
let mut rc = Rc::new(());
|
||||
|
||||
let mut tokio_current_thread = CurrentThread::new();
|
||||
dotspawn(&mut tokio_current_thread, Box::new(Never(rc.clone())));
|
||||
|
||||
drop(tokio_current_thread);
|
||||
|
||||
// Ensure the daemon is dropped
|
||||
assert!(Rc::get_mut(&mut rc).is_some());
|
||||
|
||||
// Using the global spawn fn
|
||||
|
||||
let mut rc = Rc::new(());
|
||||
|
||||
let mut tokio_current_thread = CurrentThread::new();
|
||||
|
||||
tokio_current_thread
|
||||
.block_on(lazy(|| {
|
||||
spawn(Box::new(Never(rc.clone())));
|
||||
Ok::<_, ()>(())
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
drop(tokio_current_thread);
|
||||
|
||||
// Ensure the daemon is dropped
|
||||
assert!(Rc::get_mut(&mut rc).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn() {
|
||||
test(tokio_current_thread::spawn, |rt, f| {
|
||||
rt.spawn(f);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
test(
|
||||
|f| {
|
||||
tokio_current_thread::TaskExecutor::current()
|
||||
.execute(f)
|
||||
.unwrap();
|
||||
},
|
||||
// Note: `CurrentThread` doesn't currently implement
|
||||
// `futures::Executor`, so we'll call `.spawn(...)` rather than
|
||||
// `.execute(...)` for now. If `CurrentThread` is changed to
|
||||
// implement Executor, change this to `.execute(...).unwrap()`.
|
||||
|rt, f| {
|
||||
rt.spawn(f);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn nesting_run() {
|
||||
block_on_all(lazy(|| {
|
||||
block_on_all(lazy(|| ok())).unwrap();
|
||||
|
||||
ok()
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
mod run_in_future {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn spawn() {
|
||||
block_on_all(lazy(|| {
|
||||
tokio_current_thread::spawn(lazy(|| {
|
||||
block_on_all(lazy(|| ok())).unwrap();
|
||||
ok()
|
||||
}));
|
||||
ok()
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn execute() {
|
||||
block_on_all(lazy(|| {
|
||||
tokio_current_thread::TaskExecutor::current()
|
||||
.execute(lazy(|| {
|
||||
block_on_all(lazy(|| ok())).unwrap();
|
||||
ok()
|
||||
}))
|
||||
.unwrap();
|
||||
ok()
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_on_infini_future() {
|
||||
let num = Rc::new(Cell::new(0));
|
||||
|
||||
struct Infini {
|
||||
num: Rc<Cell<usize>>,
|
||||
}
|
||||
|
||||
impl Future for Infini {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.num.set(1 + self.num.get());
|
||||
task::current().notify();
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
CurrentThread::new()
|
||||
.spawn(Infini { num: num.clone() })
|
||||
.turn(None)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, num.get());
|
||||
}
|
||||
|
||||
mod tasks_are_scheduled_fairly {
|
||||
use super::*;
|
||||
struct Spin {
|
||||
state: Rc<RefCell<[i32; 2]>>,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl Future for Spin {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
if self.idx == 0 {
|
||||
let diff = state[0] - state[1];
|
||||
|
||||
assert!(diff.abs() <= 1);
|
||||
|
||||
if state[0] >= 50 {
|
||||
return Ok(().into());
|
||||
}
|
||||
}
|
||||
|
||||
state[self.idx] += 1;
|
||||
|
||||
if state[self.idx] >= 100 {
|
||||
return Ok(().into());
|
||||
}
|
||||
|
||||
task::current().notify();
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
fn test<F: Fn(Spin)>(spawn: F) {
|
||||
let state = Rc::new(RefCell::new([0, 0]));
|
||||
|
||||
block_on_all(lazy(|| {
|
||||
spawn(Spin {
|
||||
state: state.clone(),
|
||||
idx: 0,
|
||||
});
|
||||
|
||||
spawn(Spin {
|
||||
state: state,
|
||||
idx: 1,
|
||||
});
|
||||
|
||||
ok()
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn() {
|
||||
test(tokio_current_thread::spawn)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
test(|f| {
|
||||
tokio_current_thread::TaskExecutor::current()
|
||||
.execute(f)
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod and_turn {
|
||||
use super::*;
|
||||
|
||||
fn test<F, G>(spawn: F, dotspawn: G)
|
||||
where
|
||||
F: Fn(Box<dyn Future<Item = (), Error = ()>>) + 'static,
|
||||
G: Fn(&mut CurrentThread, Box<dyn Future<Item = (), Error = ()>>),
|
||||
{
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
let mut tokio_current_thread = CurrentThread::new();
|
||||
|
||||
// Spawn a basic task to get the executor to turn
|
||||
dotspawn(&mut tokio_current_thread, Box::new(lazy(move || Ok(()))));
|
||||
|
||||
// Turn once...
|
||||
tokio_current_thread.turn(None).unwrap();
|
||||
|
||||
dotspawn(
|
||||
&mut tokio_current_thread,
|
||||
Box::new(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
|
||||
// Spawn!
|
||||
spawn(Box::new(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
Ok::<(), ()>(())
|
||||
})));
|
||||
|
||||
Ok(())
|
||||
})),
|
||||
);
|
||||
|
||||
// This does not run the newly spawned thread
|
||||
tokio_current_thread.turn(None).unwrap();
|
||||
assert_eq!(1, cnt.get());
|
||||
|
||||
// This runs the newly spawned thread
|
||||
tokio_current_thread.turn(None).unwrap();
|
||||
assert_eq!(2, cnt.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn() {
|
||||
test(tokio_current_thread::spawn, |rt, f| {
|
||||
rt.spawn(f);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
test(
|
||||
|f| {
|
||||
tokio_current_thread::TaskExecutor::current()
|
||||
.execute(f)
|
||||
.unwrap();
|
||||
},
|
||||
// Note: `CurrentThread` doesn't currently implement
|
||||
// `futures::Executor`, so we'll call `.spawn(...)` rather than
|
||||
// `.execute(...)` for now. If `CurrentThread` is changed to
|
||||
// implement Executor, change this to `.execute(...).unwrap()`.
|
||||
|rt, f| {
|
||||
rt.spawn(f);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod in_drop {
|
||||
use super::*;
|
||||
struct OnDrop<F: FnOnce()>(Option<F>);
|
||||
|
||||
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.0.take().unwrap())();
|
||||
}
|
||||
}
|
||||
|
||||
struct MyFuture {
|
||||
_data: Box<dyn Any>,
|
||||
}
|
||||
|
||||
impl Future for MyFuture {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
fn test<F, G>(spawn: F, dotspawn: G)
|
||||
where
|
||||
F: Fn(Box<dyn Future<Item = (), Error = ()>>) + 'static,
|
||||
G: Fn(&mut CurrentThread, Box<dyn Future<Item = (), Error = ()>>),
|
||||
{
|
||||
let mut tokio_current_thread = CurrentThread::new();
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
dotspawn(
|
||||
&mut tokio_current_thread,
|
||||
Box::new(MyFuture {
|
||||
_data: Box::new(OnDrop(Some(move || {
|
||||
spawn(Box::new(lazy(move || {
|
||||
tx.send(()).unwrap();
|
||||
Ok(())
|
||||
})));
|
||||
}))),
|
||||
}),
|
||||
);
|
||||
|
||||
tokio_current_thread.block_on(rx).unwrap();
|
||||
tokio_current_thread.run().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn() {
|
||||
test(tokio_current_thread::spawn, |rt, f| {
|
||||
rt.spawn(f);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute() {
|
||||
test(
|
||||
|f| {
|
||||
tokio_current_thread::TaskExecutor::current()
|
||||
.execute(f)
|
||||
.unwrap();
|
||||
},
|
||||
// Note: `CurrentThread` doesn't currently implement
|
||||
// `futures::Executor`, so we'll call `.spawn(...)` rather than
|
||||
// `.execute(...)` for now. If `CurrentThread` is changed to
|
||||
// implement Executor, change this to `.execute(...).unwrap()`.
|
||||
|rt, f| {
|
||||
rt.spawn(f);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_turn() {
|
||||
use futures::sync::mpsc;
|
||||
|
||||
const ITER: usize = 100;
|
||||
const N: usize = 100;
|
||||
const THREADS: usize = 4;
|
||||
|
||||
for _ in 0..ITER {
|
||||
let mut ths = vec![];
|
||||
|
||||
// Add some jitter
|
||||
for _ in 0..THREADS {
|
||||
let th = thread::spawn(|| {
|
||||
let mut tokio_current_thread = CurrentThread::new();
|
||||
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
tokio_current_thread.spawn({
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
rx.for_each(move |_| {
|
||||
c.set(1 + c.get());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| panic!("err={:?}", e))
|
||||
.map(move |v| {
|
||||
assert_eq!(N, cnt.get());
|
||||
v
|
||||
})
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
for _ in 0..N {
|
||||
tx.unbounded_send(()).unwrap();
|
||||
thread::yield_now();
|
||||
}
|
||||
});
|
||||
|
||||
while !tokio_current_thread.is_idle() {
|
||||
tokio_current_thread.turn(None).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ths.push(th);
|
||||
}
|
||||
|
||||
for th in ths {
|
||||
th.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_has_polled() {
|
||||
let mut tokio_current_thread = CurrentThread::new();
|
||||
|
||||
// Spawn oneshot receiver
|
||||
let (sender, receiver) = oneshot::channel::<()>();
|
||||
tokio_current_thread.spawn(receiver.then(|_| Ok(())));
|
||||
|
||||
// Turn once...
|
||||
let res = tokio_current_thread
|
||||
.turn(Some(Duration::from_millis(0)))
|
||||
.unwrap();
|
||||
|
||||
// Should've polled the receiver once, but considered it not ready
|
||||
assert!(res.has_polled());
|
||||
|
||||
// Turn another time
|
||||
let res = tokio_current_thread
|
||||
.turn(Some(Duration::from_millis(0)))
|
||||
.unwrap();
|
||||
|
||||
// Should've polled nothing, the receiver is not ready yet
|
||||
assert!(!res.has_polled());
|
||||
|
||||
// Make the receiver ready
|
||||
sender.send(()).unwrap();
|
||||
|
||||
// Turn another time
|
||||
let res = tokio_current_thread
|
||||
.turn(Some(Duration::from_millis(0)))
|
||||
.unwrap();
|
||||
|
||||
// Should've polled the receiver, it's ready now
|
||||
assert!(res.has_polled());
|
||||
|
||||
// Now the executor should be empty
|
||||
assert!(tokio_current_thread.is_idle());
|
||||
let res = tokio_current_thread
|
||||
.turn(Some(Duration::from_millis(0)))
|
||||
.unwrap();
|
||||
|
||||
// So should've polled nothing
|
||||
assert!(!res.has_polled());
|
||||
}
|
||||
|
||||
// Our own mock Park that is never really waiting and the only
|
||||
// thing it does is to send, on request, something (once) to a oneshot
|
||||
// channel
|
||||
struct MyPark {
|
||||
sender: Option<oneshot::Sender<()>>,
|
||||
send_now: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
struct MyUnpark;
|
||||
|
||||
impl tokio_executor::park::Park for MyPark {
|
||||
type Unpark = MyUnpark;
|
||||
type Error = ();
|
||||
|
||||
fn unpark(&self) -> Self::Unpark {
|
||||
MyUnpark
|
||||
}
|
||||
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
// If called twice with send_now, this will intentionally panic
|
||||
if self.send_now.get() {
|
||||
self.sender.take().unwrap().send(()).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn park_timeout(&mut self, _duration: Duration) -> Result<(), Self::Error> {
|
||||
self.park()
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_executor::park::Unpark for MyUnpark {
|
||||
fn unpark(&self) {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_fair() {
|
||||
let send_now = Rc::new(Cell::new(false));
|
||||
|
||||
let (sender, receiver) = oneshot::channel::<()>();
|
||||
let (sender_2, receiver_2) = oneshot::channel::<()>();
|
||||
let (sender_3, receiver_3) = oneshot::channel::<()>();
|
||||
|
||||
let my_park = MyPark {
|
||||
sender: Some(sender_3),
|
||||
send_now: send_now.clone(),
|
||||
};
|
||||
|
||||
let mut tokio_current_thread = CurrentThread::new_with_park(my_park);
|
||||
|
||||
let receiver_1_done = Rc::new(Cell::new(false));
|
||||
let receiver_1_done_clone = receiver_1_done.clone();
|
||||
|
||||
// Once an item is received on the oneshot channel, it will immediately
|
||||
// immediately make the second oneshot channel ready
|
||||
tokio_current_thread.spawn(receiver.map_err(|_| unreachable!()).and_then(move |_| {
|
||||
sender_2.send(()).unwrap();
|
||||
receiver_1_done_clone.set(true);
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
let receiver_2_done = Rc::new(Cell::new(false));
|
||||
let receiver_2_done_clone = receiver_2_done.clone();
|
||||
|
||||
tokio_current_thread.spawn(receiver_2.map_err(|_| unreachable!()).and_then(move |_| {
|
||||
receiver_2_done_clone.set(true);
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
// The third receiver is only woken up from our Park implementation, it simulates
|
||||
// e.g. a socket that first has to be polled to know if it is ready now
|
||||
let receiver_3_done = Rc::new(Cell::new(false));
|
||||
let receiver_3_done_clone = receiver_3_done.clone();
|
||||
|
||||
tokio_current_thread.spawn(receiver_3.map_err(|_| unreachable!()).and_then(move |_| {
|
||||
receiver_3_done_clone.set(true);
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
// First turn should've polled both and considered them not ready
|
||||
let res = tokio_current_thread
|
||||
.turn(Some(Duration::from_millis(0)))
|
||||
.unwrap();
|
||||
assert!(res.has_polled());
|
||||
|
||||
// Next turn should've polled nothing
|
||||
let res = tokio_current_thread
|
||||
.turn(Some(Duration::from_millis(0)))
|
||||
.unwrap();
|
||||
assert!(!res.has_polled());
|
||||
|
||||
assert!(!receiver_1_done.get());
|
||||
assert!(!receiver_2_done.get());
|
||||
assert!(!receiver_3_done.get());
|
||||
|
||||
// After this the receiver future will wake up the second receiver future,
|
||||
// so there are pending futures again
|
||||
sender.send(()).unwrap();
|
||||
|
||||
// Now the first receiver should be done, the second receiver should be ready
|
||||
// to be polled again and the socket not yet
|
||||
let res = tokio_current_thread.turn(None).unwrap();
|
||||
assert!(res.has_polled());
|
||||
|
||||
assert!(receiver_1_done.get());
|
||||
assert!(!receiver_2_done.get());
|
||||
assert!(!receiver_3_done.get());
|
||||
|
||||
// Now let our park implementation know that it should send something to sender 3
|
||||
send_now.set(true);
|
||||
|
||||
// This should resolve the second receiver directly, but also poll the socket
|
||||
// and read the packet from it. If it didn't do both here, we would handle
|
||||
// futures that are woken up from the reactor and directly unfairly and would
|
||||
// favour the ones that are woken up directly.
|
||||
let res = tokio_current_thread.turn(None).unwrap();
|
||||
assert!(res.has_polled());
|
||||
|
||||
assert!(receiver_1_done.get());
|
||||
assert!(receiver_2_done.get());
|
||||
assert!(receiver_3_done.get());
|
||||
|
||||
// Don't send again
|
||||
send_now.set(false);
|
||||
|
||||
// Now we should be idle and turning should not poll anything
|
||||
assert!(tokio_current_thread.is_idle());
|
||||
let res = tokio_current_thread.turn(None).unwrap();
|
||||
assert!(!res.has_polled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_from_other_thread() {
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let handle = current_thread.handle();
|
||||
let (sender, receiver) = oneshot::channel::<()>();
|
||||
|
||||
thread::spawn(move || {
|
||||
handle
|
||||
.spawn(lazy(move || {
|
||||
sender.send(()).unwrap();
|
||||
Ok(())
|
||||
}))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let _ = current_thread.block_on(receiver).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_from_other_thread_unpark() {
|
||||
use std::sync::mpsc::channel as mpsc_channel;
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let handle = current_thread.handle();
|
||||
let (sender_1, receiver_1) = oneshot::channel::<()>();
|
||||
let (sender_2, receiver_2) = mpsc_channel::<()>();
|
||||
|
||||
thread::spawn(move || {
|
||||
let _ = receiver_2.recv().unwrap();
|
||||
|
||||
handle
|
||||
.spawn(lazy(move || {
|
||||
sender_1.send(()).unwrap();
|
||||
Ok(())
|
||||
}))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// Ensure that unparking the executor works correctly. It will first
|
||||
// check if there are new futures (there are none), then execute the
|
||||
// lazy future below which will cause the future to be spawned from
|
||||
// the other thread. Then the executor will park but should be woken
|
||||
// up because *now* we have a new future to schedule
|
||||
let _ = current_thread
|
||||
.block_on(
|
||||
lazy(move || {
|
||||
sender_2.send(()).unwrap();
|
||||
Ok(())
|
||||
})
|
||||
.and_then(|_| receiver_1),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_from_executor_with_handle() {
|
||||
let mut current_thread = CurrentThread::new();
|
||||
let handle = current_thread.handle();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
current_thread.spawn(lazy(move || {
|
||||
handle
|
||||
.spawn(lazy(move || {
|
||||
tx.send(()).unwrap();
|
||||
Ok(())
|
||||
}))
|
||||
.unwrap();
|
||||
Ok::<_, ()>(())
|
||||
}));
|
||||
|
||||
current_thread.run();
|
||||
|
||||
rx.wait().unwrap();
|
||||
}
|
||||
|
||||
fn ok() -> future::FutureResult<(), ()> {
|
||||
future::ok(())
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# 0.1.9 (November 27, 2019)
|
||||
|
||||
### Added
|
||||
- Add `executor::set_default` which behaves like `with_default` but returns a
|
||||
drop guard (#1725).
|
||||
|
||||
# 0.1.8 (June 2, 2019)
|
||||
|
||||
### Added
|
||||
- Add `executor::exit` to allow other executors inside `threadpool::blocking` (#1155).
|
||||
|
||||
# 0.1.7 (March 22, 2019)
|
||||
|
||||
### Added
|
||||
- `TypedExecutor` for spawning futures of a specific type (#993).
|
||||
|
||||
# 0.1.6 (January 6, 2019)
|
||||
|
||||
* Implement `Unpark` for `Arc<Unpark>` (#802).
|
||||
* Switch to crossbeam's Parker / Unparker (#528).
|
||||
|
||||
# 0.1.5 (September 26, 2018)
|
||||
|
||||
* Implement `futures::Executor` for `DefaultExecutor` (#563).
|
||||
* Add `Enter::block_on(future)` (#646)
|
||||
|
||||
# 0.1.4 (August 23, 2018)
|
||||
|
||||
* Implement `std::error::Error` for error types (#511).
|
||||
|
||||
# 0.1.3 (August 6, 2018)
|
||||
|
||||
* Implement `Executor` for `Box<E: Executor>` (#420).
|
||||
* Improve `EnterError` debug message (#410).
|
||||
* Implement `status`, `Send`, and `Sync` for `DefaultExecutor` (#463, #472).
|
||||
* Fix race in `ParkThread` (#507).
|
||||
* Handle recursive calls into `DefaultExecutor` (#473).
|
||||
|
||||
# 0.1.2 (March 30, 2018)
|
||||
|
||||
* Implement `Unpark` for `Box<Unpark>`.
|
||||
|
||||
# 0.1.1 (March 22, 2018)
|
||||
|
||||
* Optionally support futures 0.2.
|
||||
|
||||
# 0.1.0 (March 09, 2018)
|
||||
|
||||
* Initial release
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "tokio-executor"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.9"
|
||||
documentation = "https://docs.rs/tokio-executor/0.1.9/tokio_executor"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://github.com/tokio-rs/tokio"
|
||||
license = "MIT"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
description = """
|
||||
Future execution primitives
|
||||
"""
|
||||
keywords = ["futures", "tokio"]
|
||||
categories = ["concurrency", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
crossbeam-utils = "0.6.2"
|
||||
futures = "0.1.19"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = "0.1.18"
|
||||
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,47 @@
|
||||
# tokio-executor
|
||||
|
||||
Task execution related traits and utilities.
|
||||
|
||||
[Documentation](https://docs.rs/tokio-executor/0.1.9/tokio_executor)
|
||||
|
||||
## Overview
|
||||
|
||||
In the Tokio execution model, futures are lazy. When a future is created, no
|
||||
work is performed. In order for the work defined by the future to happen, the
|
||||
future must be submitted to an executor. A future that is submitted to an
|
||||
executor is called a "task".
|
||||
|
||||
The executor is responsible for ensuring that [`Future::poll`] is called
|
||||
whenever the task is [notified]. Notification happens when the internal state of
|
||||
a task transitions from "not ready" to ready. For example, a socket might have
|
||||
received data and a call to `read` will now be able to succeed.
|
||||
|
||||
This crate provides traits and utilities that are necessary for building an
|
||||
executor, including:
|
||||
|
||||
* The [`Executor`] trait describes the API for spawning a future onto an
|
||||
executor.
|
||||
|
||||
* [`enter`] marks that the current thread is entering an execution
|
||||
context. This prevents a second executor from accidentally starting from
|
||||
within the context of one that is already running.
|
||||
|
||||
* [`DefaultExecutor`] spawns tasks onto the default executor for the current
|
||||
context.
|
||||
|
||||
* [`Park`] abstracts over blocking and unblocking the current thread.
|
||||
|
||||
[`Executor`]: https://docs.rs/tokio-executor/0.1.9/tokio_executor/trait.Executor.html
|
||||
[`enter`]: https://docs.rs/tokio-executor/0.1.9/tokio_executor/fn.enter.html
|
||||
[`DefaultExecutor`]: https://docs.rs/tokio-executor/0.1.9/tokio_executor/struct.DefaultExecutor.html
|
||||
[`Park`]: https://docs.rs/tokio-executor/0.1.9/tokio_executor/park/trait.Park.html
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -0,0 +1,164 @@
|
||||
use std::cell::Cell;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::prelude::v1::*;
|
||||
|
||||
use futures::{self, Future};
|
||||
|
||||
thread_local!(static ENTERED: Cell<bool> = Cell::new(false));
|
||||
|
||||
/// Represents an executor context.
|
||||
///
|
||||
/// For more details, see [`enter` documentation](fn.enter.html)
|
||||
pub struct Enter {
|
||||
on_exit: Vec<Box<dyn Callback>>,
|
||||
permanent: bool,
|
||||
}
|
||||
|
||||
/// An error returned by `enter` if an execution scope has already been
|
||||
/// entered.
|
||||
pub struct EnterError {
|
||||
_a: (),
|
||||
}
|
||||
|
||||
impl fmt::Debug for EnterError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("EnterError")
|
||||
.field("reason", &self.description())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EnterError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for EnterError {
|
||||
fn description(&self) -> &str {
|
||||
"attempted to run an executor while another executor is already running"
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the current thread as being within the dynamic extent of an
|
||||
/// executor.
|
||||
///
|
||||
/// Executor implementations should call this function before blocking the
|
||||
/// thread. If `None` is returned, the executor should fail by panicking or
|
||||
/// taking some other action without blocking the current thread. This prevents
|
||||
/// deadlocks due to multiple executors competing for the same thread.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// Returns an error if the current thread is already marked
|
||||
pub fn enter() -> Result<Enter, EnterError> {
|
||||
ENTERED.with(|c| {
|
||||
if c.get() {
|
||||
Err(EnterError { _a: () })
|
||||
} else {
|
||||
c.set(true);
|
||||
|
||||
Ok(Enter {
|
||||
on_exit: Vec::new(),
|
||||
permanent: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Forces the current "entered" state to be cleared while the closure
|
||||
// is executed.
|
||||
//
|
||||
// # Warning
|
||||
//
|
||||
// This is hidden for a reason. Do not use without fully understanding
|
||||
// executors. Misuing can easily cause your program to deadlock.
|
||||
#[doc(hidden)]
|
||||
pub fn exit<F: FnOnce() -> R, R>(f: F) -> R {
|
||||
// Reset in case the closure panics
|
||||
struct Reset;
|
||||
impl Drop for Reset {
|
||||
fn drop(&mut self) {
|
||||
ENTERED.with(|c| {
|
||||
c.set(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ENTERED.with(|c| {
|
||||
debug_assert!(c.get());
|
||||
c.set(false);
|
||||
});
|
||||
|
||||
let reset = Reset;
|
||||
let ret = f();
|
||||
::std::mem::forget(reset);
|
||||
|
||||
ENTERED.with(|c| {
|
||||
assert!(!c.get(), "closure claimed permanent executor");
|
||||
c.set(true);
|
||||
});
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
impl Enter {
|
||||
/// Register a callback to be invoked if and when the thread
|
||||
/// ceased to act as an executor.
|
||||
pub fn on_exit<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce() + 'static,
|
||||
{
|
||||
self.on_exit.push(Box::new(f));
|
||||
}
|
||||
|
||||
/// Treat the remainder of execution on this thread as part of an
|
||||
/// executor; used mostly for thread pool worker threads.
|
||||
///
|
||||
/// All registered `on_exit` callbacks are *dropped* without being
|
||||
/// invoked.
|
||||
pub fn make_permanent(mut self) {
|
||||
self.permanent = true;
|
||||
}
|
||||
|
||||
/// Blocks the thread on the specified future, returning the value with
|
||||
/// which that future completes.
|
||||
pub fn block_on<F: Future>(&mut self, f: F) -> Result<F::Item, F::Error> {
|
||||
futures::executor::spawn(f).wait_future()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Enter {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Enter").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Enter {
|
||||
fn drop(&mut self) {
|
||||
ENTERED.with(|c| {
|
||||
assert!(c.get());
|
||||
|
||||
if self.permanent {
|
||||
return;
|
||||
}
|
||||
|
||||
for callback in self.on_exit.drain(..) {
|
||||
callback.call();
|
||||
}
|
||||
|
||||
c.set(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
trait Callback: 'static {
|
||||
fn call(self: Box<Self>);
|
||||
}
|
||||
|
||||
impl<F: FnOnce() + 'static> Callback for F {
|
||||
fn call(self: Box<Self>) {
|
||||
(*self)()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
/// Errors returned by `Executor::spawn`.
|
||||
///
|
||||
/// Spawn errors should represent relatively rare scenarios. Currently, the two
|
||||
/// scenarios represented by `SpawnError` are:
|
||||
///
|
||||
/// * An executor being at capacity or full. As such, the executor is not able
|
||||
/// to accept a new future. This error state is expected to be transient.
|
||||
/// * An executor has been shutdown and can no longer accept new futures. This
|
||||
/// error state is expected to be permanent.
|
||||
#[derive(Debug)]
|
||||
pub struct SpawnError {
|
||||
is_shutdown: bool,
|
||||
}
|
||||
|
||||
impl SpawnError {
|
||||
/// Return a new `SpawnError` reflecting a shutdown executor failure.
|
||||
pub fn shutdown() -> Self {
|
||||
SpawnError { is_shutdown: true }
|
||||
}
|
||||
|
||||
/// Return a new `SpawnError` reflecting an executor at capacity failure.
|
||||
pub fn at_capacity() -> Self {
|
||||
SpawnError { is_shutdown: false }
|
||||
}
|
||||
|
||||
/// Returns `true` if the error reflects a shutdown executor failure.
|
||||
pub fn is_shutdown(&self) -> bool {
|
||||
self.is_shutdown
|
||||
}
|
||||
|
||||
/// Returns `true` if the error reflects an executor at capacity failure.
|
||||
pub fn is_at_capacity(&self) -> bool {
|
||||
!self.is_shutdown
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SpawnError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(fmt, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SpawnError {
|
||||
fn description(&self) -> &str {
|
||||
"attempted to spawn task while the executor is at capacity or shut down"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
use futures::Future;
|
||||
use SpawnError;
|
||||
|
||||
/// A value that executes futures.
|
||||
///
|
||||
/// The [`spawn`] function is used to submit a future to an executor. Once
|
||||
/// submitted, the executor takes ownership of the future and becomes
|
||||
/// responsible for driving the future to completion.
|
||||
///
|
||||
/// The strategy employed by the executor to handle the future is less defined
|
||||
/// and is left up to the `Executor` implementation. The `Executor` instance is
|
||||
/// expected to call [`poll`] on the future once it has been notified, however
|
||||
/// the "when" and "how" can vary greatly.
|
||||
///
|
||||
/// For example, the executor might be a thread pool, in which case a set of
|
||||
/// threads have already been spawned up and the future is inserted into a
|
||||
/// queue. A thread will acquire the future and poll it.
|
||||
///
|
||||
/// The `Executor` trait is only for futures that **are** `Send`. These are most
|
||||
/// common. There currently is no trait that describes executors that operate
|
||||
/// entirely on the current thread (i.e., are able to spawn futures that are not
|
||||
/// `Send`). Note that single threaded executors can still implement `Executor`,
|
||||
/// but only futures that are `Send` can be spawned via the trait.
|
||||
///
|
||||
/// This trait is primarily intended to implemented by executors and used to
|
||||
/// back `tokio::spawn`. Libraries and applications **may** use this trait to
|
||||
/// bound generics, but doing so will limit usage to futures that implement
|
||||
/// `Send`. Instead, libraries and applications are recommended to use
|
||||
/// [`TypedExecutor`] as a bound.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The [`spawn`] function returns `Result` with an error type of `SpawnError`.
|
||||
/// This error type represents the reason that the executor was unable to spawn
|
||||
/// the future. The two current represented scenarios are:
|
||||
///
|
||||
/// * An executor being at capacity or full. As such, the executor is not able
|
||||
/// to accept a new future. This error state is expected to be transient.
|
||||
/// * An executor has been shutdown and can no longer accept new futures. This
|
||||
/// error state is expected to be permanent.
|
||||
///
|
||||
/// If a caller encounters an at capacity error, the caller should try to shed
|
||||
/// load. This can be as simple as dropping the future that was spawned.
|
||||
///
|
||||
/// If the caller encounters a shutdown error, the caller should attempt to
|
||||
/// gracefully shutdown.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio_executor;
|
||||
/// # use tokio_executor::Executor;
|
||||
/// # fn docs(my_executor: &mut Executor) {
|
||||
/// use futures::future::lazy;
|
||||
/// my_executor.spawn(Box::new(lazy(|| {
|
||||
/// println!("running on the executor");
|
||||
/// Ok(())
|
||||
/// }))).unwrap();
|
||||
/// # }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// [`spawn`]: #tymethod.spawn
|
||||
/// [`poll`]: https://docs.rs/futures/0.1/futures/future/trait.Future.html#tymethod.poll
|
||||
/// [`TypedExecutor`]: ../trait.TypedExecutor.html
|
||||
pub trait Executor {
|
||||
/// Spawns a future object to run on this executor.
|
||||
///
|
||||
/// `future` is passed to the executor, which will begin running it. The
|
||||
/// future may run on the current thread or another thread at the discretion
|
||||
/// of the `Executor` implementation.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Implementations are encouraged to avoid panics. However, panics are
|
||||
/// permitted and the caller should check the implementation specific
|
||||
/// documentation for more details on possible panics.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio_executor;
|
||||
/// # use tokio_executor::Executor;
|
||||
/// # fn docs(my_executor: &mut Executor) {
|
||||
/// use futures::future::lazy;
|
||||
/// my_executor.spawn(Box::new(lazy(|| {
|
||||
/// println!("running on the executor");
|
||||
/// Ok(())
|
||||
/// }))).unwrap();
|
||||
/// # }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Box<dyn Future<Item = (), Error = ()> + Send>,
|
||||
) -> Result<(), SpawnError>;
|
||||
|
||||
/// Provides a best effort **hint** to whether or not `spawn` will succeed.
|
||||
///
|
||||
/// This function may return both false positives **and** false negatives.
|
||||
/// If `status` returns `Ok`, then a call to `spawn` will *probably*
|
||||
/// succeed, but may fail. If `status` returns `Err`, a call to `spawn` will
|
||||
/// *probably* fail, but may succeed.
|
||||
///
|
||||
/// This allows a caller to avoid creating the task if the call to `spawn`
|
||||
/// has a high likelihood of failing.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function must not panic. Implementers must ensure that panics do
|
||||
/// not happen.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio_executor;
|
||||
/// # use tokio_executor::Executor;
|
||||
/// # fn docs(my_executor: &mut Executor) {
|
||||
/// use futures::future::lazy;
|
||||
///
|
||||
/// if my_executor.status().is_ok() {
|
||||
/// my_executor.spawn(Box::new(lazy(|| {
|
||||
/// println!("running on the executor");
|
||||
/// Ok(())
|
||||
/// }))).unwrap();
|
||||
/// } else {
|
||||
/// println!("the executor is not in a good state");
|
||||
/// }
|
||||
/// # }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Executor + ?Sized> Executor for Box<E> {
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Box<dyn Future<Item = (), Error = ()> + Send>,
|
||||
) -> Result<(), SpawnError> {
|
||||
(**self).spawn(future)
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
(**self).status()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
use super::{Enter, Executor, SpawnError};
|
||||
|
||||
use futures::{future, Future};
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
/// Executes futures on the default executor for the current execution context.
|
||||
///
|
||||
/// `DefaultExecutor` implements `Executor` and can be used to spawn futures
|
||||
/// without referencing a specific executor.
|
||||
///
|
||||
/// When an executor starts, it sets the `DefaultExecutor` handle to point to an
|
||||
/// executor (usually itself) that is used to spawn new tasks.
|
||||
///
|
||||
/// The current `DefaultExecutor` reference is tracked using a thread-local
|
||||
/// variable and is set using `tokio_executor::with_default`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DefaultExecutor {
|
||||
_dummy: (),
|
||||
}
|
||||
|
||||
/// Ensures that the executor is removed from the thread-local context
|
||||
/// when leaving the scope. This handles cases that involve panicking.
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultGuard {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
impl DefaultExecutor {
|
||||
/// Returns a handle to the default executor for the current context.
|
||||
///
|
||||
/// Futures may be spawned onto the default executor using this handle.
|
||||
///
|
||||
/// The returned handle will reference whichever executor is configured as
|
||||
/// the default **at the time `spawn` is called**. This enables
|
||||
/// `DefaultExecutor::current()` to be called before an execution context is
|
||||
/// setup, then passed **into** an execution context before it is used.
|
||||
///
|
||||
/// This is also true for sending the handle across threads, so calling
|
||||
/// `DefaultExecutor::current()` on thread A and then sending the result to
|
||||
/// thread B will _not_ reference the default executor that was set on thread A.
|
||||
pub fn current() -> DefaultExecutor {
|
||||
DefaultExecutor { _dummy: () }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_current<F: FnOnce(&mut dyn Executor) -> R, R>(f: F) -> Option<R> {
|
||||
EXECUTOR.with(
|
||||
|current_executor| match current_executor.replace(State::Active) {
|
||||
State::Ready(executor_ptr) => {
|
||||
let executor = unsafe { &mut *executor_ptr };
|
||||
let result = f(executor);
|
||||
current_executor.set(State::Ready(executor_ptr));
|
||||
Some(result)
|
||||
}
|
||||
State::Empty | State::Active => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum State {
|
||||
// default executor not defined
|
||||
Empty,
|
||||
// default executor is defined and ready to be used
|
||||
Ready(*mut dyn Executor),
|
||||
// default executor is currently active (used to detect recursive calls)
|
||||
Active,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Thread-local tracking the current executor
|
||||
static EXECUTOR: Cell<State> = Cell::new(State::Empty)
|
||||
}
|
||||
|
||||
// ===== impl DefaultExecutor =====
|
||||
|
||||
impl super::Executor for DefaultExecutor {
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Box<dyn Future<Item = (), Error = ()> + Send>,
|
||||
) -> Result<(), SpawnError> {
|
||||
DefaultExecutor::with_current(|executor| executor.spawn(future))
|
||||
.unwrap_or_else(|| Err(SpawnError::shutdown()))
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
DefaultExecutor::with_current(|executor| executor.status())
|
||||
.unwrap_or_else(|| Err(SpawnError::shutdown()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> super::TypedExecutor<T> for DefaultExecutor
|
||||
where
|
||||
T: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
fn spawn(&mut self, future: T) -> Result<(), SpawnError> {
|
||||
super::Executor::spawn(self, Box::new(future))
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
super::Executor::status(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> future::Executor<T> for DefaultExecutor
|
||||
where
|
||||
T: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
fn execute(&self, future: T) -> Result<(), future::ExecuteError<T>> {
|
||||
if let Err(e) = super::Executor::status(self) {
|
||||
let kind = if e.is_at_capacity() {
|
||||
future::ExecuteErrorKind::NoCapacity
|
||||
} else {
|
||||
future::ExecuteErrorKind::Shutdown
|
||||
};
|
||||
|
||||
return Err(future::ExecuteError::new(kind, future));
|
||||
}
|
||||
|
||||
let _ = DefaultExecutor::with_current(|executor| executor.spawn(Box::new(future)));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ===== global spawn fns =====
|
||||
|
||||
/// Submits a future for execution on the default executor -- usually a
|
||||
/// threadpool.
|
||||
///
|
||||
/// Futures are lazy constructs. When they are defined, no work happens. In
|
||||
/// order for the logic defined by the future to be run, the future must be
|
||||
/// spawned on an executor. This function is the easiest way to do so.
|
||||
///
|
||||
/// This function must be called from an execution context, i.e. from a future
|
||||
/// that has been already spawned onto an executor.
|
||||
///
|
||||
/// Once spawned, the future will execute. The details of how that happens is
|
||||
/// left up to the executor instance. If the executor is a thread pool, the
|
||||
/// future will be pushed onto a queue that a worker thread polls from. If the
|
||||
/// executor is a "current thread" executor, the future might be polled
|
||||
/// immediately from within the call to `spawn` or it might be pushed onto an
|
||||
/// internal queue.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the default executor is not set or if spawning
|
||||
/// onto the default executor returns an error. To avoid the panic, use the
|
||||
/// `DefaultExecutor` handle directly.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio_executor;
|
||||
/// # use tokio_executor::spawn;
|
||||
/// # pub fn dox() {
|
||||
/// use futures::future::lazy;
|
||||
///
|
||||
/// spawn(lazy(|| {
|
||||
/// println!("running on the default executor");
|
||||
/// Ok(())
|
||||
/// }));
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
pub fn spawn<T>(future: T)
|
||||
where
|
||||
T: Future<Item = (), Error = ()> + Send + 'static,
|
||||
{
|
||||
DefaultExecutor::current().spawn(Box::new(future)).unwrap()
|
||||
}
|
||||
|
||||
/// Set the default executor for the duration of the closure
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if there already is a default executor set.
|
||||
pub fn with_default<T, F, R>(executor: &mut T, enter: &mut Enter, f: F) -> R
|
||||
where
|
||||
T: Executor,
|
||||
F: FnOnce(&mut Enter) -> R,
|
||||
{
|
||||
unsafe fn hide_lt<'a>(p: *mut (dyn Executor + 'a)) -> *mut (dyn Executor + 'static) {
|
||||
use std::mem;
|
||||
mem::transmute(p)
|
||||
}
|
||||
|
||||
EXECUTOR.with(|cell| {
|
||||
match cell.get() {
|
||||
State::Ready(_) | State::Active => {
|
||||
panic!("default executor already set for execution context")
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Ensure that the executor is removed from the thread-local context
|
||||
// when leaving the scope. This handles cases that involve panicking.
|
||||
struct Reset<'a>(&'a Cell<State>);
|
||||
|
||||
impl<'a> Drop for Reset<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.0.set(State::Empty);
|
||||
}
|
||||
}
|
||||
|
||||
let _reset = Reset(cell);
|
||||
|
||||
// While scary, this is safe. The function takes a
|
||||
// `&mut Executor`, which guarantees that the reference lives for the
|
||||
// duration of `with_default`.
|
||||
//
|
||||
// Because we are always clearing the TLS value at the end of the
|
||||
// function, we can cast the reference to 'static which thread-local
|
||||
// cells require.
|
||||
let executor = unsafe { hide_lt(executor as &mut _ as *mut _) };
|
||||
|
||||
cell.set(State::Ready(executor));
|
||||
|
||||
f(enter)
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets `executor` as the default executor, returning a guard that unsets it when
|
||||
/// dropped.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if there already is a default executor set.
|
||||
pub fn set_default<T>(executor: T) -> DefaultGuard
|
||||
where
|
||||
T: Executor + 'static,
|
||||
{
|
||||
EXECUTOR.with(|cell| {
|
||||
match cell.get() {
|
||||
State::Ready(_) | State::Active => {
|
||||
panic!("default executor already set for execution context")
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Ensure that the executor will outlive the call to set_default, even
|
||||
// if the drop guard is never dropped due to calls to `mem::forget` or
|
||||
// similar.
|
||||
let executor = Box::new(executor);
|
||||
|
||||
cell.set(State::Ready(Box::into_raw(executor)));
|
||||
});
|
||||
|
||||
DefaultGuard { _p: () }
|
||||
}
|
||||
|
||||
impl Drop for DefaultGuard {
|
||||
fn drop(&mut self) {
|
||||
let _ = EXECUTOR.try_with(|cell| {
|
||||
if let State::Ready(prev) = cell.replace(State::Empty) {
|
||||
// drop the previous executor.
|
||||
unsafe {
|
||||
let prev = Box::from_raw(prev);
|
||||
drop(prev);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{with_default, DefaultExecutor, Executor};
|
||||
|
||||
#[test]
|
||||
fn default_executor_is_send_and_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
|
||||
assert_send_sync::<DefaultExecutor>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_default_executor_status() {
|
||||
let mut enter = super::super::enter().unwrap();
|
||||
let mut executor = DefaultExecutor::current();
|
||||
|
||||
let result = with_default(&mut executor, &mut enter, |_| {
|
||||
DefaultExecutor::current().status()
|
||||
});
|
||||
|
||||
assert!(result.err().unwrap().is_shutdown())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-executor/0.1.9")]
|
||||
|
||||
//! Task execution related traits and utilities.
|
||||
//!
|
||||
//! In the Tokio execution model, futures are lazy. When a future is created, no
|
||||
//! work is performed. In order for the work defined by the future to happen,
|
||||
//! the future must be submitted to an executor. A future that is submitted to
|
||||
//! an executor is called a "task".
|
||||
//!
|
||||
//! The executor is responsible for ensuring that [`Future::poll`] is called
|
||||
//! whenever the task is notified. Notification happens when the internal
|
||||
//! state of a task transitions from *not ready* to *ready*. For example, a
|
||||
//! socket might have received data and a call to `read` will now be able to
|
||||
//! succeed.
|
||||
//!
|
||||
//! This crate provides traits and utilities that are necessary for building an
|
||||
//! executor, including:
|
||||
//!
|
||||
//! * The [`Executor`] trait spawns future object onto an executor.
|
||||
//!
|
||||
//! * The [`TypedExecutor`] trait spawns futures of a specific type onto an
|
||||
//! executor. This is used to be generic over executors that spawn futures
|
||||
//! that are either `Send` or `!Send` or implement executors that apply to
|
||||
//! specific futures.
|
||||
//!
|
||||
//! * [`enter`] marks that the current thread is entering an execution
|
||||
//! context. This prevents a second executor from accidentally starting from
|
||||
//! within the context of one that is already running.
|
||||
//!
|
||||
//! * [`DefaultExecutor`] spawns tasks onto the default executor for the current
|
||||
//! context.
|
||||
//!
|
||||
//! * [`Park`] abstracts over blocking and unblocking the current thread.
|
||||
//!
|
||||
//! # Implementing an executor
|
||||
//!
|
||||
//! Executors should always implement `TypedExecutor`. This usually is the bound
|
||||
//! that applications and libraries will use when generic over an executor. See
|
||||
//! the [trait documentation][`TypedExecutor`] for more details.
|
||||
//!
|
||||
//! If the executor is able to spawn all futures that are `Send`, then the
|
||||
//! executor should also implement the `Executor` trait. This trait is rarely
|
||||
//! used directly by applications and libraries. Instead, `tokio::spawn` is
|
||||
//! configured to dispatch to type that implements `Executor`.
|
||||
//!
|
||||
//! [`Executor`]: trait.Executor.html
|
||||
//! [`TypedExecutor`]: trait.TypedExecutor.html
|
||||
//! [`enter`]: fn.enter.html
|
||||
//! [`DefaultExecutor`]: struct.DefaultExecutor.html
|
||||
//! [`Park`]: park/index.html
|
||||
//! [`Future::poll`]: https://docs.rs/futures/0.1/futures/future/trait.Future.html#tymethod.poll
|
||||
|
||||
extern crate crossbeam_utils;
|
||||
extern crate futures;
|
||||
|
||||
mod enter;
|
||||
mod error;
|
||||
mod executor;
|
||||
mod global;
|
||||
pub mod park;
|
||||
mod typed;
|
||||
|
||||
pub use enter::{enter, exit, Enter, EnterError};
|
||||
pub use error::SpawnError;
|
||||
pub use executor::Executor;
|
||||
pub use global::{set_default, spawn, with_default, DefaultExecutor, DefaultGuard};
|
||||
pub use typed::TypedExecutor;
|
||||
@@ -28,7 +28,7 @@
|
||||
//! Some things to note:
|
||||
//!
|
||||
//! * If [`unpark`] is called before [`park`], the next call to [`park`] will
|
||||
//! **not** block the thread.
|
||||
//! **not** block the thread.
|
||||
//! * **Spurious** wakeups are permitted, i.e., the [`park`] method may unblock
|
||||
//! even if [`unpark`] was not called.
|
||||
//! * [`park_timeout`] does the same as [`park`] but allows specifying a maximum
|
||||
@@ -44,27 +44,19 @@
|
||||
//! [up]: trait.Unpark.html
|
||||
//! [mio]: https://docs.rs/mio/0.6/mio/struct.Poll.html
|
||||
|
||||
cfg_resource_drivers! {
|
||||
mod either;
|
||||
pub(crate) use self::either::Either;
|
||||
}
|
||||
|
||||
mod thread;
|
||||
pub(crate) use self::thread::ParkThread;
|
||||
|
||||
cfg_blocking_impl! {
|
||||
pub(crate) use self::thread::CachedParkThread;
|
||||
}
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam_utils::sync::{Parker, Unparker};
|
||||
|
||||
/// Block the current thread.
|
||||
///
|
||||
/// See [module documentation][mod] for more details.
|
||||
///
|
||||
/// [mod]: ../index.html
|
||||
pub(crate) trait Park {
|
||||
pub trait Park {
|
||||
/// Unpark handle type for the `Park` implementation.
|
||||
type Unpark: Unpark;
|
||||
|
||||
@@ -118,7 +110,7 @@ pub(crate) trait Park {
|
||||
///
|
||||
/// [mod]: ../index.html
|
||||
/// [`Park`]: trait.Park.html
|
||||
pub(crate) trait Unpark: Sync + Send + 'static {
|
||||
pub trait Unpark: Sync + Send + 'static {
|
||||
/// Unblock a thread that is blocked by the associated `Park` handle.
|
||||
///
|
||||
/// Calling `unpark` atomically makes available the unpark token, if it is
|
||||
@@ -147,3 +139,88 @@ impl Unpark for Arc<dyn Unpark> {
|
||||
(**self).unpark()
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocks the current thread using a condition variable.
|
||||
///
|
||||
/// Implements the [`Park`] functionality by using a condition variable. An
|
||||
/// atomic variable is also used to avoid using the condition variable if
|
||||
/// possible.
|
||||
///
|
||||
/// The condition variable is cached in a thread-local variable and is shared
|
||||
/// across all `ParkThread` instances created on the same thread. This also
|
||||
/// means that an instance of `ParkThread` might be unblocked by a handle
|
||||
/// associated with a different `ParkThread` instance.
|
||||
#[derive(Debug)]
|
||||
pub struct ParkThread {
|
||||
_anchor: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
/// Error returned by [`ParkThread`]
|
||||
///
|
||||
/// This currently is never returned, but might at some point in the future.
|
||||
///
|
||||
/// [`ParkThread`]: struct.ParkThread.html
|
||||
#[derive(Debug)]
|
||||
pub struct ParkError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
/// Unblocks a thread that was blocked by `ParkThread`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UnparkThread {
|
||||
inner: Unparker,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_PARKER: Parker = Parker::new();
|
||||
}
|
||||
|
||||
// ===== impl ParkThread =====
|
||||
|
||||
impl ParkThread {
|
||||
/// Create a new `ParkThread` handle for the current thread.
|
||||
///
|
||||
/// This type cannot be moved to other threads, so it should be created on
|
||||
/// the thread that the caller intends to park.
|
||||
pub fn new() -> ParkThread {
|
||||
ParkThread {
|
||||
_anchor: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the `ParkThread` handle for this thread.
|
||||
fn with_current<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&Parker) -> R,
|
||||
{
|
||||
CURRENT_PARKER.with(|inner| f(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl Park for ParkThread {
|
||||
type Unpark = UnparkThread;
|
||||
type Error = ParkError;
|
||||
|
||||
fn unpark(&self) -> Self::Unpark {
|
||||
let inner = self.with_current(|inner| inner.unparker().clone());
|
||||
UnparkThread { inner }
|
||||
}
|
||||
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
self.with_current(|inner| inner.park());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
|
||||
self.with_current(|inner| inner.park_timeout(duration));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl UnparkThread =====
|
||||
|
||||
impl Unpark for UnparkThread {
|
||||
fn unpark(&self) {
|
||||
self.inner.unpark();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
use SpawnError;
|
||||
|
||||
/// A value that spawns futures of a specific type.
|
||||
///
|
||||
/// The trait is generic over `T`: the type of future that can be spawened. This
|
||||
/// is useful for implementing an executor that is only able to spawn a specific
|
||||
/// type of future.
|
||||
///
|
||||
/// The [`spawn`] function is used to submit the future to the executor. Once
|
||||
/// submitted, the executor takes ownership of the future and becomes
|
||||
/// responsible for driving the future to completion.
|
||||
///
|
||||
/// This trait is useful as a bound for applications and libraries in order to
|
||||
/// be generic over futures that are `Send` vs. `!Send`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Consider a function that provides an API for draining a `Stream` in the
|
||||
/// background. To do this, a task must be spawned to perform the draining. As
|
||||
/// such, the function takes a stream and an executor on which the background
|
||||
/// task is spawned.
|
||||
///
|
||||
/// ```rust
|
||||
/// #[macro_use]
|
||||
/// extern crate futures;
|
||||
/// extern crate tokio;
|
||||
///
|
||||
/// use futures::{Future, Stream, Poll};
|
||||
/// use tokio::executor::TypedExecutor;
|
||||
/// use tokio::sync::oneshot;
|
||||
///
|
||||
/// pub fn drain<T, E>(stream: T, executor: &mut E)
|
||||
/// -> impl Future<Item = (), Error = ()>
|
||||
/// where
|
||||
/// T: Stream,
|
||||
/// E: TypedExecutor<Drain<T>>
|
||||
/// {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
///
|
||||
/// executor.spawn(Drain {
|
||||
/// stream,
|
||||
/// tx: Some(tx),
|
||||
/// }).unwrap();
|
||||
///
|
||||
/// rx.map_err(|_| ())
|
||||
/// }
|
||||
///
|
||||
/// // The background task
|
||||
/// pub struct Drain<T: Stream> {
|
||||
/// stream: T,
|
||||
/// tx: Option<oneshot::Sender<()>>,
|
||||
/// }
|
||||
///
|
||||
/// impl<T: Stream> Future for Drain<T> {
|
||||
/// type Item = ();
|
||||
/// type Error = ();
|
||||
///
|
||||
/// fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
/// loop {
|
||||
/// let item = try_ready!(
|
||||
/// self.stream.poll()
|
||||
/// .map_err(|_| ())
|
||||
/// );
|
||||
///
|
||||
/// if item.is_none() { break; }
|
||||
/// }
|
||||
///
|
||||
/// self.tx.take().unwrap().send(()).map_err(|_| ());
|
||||
/// Ok(().into())
|
||||
/// }
|
||||
/// }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// By doing this, the `drain` fn can accept a stream that is `!Send` as long as
|
||||
/// the supplied executor is able to spawn `!Send` types.
|
||||
pub trait TypedExecutor<T> {
|
||||
/// Spawns a future to run on this executor.
|
||||
///
|
||||
/// `future` is passed to the executor, which will begin running it. The
|
||||
/// executor takes ownership of the future and becomes responsible for
|
||||
/// driving the future to completion.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Implementations are encouraged to avoid panics. However, panics are
|
||||
/// permitted and the caller should check the implementation specific
|
||||
/// documentation for more details on possible panics.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio_executor;
|
||||
/// # use tokio_executor::TypedExecutor;
|
||||
/// # use futures::{Future, Poll};
|
||||
/// fn example<T>(my_executor: &mut T)
|
||||
/// where
|
||||
/// T: TypedExecutor<MyFuture>,
|
||||
/// {
|
||||
/// my_executor.spawn(MyFuture).unwrap();
|
||||
/// }
|
||||
///
|
||||
/// struct MyFuture;
|
||||
///
|
||||
/// impl Future for MyFuture {
|
||||
/// type Item = ();
|
||||
/// type Error = ();
|
||||
///
|
||||
/// fn poll(&mut self) -> Poll<(), ()> {
|
||||
/// println!("running on the executor");
|
||||
/// Ok(().into())
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn spawn(&mut self, future: T) -> Result<(), SpawnError>;
|
||||
|
||||
/// Provides a best effort **hint** to whether or not `spawn` will succeed.
|
||||
///
|
||||
/// This function may return both false positives **and** false negatives.
|
||||
/// If `status` returns `Ok`, then a call to `spawn` will *probably*
|
||||
/// succeed, but may fail. If `status` returns `Err`, a call to `spawn` will
|
||||
/// *probably* fail, but may succeed.
|
||||
///
|
||||
/// This allows a caller to avoid creating the task if the call to `spawn`
|
||||
/// has a high likelihood of failing.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function must not panic. Implementers must ensure that panics do
|
||||
/// not happen.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio_executor;
|
||||
/// # use tokio_executor::TypedExecutor;
|
||||
/// # use futures::{Future, Poll};
|
||||
/// fn example<T>(my_executor: &mut T)
|
||||
/// where
|
||||
/// T: TypedExecutor<MyFuture>,
|
||||
/// {
|
||||
/// if my_executor.status().is_ok() {
|
||||
/// my_executor.spawn(MyFuture).unwrap();
|
||||
/// } else {
|
||||
/// println!("the executor is not in a good state");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct MyFuture;
|
||||
///
|
||||
/// impl Future for MyFuture {
|
||||
/// type Item = ();
|
||||
/// type Error = ();
|
||||
///
|
||||
/// fn poll(&mut self) -> Poll<(), ()> {
|
||||
/// println!("running on the executor");
|
||||
/// Ok(().into())
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, T> TypedExecutor<T> for Box<E>
|
||||
where
|
||||
E: TypedExecutor<T>,
|
||||
{
|
||||
fn spawn(&mut self, future: T) -> Result<(), SpawnError> {
|
||||
(**self).spawn(future)
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
(**self).status()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user