mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
324
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6fc1db698 | ||
|
|
d44ce338af | ||
|
|
8b7ea0ff5c | ||
|
|
7207bf355e | ||
|
|
a4c4ac254b | ||
|
|
0589acc9ff | ||
|
|
1dadc701c0 | ||
|
|
10f1507cf4 | ||
|
|
b9cc032d3b | ||
|
|
4213b79461 | ||
|
|
7b8ce356c3 | ||
|
|
0605abacfc | ||
|
|
b37e4a4380 | ||
|
|
41576e6c48 | ||
|
|
09b5f47381 | ||
|
|
fc65951731 | ||
|
|
564da5c128 | ||
|
|
466dd4a851 | ||
|
|
2eed6d00f5 | ||
|
|
c1232a6520 | ||
|
|
5e75b0446d | ||
|
|
d49e6ae1b3 | ||
|
|
874264a4ef | ||
|
|
55b5e1b6ad | ||
|
|
513671f8de | ||
|
|
79e4514283 | ||
|
|
64e75ad1b0 | ||
|
|
1a5de2c79d | ||
|
|
ab24a655ad | ||
|
|
c3d56b85c3 | ||
|
|
1eee6508fc | ||
|
|
116a18b849 | ||
|
|
9d6b99494b | ||
|
|
560d0fa548 | ||
|
|
6232c74724 | ||
|
|
be832f20cb | ||
|
|
326f724978 | ||
|
|
81c20d8454 | ||
|
|
b70f1ce3c0 | ||
|
|
64e4bd1b2f | ||
|
|
8ed209b612 | ||
|
|
4a24c7063b | ||
|
|
e2230f3392 | ||
|
|
00e3c29e48 | ||
|
|
bcba4aaa54 | ||
|
|
71c47fabf4 | ||
|
|
4996e27673 | ||
|
|
5bf06f2b5a | ||
|
|
12be90e3ff | ||
|
|
0d49e112b2 | ||
|
|
9eca96aa21 | ||
|
|
a16c9a5a01 | ||
|
|
f0bfebb7e1 | ||
|
|
968c143acd | ||
|
|
6fbaac91e0 | ||
|
|
e35038ed79 | ||
|
|
f9ddb93604 | ||
|
|
a70f7203a4 | ||
|
|
7079bcd609 | ||
|
|
f8714e9901 | ||
|
|
0545b349e1 | ||
|
|
8cf98d6946 | ||
|
|
f9ea576cca | ||
|
|
7f580071f3 | ||
|
|
5fe2df0fba | ||
|
|
176df2448a | ||
|
|
5bbf976268 | ||
|
|
90969420a2 | ||
|
|
bffbaab30d | ||
|
|
a5e774bb38 | ||
|
|
0bb17300f7 | ||
|
|
c7719a2d29 | ||
|
|
38bff0adda | ||
|
|
fbe143b142 | ||
|
|
9df805ff54 | ||
|
|
5d82ac2d1e | ||
|
|
5bf78d77ad | ||
|
|
3176d0a48a | ||
|
|
bb6c3839ef | ||
|
|
1475448bdf | ||
|
|
7eb8d447ad | ||
|
|
1222d81741 | ||
|
|
619d730d61 | ||
|
|
476bf0084a | ||
|
|
bd8971cd95 | ||
|
|
eb1a8e1792 | ||
|
|
5b091fa3f0 | ||
|
|
7c3f1cb4a3 | ||
|
|
64d2389911 | ||
|
|
8471e0a0ee | ||
|
|
0ba6e9abdb | ||
|
|
a939dc48b0 | ||
|
|
cfd9b36d89 | ||
|
|
f5c20cd228 | ||
|
|
c7c74a5a76 | ||
|
|
b34a849b79 | ||
|
|
a7a79f28a8 | ||
|
|
bd28a7a767 | ||
|
|
275769b5b9 | ||
|
|
b70615b299 | ||
|
|
6406328176 | ||
|
|
f28c9f0d17 | ||
|
|
7ee5542182 | ||
|
|
7fb54315f1 | ||
|
|
ffd4025fce | ||
|
|
8bf4696f31 | ||
|
|
10398b20c0 | ||
|
|
780d6f91a0 | ||
|
|
45da5f3510 | ||
|
|
855d39f849 | ||
|
|
798e86821f | ||
|
|
0193df3a59 | ||
|
|
5930acef73 | ||
|
|
3540c5b9ee | ||
|
|
188fc6e0d2 | ||
|
|
d45f61c183 | ||
|
|
dcfa895b51 | ||
|
|
f0006006ed | ||
|
|
84ff73e687 | ||
|
|
32e15b3a24 | ||
|
|
efcbf9613f | ||
|
|
3736467dbb | ||
|
|
e43f28f6a8 | ||
|
|
3cf91db4b6 | ||
|
|
e8fcf55881 | ||
|
|
a515f9c459 | ||
|
|
50b91c0247 | ||
|
|
67bf9c36f3 | ||
|
|
101f770af3 | ||
|
|
6ff4e349e2 | ||
|
|
adc5186ebd | ||
|
|
7b53b7b659 | ||
|
|
99fa93bf0e | ||
|
|
0133bc1883 | ||
|
|
a854094825 | ||
|
|
3d1b4b3058 | ||
|
|
8656b7b8eb | ||
|
|
f309b295bb | ||
|
|
b1266a48c4 | ||
|
|
de5ec6e1bc | ||
|
|
3dcd76a38f | ||
|
|
3b9c7b1715 | ||
|
|
3bff5a3ffe | ||
|
|
248bf2144f | ||
|
|
93ab70a9a0 | ||
|
|
58b5abdb99 | ||
|
|
2d78cfe56a | ||
|
|
4c645866ef | ||
|
|
b0836ece7a | ||
|
|
0c0f682010 | ||
|
|
b24ad9fe86 | ||
|
|
7c010ed030 | ||
|
|
42c942de14 | ||
|
|
5e8f7eb03c | ||
|
|
9211adbe01 | ||
|
|
e5b99b0f7a | ||
|
|
83cd754bc8 | ||
|
|
17e424112d | ||
|
|
41d15ea212 | ||
|
|
8add90210b | ||
|
|
efb4b67a54 | ||
|
|
74d33a1b2f | ||
|
|
69885e214c | ||
|
|
4b85565bd7 | ||
|
|
d593c5b051 | ||
|
|
91ecb4b4c2 | ||
|
|
8abaf89e5f | ||
|
|
b560df9e66 | ||
|
|
df8278acb6 | ||
|
|
5862b9a2e0 | ||
|
|
c0953d41a5 | ||
|
|
24cd6d67f7 | ||
|
|
975576952f | ||
|
|
5d5755dca4 | ||
|
|
41ffdbb7d9 | ||
|
|
2450b5bfc9 | ||
|
|
80abff0e57 | ||
|
|
c632337e6f | ||
|
|
a53f94ab61 | ||
|
|
98c9a77f18 | ||
|
|
e00c49611a | ||
|
|
9c9fabc44b | ||
|
|
c3461b3ef3 | ||
|
|
b7ecd35036 | ||
|
|
dbcd1f9a09 | ||
|
|
0e729aa341 | ||
|
|
cbe369a3ed | ||
|
|
8bcbe78dbe | ||
|
|
6efe07c3fb | ||
|
|
8a2160a913 | ||
|
|
38c361781f | ||
|
|
07451f8b94 | ||
|
|
e87df0557d | ||
|
|
a8a4a9f0fc | ||
|
|
8b60c5386a | ||
|
|
af07f5bee7 | ||
|
|
939a0dd7b0 | ||
|
|
1ea6733568 | ||
|
|
8ce408492a | ||
|
|
b559a0cd9a | ||
|
|
417460cf86 | ||
|
|
adaba1a0bc | ||
|
|
467b6ea783 | ||
|
|
a2cfc877a7 | ||
|
|
ec7f2ae306 | ||
|
|
4261ab6627 | ||
|
|
aef434c089 | ||
|
|
cd73951130 | ||
|
|
524e66314f | ||
|
|
34d751bf92 | ||
|
|
942feab040 | ||
|
|
dc356a4158 | ||
|
|
2cd1d74092 | ||
|
|
632ee507ba | ||
|
|
7f605ee27f | ||
|
|
38e602f4d8 | ||
|
|
8e83a9f2c3 | ||
|
|
c146f48f0b | ||
|
|
ebf5f37989 | ||
|
|
abfa857f09 | ||
|
|
a81e2722a4 | ||
|
|
4ddc437170 | ||
|
|
3ecaa6d91c | ||
|
|
0bc68adb34 | ||
|
|
e20dff39ce | ||
|
|
7b4c999341 | ||
|
|
e1b1e216c5 | ||
|
|
7cd63fb946 | ||
|
|
bf741fec35 | ||
|
|
9b2aa14bb1 | ||
|
|
8546ff826d | ||
|
|
6866fe426c | ||
|
|
d88846c4eb | ||
|
|
7e6a10fccd | ||
|
|
502cf5d95c | ||
|
|
c223db3589 | ||
|
|
5cd665afd7 | ||
|
|
3e643c7b81 | ||
|
|
bc150cd0b5 | ||
|
|
15dce2d11a | ||
|
|
d4fec2c5d6 | ||
|
|
69975fb960 | ||
|
|
7c8b8877d4 | ||
|
|
0d38936b35 | ||
|
|
13b6e9939e | ||
|
|
44f10fe47f | ||
|
|
c147be0437 | ||
|
|
b1d9e55487 | ||
|
|
66cbed3ce3 | ||
|
|
4d19a99937 | ||
|
|
10dc659450 | ||
|
|
320c84a433 | ||
|
|
19f1fc36bd | ||
|
|
3f0eabe779 | ||
|
|
1474794055 | ||
|
|
92eb635669 | ||
|
|
8a7e57786a | ||
|
|
930679587a | ||
|
|
27e5b41067 | ||
|
|
e3df2eafd3 | ||
|
|
c15e01a09b | ||
|
|
64f2bf0072 | ||
|
|
7e35922a1d | ||
|
|
4dbe6af0a1 | ||
|
|
9bec094150 | ||
|
|
6f8b986bdb | ||
|
|
1a7f6fb201 | ||
|
|
0da23aad77 | ||
|
|
d5c1119c88 | ||
|
|
a6253ed05a | ||
|
|
94f9b04b06 | ||
|
|
966ccd5d53 | ||
|
|
3948e16292 | ||
|
|
6b35a1e8b0 | ||
|
|
e19bd77ef0 | ||
|
|
c8fdbed27a | ||
|
|
3e7d0be51d | ||
|
|
d70c928d88 | ||
|
|
742d89b0f3 | ||
|
|
20993341bd | ||
|
|
e699d46534 | ||
|
|
72caede7be | ||
|
|
64c26ab1ee | ||
|
|
02f7264008 | ||
|
|
2902e39db0 | ||
|
|
630d3136dd | ||
|
|
2c870b588f | ||
|
|
109fd3086b | ||
|
|
e3261440e5 | ||
|
|
2b909d6805 | ||
|
|
c62ef2d232 | ||
|
|
7eb264a0d0 | ||
|
|
1195263584 | ||
|
|
bccb713d98 | ||
|
|
474befd23c | ||
|
|
987ba7373c | ||
|
|
227533d456 | ||
|
|
03a9378297 | ||
|
|
99940aeeb4 | ||
|
|
cfc15617a5 | ||
|
|
b8cee1a60a | ||
|
|
c9bcbe77b9 | ||
|
|
978013a215 | ||
|
|
6aa6ebb5bc | ||
|
|
4bee94eb06 | ||
|
|
ed5a94eb2d | ||
|
|
2a181320b7 | ||
|
|
4c97e9dc28 | ||
|
|
1cae04f8b3 | ||
|
|
29f35df7f8 | ||
|
|
741bef8fe1 | ||
|
|
804dbd6f8e | ||
|
|
69fe65e972 | ||
|
|
b8913ec7c0 | ||
|
|
8aa520e2bd | ||
|
|
ab2f71a612 | ||
|
|
42a5cb1508 | ||
|
|
2b4b0619d7 | ||
|
|
55caddb9ce | ||
|
|
aefaef3abf | ||
|
|
c78c9168d7 | ||
|
|
9e1eef829a | ||
|
|
f48980ae52 | ||
|
|
a1d1eb5eb3 |
+1
-2
@@ -13,8 +13,7 @@ task:
|
||||
setup_script:
|
||||
- pkg install -y curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
# TODO: switch back to nightly
|
||||
- sh rustup.sh -y --default-toolchain nightly-2019-08-21
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
- . $HOME/.cargo/env
|
||||
- rustup target add i686-unknown-freebsd
|
||||
- |
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Code of Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). This describes the minimum behavior expected from all contributors.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of violations of the Code of Conduct can be reported by contacting the project team at [[email protected]](mailto:[email protected]).
|
||||
+3
-3
@@ -12,15 +12,15 @@ 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.
|
||||
|
||||
You may also get help with contributing in the [dev channel][dev], please join
|
||||
The [dev channel][dev] is available for any concerns not covered in this guide, please join
|
||||
us!
|
||||
|
||||
[dev]: https://gitter.im/tokio-rs/dev
|
||||
[dev]: https://discord.gg/6yGkFeN
|
||||
|
||||
## Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
|
||||
the _minimum_ behavior expected from all contributors.
|
||||
the _minimum_ behavior expected from all contributors. Instances of violations of the Code of Conduct can be reported by contacting the project team at [[email protected]](mailto:[email protected]).
|
||||
|
||||
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
|
||||
+7
-8
@@ -2,15 +2,14 @@
|
||||
|
||||
members = [
|
||||
"tokio",
|
||||
"tokio-codec",
|
||||
"tokio-executor",
|
||||
"tokio-fs",
|
||||
"tokio-io",
|
||||
"tokio-macros",
|
||||
"tokio-net",
|
||||
"tokio-sync",
|
||||
"tokio-test",
|
||||
"tokio-timer",
|
||||
"tokio-tls",
|
||||
"build-tests",
|
||||
"tokio-util",
|
||||
|
||||
# Internal
|
||||
"benches",
|
||||
"examples",
|
||||
"tests-build",
|
||||
"tests-integration",
|
||||
]
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Tokio
|
||||
|
||||
**NOTE**: Tokio's [`master`](https://github.com/tokio-rs/tokio) is currently undergoing heavy development. This branch and the alpha releases will see API breaking changes and there are currently significant performance regressions that still need to be fixed before the final release. Use the [`v0.1.x`](https://github.com/tokio-rs/tokio/tree/v0.1.x) branch for stable releases.
|
||||
|
||||
A runtime for writing reliable, asynchronous, and slim applications with
|
||||
the Rust programming language. It is:
|
||||
|
||||
@@ -17,7 +15,7 @@ the Rust programming language. It is:
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Gitter chat][gitter-badge]][gitter-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
@@ -25,13 +23,14 @@ the Rust programming language. It is:
|
||||
[mit-url]: LICENSE
|
||||
[azure-badge]: https://dev.azure.com/tokio-rs/Tokio/_apis/build/status/tokio-rs.tokio?branchName=master
|
||||
[azure-url]: https://dev.azure.com/tokio-rs/Tokio/_build/latest?definitionId=1&branchName=master
|
||||
[gitter-badge]: https://img.shields.io/gitter/room/tokio-rs/tokio.svg
|
||||
[gitter-url]: https://gitter.im/tokio-rs/tokio
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/tokio
|
||||
|
||||
[Website](https://tokio.rs) |
|
||||
[Guides](https://tokio.rs/docs/) |
|
||||
[Guides](https://tokio.rs/docs/overview/) |
|
||||
[API Docs](https://docs.rs/tokio/latest/tokio) |
|
||||
[Chat](https://gitter.im/tokio-rs/tokio)
|
||||
[Roadmap](https://github.com/tokio-rs/tokio/blob/master/ROADMAP.md) |
|
||||
[Chat](https://discord.gg/tokio)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -54,15 +53,13 @@ an asynchronous application.
|
||||
|
||||
A basic TCP echo server with Tokio:
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "127.0.0.1:8080".parse::<SocketAddr>()?;
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
@@ -77,39 +74,34 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; err = {:?}", e);
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Write the data back
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
println!("failed to write to socket; err = {:?}", e);
|
||||
eprintln!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
More examples can be found [here](tokio/examples). Note that the `master` branch
|
||||
is currently being updated to use `async` / `await`. The examples are
|
||||
not fully ported. Examples for stable Tokio can be found
|
||||
[here](https://github.com/tokio-rs/tokio/tree/v0.1.x/tokio/examples).
|
||||
|
||||
More examples can be found [here](examples).
|
||||
|
||||
## Getting Help
|
||||
|
||||
First, see if the answer to your question can be found in the [Guides] or the
|
||||
[API documentation]. If the answer is not there, there is an active community in
|
||||
the [Tokio Gitter channel][chat]. We would be happy to try to answer your
|
||||
question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
the [Tokio Discord server][chat]. We would be happy to try to answer your
|
||||
question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
|
||||
[Guides]: https://tokio.rs/docs/
|
||||
[API documentation]: https://docs.rs/tokio/latest/tokio
|
||||
[chat]: https://gitter.im/tokio-rs/tokio
|
||||
[chat]: https://discord.gg/tokio
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues/new
|
||||
|
||||
## Contributing
|
||||
@@ -118,54 +110,39 @@ question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
you! We have a [contributing guide][guide] to help you get involved in the Tokio
|
||||
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-executor`]: Task executors and related utilities. Includes a
|
||||
single-threaded executor and a multi-threaded, work-stealing, executor.
|
||||
|
||||
* [`tokio-fs`]: Filesystem (and standard in / out) APIs.
|
||||
|
||||
* [`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-net`]: Event loop that drives I/O resources as well as TCP, UDP, and
|
||||
unix domain socket apis.
|
||||
|
||||
* [ `tokio-timer`]: Time related APIs.
|
||||
|
||||
[`tokio-codec`]: tokio-codec
|
||||
[`tokio-current-thread`]: tokio-current-thread
|
||||
[`tokio-executor`]: tokio-executor
|
||||
[`tokio-fs`]: tokio-fs
|
||||
[`tokio-io`]: tokio-io
|
||||
[`tokio-macros`]: tokio-macros
|
||||
[`tokio-net`]: tokio-net
|
||||
[`tokio-timer`]: tokio-timer
|
||||
[guide]: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
|
||||
|
||||
## Related Projects
|
||||
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
|
||||
|
||||
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
|
||||
|
||||
* [`warp`]: A super-easy, composable, web server framework for warp speeds.
|
||||
|
||||
* [`tower`]: A library of modular and reusable components for building robust networking clients and servers.
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level
|
||||
tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
* [`loom`]: A testing tool for concurrent Rust code
|
||||
|
||||
[`warp`]: https://github.com/seanmonstar/warp
|
||||
[`hyper`]: https://github.com/hyperium/hyper
|
||||
[`tonic`]: https://github.com/hyperium/tonic
|
||||
[`tower`]: https://github.com/tower-rs/tower
|
||||
[`loom`]: https://github.com/tokio-rs/loom
|
||||
[`rdbc`]: https://github.com/tokio-rs/rdbc
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# Tokio Roadmap
|
||||
|
||||
## A Roadmap to 1.0
|
||||
|
||||
The question of "why not 1.0?" has come up a few times. After all, Tokio 0.1 has
|
||||
been stable for three years. The short answer: because it isn't time. There is
|
||||
nobody who would rather ship a Tokio 1.0 than us. It also isn't something to rush.
|
||||
|
||||
After all, `async / await` only landed in the stable Rust channel weeks ago.
|
||||
There has been no significant production validation yet, except maybe fuchsia
|
||||
and that seems like a fairly specialized use case. This release of Tokio
|
||||
includes significant new code and new strategies with feature flags. Also, there
|
||||
are still big open questions, such as the [proposed changes][pr-1744] to
|
||||
`AsyncRead` and `AsyncWrite`.
|
||||
|
||||
Tokio 1.0 will be released as soon as the APIs are proven to handle real-world
|
||||
production cases.
|
||||
|
||||
### Tokio 1.0 in Q3 2020 with LTS support
|
||||
|
||||
The Tokio 1.0 release will be **no later** than Q3 2020. It will also come with
|
||||
"long-term support" guarantees:
|
||||
|
||||
* A minimum of 5 years of maintenance.
|
||||
* A minimum of 3 years before a hypothetical 2.0 release.
|
||||
|
||||
When Tokio 1.0 is released in Q3 2020, on-going support, security fixes, and
|
||||
critical bug fixes are guaranteed until **at least** Q3 2025. Tokio 2.0 will not
|
||||
be released until **at least** Q3 2023 (though, ideally there will never be a
|
||||
Tokio 2.0 release).
|
||||
|
||||
### How to get there
|
||||
|
||||
While Tokio 0.1 probably should have been a 1.0, Tokio 0.2 will be a **true**
|
||||
0.2 release. There will be breaking change releases every 2 ~ 3 months until 1.0.
|
||||
These changes will be **much** smaller than going from 0.1 -> 0.2. It is
|
||||
expected that the 1.0 release will look a lot like 0.2.
|
||||
|
||||
### What is expected to change
|
||||
|
||||
The biggest change will be the `AsyncRead` and `AsyncWrite` traits. Based on
|
||||
experience gained over the past 3 years, there are a couple of issues to
|
||||
address:
|
||||
|
||||
* Be able to **safely** use uninitialized memory as a read buffer.
|
||||
* Practical read vectored and write vectored APIs.
|
||||
|
||||
There are a few strategies to solve these problems. These strategies need to be
|
||||
investigated and the solution validated. You can see [this comment][pr-1744-comment] for a
|
||||
detailed statement of the problem.
|
||||
|
||||
The other major change, which has been in the works for a while, is updating
|
||||
Mio. Mio 0.6 was first released almost 4 years ago and has not had a breaking
|
||||
change since. Mio 0.7 has been in the works for a while. It includes a full
|
||||
rewrite of the windows support as well as a refined API. More will be written
|
||||
about this shortly.
|
||||
|
||||
Finally, now that the API is starting to stabilize, effort will be put into
|
||||
documentation. Tokio 0.2 is being released before updating the website and many
|
||||
of the old content will no longer be relevant. In the coming weeks, expect to
|
||||
see updates there.
|
||||
|
||||
So, we have our work cut out for us. We hope you enjoy this 0.2 release and are
|
||||
looking forward to your feedback and help.
|
||||
|
||||
[pr-1744]: https://github.com/tokio-rs/tokio/pull/1744
|
||||
[pr-1744-comment]: https://github.com/tokio-rs/tokio/pull/1744#issuecomment-553575438
|
||||
+74
-90
@@ -1,131 +1,115 @@
|
||||
trigger: ["master", "std-future"]
|
||||
pr: ["master", "std-future"]
|
||||
trigger: ["master"]
|
||||
pr: ["master"]
|
||||
|
||||
variables:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
nightly: nightly-2020-01-25
|
||||
|
||||
jobs:
|
||||
# Check formatting
|
||||
- template: ci/azure-rustfmt.yml
|
||||
parameters:
|
||||
rust: beta
|
||||
name: rustfmt
|
||||
|
||||
# Apply clippy lints to all crates
|
||||
- template: ci/azure-clippy.yml
|
||||
parameters:
|
||||
rust: beta
|
||||
name: clippy
|
||||
|
||||
# Test top level crate
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_tokio
|
||||
rust: beta
|
||||
rust: stable
|
||||
displayName: Test tokio
|
||||
cross: true
|
||||
crates:
|
||||
tokio:
|
||||
- codec
|
||||
- fs
|
||||
- io
|
||||
- rt-full
|
||||
- net
|
||||
- sync
|
||||
- tcp
|
||||
- timer
|
||||
- udp
|
||||
- uds
|
||||
- tokio
|
||||
- tests-integration
|
||||
|
||||
# Test crates that are platform specific
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_sub_cross
|
||||
displayName: Test sub crates (cross) -
|
||||
cross: true
|
||||
rust: beta
|
||||
crates:
|
||||
tokio-fs: []
|
||||
tokio-net:
|
||||
- process
|
||||
- signal
|
||||
- tcp
|
||||
- udp
|
||||
- uds
|
||||
|
||||
# Test crates that are NOT platform specific
|
||||
# Test sub crates
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_linux
|
||||
displayName: Test sub crates -
|
||||
rust: beta
|
||||
rust: stable
|
||||
crates:
|
||||
tokio-codec: []
|
||||
tokio-executor:
|
||||
- current-thread
|
||||
- threadpool
|
||||
tokio-io:
|
||||
- util
|
||||
tokio-sync:
|
||||
- async-traits
|
||||
tokio-macros: []
|
||||
tokio-timer:
|
||||
- async-traits
|
||||
tokio-test: []
|
||||
- tokio-macros
|
||||
- tokio-test
|
||||
- tokio-tls
|
||||
- tokio-util
|
||||
- examples
|
||||
|
||||
# Test compilation failure
|
||||
- template: ci/azure-test-stable.yml
|
||||
# Run integration tests
|
||||
- template: ci/azure-test-integration.yml
|
||||
parameters:
|
||||
name: test_features
|
||||
displayName: Test feature flags
|
||||
rust: beta
|
||||
name: test_integration
|
||||
displayName: Integration tests
|
||||
rust: stable
|
||||
|
||||
# Run tests from `tests-build`. This requires a different process
|
||||
- template: ci/azure-test-build.yml
|
||||
parameters:
|
||||
name: test_build
|
||||
displayName: Test build permutations
|
||||
rust: stable
|
||||
|
||||
# Run loom tests
|
||||
- template: ci/azure-loom.yml
|
||||
parameters:
|
||||
name: loom
|
||||
rust: stable
|
||||
crates:
|
||||
build-tests:
|
||||
- tokio-executor
|
||||
- tokio-net
|
||||
- executor-without-current-thread
|
||||
- macros-invalid-input
|
||||
- net-no-features
|
||||
- net-with-tcp
|
||||
- net-with-udp
|
||||
- net-with-uds
|
||||
- tokio-no-features
|
||||
- tokio-with-net
|
||||
- tokio
|
||||
|
||||
# Try cross compiling
|
||||
- template: ci/azure-cross-compile.yml
|
||||
parameters:
|
||||
name: cross
|
||||
rust: beta
|
||||
rust: stable
|
||||
|
||||
# # This represents the minimum Rust version supported by
|
||||
# # Tokio. Updating this should be done in a dedicated PR and
|
||||
# # cannot be greater than two 0.x releases prior to the
|
||||
# # current stable.
|
||||
# #
|
||||
# # Tests are not run as tests may require newer versions of
|
||||
# # rust.
|
||||
# - template: ci/azure-check-minrust.yml
|
||||
# parameters:
|
||||
# name: minrust
|
||||
# rust_version: 1.34.0
|
||||
# Check each feature works properly
|
||||
- template: ci/azure-check-features.yml
|
||||
parameters:
|
||||
rust: $(nightly)
|
||||
name: check_features
|
||||
|
||||
# This represents the minimum Rust version supported by
|
||||
# Tokio. Updating this should be done in a dedicated PR and
|
||||
# cannot be greater than two 0.x releases prior to the
|
||||
# current stable.
|
||||
#
|
||||
# Tests are not run as tests may require newer versions of
|
||||
# rust.
|
||||
- template: ci/azure-check-minrust.yml
|
||||
parameters:
|
||||
name: minrust
|
||||
rust: 1.39.0
|
||||
|
||||
# Check formatting
|
||||
- template: ci/azure-rustfmt.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: beta
|
||||
# rust: stable
|
||||
|
||||
- template: ci/azure-deploy-docs.yml
|
||||
parameters:
|
||||
rust: beta
|
||||
rust: stable
|
||||
dependsOn:
|
||||
- rustfmt
|
||||
- clippy
|
||||
- test_tokio
|
||||
- test_sub_cross
|
||||
- test_linux
|
||||
- test_features
|
||||
# - test_nightly
|
||||
- test_build
|
||||
- loom
|
||||
- cross
|
||||
# - minrust
|
||||
- minrust
|
||||
- check_features
|
||||
# - tsan
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
|
||||
[[bench]]
|
||||
name = "spawn"
|
||||
path = "spawn.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "mpsc"
|
||||
path = "mpsc.rs"
|
||||
harness = false
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
type Medium = [usize; 64];
|
||||
type Large = [Medium; 64];
|
||||
|
||||
fn create_1_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(1));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_000_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100_000));
|
||||
});
|
||||
}
|
||||
|
||||
fn send_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
|
||||
let _ = tx.try_send([0; 64]);
|
||||
|
||||
rx.try_recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn send_large(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
let _ = tx.try_send([[0; 64]; 64]);
|
||||
|
||||
rx.try_recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded_full(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (mut tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
create,
|
||||
create_1_medium,
|
||||
create_100_medium,
|
||||
create_100_000_medium
|
||||
);
|
||||
|
||||
bencher::benchmark_group!(send, send_medium, send_large);
|
||||
|
||||
bencher::benchmark_group!(
|
||||
contention,
|
||||
contention_bounded,
|
||||
contention_bounded_full,
|
||||
contention_unbounded,
|
||||
uncontented_bounded,
|
||||
uncontented_unbounded
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(create, send, contention);
|
||||
@@ -0,0 +1,70 @@
|
||||
//! Benchmark spawning a task onto the basic and threaded Tokio executors.
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
|
||||
async fn work() -> usize {
|
||||
let val = 1 + 1;
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
bench.iter(|| {
|
||||
let h = tokio::spawn(work());
|
||||
black_box(h);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
bench.iter(|| {
|
||||
let h = tokio::spawn(work());
|
||||
black_box(h);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn basic_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
spawn,
|
||||
basic_scheduler_local_spawn,
|
||||
threaded_scheduler_local_spawn,
|
||||
basic_scheduler_remote_spawn,
|
||||
threaded_scheduler_remote_spawn
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(spawn);
|
||||
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "build-tests"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
executor-without-current-thread = ["tokio-executor"]
|
||||
macros-invalid-input = ["tokio/rt-full"]
|
||||
net-no-features = ["tokio-net"]
|
||||
net-with-tcp = ["tokio-net/tcp"]
|
||||
net-with-udp = ["tokio-net/udp"]
|
||||
net-with-uds = ["tokio-net/uds"]
|
||||
net-with-process = ["tokio-net/process"]
|
||||
tokio-no-features = ["tokio"]
|
||||
tokio-with-net = ["tokio/net"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio-executor = { path = "../tokio-executor", optional = true }
|
||||
tokio-net = { path = "../tokio-net", optional = true }
|
||||
tokio = { path = "../tokio", optional = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
@@ -1,8 +0,0 @@
|
||||
#[cfg(feature = "tokio-executor")]
|
||||
pub use tokio_executor;
|
||||
|
||||
#[cfg(feature = "tokio-net")]
|
||||
pub use tokio_net;
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
pub use tokio;
|
||||
@@ -1,3 +0,0 @@
|
||||
use build_tests::tokio_executor::current_thread;
|
||||
|
||||
fn main() {}
|
||||
@@ -1,7 +0,0 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio_executor::current_thread`
|
||||
--> $DIR/executor_without_current_thread.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio_executor::current_thread;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `current_thread` in `tokio_executor`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -1,47 +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: the main function cannot accept arguments
|
||||
--> $DIR/macros_invalid_input.rs:7:27
|
||||
|
|
||||
7 | async fn main_fn_has_args(_x: u8) {}
|
||||
| ^^^^^^
|
||||
|
||||
error: Unknown attribute foo is specified
|
||||
--> $DIR/macros_invalid_input.rs:9:15
|
||||
|
|
||||
9 | #[tokio::main(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Must have specified ident
|
||||
--> $DIR/macros_invalid_input.rs:12:15
|
||||
|
|
||||
12 | #[tokio::main(multi_thread::bar)]
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: the async keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:16:1
|
||||
|
|
||||
16 | fn test_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: the test function cannot accept arguments
|
||||
--> $DIR/macros_invalid_input.rs:19:27
|
||||
|
|
||||
19 | async fn test_fn_has_args(_x: u8) {}
|
||||
| ^^^^^^
|
||||
|
||||
error: unexpected token
|
||||
--> $DIR/macros_invalid_input.rs:21:15
|
||||
|
|
||||
21 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
|
||||
error: second test attribute is supplied
|
||||
--> $DIR/macros_invalid_input.rs:25:1
|
||||
|
|
||||
25 | #[test]
|
||||
| ^^^^^^^
|
||||
@@ -1,4 +0,0 @@
|
||||
use build_tests::tokio_net::tcp;
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio_net::tcp`
|
||||
--> $DIR/net_without_tcp_missing_tcp.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio_net::tcp;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `tcp` in `tokio_net`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -1,4 +0,0 @@
|
||||
use build_tests::tokio_net::udp;
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio_net::udp`
|
||||
--> $DIR/net_without_udp_missing_udp.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio_net::udp;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `udp` in `tokio_net`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -1,4 +0,0 @@
|
||||
use build_tests::tokio_net::uds;
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio_net::uds`
|
||||
--> $DIR/net_without_uds_missing_uds.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio_net::uds;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `uds` in `tokio_net`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -1,3 +0,0 @@
|
||||
use build_tests::tokio::net;
|
||||
|
||||
fn main() {}
|
||||
@@ -1,7 +0,0 @@
|
||||
error[E0432]: unresolved import `build_tests::tokio::net`
|
||||
--> $DIR/tokio_without_net_missing_net.rs:1:5
|
||||
|
|
||||
1 | use build_tests::tokio::net;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ no `net` in `tokio`
|
||||
|
||||
For more information about this error, try `rustc --explain E0432`.
|
||||
@@ -1,62 +0,0 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "tokio-net")]
|
||||
fn net_default() {
|
||||
use build_tests::tokio_net::driver::{set_default, Handle, Reactor, Registration};
|
||||
use build_tests::tokio_net::util::PollEvented;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "net-with-tcp")]
|
||||
fn net_with_tcp() {
|
||||
use build_tests::tokio_net::tcp;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "net-with-udp")]
|
||||
fn net_with_udp() {
|
||||
use build_tests::tokio_net::udp;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "net-with-uds")]
|
||||
fn net_with_uds() {
|
||||
use build_tests::tokio_net::uds;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "net-with-process")]
|
||||
fn net_with_process() {
|
||||
use build_tests::tokio_net::process;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "tokio-with-net")]
|
||||
fn tokio_with_net() {
|
||||
// net is present
|
||||
use build_tests::tokio::net;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_fail() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
#[cfg(feature = "executor-without-current-thread")]
|
||||
t.compile_fail("tests/fail/executor_without_current_thread.rs");
|
||||
|
||||
#[cfg(feature = "macros-invalid-input")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
#[cfg(feature = "net-no-features")]
|
||||
{
|
||||
t.compile_fail("tests/fail/net_without_tcp_missing_tcp.rs");
|
||||
t.compile_fail("tests/fail/net_without_udp_missing_udp.rs");
|
||||
t.compile_fail("tests/fail/net_without_uds_missing_uds.rs");
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio-no-features")]
|
||||
t.compile_fail("tests/fail/tokio_without_net_missing_net.rs");
|
||||
|
||||
drop(t);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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_version }}
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
|
||||
+1
-1
@@ -12,5 +12,5 @@ jobs:
|
||||
cargo clippy --version
|
||||
displayName: Install clippy
|
||||
- script: |
|
||||
cargo clippy --all --all-features -- -A clippy::mutex-atomic
|
||||
cargo clippy --all --all-features
|
||||
displayName: cargo clippy --all
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
target: mips-unknown-linux-gnu
|
||||
arm:
|
||||
vmImage: ubuntu-16.04
|
||||
target: arm-unknown-linux-gnueabi
|
||||
target: arm-linux-androideabi
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
|
||||
@@ -2,7 +2,7 @@ steps:
|
||||
# Linux and macOS.
|
||||
- script: |
|
||||
set -e
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain none
|
||||
export PATH=$PATH:$HOME/.cargo/bin
|
||||
rustup toolchain install $RUSTUP_TOOLCHAIN
|
||||
rustup default $RUSTUP_TOOLCHAIN
|
||||
@@ -15,7 +15,7 @@ steps:
|
||||
# Windows.
|
||||
- script: |
|
||||
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||
rustup-init.exe -y --default-toolchain none
|
||||
rustup-init.exe -y --profile minimal --default-toolchain none
|
||||
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
rustup toolchain install %RUSTUP_TOOLCHAIN%
|
||||
rustup default %RUSTUP_TOOLCHAIN%
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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 }}
|
||||
@@ -13,5 +13,6 @@ jobs:
|
||||
cargo fmt --version
|
||||
displayName: Install rustfmt
|
||||
- script: |
|
||||
cargo fmt --all -- --check
|
||||
# Workaround for rust-lang/cargo#7732
|
||||
rustfmt --check --edition 2018 $(find . -name '*.rs' -print)
|
||||
displayName: Check formatting
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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
|
||||
@@ -0,0 +1,28 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: ubuntu-16.04
|
||||
MacOS:
|
||||
vmImage: macOS-10.13
|
||||
Windows:
|
||||
vmImage: vs2017-win2016
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: cargo install cargo-hack
|
||||
displayName: Install cargo-hack
|
||||
|
||||
# Run with all crate features
|
||||
- script: cargo hack test --each-feature
|
||||
env:
|
||||
CI: 'True'
|
||||
displayName: cargo hack test --each-feature
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests-integration
|
||||
+12
-26
@@ -17,45 +17,31 @@ jobs:
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
# rust_version: stable
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-is-release.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
# Run with default crate features
|
||||
- script: cargo test
|
||||
# Run with all crate features
|
||||
- script: cargo test --all-features
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate.key }} - cargo test
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
displayName: ${{ crate }} - cargo test --all-features
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
|
||||
# Run with each specified feature
|
||||
- ${{ each feature in crate.value }}:
|
||||
- script: cargo test --no-default-features --features ${{ feature }}
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate.key }} - cargo test --features ${{ feature }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
# Check benches
|
||||
- script: cargo check --all-features --benches
|
||||
displayName: ${{ crate }} - cargo check --benches
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
# Run with default crate features
|
||||
- script: cargo test
|
||||
# Run with all crate features
|
||||
- script: cargo test --all-features
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate.key }} - cargo test
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
|
||||
# Run with each specified feature
|
||||
- ${{ each feature in crate.value }}:
|
||||
- script: cargo test --no-default-features --features ${{ feature }}
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate.key }} - cargo test --features ${{ feature }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
displayName: ${{ crate }} - cargo test --all-features
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
|
||||
@@ -5,8 +5,6 @@ jobs:
|
||||
matrix:
|
||||
Timer:
|
||||
cmd: cargo test -p tokio-timer --test hammer
|
||||
Threadpool:
|
||||
cmd: cargo test -p tokio-executor --tests --features threadpool
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
|
||||
+2
-7
@@ -2,12 +2,7 @@
|
||||
# repository.
|
||||
[patch.crates-io]
|
||||
tokio = { path = "tokio" }
|
||||
tokio-codec = { path = "tokio-codec" }
|
||||
tokio-executor = { path = "tokio-executor" }
|
||||
tokio-fs = { path = "tokio-fs" }
|
||||
tokio-io = { path = "tokio-io" }
|
||||
tokio-macros = { path = "tokio-macros" }
|
||||
tokio-net = { path = "tokio-net" }
|
||||
tokio-sync = { path = "tokio-sync" }
|
||||
tokio-timer = { path = "tokio-timer" }
|
||||
tokio-test = { path = "tokio-test" }
|
||||
tokio-tls = { path = "tokio-tls" }
|
||||
tokio-util = { path = "tokio-util" }
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
[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"
|
||||
@@ -0,0 +1,20 @@
|
||||
## Examples of how to use Tokio
|
||||
|
||||
This directory contains a number of examples showcasing various capabilities of
|
||||
the `tokio` crate.
|
||||
|
||||
All examples can be executed with:
|
||||
|
||||
```
|
||||
cargo run --example $name
|
||||
```
|
||||
|
||||
A good starting point for the examples would be [`hello_world`](hello_world.rs)
|
||||
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
|
||||
additional guides for some of the examples.
|
||||
|
||||
If you've got an example you'd like to see here, please feel free to open an
|
||||
issue. Otherwise if you've got an example you'd like to add, please feel free
|
||||
to make a PR!
|
||||
|
||||
[tokioweb]: https://tokio.rs/docs/overview/
|
||||
@@ -26,17 +26,20 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures::{Poll, SinkExt, Stream, StreamExt};
|
||||
use std::{
|
||||
collections::HashMap, env, error::Error, io, net::SocketAddr, pin::Pin, sync::Arc,
|
||||
task::Context,
|
||||
};
|
||||
use tokio::{
|
||||
self,
|
||||
codec::{Framed, LinesCodec, LinesCodecError},
|
||||
net::{TcpListener, TcpStream},
|
||||
sync::{mpsc, Mutex},
|
||||
};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::{Stream, StreamExt};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
|
||||
|
||||
use futures::SinkExt;
|
||||
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>> {
|
||||
@@ -47,7 +50,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// client connection.
|
||||
let state = Arc::new(Mutex::new(Shared::new()));
|
||||
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:6142".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:6142".to_string());
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
@@ -66,7 +71,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// 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);
|
||||
println!("an error occurred; error = {:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -114,18 +119,12 @@ impl Shared {
|
||||
|
||||
/// Send a `LineCodec` encoded message to every peer, except
|
||||
/// for the sender.
|
||||
async fn broadcast(
|
||||
&mut self,
|
||||
sender: SocketAddr,
|
||||
message: &str,
|
||||
) -> Result<(), mpsc::error::UnboundedSendError> {
|
||||
async fn broadcast(&mut self, sender: SocketAddr, message: &str) {
|
||||
for peer in self.peers.iter_mut() {
|
||||
if *peer.0 != sender {
|
||||
peer.1.send(message.into()).await?;
|
||||
let _ = peer.1.send(message.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,18 +164,18 @@ impl Stream for Peer {
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// First poll the `UnboundedReceiver`.
|
||||
|
||||
if let Poll::Ready(Some(v)) = self.rx.poll_next_unpin(cx) {
|
||||
if let Poll::Ready(Some(v)) = Pin::new(&mut self.rx).poll_next(cx) {
|
||||
return Poll::Ready(Some(Ok(Message::Received(v))));
|
||||
}
|
||||
|
||||
// Secondly poll the `Framed` stream.
|
||||
let result: Option<_> = futures::ready!(self.lines.poll_next_unpin(cx));
|
||||
let result: Option<_> = futures::ready!(Pin::new(&mut self.lines).poll_next(cx));
|
||||
|
||||
Poll::Ready(match result {
|
||||
// We've received a message we should broadcast to others.
|
||||
Some(Ok(message)) => Some(Ok(Message::Broadcast(message))),
|
||||
|
||||
// An error occured.
|
||||
// An error occurred.
|
||||
Some(Err(e)) => Some(Err(e)),
|
||||
|
||||
// The stream has been exhausted.
|
||||
@@ -216,7 +215,7 @@ async fn process(
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{} has joined the chat", username);
|
||||
println!("{}", msg);
|
||||
state.broadcast(addr, &msg).await?;
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
// Process incoming messages until our stream is exhausted by a disconnect.
|
||||
@@ -228,7 +227,7 @@ async fn process(
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{}: {}", username, msg);
|
||||
|
||||
state.broadcast(addr, &msg).await?;
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
// A message was received from a peer. Send it to the
|
||||
// current user.
|
||||
@@ -237,7 +236,7 @@ async fn process(
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"an error occured while processing messages for {}; error = {:?}",
|
||||
"an error occurred while processing messages for {}; error = {:?}",
|
||||
username, e
|
||||
);
|
||||
}
|
||||
@@ -252,7 +251,7 @@ async fn process(
|
||||
|
||||
let msg = format!("{} has left the chat", username);
|
||||
println!("{}", msg);
|
||||
state.broadcast(addr, &msg).await?;
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -0,0 +1,149 @@
|
||||
//! 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 futures::StreamExt;
|
||||
use tokio::io;
|
||||
use tokio_util::codec::{BytesCodec, 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 = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(io::stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(io::stdout(), BytesCodec::new());
|
||||
|
||||
if tcp {
|
||||
tcp::connect(&addr, stdin, stdout).await?;
|
||||
} else {
|
||||
udp::connect(&addr, stdin, stdout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod tcp {
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::{error::Error, io, net::SocketAddr};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={}", e);
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
|
||||
(Err(e), _) | (_, Err(e)) => Err(e.into()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::udp::{RecvHalf, SendHalf};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
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<Bytes, 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<Bytes, 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(Bytes::from(buf)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@ impl Server {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let socket = UdpSocket::bind(&addr).await?;
|
||||
println!("Listening on: {}", socket.local_addr()?);
|
||||
@@ -33,7 +33,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
// program, but otherwise we'll just set up our TCP listener on
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
@@ -54,10 +54,9 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio;
|
||||
use tokio::codec::{BytesCodec, Decoder};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{BytesCodec, Decoder};
|
||||
|
||||
use std::env;
|
||||
|
||||
@@ -66,7 +65,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
// program, but otherwise we'll just set up our TCP listener on
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
@@ -22,24 +22,29 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures::{future::try_join, FutureExt, StreamExt};
|
||||
use std::{env, error::Error};
|
||||
use tokio::{
|
||||
io::AsyncReadExt,
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
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());
|
||||
let listen_addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8081".to_string());
|
||||
let server_addr = env::args()
|
||||
.nth(2)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
let mut incoming = TcpListener::bind(listen_addr).await?.incoming();
|
||||
let mut listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Some(Ok(inbound)) = incoming.next().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);
|
||||
@@ -58,8 +63,8 @@ async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<
|
||||
let (mut ri, mut wi) = inbound.split();
|
||||
let (mut ro, mut wo) = outbound.split();
|
||||
|
||||
let client_to_server = ri.copy(&mut wo);
|
||||
let server_to_client = ro.copy(&mut wi);
|
||||
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?;
|
||||
|
||||
@@ -41,17 +41,16 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{Framed, LinesCodec};
|
||||
|
||||
use futures::SinkExt;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use tokio;
|
||||
use tokio::codec::{Framed, LinesCodec};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
|
||||
/// The in-memory database shared amongst all clients.
|
||||
///
|
||||
/// This database will be shared via `Arc`, so to mutate the internal map we're
|
||||
@@ -86,7 +85,9 @@ enum Response {
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse the address we're going to run this server on
|
||||
// and set up our TCP listener to accept connections.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
@@ -177,15 +178,12 @@ fn handle_request(line: &str, db: &Arc<Database>) -> Response {
|
||||
|
||||
impl Request {
|
||||
fn parse(input: &str) -> Result<Request, String> {
|
||||
let mut parts = input.splitn(3, " ");
|
||||
let mut parts = input.splitn(3, ' ');
|
||||
match parts.next() {
|
||||
Some("GET") => {
|
||||
let key = match parts.next() {
|
||||
Some(key) => key,
|
||||
None => return Err(format!("GET must be followed by a key")),
|
||||
};
|
||||
let key = parts.next().ok_or("GET must be followed by a key")?;
|
||||
if parts.next().is_some() {
|
||||
return Err(format!("GET's key must not be followed by anything"));
|
||||
return Err("GET's key must not be followed by anything".into());
|
||||
}
|
||||
Ok(Request::Get {
|
||||
key: key.to_string(),
|
||||
@@ -194,11 +192,11 @@ impl Request {
|
||||
Some("SET") => {
|
||||
let key = match parts.next() {
|
||||
Some(key) => key,
|
||||
None => return Err(format!("SET must be followed by a key")),
|
||||
None => return Err("SET must be followed by a key".into()),
|
||||
};
|
||||
let value = match parts.next() {
|
||||
Some(value) => value,
|
||||
None => return Err(format!("SET needs a value")),
|
||||
None => return Err("SET needs a value".into()),
|
||||
};
|
||||
Ok(Request::Set {
|
||||
key: key.to_string(),
|
||||
@@ -206,7 +204,7 @@ impl Request {
|
||||
})
|
||||
}
|
||||
Some(cmd) => Err(format!("unknown command: {}", cmd)),
|
||||
None => Err(format!("empty input")),
|
||||
None => Err("empty input".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,22 +14,25 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use futures::SinkExt;
|
||||
use http::{header::HeaderValue, Request, Response, StatusCode};
|
||||
use serde::Serialize;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
use serde_json;
|
||||
use std::{env, error::Error, fmt, io};
|
||||
use tokio::{
|
||||
codec::{Decoder, Encoder, Framed},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse the arguments, bind the TCP socket we'll be listening to, spin up
|
||||
// our worker threads, and start shipping sockets to those worker threads.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
|
||||
let mut incoming = TcpListener::bind(&addr).await?.incoming();
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let mut server = TcpListener::bind(&addr).await?;
|
||||
let mut incoming = server.incoming();
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
while let Some(Ok(stream)) = incoming.next().await {
|
||||
@@ -63,11 +66,11 @@ async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
|
||||
let mut response = Response::builder();
|
||||
let body = match req.uri().path() {
|
||||
"/plaintext" => {
|
||||
response.header("Content-Type", "text/plain");
|
||||
response = response.header("Content-Type", "text/plain");
|
||||
"Hello, World!".to_string()
|
||||
}
|
||||
"/json" => {
|
||||
response.header("Content-Type", "application/json");
|
||||
response = response.header("Content-Type", "application/json");
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Message {
|
||||
@@ -78,7 +81,7 @@ async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
|
||||
})?
|
||||
}
|
||||
_ => {
|
||||
response.status(StatusCode::NOT_FOUND);
|
||||
response = response.status(StatusCode::NOT_FOUND);
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
@@ -196,16 +199,19 @@ impl Decoder for Http {
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
ret.method(&data[method.0..method.1]);
|
||||
ret.uri(data.slice(path.0, path.1));
|
||||
ret.version(http::Version::HTTP_11);
|
||||
ret = ret.method(&data[method.0..method.1]);
|
||||
let s = data.slice(path.0..path.1);
|
||||
let s = unsafe { String::from_utf8_unchecked(Vec::from(s.as_ref())) };
|
||||
ret = ret.uri(s);
|
||||
ret = ret.version(http::Version::HTTP_11);
|
||||
for header in headers.iter() {
|
||||
let (k, v) = match *header {
|
||||
Some((ref k, ref v)) => (k, v),
|
||||
None => break,
|
||||
};
|
||||
let value = unsafe { HeaderValue::from_shared_unchecked(data.slice(v.0, v.1)) };
|
||||
ret.header(&data[k.0..k.1], value);
|
||||
let value = HeaderValue::from_bytes(data.slice(v.0..v.1).as_ref())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "header decode error"))?;
|
||||
ret = ret.header(&data[k.0..k.1], value);
|
||||
}
|
||||
|
||||
let req = ret
|
||||
@@ -44,7 +44,7 @@ fn get_stdin_data() -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let remote_addr: SocketAddr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or("127.0.0.1:8080".into())
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".into())
|
||||
.parse()?;
|
||||
|
||||
// We use port 0 to let the operating system allocate an available port for us.
|
||||
@@ -6,27 +6,26 @@
|
||||
//! 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.
|
||||
|
||||
#![cfg(feature = "rt-full")]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio::{io, time};
|
||||
use tokio_util::codec::BytesCodec;
|
||||
use tokio_util::udp::UdpFramed;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{FutureExt, SinkExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use tokio::codec::BytesCodec;
|
||||
use tokio::future::FutureExt as TokioFutureExt;
|
||||
use tokio::io;
|
||||
use tokio::net::{UdpFramed, UdpSocket};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let _ = env_logger::init();
|
||||
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:0".to_string());
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:0".to_string());
|
||||
|
||||
// Bind both our sockets and then figure out what ports we got.
|
||||
let a = UdpSocket::bind(&addr).await?;
|
||||
@@ -47,7 +46,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// 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),
|
||||
Err(e) => println!("an error occurred; error = {:?}", e),
|
||||
_ => println!("done!"),
|
||||
}
|
||||
|
||||
@@ -71,7 +70,7 @@ async fn ping(socket: &mut UdpFramed<BytesCodec>, b_addr: SocketAddr) -> Result<
|
||||
async fn pong(socket: &mut UdpFramed<BytesCodec>) -> Result<(), io::Error> {
|
||||
let timeout = Duration::from_millis(200);
|
||||
|
||||
while let Ok(Some(Ok((bytes, addr)))) = socket.next().timeout(timeout).await {
|
||||
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?;
|
||||
@@ -1 +0,0 @@
|
||||
nightly-2019-08-21
|
||||
@@ -1 +0,0 @@
|
||||
edition = "2018"
|
||||
@@ -0,0 +1,15 @@
|
||||
[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"
|
||||
@@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "tokio")]
|
||||
pub use tokio;
|
||||
+2
-5
@@ -1,15 +1,12 @@
|
||||
use build_tests::tokio;
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
fn main_is_not_async() {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main_fn_has_args(_x: u8) {}
|
||||
|
||||
#[tokio::main(foo)]
|
||||
async fn main_attr_has_unknown_args() {}
|
||||
|
||||
#[tokio::main(multi_thread::bar)]
|
||||
#[tokio::main(threadpool::bar)]
|
||||
async fn main_attr_has_path_args() {}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -0,0 +1,41 @@
|
||||
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]
|
||||
| ^^^^^^^
|
||||
@@ -0,0 +1,9 @@
|
||||
#[test]
|
||||
fn compile_fail() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
drop(t);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
full = [
|
||||
"macros",
|
||||
"rt-core",
|
||||
"rt-threaded",
|
||||
|
||||
"tokio/full",
|
||||
"tokio-test"
|
||||
]
|
||||
macros = ["tokio/macros"]
|
||||
rt-core = ["tokio/rt-core"]
|
||||
rt-threaded = ["rt-core", "tokio/rt-threaded"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio" }
|
||||
tokio-test = { path = "../tokio-test", optional = true }
|
||||
doc-comment = "0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
@@ -0,0 +1 @@
|
||||
Tests that require additional components than just the `tokio` crate.
|
||||
@@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "full")]
|
||||
doc_comment::doc_comment!(include_str!("../../README.md"));
|
||||
@@ -0,0 +1,31 @@
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
#[tokio::main]
|
||||
async fn basic_main() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn generic_fun<T: Default>() -> T {
|
||||
T::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "rt-core")]
|
||||
mod spawn {
|
||||
#[tokio::main]
|
||||
async fn spawning() -> usize {
|
||||
let join = tokio::spawn(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell() {
|
||||
assert_eq!(1, basic_main());
|
||||
assert_eq!(bool::default(), generic_fun::<bool>())
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use futures::executor::block_on;
|
||||
|
||||
async fn my_async_fn() {}
|
||||
|
||||
#[test]
|
||||
fn pin() {
|
||||
block_on(async {
|
||||
let future = my_async_fn();
|
||||
tokio::pin!(future);
|
||||
(&mut future).await
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use futures::executor::block_on;
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn join_with_select() {
|
||||
block_on(async {
|
||||
let (tx1, mut rx1) = oneshot::channel::<i32>();
|
||||
let (tx2, mut rx2) = oneshot::channel::<i32>();
|
||||
|
||||
thread::spawn(move || {
|
||||
tx1.send(123).unwrap();
|
||||
tx2.send(456).unwrap();
|
||||
});
|
||||
|
||||
let mut a = None;
|
||||
let mut b = None;
|
||||
|
||||
while a.is_none() || b.is_none() {
|
||||
tokio::select! {
|
||||
v1 = (&mut rx1), if a.is_none() => a = Some(v1.unwrap()),
|
||||
v2 = (&mut rx2), if b.is_none() => b = Some(v2.unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
let (a, b) = (a.unwrap(), b.unwrap());
|
||||
|
||||
assert_eq!(a, 123);
|
||||
assert_eq!(b, 456);
|
||||
});
|
||||
}
|
||||
@@ -1,23 +1,15 @@
|
||||
#![cfg(feature = "process")]
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
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};
|
||||
|
||||
use futures_util::future;
|
||||
use futures_util::future::FutureExt;
|
||||
use futures_util::stream::StreamExt;
|
||||
use tokio::codec::{FramedRead, LinesCodec};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio_net::process::{Child, Command};
|
||||
|
||||
mod support;
|
||||
use support::*;
|
||||
|
||||
fn cat() -> Command {
|
||||
let mut me = env::current_exe().unwrap();
|
||||
me.pop();
|
||||
@@ -34,15 +26,12 @@ fn cat() -> Command {
|
||||
}
|
||||
|
||||
async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
let mut stdin = cat.stdin().take().unwrap();
|
||||
let stdout = cat.stdout().take().unwrap();
|
||||
let mut stdin = cat.stdin.take().unwrap();
|
||||
let stdout = cat.stdout.take().unwrap();
|
||||
|
||||
// Produce n lines on the child's stdout.
|
||||
let write = async {
|
||||
debug!("starting to feed");
|
||||
|
||||
for i in 0..n {
|
||||
debug!("sending line {} to child", i);
|
||||
let bytes = format!("line {}\n", i).into_bytes();
|
||||
stdin.write_all(&bytes).await.unwrap();
|
||||
}
|
||||
@@ -51,28 +40,21 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
};
|
||||
|
||||
let read = async {
|
||||
let mut reader = FramedRead::new(stdout, LinesCodec::new());
|
||||
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 {
|
||||
debug!("starting read from child");
|
||||
|
||||
let data = reader
|
||||
.next()
|
||||
.next_line()
|
||||
.await
|
||||
.unwrap_or_else(|| Ok(String::new()))
|
||||
.unwrap_or_else(|_| Some(String::new()))
|
||||
.expect("failed to read line");
|
||||
|
||||
let num_read = data.len();
|
||||
let done = num_lines >= n;
|
||||
|
||||
debug!(
|
||||
"read line {} from child ({} bytes, done: {})",
|
||||
num_lines, num_read, done
|
||||
);
|
||||
|
||||
match (done, num_read) {
|
||||
(false, 0) => panic!("broken pipe"),
|
||||
(true, n) if n != 0 => panic!("extraneous data"),
|
||||
@@ -109,14 +91,14 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
#[tokio::test]
|
||||
async fn feed_a_lot() {
|
||||
let child = cat().spawn().unwrap();
|
||||
let status = with_timeout(feed_cat(child, 10000)).await.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 mut stdin = child.stdin.take().unwrap();
|
||||
|
||||
let write_bytes = b"1234";
|
||||
|
||||
@@ -127,7 +109,7 @@ async fn wait_with_output_captures() {
|
||||
out.await
|
||||
};
|
||||
|
||||
let output = with_timeout(future).await.unwrap();
|
||||
let output = future.await.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
assert_eq!(output.stdout, write_bytes);
|
||||
@@ -141,7 +123,5 @@ async fn status_closes_any_pipes() {
|
||||
// we would end up blocking forever (and time out).
|
||||
let child = cat().status();
|
||||
|
||||
with_timeout(child)
|
||||
.await
|
||||
.expect("time out exceeded! did we get stuck waiting on the child?");
|
||||
assert_ok!(child.await);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
# 0.2.0-alpha.6 (September 30, 2019)
|
||||
|
||||
- Move to `futures-*-preview 0.3.0-alpha.19`
|
||||
- Move to `pin-project 0.4`
|
||||
|
||||
# 0.2.0-alpha.5 (September 19, 2019)
|
||||
|
||||
- Track tokio release
|
||||
|
||||
# 0.2.0-alpha.4 (August 29, 2019)
|
||||
|
||||
- Track tokio release.
|
||||
|
||||
# 0.2.0-alpha.3 (August 28, 2019)
|
||||
|
||||
### Fix
|
||||
- Infinite loop in `LinesCodec` (#1489).
|
||||
|
||||
# 0.2.0-alpha.2 (August 17, 2019)
|
||||
|
||||
### Changed
|
||||
- Update `futures` dependency to 0.3.0-alpha.18.
|
||||
|
||||
# 0.2.0-alpha.1 (August 8, 2019)
|
||||
|
||||
### Changed
|
||||
- Switch to `async`, `await`, and `std::future`.
|
||||
|
||||
# 0.1.1 (September 26, 2018)
|
||||
|
||||
* Allow setting max line length with `LinesCodec` (#632)
|
||||
|
||||
# 0.1.0 (June 13, 2018)
|
||||
|
||||
* Initial release (#353)
|
||||
@@ -1,37 +0,0 @@
|
||||
[package]
|
||||
name = "tokio-codec"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0-alpha.6"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-codec/0.2.0-alpha.6/tokio_codec"
|
||||
description = """
|
||||
Utilities for encoding and decoding frames.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
tokio-io = { version = "=0.2.0-alpha.6", path = "../tokio-io" }
|
||||
|
||||
bytes = "0.4.7"
|
||||
futures-core-preview = "=0.3.0-alpha.19"
|
||||
futures-sink-preview = "=0.3.0-alpha.19"
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "=0.2.0-alpha.6", path = "../tokio" }
|
||||
tokio-test = { version = "=0.2.0-alpha.6", path = "../tokio-test" }
|
||||
|
||||
futures-util-preview = "=0.3.0-alpha.19"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -1,40 +0,0 @@
|
||||
use crate::decoder::Decoder;
|
||||
use crate::encoder::Encoder;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use std::io;
|
||||
|
||||
/// A simple `Codec` implementation that just ships bytes around.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
|
||||
pub struct BytesCodec(());
|
||||
|
||||
impl BytesCodec {
|
||||
/// Creates a new `BytesCodec` for shipping around raw bytes.
|
||||
pub fn new() -> BytesCodec {
|
||||
BytesCodec(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for BytesCodec {
|
||||
type Item = BytesMut;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, io::Error> {
|
||||
if !buf.is_empty() {
|
||||
let len = buf.len();
|
||||
Ok(Some(buf.split_to(len)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for BytesCodec {
|
||||
type Item = Bytes;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, data: Bytes, buf: &mut BytesMut) -> Result<(), io::Error> {
|
||||
buf.reserve(data.len());
|
||||
buf.put(data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-codec/0.2.0-alpha.6")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
|
||||
//! 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`]: https://docs.rs/tokio/*/tokio/io/trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: https://docs.rs/tokio/*/tokio/io/trait.AsyncWrite.html
|
||||
//! [`Sink`]: https://docs.rs/futures-sink-preview/*/futures_sink/trait.Sink.html
|
||||
//! [`Stream`]: https://docs.rs/futures-core-preview/*/futures_core/stream/trait.Stream.html
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod bytes_codec;
|
||||
mod decoder;
|
||||
mod encoder;
|
||||
mod framed;
|
||||
mod framed_read;
|
||||
mod framed_write;
|
||||
pub mod length_delimited;
|
||||
mod lines_codec;
|
||||
|
||||
pub use crate::bytes_codec::BytesCodec;
|
||||
pub use crate::decoder::Decoder;
|
||||
pub use crate::encoder::Encoder;
|
||||
pub use crate::framed::{Framed, FramedParts};
|
||||
pub use crate::framed_read::FramedRead;
|
||||
pub use crate::framed_write::FramedWrite;
|
||||
pub use crate::length_delimited::{LengthDelimitedCodec, LengthDelimitedCodecError};
|
||||
pub use crate::lines_codec::{LinesCodec, LinesCodecError};
|
||||
@@ -1,7 +0,0 @@
|
||||
/// A macro to reduce some of the boilerplate for projecting from
|
||||
/// `Pin<&mut T>` to `Pin<&mut T.field>`
|
||||
macro_rules! pin {
|
||||
($e:expr) => {
|
||||
std::pin::Pin::new(&mut $e)
|
||||
};
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
# 0.2.0-alpha.6 (September 30, 2019)
|
||||
|
||||
- Move to `futures-*-preview 0.3.0-alpha.19`
|
||||
- Move to `pin-project 0.4`
|
||||
|
||||
# 0.2.0-alpha.5 (September 19, 2019)
|
||||
|
||||
### Fix
|
||||
- shutdown blocking pool threads when idle (#1562, #1514).
|
||||
|
||||
# 0.2.0-alpha.4 (August 29, 2019)
|
||||
|
||||
- Track tokio release.
|
||||
|
||||
# 0.2.0-alpha.3 (August 28, 2019)
|
||||
|
||||
### Changed
|
||||
- use `tracing` instead of `log`
|
||||
|
||||
### Added
|
||||
- thread pool dedicated to blocking operations (#1495).
|
||||
- `Executor::spawn_with_handle` (#1492).
|
||||
|
||||
# 0.2.0-alpha.2 (August 17, 2019)
|
||||
|
||||
### Fixed
|
||||
- allow running executor from within blocking clause (#1433).
|
||||
|
||||
### Changed
|
||||
- Update `futures` dependency to 0.3.0-alpha.18.
|
||||
|
||||
### Added
|
||||
- Import `current-thread` executor (#1447).
|
||||
- Import `threadpool` executor (#1152).
|
||||
|
||||
# 0.2.0-alpha.1 (August 8, 2019)
|
||||
|
||||
### Changed
|
||||
- Switch to `async`, `await`, and `std::future`.
|
||||
|
||||
### Removed
|
||||
- `Enter::make_permanent` and `Enter::on_exit` (#???)
|
||||
|
||||
# 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
|
||||
@@ -1,63 +0,0 @@
|
||||
[package]
|
||||
name = "tokio-executor"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0-alpha.6"
|
||||
edition = "2018"
|
||||
documentation = "https://docs.rs/tokio-executor/0.2.0-alpha.6/tokio_executor"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://github.com/tokio-rs/tokio"
|
||||
license = "MIT"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
description = """
|
||||
Future execution primitives
|
||||
"""
|
||||
keywords = ["futures", "tokio"]
|
||||
categories = ["concurrency", "asynchronous"]
|
||||
|
||||
[features]
|
||||
blocking = ["tokio-sync", "lazy_static"]
|
||||
current-thread = ["crossbeam-channel"]
|
||||
threadpool = [
|
||||
"tokio-sync",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"futures-core-preview",
|
||||
"num_cpus",
|
||||
"lazy_static",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tokio-sync = { version = "=0.2.0-alpha.6", optional = true, path = "../tokio-sync" }
|
||||
|
||||
tracing = { version = "0.1.5", optional = true }
|
||||
futures-util-preview = { version = "=0.3.0-alpha.19", features = ["channel"] }
|
||||
|
||||
# current-thread dependencies
|
||||
crossbeam-channel = { version = "0.3.8", optional = true }
|
||||
|
||||
# threadpool dependencies
|
||||
crossbeam-deque = { version = "0.7.0", optional = true }
|
||||
crossbeam-queue = { version = "0.1.0", optional = true }
|
||||
crossbeam-utils = { version = "0.6.4", optional = true }
|
||||
futures-core-preview = { version = "=0.3.0-alpha.19", optional = true }
|
||||
num_cpus = { version = "1.2", optional = true }
|
||||
lazy_static = { version = "1", optional = true }
|
||||
slab = { version = "0.4.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "=0.2.0-alpha.6", path = "../tokio" }
|
||||
tokio-test = { version = "=0.2.0-alpha.6", path = "../tokio-test" }
|
||||
|
||||
futures-core-preview = "=0.3.0-alpha.19"
|
||||
rand = "0.7"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -1,25 +0,0 @@
|
||||
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.
|
||||
@@ -1,13 +0,0 @@
|
||||
# tokio-executor
|
||||
|
||||
Task execution related traits and utilities.
|
||||
|
||||
## 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,133 +0,0 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
const ITER: usize = 1_000;
|
||||
|
||||
mod blocking {
|
||||
use super::*;
|
||||
use futures::future::*;
|
||||
use tokio_executor::threadpool::{blocking, Builder};
|
||||
|
||||
#[bench]
|
||||
fn cpu_bound(b: &mut test::Bencher) {
|
||||
let pool = Builder::new().pool_size(2).max_blocking(20).build();
|
||||
|
||||
b.iter(|| {
|
||||
let count_down = Arc::new(CountDown::new(ITER));
|
||||
|
||||
for _ in 0..ITER {
|
||||
let count_down = count_down.clone();
|
||||
|
||||
pool.spawn(lazy(move || {
|
||||
poll_fn(|| blocking(|| perform_complex_computation()).map_err(|_| panic!()))
|
||||
.and_then(move |_| {
|
||||
// Do something with the value
|
||||
count_down.dec();
|
||||
Ok(())
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
count_down.wait();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod message_passing {
|
||||
use super::*;
|
||||
use futures::future::*;
|
||||
use futures::sync::oneshot;
|
||||
use tokio_executor::threadpool::Builder;
|
||||
|
||||
#[bench]
|
||||
fn cpu_bound(b: &mut test::Bencher) {
|
||||
let pool = Builder::new().pool_size(2).max_blocking(20).build();
|
||||
|
||||
let blocking = threadpool::ThreadPool::new(20);
|
||||
|
||||
b.iter(|| {
|
||||
let count_down = Arc::new(CountDown::new(ITER));
|
||||
|
||||
for _ in 0..ITER {
|
||||
let count_down = count_down.clone();
|
||||
let blocking = blocking.clone();
|
||||
|
||||
pool.spawn(lazy(move || {
|
||||
// Create a channel to receive the return value.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// Spawn a task on the blocking thread pool to process the
|
||||
// computation.
|
||||
blocking.execute(move || {
|
||||
let res = perform_complex_computation();
|
||||
tx.send(res).unwrap();
|
||||
});
|
||||
|
||||
rx.and_then(move |_| {
|
||||
count_down.dec();
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|_| panic!())
|
||||
}));
|
||||
}
|
||||
|
||||
count_down.wait();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_complex_computation() -> usize {
|
||||
use rand::*;
|
||||
|
||||
// Simulate a CPU heavy computation
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.gen()
|
||||
}
|
||||
|
||||
// Util for waiting until the tasks complete
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::*;
|
||||
use std::sync::*;
|
||||
|
||||
struct CountDown {
|
||||
rem: AtomicUsize,
|
||||
mutex: Mutex<()>,
|
||||
condvar: Condvar,
|
||||
}
|
||||
|
||||
impl CountDown {
|
||||
fn new(rem: usize) -> Self {
|
||||
CountDown {
|
||||
rem: AtomicUsize::new(rem),
|
||||
mutex: Mutex::new(()),
|
||||
condvar: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dec(&self) {
|
||||
let prev = self.rem.fetch_sub(1, AcqRel);
|
||||
|
||||
if prev != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let _lock = self.mutex.lock().unwrap();
|
||||
self.condvar.notify_all();
|
||||
}
|
||||
|
||||
fn wait(&self) {
|
||||
let mut lock = self.mutex.lock().unwrap();
|
||||
|
||||
loop {
|
||||
if self.rem.load(Acquire) == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
lock = self.condvar.wait(lock).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS_PER_CPU: usize = 50;
|
||||
|
||||
mod threadpool {
|
||||
use futures::{future, task, Async};
|
||||
use num_cpus;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use tokio_executor::threadpool::*;
|
||||
|
||||
#[bench]
|
||||
fn spawn_many(b: &mut test::Bencher) {
|
||||
let threadpool = ThreadPool::new();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(10);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(move || {
|
||||
rem.store(super::NUM_SPAWN, SeqCst);
|
||||
|
||||
for _ in 0..super::NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
|
||||
threadpool.spawn(future::lazy(move || {
|
||||
if 1 == rem.fetch_sub(1, SeqCst) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn yield_many(b: &mut test::Bencher) {
|
||||
let threadpool = ThreadPool::new();
|
||||
let tasks = super::TASKS_PER_CPU * num_cpus::get();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(tasks);
|
||||
|
||||
b.iter(move || {
|
||||
for _ in 0..tasks {
|
||||
let mut rem = super::NUM_YIELD;
|
||||
let tx = tx.clone();
|
||||
|
||||
threadpool.spawn(future::poll_fn(move || {
|
||||
rem -= 1;
|
||||
|
||||
if rem == 0 {
|
||||
tx.send(()).unwrap();
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
// Notify the current task
|
||||
task::current().notify();
|
||||
|
||||
// Not ready
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for _ in 0..tasks {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// In this case, CPU pool completes the benchmark faster, but this is due to how
|
||||
// CpuPool currently behaves, starving other futures. This completes the
|
||||
// benchmark quickly but results in poor runtime characteristics for a thread
|
||||
// pool.
|
||||
//
|
||||
// See rust-lang-nursery/futures-rs#617
|
||||
//
|
||||
mod cpupool {
|
||||
use futures::future::{self, Executor};
|
||||
use futures::{task, Async};
|
||||
use futures_cpupool::*;
|
||||
use num_cpus;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
#[bench]
|
||||
fn spawn_many(b: &mut test::Bencher) {
|
||||
let pool = CpuPool::new(num_cpus::get());
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(10);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(move || {
|
||||
rem.store(super::NUM_SPAWN, SeqCst);
|
||||
|
||||
for _ in 0..super::NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
|
||||
pool.execute(future::lazy(move || {
|
||||
if 1 == rem.fetch_sub(1, SeqCst) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}))
|
||||
.ok()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn yield_many(b: &mut test::Bencher) {
|
||||
let pool = CpuPool::new(num_cpus::get());
|
||||
let tasks = super::TASKS_PER_CPU * num_cpus::get();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(tasks);
|
||||
|
||||
b.iter(move || {
|
||||
for _ in 0..tasks {
|
||||
let mut rem = super::NUM_YIELD;
|
||||
let tx = tx.clone();
|
||||
|
||||
pool.execute(future::poll_fn(move || {
|
||||
rem -= 1;
|
||||
|
||||
if rem == 0 {
|
||||
tx.send(()).unwrap();
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
// Notify the current task
|
||||
task::current().notify();
|
||||
|
||||
// Not ready
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}))
|
||||
.ok()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..tasks {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#![cfg(feature = "broken")]
|
||||
#![feature(test)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
const ITER: usize = 20_000;
|
||||
|
||||
mod us {
|
||||
use futures::future;
|
||||
use std::sync::mpsc;
|
||||
use tokio_executor::threadpool::*;
|
||||
|
||||
#[bench]
|
||||
fn chained_spawn(b: &mut test::Bencher) {
|
||||
let threadpool = ThreadPool::new();
|
||||
|
||||
fn spawn(pool_tx: Sender, res_tx: mpsc::Sender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
res_tx.send(()).unwrap();
|
||||
} else {
|
||||
let pool_tx2 = pool_tx.clone();
|
||||
pool_tx
|
||||
.spawn(future::lazy(move || {
|
||||
spawn(pool_tx2, res_tx, n - 1);
|
||||
Ok(())
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
b.iter(move || {
|
||||
let (res_tx, res_rx) = mpsc::channel();
|
||||
|
||||
spawn(threadpool.sender().clone(), res_tx, super::ITER);
|
||||
res_rx.recv().unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod cpupool {
|
||||
use futures::future::{self, Executor};
|
||||
use futures_cpupool::*;
|
||||
use num_cpus;
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[bench]
|
||||
fn chained_spawn(b: &mut test::Bencher) {
|
||||
let pool = CpuPool::new(num_cpus::get());
|
||||
|
||||
fn spawn(pool: CpuPool, res_tx: mpsc::Sender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
res_tx.send(()).unwrap();
|
||||
} else {
|
||||
let pool2 = pool.clone();
|
||||
pool.execute(future::lazy(move || {
|
||||
spawn(pool2, res_tx, n - 1);
|
||||
Ok(())
|
||||
}))
|
||||
.ok()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
b.iter(move || {
|
||||
let (res_tx, res_rx) = mpsc::channel();
|
||||
|
||||
spawn(pool.clone(), res_tx, super::ITER);
|
||||
res_rx.recv().unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
//! Thread pool for blocking operations
|
||||
|
||||
use tokio_sync::oneshot;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Condvar, Mutex};
|
||||
use std::task::{Context, Poll};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
struct Pool {
|
||||
shared: Mutex<Shared>,
|
||||
condvar: Condvar,
|
||||
}
|
||||
|
||||
struct Shared {
|
||||
queue: VecDeque<Box<dyn FnOnce() + Send>>,
|
||||
num_th: u32,
|
||||
num_idle: u32,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref POOL: Pool = Pool::new();
|
||||
}
|
||||
|
||||
const MAX_THREADS: u32 = 1_000;
|
||||
const KEEP_ALIVE: Duration = Duration::from_secs(10);
|
||||
|
||||
/// Result of a blocking operation running on the blocking thread pool.
|
||||
#[derive(Debug)]
|
||||
pub struct Blocking<T> {
|
||||
rx: oneshot::Receiver<T>,
|
||||
}
|
||||
|
||||
/// Run the provided function on a threadpool dedicated to blocking operations.
|
||||
pub fn run<F, R>(f: F) -> Blocking<R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let should_spawn = {
|
||||
let mut shared = POOL.shared.lock().unwrap();
|
||||
|
||||
shared.queue.push_back(Box::new(move || {
|
||||
// The receiver may have dropped
|
||||
let _ = tx.send(f());
|
||||
}));
|
||||
|
||||
if shared.num_idle == 0 {
|
||||
// No threads are able to process the task
|
||||
|
||||
if shared.num_th == MAX_THREADS {
|
||||
// At max number of threads
|
||||
false
|
||||
} else {
|
||||
shared.num_th += 1;
|
||||
true
|
||||
}
|
||||
} else {
|
||||
shared.num_idle -= 1;
|
||||
POOL.condvar.notify_one();
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if should_spawn {
|
||||
spawn_thread();
|
||||
}
|
||||
|
||||
Blocking { rx }
|
||||
}
|
||||
|
||||
impl<T> Future for Blocking<T> {
|
||||
type Output = T;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
use std::task::Poll::*;
|
||||
|
||||
match Pin::new(&mut self.rx).poll(cx) {
|
||||
Ready(Ok(v)) => Ready(v),
|
||||
Ready(Err(_)) => panic!(
|
||||
"the blocking operation has been dropped before completing. \
|
||||
This should not happen and is a bug."
|
||||
),
|
||||
Pending => Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_thread() {
|
||||
thread::Builder::new()
|
||||
.name("tokio-blocking-driver".to_string())
|
||||
.spawn(|| {
|
||||
'outer: loop {
|
||||
let mut shared = POOL.shared.lock().unwrap();
|
||||
|
||||
if let Some(task) = shared.queue.pop_front() {
|
||||
drop(shared);
|
||||
run_task(task);
|
||||
continue;
|
||||
}
|
||||
|
||||
// IDLE
|
||||
shared.num_idle += 1;
|
||||
|
||||
loop {
|
||||
let lock_result = POOL.condvar.wait_timeout(shared, KEEP_ALIVE).unwrap();
|
||||
shared = lock_result.0;
|
||||
let timeout_result = lock_result.1;
|
||||
|
||||
if let Some(task) = shared.queue.pop_front() {
|
||||
drop(shared);
|
||||
run_task(task);
|
||||
continue 'outer;
|
||||
} else if timeout_result.timed_out() {
|
||||
shared.num_idle = shared.num_idle.saturating_sub(1);
|
||||
shared.num_th -= 1;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn run_task(f: Box<dyn FnOnce() + Send>) {
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
|
||||
let _ = catch_unwind(AssertUnwindSafe(|| f()));
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
fn new() -> Pool {
|
||||
Pool {
|
||||
shared: Mutex::new(Shared {
|
||||
queue: VecDeque::new(),
|
||||
num_th: 0,
|
||||
num_idle: 0,
|
||||
}),
|
||||
condvar: Condvar::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,825 +0,0 @@
|
||||
//! A single-threaded executor which executes tasks on the same thread from which
|
||||
//! they are spawned.
|
||||
//!
|
||||
//! [`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.
|
||||
//!
|
||||
//! [`CurrentThread`]: struct.CurrentThread.html
|
||||
//! [`spawn`]: fn.spawn.html
|
||||
//! [`block_on_all`]: fn.block_on_all.html
|
||||
|
||||
mod scheduler;
|
||||
|
||||
use self::scheduler::Scheduler;
|
||||
use crate::park::{Park, ParkThread, Unpark};
|
||||
use crate::{EnterError, Executor, SpawnError, TypedExecutor};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{atomic, Arc};
|
||||
use std::task::{Context, Poll, Waker};
|
||||
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: crossbeam_channel::Receiver<Pin<Box<dyn Future<Output = ()> + 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> {
|
||||
executor: &'a mut CurrentThread<P>,
|
||||
}
|
||||
|
||||
/// 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, "Run error")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for RunError {}
|
||||
|
||||
/// 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 {
|
||||
let descr = if self.timeout {
|
||||
"Run timeout error (timeout)"
|
||||
} else {
|
||||
"Run timeout error (not timeout)"
|
||||
};
|
||||
write!(fmt, "{}", descr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for RunTimeoutError {}
|
||||
|
||||
/// 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, "Turn error")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TurnError {}
|
||||
|
||||
/// 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> {}
|
||||
|
||||
/// This is mostly split out to make the borrow checker happy.
|
||||
struct Borrow<'a, U> {
|
||||
id: u64,
|
||||
scheduler: &'a mut Scheduler<U>,
|
||||
num_futures: &'a atomic::AtomicUsize,
|
||||
}
|
||||
|
||||
trait SpawnLocal {
|
||||
fn spawn_local(&mut self, future: Pin<Box<dyn Future<Output = ()>>>, 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) -> F::Output
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let ret = current_thread.block_on(future);
|
||||
current_thread.run().unwrap();
|
||||
ret
|
||||
}
|
||||
|
||||
/// 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<Output = ()> + 'static,
|
||||
{
|
||||
TaskExecutor::current()
|
||||
.spawn_local(Box::pin(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) = crossbeam_channel::unbounded();
|
||||
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 waker = scheduler.waker();
|
||||
|
||||
let num_futures = Arc::new(atomic::AtomicUsize::new(0));
|
||||
|
||||
CurrentThread {
|
||||
scheduler,
|
||||
num_futures: num_futures.clone(),
|
||||
park,
|
||||
id,
|
||||
spawn_handle: Handle {
|
||||
sender: spawn_sender,
|
||||
num_futures,
|
||||
waker,
|
||||
thread,
|
||||
id,
|
||||
},
|
||||
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<Output = ()> + 'static,
|
||||
{
|
||||
self.borrow().spawn_local(Box::pin(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) -> F::Output
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
let _enter = crate::enter().expect("failed to start `current_thread::Runtime`");
|
||||
self.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 _enter = crate::enter().expect("failed to start `current_thread::Runtime`");
|
||||
self.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 _enter = crate::enter().expect("failed to start `current_thread::Runtime`");
|
||||
self.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 _enter = crate::enter().expect("failed to start `current_thread::Runtime`");
|
||||
self.enter().turn(duration)
|
||||
}
|
||||
|
||||
/// Bind `CurrentThread` instance with an execution context.
|
||||
fn enter(&mut self) -> Entered<'_, P> {
|
||||
Entered { executor: self }
|
||||
}
|
||||
|
||||
/// 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 Executor for CurrentThread {
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Pin<Box<dyn Future<Output = ()> + Send>>,
|
||||
) -> Result<(), SpawnError> {
|
||||
self.borrow().spawn_local(future, false);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypedExecutor<T> for CurrentThread
|
||||
where
|
||||
T: Future<Output = ()> + 'static,
|
||||
{
|
||||
fn spawn(&mut self, future: T) -> Result<(), SpawnError> {
|
||||
self.borrow().spawn_local(Box::pin(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<P: Park + Default> Default for CurrentThread<P> {
|
||||
fn default() -> Self {
|
||||
CurrentThread::new_with_park(P::default())
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Entered =====
|
||||
|
||||
impl<P: Park> Entered<'_, 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<Output = ()> + 'static,
|
||||
{
|
||||
self.executor.borrow().spawn_local(Box::pin(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.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the `Park` call returns an error.
|
||||
pub fn block_on<F>(&mut self, mut future: F) -> F::Output
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
// Safety: we shadow the original `future`, so it will never move
|
||||
// again.
|
||||
let mut future = unsafe { Pin::new_unchecked(&mut future) };
|
||||
let waker = self.executor.scheduler.waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
loop {
|
||||
let res = self
|
||||
.executor
|
||||
.borrow()
|
||||
.enter(|| future.as_mut().poll(&mut cx));
|
||||
|
||||
match res {
|
||||
Poll::Ready(e) => return e,
|
||||
Poll::Pending => {}
|
||||
}
|
||||
|
||||
self.tick();
|
||||
|
||||
if self.executor.park.park().is_err() {
|
||||
panic!("block_on park failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 self.executor.park.park_timeout(rem).is_err() {
|
||||
return Err(RunTimeoutError::new(false));
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
if now >= until {
|
||||
return Err(RunTimeoutError::new(true));
|
||||
}
|
||||
|
||||
time = Some((until, until - now));
|
||||
}
|
||||
None => {
|
||||
if self.executor.park.park().is_err() {
|
||||
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, borrow.num_futures)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Park> fmt::Debug for Entered<'_, P> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Entered")
|
||||
.field("executor", &self.executor)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Handle =====
|
||||
|
||||
/// Handle to spawn a future on the corresponding `CurrentThread` instance
|
||||
#[derive(Clone)]
|
||||
pub struct Handle {
|
||||
sender: crossbeam_channel::Sender<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
|
||||
num_futures: Arc<atomic::AtomicUsize>,
|
||||
/// Waker to the Scheduler
|
||||
waker: Waker,
|
||||
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.is_shut_down())
|
||||
.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<Output = ()> + Send + 'static,
|
||||
{
|
||||
if thread::current().id() == self.thread {
|
||||
let mut e = TaskExecutor::current();
|
||||
if e.id() == Some(self.id) {
|
||||
return e.spawn_local(Box::pin(future));
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
return Err(SpawnError::shutdown());
|
||||
}
|
||||
|
||||
self.sender
|
||||
.send(Box::pin(future))
|
||||
.expect("CurrentThread does not exist anymore");
|
||||
self.waker.wake_by_ref();
|
||||
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.is_shut_down() {
|
||||
return Err(SpawnError::shutdown());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_shut_down(&self) -> bool {
|
||||
// LSB of "num_futures" is the shutdown bit
|
||||
let num_futures = self.num_futures.load(atomic::Ordering::SeqCst);
|
||||
num_futures % 2 == 1
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 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: Pin<Box<dyn Future<Output = ()>>>,
|
||||
) -> Result<(), SpawnError> {
|
||||
CURRENT.with(|current| match current.spawn.get() {
|
||||
Some(spawn) => {
|
||||
unsafe { (*spawn).spawn_local(future, false) };
|
||||
Ok(())
|
||||
}
|
||||
None => Err(SpawnError::shutdown()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Executor for TaskExecutor {
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Pin<Box<dyn Future<Output = ()> + Send>>,
|
||||
) -> Result<(), SpawnError> {
|
||||
self.spawn_local(future)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> TypedExecutor<F> for TaskExecutor
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
fn spawn(&mut self, future: F) -> Result<(), SpawnError> {
|
||||
self.spawn_local(Box::pin(future))
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Borrow =====
|
||||
|
||||
impl<U: Unpark> Borrow<'_, U> {
|
||||
fn enter<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
CURRENT.with(|current| {
|
||||
current.id.set(Some(self.id));
|
||||
current.set_spawn(self, || f())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unpark> SpawnLocal for Borrow<'_, U> {
|
||||
fn spawn_local(&mut self, future: Pin<Box<dyn Future<Output = ()>>>, 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 Drop for Reset<'_> {
|
||||
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;
|
||||
// false positive: https://github.com/rust-lang/rust-clippy/issues/2906
|
||||
#[allow(clippy::transmute_ptr_to_ptr)]
|
||||
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<EnterError> for RunTimeoutError {
|
||||
fn from(_: 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<EnterError> for BlockError<T> {
|
||||
fn from(_: EnterError) -> Self {
|
||||
BlockError { inner: None }
|
||||
}
|
||||
}
|
||||
@@ -1,797 +0,0 @@
|
||||
use super::Borrow;
|
||||
use crate::park::Unpark;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
use std::pin::Pin;
|
||||
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::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
use std::thread;
|
||||
use std::usize;
|
||||
|
||||
/// A generic task-aware scheduler.
|
||||
///
|
||||
/// This is used both by `FuturesUnordered` and the current-thread executor.
|
||||
pub(crate) struct Scheduler<U> {
|
||||
inner: Arc<Inner<U>>,
|
||||
nodes: List<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> {}
|
||||
|
||||
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(Pin<Box<dyn Future<Output = ()>>>);
|
||||
|
||||
/// A task that is scheduled. `turn` must be called
|
||||
pub(crate) struct Scheduled<'a, U> {
|
||||
task: &'a mut Task,
|
||||
node: &'a Arc<Node<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(crate) 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,
|
||||
});
|
||||
|
||||
Scheduler {
|
||||
inner,
|
||||
nodes: List::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn waker(&self) -> Waker {
|
||||
waker_inner(self.inner.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn schedule(&mut self, item: Pin<Box<dyn Future<Output = ()>>>) {
|
||||
// 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(crate) 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(crate) fn tick(&mut self, eid: u64, 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> {
|
||||
borrow: &'a mut Borrow<'a, U>,
|
||||
node: Option<Arc<Node<U>>>,
|
||||
}
|
||||
|
||||
impl<U: Unpark> Drop for Bomb<'_, U> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(node) = self.node.take() {
|
||||
self.borrow.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),
|
||||
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 mut scheduled = Scheduled {
|
||||
task: item,
|
||||
node: bomb.node.as_ref().unwrap(),
|
||||
done: &mut done,
|
||||
};
|
||||
|
||||
if borrow.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<U: Unpark> Scheduled<'_, U> {
|
||||
/// Polls the task, returns `true` if the task has completed.
|
||||
pub(crate) fn tick(&mut self) -> bool {
|
||||
let waker = unsafe {
|
||||
// Safety: we don't hold this waker ref longer than
|
||||
// this `tick` function
|
||||
waker_ref(self.node)
|
||||
};
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
let ret = match self.task.0.as_mut().poll(&mut cx) {
|
||||
Poll::Ready(()) => true,
|
||||
Poll::Pending => false,
|
||||
};
|
||||
|
||||
*self.done = ret;
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub(crate) fn new(future: Pin<Box<dyn Future<Output = ()> + 'static>>) -> Self {
|
||||
Task(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() && 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;
|
||||
|
||||
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;
|
||||
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn noop(_: *const ()) {}
|
||||
|
||||
// ===== Raw Waker Inner<U> ======
|
||||
|
||||
fn waker_inner<U: Unpark>(inner: Arc<Inner<U>>) -> Waker {
|
||||
let ptr = Arc::into_raw(inner) as *const ();
|
||||
let vtable = &RawWakerVTable::new(
|
||||
clone_inner::<U>,
|
||||
wake_inner::<U>,
|
||||
wake_by_ref_inner::<U>,
|
||||
drop_inner::<U>,
|
||||
);
|
||||
|
||||
unsafe { Waker::from_raw(RawWaker::new(ptr, vtable)) }
|
||||
}
|
||||
|
||||
unsafe fn clone_inner<U: Unpark>(data: *const ()) -> RawWaker {
|
||||
let arc: Arc<Inner<U>> = Arc::from_raw(data as *const Inner<U>);
|
||||
let clone = arc.clone();
|
||||
// forget both Arcs so the refcounts don't get decremented
|
||||
mem::forget(arc);
|
||||
mem::forget(clone);
|
||||
|
||||
let vtable = &RawWakerVTable::new(
|
||||
clone_inner::<U>,
|
||||
wake_inner::<U>,
|
||||
wake_by_ref_inner::<U>,
|
||||
drop_inner::<U>,
|
||||
);
|
||||
RawWaker::new(data, vtable)
|
||||
}
|
||||
|
||||
unsafe fn wake_inner<U: Unpark>(data: *const ()) {
|
||||
let arc: Arc<Inner<U>> = Arc::from_raw(data as *const Inner<U>);
|
||||
arc.unpark.unpark();
|
||||
}
|
||||
|
||||
unsafe fn wake_by_ref_inner<U: Unpark>(data: *const ()) {
|
||||
let arc: Arc<Inner<U>> = Arc::from_raw(data as *const Inner<U>);
|
||||
arc.unpark.unpark();
|
||||
// by_ref means we don't own the Node, so forget the Arc
|
||||
mem::forget(arc);
|
||||
}
|
||||
|
||||
unsafe fn drop_inner<U>(data: *const ()) {
|
||||
drop(Arc::<Inner<U>>::from_raw(data as *const Inner<U>));
|
||||
}
|
||||
// ===== Raw Waker Node<U> ======
|
||||
|
||||
unsafe fn waker_ref<U: Unpark>(node: &Arc<Node<U>>) -> Waker {
|
||||
let ptr = &*node as &Node<U> as *const Node<U> as *const ();
|
||||
let vtable = &RawWakerVTable::new(
|
||||
clone_node::<U>,
|
||||
wake_unreachable,
|
||||
wake_by_ref_node::<U>,
|
||||
noop,
|
||||
);
|
||||
|
||||
Waker::from_raw(RawWaker::new(ptr, vtable))
|
||||
}
|
||||
|
||||
unsafe fn wake_unreachable(_data: *const ()) {
|
||||
unreachable!("waker_ref::wake()");
|
||||
}
|
||||
|
||||
unsafe fn clone_node<U: Unpark>(data: *const ()) -> RawWaker {
|
||||
let arc: Arc<Node<U>> = Arc::from_raw(data as *const Node<U>);
|
||||
let clone = arc.clone();
|
||||
// forget both Arcs so the refcounts don't get decremented
|
||||
mem::forget(arc);
|
||||
mem::forget(clone);
|
||||
|
||||
let vtable = &RawWakerVTable::new(
|
||||
clone_node::<U>,
|
||||
wake_node::<U>,
|
||||
wake_by_ref_node::<U>,
|
||||
drop_node::<U>,
|
||||
);
|
||||
RawWaker::new(data, vtable)
|
||||
}
|
||||
|
||||
unsafe fn wake_node<U: Unpark>(data: *const ()) {
|
||||
let arc: Arc<Node<U>> = Arc::from_raw(data as *const Node<U>);
|
||||
Node::<U>::notify(&arc);
|
||||
}
|
||||
|
||||
unsafe fn wake_by_ref_node<U: Unpark>(data: *const ()) {
|
||||
let arc: Arc<Node<U>> = Arc::from_raw(data as *const Node<U>);
|
||||
Node::<U>::notify(&arc);
|
||||
// by_ref means we don't own the Node, so forget the Arc
|
||||
mem::forget(arc);
|
||||
}
|
||||
|
||||
unsafe fn drop_node<U>(data: *const ()) {
|
||||
drop(Arc::<Node<U>>::from_raw(data as *const Node<U>));
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
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 {
|
||||
_p: PhantomData<RefCell<()>>,
|
||||
}
|
||||
|
||||
/// 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", &format!("{}", self))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EnterError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
fmt,
|
||||
"attempted to run an executor while another executor is already running"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for EnterError {}
|
||||
|
||||
/// 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 { _p: PhantomData })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
/// Blocks the thread on the specified future, returning the value with
|
||||
/// which that future completes.
|
||||
pub fn block_on<F: Future>(&mut self, mut f: F) -> F::Output {
|
||||
use crate::park::{Park, ParkThread};
|
||||
use std::pin::Pin;
|
||||
use std::task::Context;
|
||||
use std::task::Poll::Ready;
|
||||
|
||||
let mut park = ParkThread::new();
|
||||
let waker = park.unpark().into_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
// `block_on` takes ownership of `f`. Once it is pinned here, the original `f` binding can
|
||||
// no longer be accessed, making the pinning safe.
|
||||
let mut f = unsafe { Pin::new_unchecked(&mut f) };
|
||||
|
||||
loop {
|
||||
if let Ready(v) = f.as_mut().poll(&mut cx) {
|
||||
return v;
|
||||
}
|
||||
park.park().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
c.set(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
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,
|
||||
"attempted to spawn task while the executor is at capacity or shut down"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SpawnError {}
|
||||
@@ -1,180 +0,0 @@
|
||||
use crate::SpawnError;
|
||||
use futures_util::future::{FutureExt, RemoteHandle};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::Executor;
|
||||
///
|
||||
/// # fn docs(my_executor: &mut dyn Executor) {
|
||||
/// my_executor.spawn(Box::pin(async {
|
||||
/// println!("running on the executor");
|
||||
/// })).unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`spawn`]: #tymethod.spawn
|
||||
/// [`poll`]: https://doc.rust-lang.org/std/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
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::Executor;
|
||||
///
|
||||
/// # fn docs(my_executor: &mut dyn Executor) {
|
||||
/// my_executor.spawn(Box::pin(async {
|
||||
/// println!("running on the executor");
|
||||
/// })).unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
fn spawn(&mut self, future: Pin<Box<dyn Future<Output = ()> + 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
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::Executor;
|
||||
///
|
||||
/// # fn docs(my_executor: &mut dyn Executor) {
|
||||
/// if my_executor.status().is_ok() {
|
||||
/// my_executor.spawn(Box::pin(async {
|
||||
/// println!("running on the executor");
|
||||
/// })).unwrap();
|
||||
/// } else {
|
||||
/// println!("the executor is not in a good state");
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn Executor {
|
||||
/// Spawns a future object to run on this executor, returning a result of
|
||||
/// its `RemoteHandle`.
|
||||
///
|
||||
/// `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
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::Executor;
|
||||
/// use futures_util::future::FutureExt;
|
||||
///
|
||||
/// # fn docs(my_executor: &'static mut (dyn Executor + 'static)) {
|
||||
/// let handle = my_executor.spawn_with_handle(Box::pin(async {
|
||||
/// println!("running on the executor");
|
||||
/// })).unwrap();
|
||||
///
|
||||
/// let handle = handle.map(|_| println!("the future has completed"));
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn spawn_with_handle<Fut>(
|
||||
&mut self,
|
||||
future: Fut,
|
||||
) -> Result<RemoteHandle<Fut::Output>, SpawnError>
|
||||
where
|
||||
Fut: Future + Send + 'static,
|
||||
Fut::Output: Send,
|
||||
{
|
||||
let (future, handle) = future.remote_handle();
|
||||
self.spawn(Box::pin(future))?;
|
||||
Ok(handle)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Executor + ?Sized> Executor for Box<E> {
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Pin<Box<dyn Future<Output = ()> + Send>>,
|
||||
) -> Result<(), SpawnError> {
|
||||
(**self).spawn(future)
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
(**self).status()
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
use super::{Executor, SpawnError};
|
||||
use std::cell::Cell;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// 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: (),
|
||||
}
|
||||
|
||||
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: Pin<Box<dyn Future<Output = ()> + 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<Output = ()> + Send + 'static,
|
||||
{
|
||||
fn spawn(&mut self, future: T) -> Result<(), SpawnError> {
|
||||
super::Executor::spawn(self, Box::pin(future))
|
||||
}
|
||||
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
super::Executor::status(self)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 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
|
||||
///
|
||||
/// ```no_run
|
||||
/// tokio::spawn(async {
|
||||
/// println!("running on the default executor");
|
||||
/// });
|
||||
/// ```
|
||||
pub fn spawn<T>(future: T)
|
||||
where
|
||||
T: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
DefaultExecutor::current().spawn(Box::pin(future)).unwrap()
|
||||
}
|
||||
|
||||
/// Set the default executor for the duration of the closure
|
||||
///
|
||||
/// If a default executor is already set, it will be restored when the closure returns or if it
|
||||
/// panics.
|
||||
pub fn with_default<T, F, R>(executor: &mut T, f: F) -> R
|
||||
where
|
||||
T: Executor,
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
EXECUTOR.with(|cell| {
|
||||
let was = cell.get();
|
||||
|
||||
// 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>, State);
|
||||
|
||||
impl Drop for Reset<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.set(self.1);
|
||||
}
|
||||
}
|
||||
|
||||
let _reset = Reset(cell, was);
|
||||
|
||||
// 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()
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn hide_lt<'a>(p: *mut (dyn Executor + 'a)) -> *mut (dyn Executor + 'static) {
|
||||
use std::mem;
|
||||
// false positive: https://github.com/rust-lang/rust-clippy/issues/2906
|
||||
#[allow(clippy::transmute_ptr_to_ptr)]
|
||||
mem::transmute(p)
|
||||
}
|
||||
|
||||
#[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 _enter = super::super::enter().unwrap();
|
||||
let mut executor = DefaultExecutor::current();
|
||||
|
||||
let result = with_default(&mut executor, || DefaultExecutor::current().status());
|
||||
|
||||
assert!(result.err().unwrap().is_shutdown())
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-executor/0.2.0-alpha.6")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
|
||||
//! 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://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll
|
||||
#[cfg(any(feature = "current-thread", feature = "threadpool"))]
|
||||
#[macro_use]
|
||||
mod tracing;
|
||||
|
||||
mod enter;
|
||||
mod error;
|
||||
mod executor;
|
||||
mod global;
|
||||
pub mod park;
|
||||
mod typed;
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
pub mod blocking;
|
||||
|
||||
#[cfg(feature = "current-thread")]
|
||||
pub mod current_thread;
|
||||
|
||||
#[cfg(feature = "threadpool")]
|
||||
pub mod threadpool;
|
||||
|
||||
pub use crate::enter::{enter, exit, Enter, EnterError};
|
||||
pub use crate::error::SpawnError;
|
||||
pub use crate::executor::Executor;
|
||||
pub use crate::global::{spawn, with_default, DefaultExecutor};
|
||||
pub use crate::typed::TypedExecutor;
|
||||
pub use futures_util::future::RemoteHandle;
|
||||
@@ -1,391 +0,0 @@
|
||||
//! Abstraction over blocking and unblocking the current thread.
|
||||
//!
|
||||
//! Provides an abstraction over blocking the current thread. This is similar to
|
||||
//! the park / unpark constructs provided by [`std`] but made generic. This
|
||||
//! allows embedding custom functionality to perform when the thread is blocked.
|
||||
//!
|
||||
//! A blocked [`Park`][p] instance is unblocked by calling [`unpark`] on its
|
||||
//! [`Unpark`][up] handle.
|
||||
//!
|
||||
//! The [`ParkThread`] struct implements [`Park`][p] using
|
||||
//! [`thread::park`][`std`] to put the thread to sleep. The Tokio reactor also
|
||||
//! implements park, but uses [`mio::Poll`][mio] to block the thread instead.
|
||||
//!
|
||||
//! The [`Park`][p] trait is composable. A timer implementation might decorate a
|
||||
//! [`Park`][p] implementation by checking if any timeouts have elapsed after
|
||||
//! the inner [`Park`][p] implementation unblocks.
|
||||
//!
|
||||
//! # Model
|
||||
//!
|
||||
//! Conceptually, each [`Park`][p] instance has an associated token, which is
|
||||
//! initially not present:
|
||||
//!
|
||||
//! * The [`park`] method blocks the current thread unless or until the token
|
||||
//! is available, at which point it atomically consumes the token.
|
||||
//! * The [`unpark`] method atomically makes the token available if it wasn't
|
||||
//! already.
|
||||
//!
|
||||
//! Some things to note:
|
||||
//!
|
||||
//! * If [`unpark`] is called before [`park`], the next call to [`park`] will
|
||||
//! **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
|
||||
//! time to block the thread for.
|
||||
//!
|
||||
//! [`std`]: https://doc.rust-lang.org/std/thread/fn.park.html
|
||||
//! [`thread::park`]: https://doc.rust-lang.org/std/thread/fn.park.html
|
||||
//! [`ParkThread`]: struct.ParkThread.html
|
||||
//! [p]: trait.Park.html
|
||||
//! [`park`]: trait.Park.html#tymethod.park
|
||||
//! [`park_timeout`]: trait.Park.html#tymethod.park_timeout
|
||||
//! [`unpark`]: trait.Unpark.html#tymethod.unpark
|
||||
//! [up]: trait.Unpark.html
|
||||
//! [mio]: https://docs.rs/mio/0.6/mio/struct.Poll.html
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::task::{RawWaker, RawWakerVTable, Waker};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Block the current thread.
|
||||
///
|
||||
/// See [module documentation][mod] for more details.
|
||||
///
|
||||
/// [mod]: ../index.html
|
||||
pub trait Park {
|
||||
/// Unpark handle type for the `Park` implementation.
|
||||
type Unpark: Unpark;
|
||||
|
||||
/// Error returned by `park`
|
||||
type Error;
|
||||
|
||||
/// Get a new `Unpark` handle associated with this `Park` instance.
|
||||
fn unpark(&self) -> Self::Unpark;
|
||||
|
||||
/// Block the current thread unless or until the token is available.
|
||||
///
|
||||
/// A call to `park` does not guarantee that the thread will remain blocked
|
||||
/// forever, and callers should be prepared for this possibility. This
|
||||
/// function may wakeup spuriously for any reason.
|
||||
///
|
||||
/// See [module documentation][mod] for more details.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function **should** not panic, but ultimately, panics are left as
|
||||
/// an implementation detail. Refer to the documentation for the specific
|
||||
/// `Park` implementation
|
||||
///
|
||||
/// [mod]: ../index.html
|
||||
fn park(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Park the current thread for at most `duration`.
|
||||
///
|
||||
/// This function is the same as `park` but allows specifying a maximum time
|
||||
/// to block the thread for.
|
||||
///
|
||||
/// Same as `park`, there is no guarantee that the thread will remain
|
||||
/// blocked for any amount of time. Spurious wakeups are permitted for any
|
||||
/// reason.
|
||||
///
|
||||
/// See [module documentation][mod] for more details.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function **should** not panic, but ultimately, panics are left as
|
||||
/// an implementation detail. Refer to the documentation for the specific
|
||||
/// `Park` implementation
|
||||
///
|
||||
/// [mod]: ../index.html
|
||||
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Unblock a thread blocked by the associated [`Park`] instance.
|
||||
///
|
||||
/// See [module documentation][mod] for more details.
|
||||
///
|
||||
/// [mod]: ../index.html
|
||||
/// [`Park`]: trait.Park.html
|
||||
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
|
||||
/// not already available.
|
||||
///
|
||||
/// See [module documentation][mod] for more details.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function **should** not panic, but ultimately, panics are left as
|
||||
/// an implementation detail. Refer to the documentation for the specific
|
||||
/// `Unpark` implementation
|
||||
///
|
||||
/// [mod]: ../index.html
|
||||
fn unpark(&self);
|
||||
}
|
||||
|
||||
impl Unpark for Box<dyn Unpark> {
|
||||
fn unpark(&self) {
|
||||
(**self).unpark()
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpark for Arc<dyn Unpark> {
|
||||
fn unpark(&self) {
|
||||
(**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: (),
|
||||
}
|
||||
|
||||
struct Parker {
|
||||
unparker: Arc<Inner>,
|
||||
}
|
||||
|
||||
/// Unblocks a thread that was blocked by `ParkThread`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UnparkThread {
|
||||
inner: Arc<Inner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
state: AtomicUsize,
|
||||
mutex: Mutex<()>,
|
||||
condvar: Condvar,
|
||||
}
|
||||
|
||||
const IDLE: usize = 0;
|
||||
const NOTIFY: usize = 1;
|
||||
const SLEEP: usize = 2;
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_PARKER: Parker = Parker::new();
|
||||
}
|
||||
|
||||
// ==== impl Parker ====
|
||||
|
||||
impl Parker {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
unparker: Arc::new(Inner {
|
||||
state: AtomicUsize::new(IDLE),
|
||||
mutex: Mutex::new(()),
|
||||
condvar: Condvar::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unparker(&self) -> &Arc<Inner> {
|
||||
&self.unparker
|
||||
}
|
||||
|
||||
pub(crate) fn park(&self) -> Result<(), ParkError> {
|
||||
self.unparker.park(None)
|
||||
}
|
||||
|
||||
pub(crate) fn park_timeout(&self, timeout: Duration) -> Result<(), ParkError> {
|
||||
self.unparker.park(Some(timeout))
|
||||
}
|
||||
}
|
||||
|
||||
// ==== impl Inner ====
|
||||
|
||||
impl Inner {
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub(crate) fn into_raw(this: Arc<Inner>) -> *const () {
|
||||
Arc::into_raw(this) as *const ()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn from_raw(ptr: *const ()) -> Arc<Inner> {
|
||||
Arc::from_raw(ptr as *const Inner)
|
||||
}
|
||||
|
||||
/// Park the current thread for at most `dur`.
|
||||
pub(crate) fn park(&self, timeout: Option<Duration>) -> Result<(), ParkError> {
|
||||
// If currently notified, then we skip sleeping. This is checked outside
|
||||
// of the lock to avoid acquiring a mutex if not necessary.
|
||||
match self.state.compare_and_swap(NOTIFY, IDLE, Ordering::SeqCst) {
|
||||
NOTIFY => return Ok(()),
|
||||
IDLE => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// The state is currently idle, so obtain the lock and then try to
|
||||
// transition to a sleeping state.
|
||||
let mut m = self.mutex.lock().unwrap();
|
||||
|
||||
// Transition to sleeping
|
||||
match self.state.compare_and_swap(IDLE, SLEEP, Ordering::SeqCst) {
|
||||
NOTIFY => {
|
||||
// Notified before we could sleep, consume the notification and
|
||||
// exit
|
||||
self.state.store(IDLE, Ordering::SeqCst);
|
||||
return Ok(());
|
||||
}
|
||||
IDLE => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
m = match timeout {
|
||||
Some(timeout) => self.condvar.wait_timeout(m, timeout).unwrap().0,
|
||||
None => self.condvar.wait(m).unwrap(),
|
||||
};
|
||||
|
||||
// Transition back to idle. If the state has transitioned to `NOTIFY`,
|
||||
// this will consume that notification
|
||||
self.state.store(IDLE, Ordering::SeqCst);
|
||||
|
||||
// Explicitly drop the mutex guard. There is no real point in doing it
|
||||
// except that I find it helpful to make it explicit where we want the
|
||||
// mutex to unlock.
|
||||
drop(m);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn unpark(&self) {
|
||||
// First, try transitioning from IDLE -> NOTIFY, this does not require a
|
||||
// lock.
|
||||
match self.state.compare_and_swap(IDLE, NOTIFY, Ordering::SeqCst) {
|
||||
IDLE | NOTIFY => return,
|
||||
SLEEP => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// The other half is sleeping, this requires a lock
|
||||
let _m = self.mutex.lock().unwrap();
|
||||
|
||||
// Transition to NOTIFY
|
||||
match self.state.swap(NOTIFY, Ordering::SeqCst) {
|
||||
SLEEP => {}
|
||||
NOTIFY => return,
|
||||
IDLE => return,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// Wakeup the sleeper
|
||||
self.condvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 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 Default for ParkThread {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl UnparkThread =====
|
||||
|
||||
impl Unpark for UnparkThread {
|
||||
fn unpark(&self) {
|
||||
self.inner.unpark();
|
||||
}
|
||||
}
|
||||
|
||||
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
|
||||
|
||||
impl UnparkThread {
|
||||
pub(crate) fn into_waker(self) -> Waker {
|
||||
unsafe {
|
||||
let raw = unparker_to_raw_waker(self.inner);
|
||||
Waker::from_raw(raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn unparker_to_raw_waker(unparker: Arc<Inner>) -> RawWaker {
|
||||
RawWaker::new(Inner::into_raw(unparker), &VTABLE)
|
||||
}
|
||||
|
||||
unsafe fn clone(raw: *const ()) -> RawWaker {
|
||||
let unparker = Inner::from_raw(raw);
|
||||
|
||||
// Increment the ref count
|
||||
mem::forget(unparker.clone());
|
||||
|
||||
unparker_to_raw_waker(unparker)
|
||||
}
|
||||
|
||||
unsafe fn wake(raw: *const ()) {
|
||||
let unparker = Inner::from_raw(raw);
|
||||
unparker.unpark();
|
||||
}
|
||||
|
||||
unsafe fn wake_by_ref(raw: *const ()) {
|
||||
let unparker = Inner::from_raw(raw);
|
||||
unparker.unpark();
|
||||
|
||||
// We don't actually own a reference to the unparker
|
||||
mem::forget(unparker);
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
use super::worker::Worker;
|
||||
|
||||
use futures_core::ready;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::task::Poll;
|
||||
|
||||
/// Error raised by `blocking`.
|
||||
pub struct BlockingError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
/// Enter a blocking section of code.
|
||||
///
|
||||
/// The `blocking` function annotates a section of code that performs a blocking
|
||||
/// operation, either by issuing a blocking syscall or by performing a long
|
||||
/// running CPU-bound computation.
|
||||
///
|
||||
/// When the `blocking` function enters, it hands off the responsibility of
|
||||
/// processing the current work queue to another thread. Then, it calls the
|
||||
/// supplied closure. The closure is permitted to block indefinitely.
|
||||
///
|
||||
/// If the maximum number of concurrent `blocking` calls has been reached, then
|
||||
/// `NotReady` is returned and the task is notified once existing `blocking`
|
||||
/// calls complete. The maximum value is specified when creating a thread pool
|
||||
/// using [`Builder::max_blocking`][build]
|
||||
///
|
||||
/// NB: The entire task that called `blocking` is blocked whenever the supplied
|
||||
/// closure blocks, even if you have used future combinators such as `select` -
|
||||
/// the other futures in this task will not make progress until the closure
|
||||
/// returns.
|
||||
/// If this is not desired, ensure that `blocking` runs in its own task (e.g.
|
||||
/// using `futures::sync::oneshot::spawn`).
|
||||
///
|
||||
/// [build]: struct.Builder.html#method.max_blocking
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// When the blocking closure is executed, `Ok(Ready(T))` is returned, where
|
||||
/// `T` is the closure's return value.
|
||||
///
|
||||
/// If the thread pool has shutdown, `Err` is returned.
|
||||
///
|
||||
/// If the number of concurrent `blocking` calls has reached the maximum,
|
||||
/// `Ok(NotReady)` is returned and the current task is notified when a call to
|
||||
/// `blocking` will succeed.
|
||||
///
|
||||
/// If `blocking` is called from outside the context of a Tokio thread pool,
|
||||
/// `Err` is returned.
|
||||
///
|
||||
/// # Background
|
||||
///
|
||||
/// By default, the Tokio thread pool expects that tasks will only run for short
|
||||
/// periods at a time before yielding back to the thread pool. This is the basic
|
||||
/// premise of cooperative multitasking.
|
||||
///
|
||||
/// However, it is common to want to perform a blocking operation while
|
||||
/// processing an asynchronous computation. Examples of blocking operation
|
||||
/// include:
|
||||
///
|
||||
/// * Performing synchronous file operations (reading and writing).
|
||||
/// * Blocking on acquiring a mutex.
|
||||
/// * Performing a CPU bound computation, like cryptographic encryption or
|
||||
/// decryption.
|
||||
///
|
||||
/// One option for dealing with blocking operations in an asynchronous context
|
||||
/// is to use a thread pool dedicated to performing these operations. This not
|
||||
/// ideal as it requires bidirectional message passing as well as a channel to
|
||||
/// communicate which adds a level of buffering.
|
||||
///
|
||||
/// Instead, `blocking` hands off the responsibility of processing the work queue
|
||||
/// to another thread. This hand off is light compared to a channel and does not
|
||||
/// require buffering.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Block on receiving a message from a `std` channel. This example is a little
|
||||
/// silly as using the non-blocking channel from the `futures` crate would make
|
||||
/// more sense. The blocking receive can be replaced with any blocking operation
|
||||
/// that needs to be performed.
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio_executor::threadpool::{ThreadPool, blocking};
|
||||
///
|
||||
/// use futures_util::future::poll_fn;
|
||||
/// use std::sync::mpsc;
|
||||
/// use std::thread;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// pub fn main() {
|
||||
/// // This is a *blocking* channel
|
||||
/// let (tx, rx) = mpsc::channel();
|
||||
///
|
||||
/// // Spawn a thread to send a message
|
||||
/// thread::spawn(move || {
|
||||
/// thread::sleep(Duration::from_millis(500));
|
||||
/// tx.send("hello").unwrap();
|
||||
/// });
|
||||
///
|
||||
/// let pool = ThreadPool::new();
|
||||
///
|
||||
/// pool.spawn(async move {
|
||||
/// // Because `blocking` returns `Poll`, it is intended to be used
|
||||
/// // from the context of a `Future` implementation. Since we don't
|
||||
/// // have a complicated requirement, we can use `poll_fn` in this
|
||||
/// // case.
|
||||
/// let _ = poll_fn(move |_| {
|
||||
/// blocking(|| {
|
||||
/// let msg = rx.recv().unwrap();
|
||||
/// println!("message = {}", msg);
|
||||
/// }).map_err(|_| panic!("the threadpool shut down"))
|
||||
/// }).await;
|
||||
/// });
|
||||
///
|
||||
/// // Wait for the task we just spawned to complete.
|
||||
/// pool.shutdown_on_idle().wait();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn blocking<F, T>(f: F) -> Poll<Result<T, BlockingError>>
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
{
|
||||
let res = Worker::with_current(|worker| {
|
||||
let worker = match worker {
|
||||
Some(worker) => worker,
|
||||
None => {
|
||||
return Poll::Ready(Err(BlockingError { _p: () }));
|
||||
}
|
||||
};
|
||||
|
||||
// Transition the worker state to blocking. This will exit the fn early
|
||||
// with `NotReady` if the pool does not have enough capacity to enter
|
||||
// blocking mode.
|
||||
worker.transition_to_blocking()
|
||||
});
|
||||
|
||||
// If the transition cannot happen, exit early
|
||||
ready!(res)?;
|
||||
|
||||
// Currently in blocking mode, so call the inner closure
|
||||
//
|
||||
// "Exit" the current executor in case the blocking function wants
|
||||
// to call a different executor.
|
||||
let ret = crate::exit(move || f());
|
||||
|
||||
// Try to transition out of blocking mode. This is a fast path that takes
|
||||
// back ownership of the worker if the worker handoff didn't complete yet.
|
||||
Worker::with_current(|worker| {
|
||||
// Worker must be set since it was above.
|
||||
worker.unwrap().transition_from_blocking();
|
||||
});
|
||||
|
||||
// Return the result
|
||||
Poll::Ready(Ok(ret))
|
||||
}
|
||||
|
||||
impl fmt::Display for BlockingError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
fmt,
|
||||
"`blocking` annotation used from outside the context of a thread pool"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BlockingError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BlockingError")
|
||||
.field("reason", &format!("{}", self))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BlockingError {}
|
||||
@@ -1,426 +0,0 @@
|
||||
use super::callback::Callback;
|
||||
use super::config::{Config, MAX_WORKERS};
|
||||
use super::park::{BoxPark, BoxedPark, DefaultPark};
|
||||
use super::pool::{Pool, MAX_BACKUP};
|
||||
use super::shutdown::ShutdownTrigger;
|
||||
use super::thread_pool::ThreadPool;
|
||||
use super::worker::{self, Worker, WorkerId};
|
||||
use crate::park::Park;
|
||||
|
||||
use crossbeam_deque::Injector;
|
||||
use num_cpus;
|
||||
use std::any::Any;
|
||||
use std::cmp::max;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Builds a thread pool with custom configuration values.
|
||||
///
|
||||
/// Methods can be chained in order to set the configuration values. The thread
|
||||
/// pool is constructed by calling [`build`].
|
||||
///
|
||||
/// New instances of `Builder` are obtained via [`Builder::new`].
|
||||
///
|
||||
/// See function level documentation for details on the various configuration
|
||||
/// settings.
|
||||
///
|
||||
/// [`build`]: #method.build
|
||||
/// [`Builder::new`]: #method.new
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .pool_size(4)
|
||||
/// .keep_alive(Some(Duration::from_secs(30)))
|
||||
/// .build();
|
||||
///
|
||||
/// thread_pool.spawn(async {
|
||||
/// println!("called from a worker thread");
|
||||
/// });
|
||||
///
|
||||
/// // Gracefully shutdown the threadpool
|
||||
/// thread_pool.shutdown().wait();
|
||||
/// ```
|
||||
pub struct Builder {
|
||||
/// Thread pool specific configuration values
|
||||
config: Config,
|
||||
|
||||
/// Number of workers to spawn
|
||||
pool_size: usize,
|
||||
|
||||
/// Maximum number of futures that can be in a blocking section
|
||||
/// concurrently.
|
||||
max_blocking: usize,
|
||||
|
||||
/// Generates the `Park` instances
|
||||
new_park: Box<dyn Fn(&WorkerId) -> BoxPark>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Returns a new thread pool builder initialized with default configuration
|
||||
/// values.
|
||||
///
|
||||
/// Configuration methods can be chained on the return value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .pool_size(4)
|
||||
/// .keep_alive(Some(Duration::from_secs(30)))
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn new() -> Builder {
|
||||
let num_cpus = max(1, num_cpus::get());
|
||||
|
||||
let new_park =
|
||||
Box::new(|_: &WorkerId| Box::new(BoxedPark::new(DefaultPark::new())) as BoxPark);
|
||||
|
||||
Builder {
|
||||
pool_size: num_cpus,
|
||||
max_blocking: 100,
|
||||
config: Config {
|
||||
keep_alive: None,
|
||||
name_prefix: None,
|
||||
stack_size: None,
|
||||
around_worker: None,
|
||||
after_start: None,
|
||||
before_stop: None,
|
||||
panic_handler: None,
|
||||
},
|
||||
new_park,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the maximum number of worker threads for the thread pool instance.
|
||||
///
|
||||
/// This must be a number between 1 and 32,768 though it is advised to keep
|
||||
/// this value on the smaller side.
|
||||
///
|
||||
/// The default value is the number of cores available to the system.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .pool_size(4)
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn pool_size(&mut self, val: usize) -> &mut Self {
|
||||
assert!(val >= 1, "at least one thread required");
|
||||
assert!(val <= MAX_WORKERS, "max value is {}", MAX_WORKERS);
|
||||
|
||||
self.pool_size = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum number of concurrent blocking sections.
|
||||
///
|
||||
/// When the maximum concurrent `blocking` calls is reached, any further
|
||||
/// calls to `blocking` will return `NotReady` and the task is notified once
|
||||
/// previously in-flight calls to `blocking` return.
|
||||
///
|
||||
/// This must be a number between 1 and 32,768 though it is advised to keep
|
||||
/// this value on the smaller side.
|
||||
///
|
||||
/// The default value is 100.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .max_blocking(200)
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn max_blocking(&mut self, val: usize) -> &mut Self {
|
||||
assert!(val <= MAX_BACKUP, "max value is {}", MAX_BACKUP);
|
||||
self.max_blocking = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the thread keep alive duration
|
||||
///
|
||||
/// If set, a thread that has completed a `blocking` call will wait for up
|
||||
/// to the specified duration to become a worker thread again. Once the
|
||||
/// duration elapses, the thread will shutdown.
|
||||
///
|
||||
/// When the value is `None`, the thread will wait to become a worker
|
||||
/// thread forever.
|
||||
///
|
||||
/// The default value is `None`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .keep_alive(Some(Duration::from_secs(30)))
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn keep_alive(&mut self, val: Option<Duration>) -> &mut Self {
|
||||
self.config.keep_alive = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a callback to be triggered when a panic during a future bubbles up
|
||||
/// to Tokio. By default Tokio catches these panics, and they will be
|
||||
/// ignored. The parameter passed to this callback is the same error value
|
||||
/// returned from std::panic::catch_unwind(). To abort the process on
|
||||
/// panics, use std::panic::resume_unwind() in this callback as shown
|
||||
/// below.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .panic_handler(|err| std::panic::resume_unwind(err))
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn panic_handler<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(Box<dyn Any + Send>) + Send + Sync + 'static,
|
||||
{
|
||||
self.config.panic_handler = Some(Arc::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set name prefix of threads spawned by the scheduler
|
||||
///
|
||||
/// Thread name prefix is used for generating thread names. For example, if
|
||||
/// prefix is `my-pool-`, then threads in the pool will get names like
|
||||
/// `my-pool-1` etc.
|
||||
///
|
||||
/// If this configuration is not set, then the thread will use the system
|
||||
/// default naming scheme.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .name_prefix("my-pool-")
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn name_prefix<S: Into<String>>(&mut self, val: S) -> &mut Self {
|
||||
self.config.name_prefix = Some(val.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the stack size (in bytes) for worker threads.
|
||||
///
|
||||
/// The actual stack size may be greater than this value if the platform
|
||||
/// specifies minimal stack size.
|
||||
///
|
||||
/// The default stack size for spawned threads is 2 MiB, though this
|
||||
/// particular stack size is subject to change in the future.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .stack_size(32 * 1024)
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn stack_size(&mut self, val: usize) -> &mut Self {
|
||||
self.config.stack_size = Some(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute function `f` on each worker thread.
|
||||
///
|
||||
/// This function is provided a handle to the worker and is expected to call
|
||||
/// [`Worker::run`], otherwise the worker thread will shutdown without doing
|
||||
/// any work.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .around_worker(|worker| {
|
||||
/// println!("worker is starting up");
|
||||
/// worker.run();
|
||||
/// println!("worker is shutting down");
|
||||
/// })
|
||||
/// .build();
|
||||
/// ```
|
||||
///
|
||||
/// [`Worker::run`]: struct.Worker.html#method.run
|
||||
pub fn around_worker<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&Worker) + Send + Sync + 'static,
|
||||
{
|
||||
self.config.around_worker = Some(Callback::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute function `f` after each thread is started but before it starts
|
||||
/// doing work.
|
||||
///
|
||||
/// This is intended for bookkeeping and monitoring use cases.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .after_start(|| {
|
||||
/// println!("thread started");
|
||||
/// })
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn after_start<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn() + Send + Sync + 'static,
|
||||
{
|
||||
self.config.after_start = Some(Arc::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute function `f` before each thread stops.
|
||||
///
|
||||
/// This is intended for bookkeeping and monitoring use cases.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .before_stop(|| {
|
||||
/// println!("thread stopping");
|
||||
/// })
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn before_stop<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn() + Send + Sync + 'static,
|
||||
{
|
||||
self.config.before_stop = Some(Arc::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Customize the `park` instance used by each worker thread.
|
||||
///
|
||||
/// The provided closure `f` is called once per worker and returns a `Park`
|
||||
/// instance that is used by the worker to put itself to sleep.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
/// use tokio_executor::threadpool::park::DefaultPark;
|
||||
/// # fn decorate<F>(f: F) -> F { f }
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .custom_park(|_| {
|
||||
/// // This is the default park type that the worker would use if we
|
||||
/// // did not customize it.
|
||||
/// let park = DefaultPark::new();
|
||||
///
|
||||
/// // Decorate the `park` instance, allowing us to customize work
|
||||
/// // that happens when a worker thread goes to sleep.
|
||||
/// decorate(park)
|
||||
/// })
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn custom_park<F, P>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&WorkerId) -> P + 'static,
|
||||
P: Park + Send + 'static,
|
||||
P::Error: Error,
|
||||
{
|
||||
self.new_park = Box::new(move |id| Box::new(BoxedPark::new(f(id))));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Create the configured `ThreadPool`.
|
||||
///
|
||||
/// The returned `ThreadPool` instance is ready to spawn tasks.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_executor::threadpool::Builder;
|
||||
///
|
||||
/// let thread_pool = Builder::new()
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn build(&self) -> ThreadPool {
|
||||
trace!(message = "build;", num_workers = self.pool_size);
|
||||
|
||||
// Create the worker entry list
|
||||
let workers: Arc<[worker::Entry]> = {
|
||||
let mut workers = vec![];
|
||||
|
||||
for i in 0..self.pool_size {
|
||||
let id = WorkerId::new(i);
|
||||
let park = (self.new_park)(&id);
|
||||
let unpark = park.unpark();
|
||||
|
||||
workers.push(worker::Entry::new(park, unpark));
|
||||
}
|
||||
|
||||
workers.into()
|
||||
};
|
||||
|
||||
let queue = Arc::new(Injector::new());
|
||||
|
||||
// Create a trigger that will clean up resources on shutdown.
|
||||
//
|
||||
// The `Pool` contains a weak reference to it, while `Worker`s and the `ThreadPool` contain
|
||||
// strong references.
|
||||
let trigger = Arc::new(ShutdownTrigger::new(workers.clone(), queue.clone()));
|
||||
|
||||
// Create the pool
|
||||
let pool = Arc::new(Pool::new(
|
||||
workers,
|
||||
Arc::downgrade(&trigger),
|
||||
self.max_blocking,
|
||||
self.config.clone(),
|
||||
queue,
|
||||
));
|
||||
|
||||
ThreadPool::new2(pool, trigger)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Builder {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Builder")
|
||||
.field("config", &self.config)
|
||||
.field("pool_size", &self.pool_size)
|
||||
.field("new_park", &"Box<Fn() -> BoxPark>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Builder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use super::worker::Worker;
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Callback {
|
||||
f: Arc<dyn Fn(&Worker) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Callback {
|
||||
pub(crate) fn new<F>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&Worker) + Send + Sync + 'static,
|
||||
{
|
||||
Callback { f: Arc::new(f) }
|
||||
}
|
||||
|
||||
pub(crate) fn call(&self, worker: &Worker) {
|
||||
(self.f)(worker)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Callback {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(fmt, "Fn")
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
use super::callback::Callback;
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Thread pool specific configuration values
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Config {
|
||||
pub(crate) keep_alive: Option<Duration>,
|
||||
// Used to configure a worker thread
|
||||
pub(crate) name_prefix: Option<String>,
|
||||
pub(crate) stack_size: Option<usize>,
|
||||
pub(crate) around_worker: Option<Callback>,
|
||||
pub(crate) after_start: Option<Arc<dyn Fn() + Send + Sync>>,
|
||||
pub(crate) before_stop: Option<Arc<dyn Fn() + Send + Sync>>,
|
||||
pub(crate) panic_handler: Option<PanicHandler>,
|
||||
}
|
||||
|
||||
// Define type alias to avoid clippy::type_complexity.
|
||||
type PanicHandler = Arc<dyn Fn(Box<dyn Any + Send>) + Send + Sync>;
|
||||
|
||||
/// Max number of workers that can be part of a pool. This is the most that can
|
||||
/// fit in the scheduler state. Note, that this is the max number of **active**
|
||||
/// threads. There can be more standby threads.
|
||||
pub(crate) const MAX_WORKERS: usize = 1 << 15;
|
||||
|
||||
impl fmt::Debug for Config {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Config")
|
||||
.field("keep_alive", &self.keep_alive)
|
||||
.field("name_prefix", &self.name_prefix)
|
||||
.field("stack_size", &self.stack_size)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
//! A work-stealing based thread pool for executing futures.
|
||||
//!
|
||||
//! The Tokio thread pool supports scheduling futures and processing them on
|
||||
//! multiple CPU cores. It is optimized for the primary Tokio use case of many
|
||||
//! independent tasks with limited computation and with most tasks waiting on
|
||||
//! I/O. Usually, users will not create a `ThreadPool` instance directly, but
|
||||
//! will use one via a [`runtime`].
|
||||
//!
|
||||
//! The `ThreadPool` structure manages two sets of threads:
|
||||
//!
|
||||
//! * Worker threads.
|
||||
//! * Backup threads.
|
||||
//!
|
||||
//! Worker threads are used to schedule futures using a work-stealing strategy.
|
||||
//! Backup threads, on the other hand, are intended only to support the
|
||||
//! `blocking` API. Threads will transition between the two sets.
|
||||
//!
|
||||
//! The advantage of the work-stealing strategy is minimal cross-thread
|
||||
//! coordination. The thread pool attempts to make as much progress as possible
|
||||
//! without communicating across threads.
|
||||
//!
|
||||
//! ## Worker overview
|
||||
//!
|
||||
//! Each worker has two queues: a deque and a mpsc channel. The deque is the
|
||||
//! primary queue for tasks that are scheduled to run on the worker thread. Tasks
|
||||
//! can only be pushed onto the deque by the worker, but other workers may
|
||||
//! "steal" from that deque. The mpsc channel is used to submit futures while
|
||||
//! external to the pool.
|
||||
//!
|
||||
//! As long as the thread pool has not been shutdown, a worker will run in a
|
||||
//! loop. Each loop, it consumes all tasks on its mpsc channel and pushes it onto
|
||||
//! the deque. It then pops tasks off of the deque and executes them.
|
||||
//!
|
||||
//! If a worker has no work, i.e., both queues are empty. It attempts to steal.
|
||||
//! To do this, it randomly scans other workers' deques and tries to pop a task.
|
||||
//! If it finds no work to steal, the thread goes to sleep.
|
||||
//!
|
||||
//! When the worker detects that the pool has been shut down, it exits the loop,
|
||||
//! cleans up its state, and shuts the thread down.
|
||||
//!
|
||||
//! ## Thread pool initialization
|
||||
//!
|
||||
//! Note, users normally will use the threadpool created by a [`runtime`].
|
||||
//!
|
||||
//! By default, no threads are spawned on creation. Instead, when new futures are
|
||||
//! spawned, the pool first checks if there are enough active worker threads. If
|
||||
//! not, a new worker thread is spawned.
|
||||
//!
|
||||
//! ## Spawning futures
|
||||
//!
|
||||
//! The spawning behavior depends on whether a future was spawned from within a
|
||||
//! worker or thread or if it was spawned from an external handle.
|
||||
//!
|
||||
//! When spawning a future while external to the thread pool, the current
|
||||
//! strategy is to randomly pick a worker to submit the task to. The task is then
|
||||
//! pushed onto that worker's mpsc channel.
|
||||
//!
|
||||
//! When spawning a future while on a worker thread, the task is pushed onto the
|
||||
//! back of the current worker's deque.
|
||||
//!
|
||||
//! ## Blocking annotation strategy
|
||||
//!
|
||||
//! The [`blocking`] function is used to annotate a section of code that
|
||||
//! performs a blocking operation, either by issuing a blocking syscall or
|
||||
//! performing any long running CPU-bound computation.
|
||||
//!
|
||||
//! The strategy for handling blocking closures is to hand off the worker to a
|
||||
//! new thread. This implies handing off the `deque` and `mpsc`. Once this is
|
||||
//! done, the new thread continues to process the work queue and the original
|
||||
//! thread is able to block. Once it finishes processing the blocking future, the
|
||||
//! thread has no additional work and is inserted into the backup pool. This
|
||||
//! makes it available to other workers that encounter a [`blocking`] call.
|
||||
//!
|
||||
//! [`blocking`]: fn.blocking.html
|
||||
//! [`runtime`]: https://docs.rs/tokio/0.1/tokio/runtime/
|
||||
|
||||
// ## Crate layout
|
||||
//
|
||||
// The primary type, `Pool`, holds the majority of a thread pool's state,
|
||||
// including the state for each worker. Each worker's state is maintained in an
|
||||
// instance of `worker::Entry`.
|
||||
//
|
||||
// `Worker` contains the logic that runs on each worker thread. It holds an
|
||||
// `Arc` to `Pool` and is able to access its state from `Pool`.
|
||||
//
|
||||
// `Task` is a harness around an individual future. It manages polling and
|
||||
// scheduling that future.
|
||||
//
|
||||
// ## Sleeping workers
|
||||
//
|
||||
// Sleeping workers are tracked using a [Treiber stack]. This results in the
|
||||
// thread that most recently went to sleep getting woken up first. When the pool
|
||||
// is not under load, this helps threads shutdown faster.
|
||||
//
|
||||
// Sleeping is done by using `tokio_executor::Park` implementations. This allows
|
||||
// the user of the thread pool to customize the work that is performed to sleep.
|
||||
// This is how injecting timers and other functionality into the thread pool is
|
||||
// done.
|
||||
//
|
||||
// ## Notifying workers
|
||||
//
|
||||
// When there is work to be done, workers must be notified. However, notifying a
|
||||
// worker requires cross thread coordination. Ideally, a worker would only be
|
||||
// notified when it is sleeping, but there is no way to know if a worker is
|
||||
// sleeping without cross thread communication.
|
||||
//
|
||||
// The two cases when a worker might need to be notified are:
|
||||
//
|
||||
// 1. A task is externally submitted to a worker via the mpsc channel.
|
||||
// 2. A worker has a back log of work and needs other workers to steal from it.
|
||||
//
|
||||
// In the first case, the worker will always be notified. However, it could be
|
||||
// possible to avoid the notification if the mpsc channel has two or greater
|
||||
// number of tasks *after* the task is submitted. In this case, we are able to
|
||||
// assume that the worker has previously been notified.
|
||||
//
|
||||
// The second case is trickier. Currently, whenever a worker spawns a new future
|
||||
// (pushing it onto its deque) and when it pops a future from its mpsc, it tries
|
||||
// to notify a sleeping worker to wake up and start stealing. This is a lot of
|
||||
// notification and it **might** be possible to reduce it.
|
||||
//
|
||||
// Also, whenever a worker is woken up via a signal and it does find work, it,
|
||||
// in turn, will try to wake up a new worker.
|
||||
//
|
||||
// [Treiber stack]: https://en.wikipedia.org/wiki/Treiber_Stack
|
||||
|
||||
pub mod park;
|
||||
|
||||
mod blocking;
|
||||
mod builder;
|
||||
mod callback;
|
||||
mod config;
|
||||
mod pool;
|
||||
mod sender;
|
||||
mod shutdown;
|
||||
mod task;
|
||||
mod thread_pool;
|
||||
mod waker;
|
||||
mod worker;
|
||||
|
||||
pub use self::blocking::{blocking, BlockingError};
|
||||
pub use self::builder::Builder;
|
||||
pub use self::sender::Sender;
|
||||
pub use self::shutdown::Shutdown;
|
||||
pub use self::thread_pool::ThreadPool;
|
||||
pub use self::worker::{Worker, WorkerId};
|
||||
@@ -1,46 +0,0 @@
|
||||
use crate::park::{Park, Unpark};
|
||||
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) type BoxPark = Box<dyn Park<Unpark = BoxUnpark, Error = ()> + Send>;
|
||||
pub(crate) type BoxUnpark = Box<dyn Unpark>;
|
||||
|
||||
pub(crate) struct BoxedPark<T>(T);
|
||||
|
||||
impl<T> BoxedPark<T> {
|
||||
pub(crate) fn new(inner: T) -> Self {
|
||||
BoxedPark(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Park + Send> Park for BoxedPark<T>
|
||||
where
|
||||
T::Error: Error,
|
||||
{
|
||||
type Unpark = BoxUnpark;
|
||||
type Error = ();
|
||||
|
||||
fn unpark(&self) -> Self::Unpark {
|
||||
Box::new(self.0.unpark())
|
||||
}
|
||||
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
self.0.park().map_err(|_e| {
|
||||
// if tracing is disabled, the compiler will flag this as unused.
|
||||
warn!(
|
||||
message = "calling `park` on worker thread errored -- shutting down thread",
|
||||
error = %_e
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
|
||||
self.0.park_timeout(duration).map_err(|_e| {
|
||||
warn!(
|
||||
message = "calling `park` on worker thread errored -- shutting down thread",
|
||||
error = %_e,
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
use crate::park::{Park, Unpark};
|
||||
|
||||
use crossbeam_utils::sync::{Parker, Unparker};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Parks the thread.
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultPark {
|
||||
inner: Parker,
|
||||
}
|
||||
|
||||
/// Unparks threads that were parked by `DefaultPark`.
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultUnpark {
|
||||
inner: Unparker,
|
||||
}
|
||||
|
||||
/// 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: (),
|
||||
}
|
||||
|
||||
// ===== impl DefaultPark =====
|
||||
|
||||
impl DefaultPark {
|
||||
/// Creates a new `DefaultPark` instance.
|
||||
pub fn new() -> DefaultPark {
|
||||
DefaultPark {
|
||||
inner: Parker::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Unpark the thread without having to clone the unpark handle.
|
||||
///
|
||||
/// Named `notify` to avoid conflicting with the `unpark` fn.
|
||||
pub(crate) fn notify(&self) {
|
||||
self.inner.unparker().unpark();
|
||||
}
|
||||
|
||||
pub(crate) fn park_sync(&self, duration: Option<Duration>) {
|
||||
match duration {
|
||||
None => self.inner.park(),
|
||||
Some(duration) => self.inner.park_timeout(duration),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Park for DefaultPark {
|
||||
type Unpark = DefaultUnpark;
|
||||
type Error = ParkError;
|
||||
|
||||
fn unpark(&self) -> Self::Unpark {
|
||||
DefaultUnpark {
|
||||
inner: self.inner.unparker().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
self.inner.park();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
|
||||
self.inner.park_timeout(duration);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DefaultPark {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl DefaultUnpark =====
|
||||
|
||||
impl Unpark for DefaultUnpark {
|
||||
fn unpark(&self) {
|
||||
self.inner.unpark();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl ParkError =====
|
||||
|
||||
impl fmt::Display for ParkError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(fmt, "unknown park error")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParkError {}
|
||||
@@ -1,8 +0,0 @@
|
||||
//! Thread parking utilities.
|
||||
|
||||
mod boxed;
|
||||
mod default_park;
|
||||
|
||||
pub use self::default_park::{DefaultPark, DefaultUnpark, ParkError};
|
||||
|
||||
pub(crate) use self::boxed::{BoxPark, BoxUnpark, BoxedPark};
|
||||
@@ -1,308 +0,0 @@
|
||||
use super::super::park::DefaultPark;
|
||||
use super::super::worker::WorkerId;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::{self, AcqRel, Acquire, Relaxed};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// State associated with a thread in the thread pool.
|
||||
///
|
||||
/// The pool manages a number of threads. Some of those threads are considered
|
||||
/// "primary" threads and process the work queue. When a task being run on a
|
||||
/// primary thread enters a blocking context, the responsibility of processing
|
||||
/// the work queue must be handed off to another thread. This is done by first
|
||||
/// checking for idle threads on the backup stack. If one is found, the worker
|
||||
/// token (`WorkerId`) is handed off to that running thread. If none are found,
|
||||
/// a new thread is spawned.
|
||||
///
|
||||
/// This state manages the exchange. A thread that is idle, not assigned to a
|
||||
/// work queue, sits around for a specified amount of time. When the worker
|
||||
/// token is handed off, it is first stored in `handoff`. The backup thread is
|
||||
/// then signaled. At this point, the backup thread wakes up from sleep and
|
||||
/// reads `handoff`. At that point, it has been promoted to a primary thread and
|
||||
/// will begin processing inbound work on the work queue.
|
||||
///
|
||||
/// The name `Backup` isn't really great for what the type does, but I have not
|
||||
/// come up with a better name... Maybe it should just be named `Thread`.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Backup {
|
||||
/// Worker ID that is being handed to this thread.
|
||||
handoff: UnsafeCell<Option<WorkerId>>,
|
||||
|
||||
/// Thread state.
|
||||
///
|
||||
/// This tracks:
|
||||
///
|
||||
/// * Is queued flag
|
||||
/// * If the pool is shutting down.
|
||||
/// * If the thread is running
|
||||
state: AtomicUsize,
|
||||
|
||||
/// Next entry in the Treiber stack.
|
||||
next_sleeper: UnsafeCell<BackupId>,
|
||||
|
||||
/// Used to put the thread to sleep
|
||||
park: DefaultPark,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub(crate) struct BackupId(pub(crate) usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Handoff {
|
||||
Worker(WorkerId),
|
||||
Idle,
|
||||
Terminated,
|
||||
}
|
||||
|
||||
/// Tracks thread state.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
struct State(usize);
|
||||
|
||||
/// Set when the worker is pushed onto the scheduler's stack of sleeping
|
||||
/// threads.
|
||||
///
|
||||
/// This flag also serves as a "notification" bit. If another thread is
|
||||
/// attempting to hand off a worker to the backup thread, then the pushed bit
|
||||
/// will not be set when the thread tries to shutdown.
|
||||
pub(crate) const PUSHED: usize = 0b001;
|
||||
|
||||
/// Set when the thread is running
|
||||
pub(crate) const RUNNING: usize = 0b010;
|
||||
|
||||
/// Set when the thread pool has terminated
|
||||
pub(crate) const TERMINATED: usize = 0b100;
|
||||
|
||||
// ===== impl Backup =====
|
||||
|
||||
impl Backup {
|
||||
pub(crate) fn new() -> Backup {
|
||||
Backup {
|
||||
handoff: UnsafeCell::new(None),
|
||||
state: AtomicUsize::new(State::new().into()),
|
||||
next_sleeper: UnsafeCell::new(BackupId(0)),
|
||||
park: DefaultPark::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the thread is starting
|
||||
pub(crate) fn start(&self, worker_id: &WorkerId) {
|
||||
debug_assert!({
|
||||
let state: State = self.state.load(Relaxed).into();
|
||||
|
||||
debug_assert!(!state.is_pushed());
|
||||
debug_assert!(state.is_running());
|
||||
debug_assert!(!state.is_terminated());
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
// The handoff value is equal to `worker_id`
|
||||
debug_assert_eq!(unsafe { (*self.handoff.get()).as_ref() }, Some(worker_id));
|
||||
|
||||
unsafe {
|
||||
*self.handoff.get() = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_running(&self) -> bool {
|
||||
let state: State = self.state.load(Relaxed).into();
|
||||
state.is_running()
|
||||
}
|
||||
|
||||
/// Hands off the worker to a thread.
|
||||
///
|
||||
/// Returns `true` if the thread needs to be spawned.
|
||||
pub(crate) fn worker_handoff(&self, worker_id: WorkerId) -> bool {
|
||||
unsafe {
|
||||
// The backup worker should not already have been handoff a worker.
|
||||
debug_assert!((*self.handoff.get()).is_none());
|
||||
|
||||
// Set the handoff
|
||||
*self.handoff.get() = Some(worker_id);
|
||||
}
|
||||
|
||||
// This *probably* can just be `Release`... memory orderings, how do
|
||||
// they work?
|
||||
let prev = State::worker_handoff(&self.state);
|
||||
debug_assert!(prev.is_pushed());
|
||||
|
||||
if prev.is_running() {
|
||||
// Wakeup the backup thread
|
||||
self.park.notify();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Terminate the worker
|
||||
pub(crate) fn signal_stop(&self) {
|
||||
let prev: State = self.state.fetch_xor(TERMINATED | PUSHED, AcqRel).into();
|
||||
|
||||
debug_assert!(!prev.is_terminated());
|
||||
debug_assert!(prev.is_pushed());
|
||||
|
||||
if prev.is_running() {
|
||||
self.park.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/// Release the worker
|
||||
pub(crate) fn release(&self) {
|
||||
let prev: State = self.state.fetch_xor(RUNNING, AcqRel).into();
|
||||
|
||||
debug_assert!(prev.is_running());
|
||||
}
|
||||
|
||||
/// Wait for a worker handoff
|
||||
pub(crate) fn wait_for_handoff(&self, timeout: Option<Duration>) -> Handoff {
|
||||
let sleep_until = timeout.map(|dur| Instant::now() + dur);
|
||||
let mut state: State = self.state.load(Acquire).into();
|
||||
|
||||
// Run in a loop since there can be spurious wakeups
|
||||
loop {
|
||||
if !state.is_pushed() {
|
||||
if state.is_terminated() {
|
||||
return Handoff::Terminated;
|
||||
}
|
||||
|
||||
let worker_id = unsafe { (*self.handoff.get()).take().expect("no worker handoff") };
|
||||
return Handoff::Worker(worker_id);
|
||||
}
|
||||
|
||||
match sleep_until {
|
||||
None => {
|
||||
self.park.park_sync(None);
|
||||
state = self.state.load(Acquire).into();
|
||||
}
|
||||
Some(when) => {
|
||||
let now = Instant::now();
|
||||
|
||||
if now < when {
|
||||
self.park.park_sync(Some(when - now));
|
||||
state = self.state.load(Acquire).into();
|
||||
} else {
|
||||
debug_assert!(state.is_running());
|
||||
|
||||
// Transition out of running
|
||||
let mut next = state;
|
||||
next.unset_running();
|
||||
|
||||
let actual = self
|
||||
.state
|
||||
.compare_and_swap(state.into(), next.into(), AcqRel)
|
||||
.into();
|
||||
|
||||
if actual == state {
|
||||
debug_assert!(!next.is_running());
|
||||
return Handoff::Idle;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_pushed(&self) -> bool {
|
||||
let state: State = self.state.load(Relaxed).into();
|
||||
state.is_pushed()
|
||||
}
|
||||
|
||||
pub(crate) fn set_pushed(&self, ordering: Ordering) {
|
||||
let prev: State = self.state.fetch_or(PUSHED, ordering).into();
|
||||
debug_assert!(!prev.is_pushed());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn next_sleeper(&self) -> BackupId {
|
||||
unsafe { *self.next_sleeper.get() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_next_sleeper(&self, val: BackupId) {
|
||||
unsafe {
|
||||
*self.next_sleeper.get() = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl State =====
|
||||
|
||||
impl State {
|
||||
/// Returns a new, default, thread `State`
|
||||
pub(crate) fn new() -> State {
|
||||
State(0)
|
||||
}
|
||||
|
||||
/// Returns true if the thread entry is pushed in the sleeper stack
|
||||
pub(crate) fn is_pushed(self) -> bool {
|
||||
self.0 & PUSHED == PUSHED
|
||||
}
|
||||
|
||||
fn unset_pushed(&mut self) {
|
||||
self.0 &= !PUSHED;
|
||||
}
|
||||
|
||||
pub(crate) fn is_running(self) -> bool {
|
||||
self.0 & RUNNING == RUNNING
|
||||
}
|
||||
|
||||
pub(crate) fn set_running(&mut self) {
|
||||
self.0 |= RUNNING;
|
||||
}
|
||||
|
||||
pub(crate) fn unset_running(&mut self) {
|
||||
self.0 &= !RUNNING;
|
||||
}
|
||||
|
||||
pub(crate) fn is_terminated(self) -> bool {
|
||||
self.0 & TERMINATED == TERMINATED
|
||||
}
|
||||
|
||||
fn worker_handoff(state: &AtomicUsize) -> State {
|
||||
let mut curr: State = state.load(Acquire).into();
|
||||
|
||||
loop {
|
||||
let mut next = curr;
|
||||
next.set_running();
|
||||
next.unset_pushed();
|
||||
|
||||
let actual = state
|
||||
.compare_and_swap(curr.into(), next.into(), AcqRel)
|
||||
.into();
|
||||
|
||||
if actual == curr {
|
||||
return curr;
|
||||
}
|
||||
|
||||
curr = actual;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for State {
|
||||
fn from(src: usize) -> State {
|
||||
State(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<State> for usize {
|
||||
fn from(src: State) -> usize {
|
||||
src.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for State {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("backup::State")
|
||||
.field("is_pushed", &self.is_pushed())
|
||||
.field("is_running", &self.is_running())
|
||||
.field("is_terminated", &self.is_terminated())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
use super::{Backup, BackupId};
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BackupStack {
|
||||
state: AtomicUsize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
struct State(usize);
|
||||
|
||||
pub(crate) const MAX_BACKUP: usize = 1 << 15;
|
||||
|
||||
/// Extracts the head of the backup stack from the state
|
||||
const STACK_MASK: usize = ((1 << 16) - 1);
|
||||
|
||||
/// Used to mark the stack as empty
|
||||
pub(crate) const EMPTY: BackupId = BackupId(MAX_BACKUP);
|
||||
|
||||
/// Used to mark the stack as terminated
|
||||
pub(crate) const TERMINATED: BackupId = BackupId(EMPTY.0 + 1);
|
||||
|
||||
/// How many bits the Treiber ABA guard is offset by
|
||||
const ABA_GUARD_SHIFT: usize = 16;
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const ABA_GUARD_MASK: usize = (1 << (64 - ABA_GUARD_SHIFT)) - 1;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const ABA_GUARD_MASK: usize = (1 << (32 - ABA_GUARD_SHIFT)) - 1;
|
||||
|
||||
// ===== impl BackupStack =====
|
||||
|
||||
impl BackupStack {
|
||||
pub(crate) fn new() -> BackupStack {
|
||||
let state = AtomicUsize::new(State::new().into());
|
||||
BackupStack { state }
|
||||
}
|
||||
|
||||
/// Push a backup thread onto the stack
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// Returns `Ok` on success.
|
||||
///
|
||||
/// Returns `Err` if the pool has transitioned to the `TERMINATED` state.
|
||||
/// When terminated, pushing new entries is no longer permitted.
|
||||
pub(crate) fn push(&self, entries: &[Backup], id: BackupId) -> Result<(), ()> {
|
||||
let mut state: State = self.state.load(Acquire).into();
|
||||
|
||||
entries[id.0].set_pushed(AcqRel);
|
||||
|
||||
loop {
|
||||
let mut next = state;
|
||||
|
||||
let head = state.head();
|
||||
|
||||
if head == TERMINATED {
|
||||
// The pool is terminated, cannot push the sleeper.
|
||||
return Err(());
|
||||
}
|
||||
|
||||
entries[id.0].set_next_sleeper(head);
|
||||
next.set_head(id);
|
||||
|
||||
let actual = self
|
||||
.state
|
||||
.compare_and_swap(state.into(), next.into(), AcqRel)
|
||||
.into();
|
||||
|
||||
if state == actual {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
}
|
||||
|
||||
/// Pop a backup thread off the stack.
|
||||
///
|
||||
/// If `terminate` is set and the stack is empty when this function is
|
||||
/// called, the state of the stack is transitioned to "terminated". At this
|
||||
/// point, no further entries can be pushed onto the stack.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// * Returns the index of the popped worker and the worker's observed
|
||||
/// state.
|
||||
///
|
||||
/// * `Ok(None)` if the stack is empty.
|
||||
/// * `Err(_)` is returned if the pool has been shutdown.
|
||||
pub(crate) fn pop(&self, entries: &[Backup], terminate: bool) -> Result<Option<BackupId>, ()> {
|
||||
// Figure out the empty value
|
||||
let terminal = if terminate { TERMINATED } else { EMPTY };
|
||||
|
||||
let mut state: State = self.state.load(Acquire).into();
|
||||
|
||||
loop {
|
||||
let head = state.head();
|
||||
|
||||
if head == EMPTY {
|
||||
let mut next = state;
|
||||
next.set_head(terminal);
|
||||
|
||||
if next == state {
|
||||
debug_assert!(terminal == EMPTY);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let actual = self
|
||||
.state
|
||||
.compare_and_swap(state.into(), next.into(), AcqRel)
|
||||
.into();
|
||||
|
||||
if actual != state {
|
||||
state = actual;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(None);
|
||||
} else if head == TERMINATED {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
debug_assert!(head.0 < MAX_BACKUP);
|
||||
|
||||
let mut next = state;
|
||||
|
||||
let next_head = entries[head.0].next_sleeper();
|
||||
|
||||
// TERMINATED can never be set as the "next pointer" on a worker.
|
||||
debug_assert!(next_head != TERMINATED);
|
||||
|
||||
if next_head == EMPTY {
|
||||
next.set_head(terminal);
|
||||
} else {
|
||||
next.set_head(next_head);
|
||||
}
|
||||
|
||||
let actual = self
|
||||
.state
|
||||
.compare_and_swap(state.into(), next.into(), AcqRel)
|
||||
.into();
|
||||
|
||||
if actual == state {
|
||||
debug_assert!(entries[head.0].is_pushed());
|
||||
return Ok(Some(head));
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl State =====
|
||||
|
||||
impl State {
|
||||
fn new() -> State {
|
||||
State(EMPTY.0)
|
||||
}
|
||||
|
||||
fn head(self) -> BackupId {
|
||||
BackupId(self.0 & STACK_MASK)
|
||||
}
|
||||
|
||||
fn set_head(&mut self, val: BackupId) {
|
||||
let val = val.0;
|
||||
|
||||
// The ABA guard protects against the ABA problem w/ Treiber stacks
|
||||
let aba_guard = ((self.0 >> ABA_GUARD_SHIFT) + 1) & ABA_GUARD_MASK;
|
||||
|
||||
self.0 = (aba_guard << ABA_GUARD_SHIFT) | val;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for State {
|
||||
fn from(src: usize) -> Self {
|
||||
State(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<State> for usize {
|
||||
fn from(src: State) -> Self {
|
||||
src.0
|
||||
}
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
mod backup;
|
||||
mod backup_stack;
|
||||
mod state;
|
||||
|
||||
pub(crate) use self::backup::{Backup, BackupId};
|
||||
pub(crate) use self::backup_stack::MAX_BACKUP;
|
||||
pub(crate) use self::state::{Lifecycle, State, MAX_FUTURES};
|
||||
|
||||
use self::backup::Handoff;
|
||||
use self::backup_stack::BackupStack;
|
||||
use super::config::Config;
|
||||
use super::shutdown::ShutdownTrigger;
|
||||
use super::task::{Blocking, Task};
|
||||
use super::worker::{self, Worker, WorkerId};
|
||||
use super::BlockingError;
|
||||
|
||||
use crossbeam_deque::Injector;
|
||||
use crossbeam_utils::CachePadded;
|
||||
use lazy_static::lazy_static;
|
||||
use std::cell::Cell;
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::num::Wrapping;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::task::Poll;
|
||||
use std::thread;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pool {
|
||||
// Tracks the state of the thread pool (running, shutting down, ...).
|
||||
//
|
||||
// While workers check this field as a hint to detect shutdown, it is
|
||||
// **not** used as a primary point of coordination for workers. The sleep
|
||||
// stack is used as the primary point of coordination for workers.
|
||||
//
|
||||
// The value of this atomic is deserialized into a `pool::State` instance.
|
||||
// See comments for that type.
|
||||
pub(crate) state: CachePadded<AtomicUsize>,
|
||||
|
||||
// Stack tracking sleeping workers.
|
||||
sleep_stack: CachePadded<worker::Stack>,
|
||||
|
||||
// Worker state
|
||||
//
|
||||
// A worker is a thread that is processing the work queue and polling
|
||||
// futures.
|
||||
//
|
||||
// The number of workers will *usually* be small.
|
||||
pub(crate) workers: Arc<[worker::Entry]>,
|
||||
|
||||
// The global MPMC queue of tasks.
|
||||
//
|
||||
// Spawned tasks are pushed into this queue. Although worker threads have their own dedicated
|
||||
// task queues, they periodically steal tasks from this global queue, too.
|
||||
pub(crate) queue: Arc<Injector<Arc<Task>>>,
|
||||
|
||||
// Completes the shutdown process when the `ThreadPool` and all `Worker`s get dropped.
|
||||
//
|
||||
// When spawning a new `Worker`, this weak reference is upgraded and handed out to the new
|
||||
// thread.
|
||||
pub(crate) trigger: Weak<ShutdownTrigger>,
|
||||
|
||||
// Backup thread state
|
||||
//
|
||||
// In order to efficiently support `blocking`, a pool of backup threads is
|
||||
// needed. These backup threads are ready to take over a worker if the
|
||||
// future being processed requires blocking.
|
||||
backup: Box<[Backup]>,
|
||||
|
||||
// Stack of sleeping backup threads
|
||||
pub(crate) backup_stack: BackupStack,
|
||||
|
||||
// State regarding coordinating blocking sections and tracking tasks that
|
||||
// are pending blocking capacity.
|
||||
blocking: Blocking,
|
||||
|
||||
// Configuration
|
||||
pub(crate) config: Config,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
/// Create a new `Pool`
|
||||
pub(crate) fn new(
|
||||
workers: Arc<[worker::Entry]>,
|
||||
trigger: Weak<ShutdownTrigger>,
|
||||
max_blocking: usize,
|
||||
config: Config,
|
||||
queue: Arc<Injector<Arc<Task>>>,
|
||||
) -> Pool {
|
||||
let pool_size = workers.len();
|
||||
let total_size = max_blocking + pool_size;
|
||||
|
||||
// Create the set of backup entries
|
||||
//
|
||||
// This is `backup + pool_size` because the core thread pool running the
|
||||
// workers is spawned from backup as well.
|
||||
let backup = (0..total_size)
|
||||
.map(|_| Backup::new())
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice();
|
||||
|
||||
let backup_stack = BackupStack::new();
|
||||
|
||||
for i in (0..backup.len()).rev() {
|
||||
backup_stack.push(&backup, BackupId(i)).unwrap();
|
||||
}
|
||||
|
||||
// Initialize the blocking state
|
||||
let blocking = Blocking::new(max_blocking);
|
||||
|
||||
let ret = Pool {
|
||||
state: CachePadded::new(AtomicUsize::new(State::new().into())),
|
||||
sleep_stack: CachePadded::new(worker::Stack::new()),
|
||||
workers,
|
||||
queue,
|
||||
trigger,
|
||||
backup,
|
||||
backup_stack,
|
||||
blocking,
|
||||
config,
|
||||
};
|
||||
|
||||
// Now, we prime the sleeper stack
|
||||
for i in 0..pool_size {
|
||||
ret.sleep_stack.push(&ret.workers, i).unwrap();
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Start shutting down the pool. This means that no new futures will be
|
||||
/// accepted.
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace"))]
|
||||
pub(crate) fn shutdown(&self, now: bool, purge_queue: bool) {
|
||||
let mut state: State = self.state.load(Acquire).into();
|
||||
trace!(?state);
|
||||
|
||||
// For now, this must be true
|
||||
debug_assert!(!purge_queue || now);
|
||||
|
||||
// Start by setting the shutdown flag
|
||||
loop {
|
||||
let mut next = state;
|
||||
|
||||
let num_futures = next.num_futures();
|
||||
|
||||
if next.lifecycle() == Lifecycle::ShutdownNow {
|
||||
// Already transitioned to shutting down state
|
||||
|
||||
if !purge_queue || num_futures == 0 {
|
||||
// Nothing more to do
|
||||
return;
|
||||
}
|
||||
|
||||
// The queue must be purged
|
||||
debug_assert!(purge_queue);
|
||||
next.clear_num_futures();
|
||||
} else {
|
||||
next.set_lifecycle(if now || num_futures == 0 {
|
||||
// If already idle, always transition to shutdown now.
|
||||
Lifecycle::ShutdownNow
|
||||
} else {
|
||||
Lifecycle::ShutdownOnIdle
|
||||
});
|
||||
|
||||
if purge_queue {
|
||||
next.clear_num_futures();
|
||||
}
|
||||
}
|
||||
|
||||
let actual = self
|
||||
.state
|
||||
.compare_and_swap(state.into(), next.into(), AcqRel)
|
||||
.into();
|
||||
|
||||
if state == actual {
|
||||
state = next;
|
||||
break;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
|
||||
trace!("transitioned to shutdown");
|
||||
|
||||
// Only transition to terminate if there are no futures currently on the
|
||||
// pool
|
||||
if state.num_futures() != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.terminate_sleeping_workers();
|
||||
}
|
||||
|
||||
/// Called by `Worker` as it tries to enter a sleeping state. Before it
|
||||
/// sleeps, it must push itself onto the sleep stack. This enables other
|
||||
/// threads to see it when signaling work.
|
||||
pub(crate) fn push_sleeper(&self, idx: usize) -> Result<(), ()> {
|
||||
self.sleep_stack.push(&self.workers, idx)
|
||||
}
|
||||
|
||||
pub(crate) fn terminate_sleeping_workers(&self) {
|
||||
use super::worker::Lifecycle::Signaled;
|
||||
|
||||
trace!("shutting down workers");
|
||||
// Wakeup all sleeping workers. They will wake up, see the state
|
||||
// transition, and terminate.
|
||||
while let Some((idx, worker_state)) = self.sleep_stack.pop(&self.workers, Signaled, true) {
|
||||
self.workers[idx].signal_stop(worker_state);
|
||||
}
|
||||
|
||||
// Now terminate any backup threads
|
||||
//
|
||||
// The call to `pop` must be successful because shutting down the pool
|
||||
// is coordinated and at this point, this is the only thread that will
|
||||
// attempt to transition the backup stack to "terminated".
|
||||
while let Ok(Some(backup_id)) = self.backup_stack.pop(&self.backup, true) {
|
||||
self.backup[backup_id.0].signal_stop();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn poll_blocking_capacity(
|
||||
&self,
|
||||
task: &Arc<Task>,
|
||||
) -> Poll<Result<(), BlockingError>> {
|
||||
self.blocking.poll_blocking_capacity(task)
|
||||
}
|
||||
|
||||
/// Submit a task to the scheduler.
|
||||
///
|
||||
/// Called from either inside or outside of the scheduler. If currently on
|
||||
/// the scheduler, then a fast path is taken.
|
||||
pub(crate) fn submit(&self, task: Arc<Task>, pool: &Arc<Pool>) {
|
||||
debug_assert_eq!(*self, **pool);
|
||||
|
||||
Worker::with_current(|worker| {
|
||||
if let Some(worker) = worker {
|
||||
// If the worker is in blocking mode, then even though the
|
||||
// thread-local variable is set, the current thread does not
|
||||
// have ownership of that worker entry. This is because the
|
||||
// worker entry has already been handed off to another thread.
|
||||
//
|
||||
// The second check handles the case where the current thread is
|
||||
// part of a different threadpool than the one being submitted
|
||||
// to.
|
||||
if !worker.is_blocking() && *self == *worker.pool {
|
||||
let idx = worker.id.0;
|
||||
|
||||
trace!(message = "submit internal;", idx);
|
||||
|
||||
worker.pool.workers[idx].submit_internal(task);
|
||||
worker.pool.signal_work(pool);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.submit_external(task, pool);
|
||||
});
|
||||
}
|
||||
|
||||
/// Submit a task to the scheduler from off worker
|
||||
///
|
||||
/// Called from outside of the scheduler, this function is how new tasks
|
||||
/// enter the system.
|
||||
pub(crate) fn submit_external(&self, task: Arc<Task>, pool: &Arc<Pool>) {
|
||||
debug_assert_eq!(*self, **pool);
|
||||
|
||||
trace!("submit external");
|
||||
|
||||
self.queue.push(task);
|
||||
self.signal_work(pool);
|
||||
}
|
||||
|
||||
pub(crate) fn release_backup(&self, backup_id: BackupId) -> Result<(), ()> {
|
||||
// First update the state, this cannot fail because the caller must have
|
||||
// exclusive access to the backup token.
|
||||
self.backup[backup_id.0].release();
|
||||
|
||||
// Push the backup entry back on the stack
|
||||
self.backup_stack.push(&self.backup, backup_id)
|
||||
}
|
||||
|
||||
pub(crate) fn notify_blocking_task(&self, pool: &Arc<Pool>) {
|
||||
debug_assert_eq!(*self, **pool);
|
||||
self.blocking.notify_task(&pool);
|
||||
}
|
||||
|
||||
/// Provision a thread to run a worker
|
||||
pub(crate) fn spawn_thread(&self, id: WorkerId, pool: &Arc<Pool>) {
|
||||
debug_assert_eq!(*self, **pool);
|
||||
|
||||
let backup_id = match self.backup_stack.pop(&self.backup, false) {
|
||||
Ok(Some(backup_id)) => backup_id,
|
||||
Ok(None) => panic!("no thread available"),
|
||||
Err(_) => {
|
||||
debug!("failed to spawn worker thread due to the thread pool shutting down");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let need_spawn = self.backup[backup_id.0].worker_handoff(id.clone());
|
||||
|
||||
if !need_spawn {
|
||||
return;
|
||||
}
|
||||
|
||||
let trigger = match self.trigger.upgrade() {
|
||||
None => {
|
||||
// The pool is shutting down.
|
||||
return;
|
||||
}
|
||||
Some(t) => t,
|
||||
};
|
||||
|
||||
let mut th = thread::Builder::new();
|
||||
|
||||
if let Some(ref prefix) = pool.config.name_prefix {
|
||||
th = th.name(format!("{}{}", prefix, backup_id.0));
|
||||
}
|
||||
|
||||
if let Some(stack) = pool.config.stack_size {
|
||||
th = th.stack_size(stack);
|
||||
}
|
||||
|
||||
let pool = pool.clone();
|
||||
|
||||
let res = th.spawn(move || {
|
||||
if let Some(ref f) = pool.config.after_start {
|
||||
f();
|
||||
}
|
||||
|
||||
let mut worker_id = id;
|
||||
|
||||
pool.backup[backup_id.0].start(&worker_id);
|
||||
|
||||
loop {
|
||||
// The backup token should be in the running state.
|
||||
debug_assert!(pool.backup[backup_id.0].is_running());
|
||||
|
||||
// TODO: Avoid always cloning
|
||||
let worker = Worker::new(worker_id, backup_id, pool.clone(), trigger.clone());
|
||||
|
||||
// Run the worker. If the worker transitioned to a "blocking"
|
||||
// state, then `is_blocking` will be true.
|
||||
if !worker.do_run() {
|
||||
// The worker shutdown, so exit the thread.
|
||||
break;
|
||||
}
|
||||
|
||||
debug_assert!(!pool.backup[backup_id.0].is_pushed());
|
||||
|
||||
// Push the thread back onto the backup stack. This makes it
|
||||
// available for future handoffs.
|
||||
//
|
||||
// This **must** happen before notifying the task.
|
||||
let res = pool.backup_stack.push(&pool.backup, backup_id);
|
||||
|
||||
if res.is_err() {
|
||||
// The pool is being shutdown.
|
||||
break;
|
||||
}
|
||||
|
||||
// The task switched the current thread to blocking mode.
|
||||
// Now that the blocking task completed, any tasks
|
||||
pool.notify_blocking_task(&pool);
|
||||
|
||||
debug_assert!(pool.backup[backup_id.0].is_running());
|
||||
|
||||
// Wait for a handoff
|
||||
let handoff = pool.backup[backup_id.0].wait_for_handoff(pool.config.keep_alive);
|
||||
|
||||
match handoff {
|
||||
Handoff::Worker(id) => {
|
||||
debug_assert!(pool.backup[backup_id.0].is_running());
|
||||
worker_id = id;
|
||||
}
|
||||
Handoff::Idle | Handoff::Terminated => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref f) = pool.config.before_stop {
|
||||
f();
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(err) = res {
|
||||
error!(message = "failed to spawn worker thread;", ?err);
|
||||
panic!("failed to spawn worker thread: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
/// If there are any other workers currently relaxing, signal them that work
|
||||
/// is available so that they can try to find more work to process.
|
||||
#[allow(clippy::cognitive_complexity)] // https://github.com/rust-lang/rust-clippy/issues/3900
|
||||
pub(crate) fn signal_work(&self, pool: &Arc<Pool>) {
|
||||
debug_assert_eq!(*self, **pool);
|
||||
|
||||
use super::worker::Lifecycle::Signaled;
|
||||
|
||||
if let Some((idx, worker_state)) = self.sleep_stack.pop(&self.workers, Signaled, false) {
|
||||
let span = trace_span!("signal_work", idx);
|
||||
let _enter = span.enter();
|
||||
|
||||
let entry = &self.workers[idx];
|
||||
|
||||
debug_assert!(
|
||||
worker_state.lifecycle() != Signaled,
|
||||
"actual={:?}",
|
||||
worker_state.lifecycle(),
|
||||
);
|
||||
|
||||
trace!("notify");
|
||||
|
||||
if !entry.notify(worker_state) {
|
||||
trace!("spawn;");
|
||||
self.spawn_thread(WorkerId(idx), pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a random number
|
||||
///
|
||||
/// Uses a thread-local random number generator based on XorShift.
|
||||
pub(crate) fn rand_usize(&self) -> usize {
|
||||
thread_local! {
|
||||
static RNG: Cell<Wrapping<u32>> = Cell::new(Wrapping(prng_seed()));
|
||||
}
|
||||
|
||||
RNG.with(|rng| {
|
||||
// This is the 32-bit variant of Xorshift.
|
||||
// https://en.wikipedia.org/wiki/Xorshift
|
||||
let mut x = rng.get();
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
rng.set(x);
|
||||
x.0 as usize
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Pool {
|
||||
fn eq(&self, other: &Pool) -> bool {
|
||||
self as *const _ == other as *const _
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Pool {}
|
||||
unsafe impl Sync for Pool {}
|
||||
|
||||
// Return a thread-specific, 32-bit, non-zero seed value suitable for a 32-bit
|
||||
// PRNG. This uses one libstd RandomState for a default hasher and hashes on
|
||||
// the current thread ID to obtain an unpredictable, collision resistant seed.
|
||||
fn prng_seed() -> u32 {
|
||||
// This obtains a small number of random bytes from the host system (for
|
||||
// example, on unix via getrandom(2)) in order to seed an unpredictable and
|
||||
// HashDoS resistant 64-bit hash function (currently: `SipHasher13` with
|
||||
// 128-bit state). We only need one of these, to make the seeds for all
|
||||
// process threads different via hashed IDs, collision resistant, and
|
||||
// unpredictable.
|
||||
lazy_static! {
|
||||
static ref RND_STATE: RandomState = RandomState::new();
|
||||
}
|
||||
|
||||
// Hash the current thread ID to produce a u32 value
|
||||
let mut hasher = RND_STATE.build_hasher();
|
||||
thread::current().id().hash(&mut hasher);
|
||||
let hash: u64 = hasher.finish();
|
||||
let seed = (hash as u32) ^ ((hash >> 32) as u32);
|
||||
|
||||
// Ensure non-zero seed (Xorshift yields only zero's for that seed)
|
||||
if seed == 0 {
|
||||
0x9b4e_6d25 // misc bits, could be any non-zero
|
||||
} else {
|
||||
seed
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
use std::{fmt, usize};
|
||||
|
||||
/// ThreadPool state.
|
||||
///
|
||||
/// The two least significant bits are the shutdown flags. (0 for active, 1 for
|
||||
/// shutdown on idle, 2 for shutting down). The remaining bits represent the
|
||||
/// number of futures that still need to complete.
|
||||
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||
pub(crate) struct State(usize);
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
|
||||
#[repr(usize)]
|
||||
pub(crate) enum Lifecycle {
|
||||
/// The thread pool is currently running
|
||||
Running = 0,
|
||||
|
||||
/// The thread pool should shutdown once it reaches an idle state.
|
||||
ShutdownOnIdle = 1,
|
||||
|
||||
/// The thread pool should start the process of shutting down.
|
||||
ShutdownNow = 2,
|
||||
}
|
||||
|
||||
/// Mask used to extract the number of futures from the state
|
||||
const LIFECYCLE_MASK: usize = 0b11;
|
||||
const NUM_FUTURES_MASK: usize = !LIFECYCLE_MASK;
|
||||
const NUM_FUTURES_OFFSET: usize = 2;
|
||||
|
||||
/// Max number of futures the pool can handle.
|
||||
pub(crate) const MAX_FUTURES: usize = usize::MAX >> NUM_FUTURES_OFFSET;
|
||||
|
||||
// ===== impl State =====
|
||||
|
||||
impl State {
|
||||
#[inline]
|
||||
pub(crate) fn new() -> State {
|
||||
State(0)
|
||||
}
|
||||
|
||||
/// Returns the number of futures still pending completion.
|
||||
pub(crate) fn num_futures(self) -> usize {
|
||||
self.0 >> NUM_FUTURES_OFFSET
|
||||
}
|
||||
|
||||
/// Increment the number of futures pending completion.
|
||||
///
|
||||
/// Returns false on failure.
|
||||
pub(crate) fn inc_num_futures(&mut self) {
|
||||
debug_assert!(self.num_futures() < MAX_FUTURES);
|
||||
debug_assert!(self.lifecycle() < Lifecycle::ShutdownNow);
|
||||
|
||||
self.0 += 1 << NUM_FUTURES_OFFSET;
|
||||
}
|
||||
|
||||
/// Decrement the number of futures pending completion.
|
||||
pub(crate) fn dec_num_futures(&mut self) {
|
||||
let num_futures = self.num_futures();
|
||||
|
||||
if num_futures == 0 {
|
||||
// Already zero
|
||||
return;
|
||||
}
|
||||
|
||||
self.0 -= 1 << NUM_FUTURES_OFFSET;
|
||||
|
||||
if self.lifecycle() == Lifecycle::ShutdownOnIdle && num_futures == 1 {
|
||||
self.set_lifecycle(Lifecycle::ShutdownNow);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the number of futures pending completion to zero
|
||||
pub(crate) fn clear_num_futures(&mut self) {
|
||||
self.0 &= LIFECYCLE_MASK;
|
||||
}
|
||||
|
||||
pub(crate) fn lifecycle(self) -> Lifecycle {
|
||||
(self.0 & LIFECYCLE_MASK).into()
|
||||
}
|
||||
|
||||
pub(crate) fn set_lifecycle(&mut self, val: Lifecycle) {
|
||||
self.0 = (self.0 & NUM_FUTURES_MASK) | (val as usize);
|
||||
}
|
||||
|
||||
pub(crate) fn is_terminated(self) -> bool {
|
||||
self.lifecycle() == Lifecycle::ShutdownNow && self.num_futures() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for State {
|
||||
fn from(src: usize) -> Self {
|
||||
State(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<State> for usize {
|
||||
fn from(src: State) -> Self {
|
||||
src.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for State {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("pool::State")
|
||||
.field("lifecycle", &self.lifecycle())
|
||||
.field("num_futures", &self.num_futures())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Lifecycle =====
|
||||
|
||||
impl From<usize> for Lifecycle {
|
||||
fn from(src: usize) -> Lifecycle {
|
||||
use self::Lifecycle::*;
|
||||
|
||||
debug_assert!(
|
||||
src == Running as usize
|
||||
|| src == ShutdownOnIdle as usize
|
||||
|| src == ShutdownNow as usize
|
||||
);
|
||||
|
||||
unsafe { ::std::mem::transmute(src) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Lifecycle> for usize {
|
||||
fn from(src: Lifecycle) -> usize {
|
||||
let v = src as usize;
|
||||
debug_assert!(v & LIFECYCLE_MASK == v);
|
||||
v
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
use super::pool::{self, Lifecycle, Pool, MAX_FUTURES};
|
||||
use super::task::Task;
|
||||
|
||||
use crate::{Executor, SpawnError, TypedExecutor};
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Submit futures to the associated thread pool for execution.
|
||||
///
|
||||
/// A `Sender` instance is a handle to a single thread pool, allowing the owner
|
||||
/// of the handle to spawn futures onto the thread pool. New futures are spawned
|
||||
/// using [`Sender::spawn`].
|
||||
///
|
||||
/// The `Sender` handle is *only* used for spawning new futures. It does not
|
||||
/// impact the lifecycle of the thread pool in any way.
|
||||
///
|
||||
/// `Sender` instances are obtained by calling [`ThreadPool::sender`]. The
|
||||
/// `Sender` struct implements the `Executor` trait.
|
||||
///
|
||||
/// [`Sender::spawn`]: #method.spawn
|
||||
/// [`ThreadPool::sender`]: struct.ThreadPool.html#method.sender
|
||||
#[derive(Debug)]
|
||||
pub struct Sender {
|
||||
pub(crate) pool: Arc<Pool>,
|
||||
}
|
||||
|
||||
impl Sender {
|
||||
/// Spawn a future onto the thread pool
|
||||
///
|
||||
/// This function takes ownership of the future and spawns it onto the
|
||||
/// thread pool, assigning it to a worker thread. The exact strategy used to
|
||||
/// assign a future to a worker depends on if the caller is already on a
|
||||
/// worker thread or external to the thread pool.
|
||||
///
|
||||
/// If the caller is currently on the thread pool, the spawned future will
|
||||
/// be assigned to the same worker that the caller is on. If the caller is
|
||||
/// external to the thread pool, the future will be assigned to a random
|
||||
/// worker.
|
||||
///
|
||||
/// If `spawn` returns `Ok`, this does not mean that the future will be
|
||||
/// executed. The thread pool can be forcibly shutdown between the time
|
||||
/// `spawn` is called and the future has a chance to execute.
|
||||
///
|
||||
/// If `spawn` returns `Err`, then the future failed to be spawned. There
|
||||
/// are two possible causes:
|
||||
///
|
||||
/// * The thread pool is at capacity and is unable to spawn a new future.
|
||||
/// This is a temporary failure. At some point in the future, the thread
|
||||
/// pool might be able to spawn new futures.
|
||||
/// * The thread pool is shutdown. This is a permanent failure indicating
|
||||
/// that the handle will never be able to spawn new futures.
|
||||
///
|
||||
/// The status of the thread pool can be queried before calling `spawn`
|
||||
/// using the `status` function (part of the `Executor` trait).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use tokio_executor::threadpool::ThreadPool;
|
||||
///
|
||||
/// // Create a thread pool with default configuration values
|
||||
/// let thread_pool = ThreadPool::new();
|
||||
///
|
||||
/// thread_pool.sender().spawn(async {
|
||||
/// println!("called from a worker thread");
|
||||
/// }).unwrap();
|
||||
///
|
||||
/// // Gracefully shutdown the threadpool
|
||||
/// thread_pool.shutdown().wait();
|
||||
/// ```
|
||||
pub fn spawn<F>(&self, future: F) -> Result<(), SpawnError>
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
let mut s = self;
|
||||
Executor::spawn(&mut s, Box::pin(future))
|
||||
}
|
||||
|
||||
/// Logic to prepare for spawning
|
||||
fn prepare_for_spawn(&self) -> Result<(), SpawnError> {
|
||||
let mut state: pool::State = self.pool.state.load(Acquire).into();
|
||||
|
||||
// Increment the number of futures spawned on the pool as well as
|
||||
// validate that the pool is still running/
|
||||
loop {
|
||||
let mut next = state;
|
||||
|
||||
if next.num_futures() == MAX_FUTURES {
|
||||
// No capacity
|
||||
return Err(SpawnError::at_capacity());
|
||||
}
|
||||
|
||||
if next.lifecycle() == Lifecycle::ShutdownNow {
|
||||
// Cannot execute the future, executor is shutdown.
|
||||
return Err(SpawnError::shutdown());
|
||||
}
|
||||
|
||||
next.inc_num_futures();
|
||||
|
||||
let actual = self
|
||||
.pool
|
||||
.state
|
||||
.compare_and_swap(state.into(), next.into(), AcqRel)
|
||||
.into();
|
||||
|
||||
if actual == state {
|
||||
trace!(message = "execute;", count = next.num_futures());
|
||||
break;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Executor for Sender {
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
let s = self;
|
||||
Executor::status(&s)
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Pin<Box<dyn Future<Output = ()> + Send>>,
|
||||
) -> Result<(), SpawnError> {
|
||||
let mut s = &*self;
|
||||
Executor::spawn(&mut s, future)
|
||||
}
|
||||
}
|
||||
|
||||
impl Executor for &Sender {
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
let state: pool::State = self.pool.state.load(Acquire).into();
|
||||
|
||||
if state.num_futures() == MAX_FUTURES {
|
||||
// No capacity
|
||||
return Err(SpawnError::at_capacity());
|
||||
}
|
||||
|
||||
if state.lifecycle() == Lifecycle::ShutdownNow {
|
||||
// Cannot execute the future, executor is shutdown.
|
||||
return Err(SpawnError::shutdown());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
&mut self,
|
||||
future: Pin<Box<dyn Future<Output = ()> + Send>>,
|
||||
) -> Result<(), SpawnError> {
|
||||
self.prepare_for_spawn()?;
|
||||
|
||||
// At this point, the pool has accepted the future, so schedule it for
|
||||
// execution.
|
||||
|
||||
// Create a new task for the future
|
||||
let task = Arc::new(Task::new(future));
|
||||
|
||||
// Call `submit_external()` in order to place the task into the global
|
||||
// queue. This way all workers have equal chance of running this task,
|
||||
// which means IO handles will be assigned to reactors more evenly.
|
||||
self.pool.submit_external(task, &self.pool);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypedExecutor<T> for Sender
|
||||
where
|
||||
T: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
fn status(&self) -> Result<(), SpawnError> {
|
||||
Executor::status(self)
|
||||
}
|
||||
|
||||
fn spawn(&mut self, future: T) -> Result<(), SpawnError> {
|
||||
Executor::spawn(self, Box::pin(future))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Sender {
|
||||
#[inline]
|
||||
fn clone(&self) -> Sender {
|
||||
let pool = self.pool.clone();
|
||||
Sender { pool }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user