mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
396
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e4945025c | ||
|
|
3c8f110730 | ||
|
|
678f15bd48 | ||
|
|
b1172f8074 | ||
|
|
cdde2e7a27 | ||
|
|
85487727d4 | ||
|
|
92d51202ef | ||
|
|
cb55bf4012 | ||
|
|
987ccfc8ac | ||
|
|
1bc6d75543 | ||
|
|
27148d6110 | ||
|
|
a1871b1480 | ||
|
|
acd08eb23d | ||
|
|
90b1a01010 | ||
|
|
676824988e | ||
|
|
46149f031e | ||
|
|
5510ba6dba | ||
|
|
b8f63308d7 | ||
|
|
4313d65b38 | ||
|
|
e780fccce4 | ||
|
|
b01e71b3d8 | ||
|
|
7f911b6b70 | ||
|
|
d88aba8d1c | ||
|
|
6fbef0a528 | ||
|
|
9be5f3f9ff | ||
|
|
e28856cffe | ||
|
|
85e3bd34af | ||
|
|
db4019d84a | ||
|
|
195c4b0496 | ||
|
|
619d3b163b | ||
|
|
5ff6e37c59 | ||
|
|
43d69d77e2 | ||
|
|
dbb04e310c | ||
|
|
0e2e07812a | ||
|
|
047d0b821c | ||
|
|
70f4fc481c | ||
|
|
7039f02bb2 | ||
|
|
4985e0c608 | ||
|
|
fd22090df8 | ||
|
|
02a5091885 | ||
|
|
80162306e7 | ||
|
|
ab595d0825 | ||
|
|
41a2245b85 | ||
|
|
7ca4f3ec4b | ||
|
|
0da649727c | ||
|
|
1cf5f73651 | ||
|
|
beb639a030 | ||
|
|
75ab7c9e9b | ||
|
|
cec9efeb7a | ||
|
|
f9345f99bb | ||
|
|
ab206b976c | ||
|
|
3d787b16c7 | ||
|
|
f513558076 | ||
|
|
d0cdcff8aa | ||
|
|
e3115231dd | ||
|
|
2d5aa82341 | ||
|
|
dd66096ea0 | ||
|
|
c08e73c8d4 | ||
|
|
d1d72dc1c8 | ||
|
|
27a42b980c | ||
|
|
7a50e09495 | ||
|
|
d7a556fe8b | ||
|
|
860ca79d62 | ||
|
|
7b98bf7da3 | ||
|
|
49774f6af1 | ||
|
|
ec22fb9843 | ||
|
|
ce2147d2b6 | ||
|
|
fca41d4e73 | ||
|
|
a69aca850c | ||
|
|
13c96187f8 | ||
|
|
61d4aa98e4 | ||
|
|
9d6d142bed | ||
|
|
95b0eec8af | ||
|
|
e1a07ce50c | ||
|
|
11e2af66a8 | ||
|
|
a4aae1459c | ||
|
|
12546d1d9c | ||
|
|
fbad6297c5 | ||
|
|
0ec8986b0b | ||
|
|
c6f8bdb249 | ||
|
|
c6f9a069a5 | ||
|
|
9f356d6244 | ||
|
|
13083153aa | ||
|
|
91f20e33a4 | ||
|
|
983e9d1b67 | ||
|
|
4c8f274db9 | ||
|
|
c980837581 | ||
|
|
eec370cae8 | ||
|
|
733d432b80 | ||
|
|
74c473d68f | ||
|
|
d95c697781 | ||
|
|
25e835c5b7 | ||
|
|
961aae41c4 | ||
|
|
74c73b218e | ||
|
|
7a49ebb65e | ||
|
|
a687922746 | ||
|
|
df299ced45 | ||
|
|
78d1fe0eb0 | ||
|
|
fc8cde383a | ||
|
|
76198f63d7 | ||
|
|
39dc5706b7 | ||
|
|
cbecb87797 | ||
|
|
f0bdf1980c | ||
|
|
5e2d93f060 | ||
|
|
9a8d087c69 | ||
|
|
30f59670c8 | ||
|
|
9e4ddaeaf3 | ||
|
|
03e2e864f3 | ||
|
|
c8a990eda4 | ||
|
|
1a5026324f | ||
|
|
fdf4aba621 | ||
|
|
201b6ce53a | ||
|
|
db69275202 | ||
|
|
af85cb3430 | ||
|
|
36f1a19ac8 | ||
|
|
6aa990ea75 | ||
|
|
760a7667d6 | ||
|
|
2283b63e9e | ||
|
|
8263e5f18d | ||
|
|
b3e57b60d0 | ||
|
|
1cd0ebfc5e | ||
|
|
4797d79950 | ||
|
|
e7d9ba7e51 | ||
|
|
527dc0a66f | ||
|
|
b117fc1d65 | ||
|
|
272e09d349 | ||
|
|
3235749006 | ||
|
|
9c037044c4 | ||
|
|
3658e10045 | ||
|
|
ed3ece266b | ||
|
|
9b1a45cc6a | ||
|
|
477fa5580a | ||
|
|
bb6cca8ff0 | ||
|
|
e166c4d912 | ||
|
|
b7506cf663 | ||
|
|
dc4a29359f | ||
|
|
d3dca4552b | ||
|
|
42a0df1ea4 | ||
|
|
a98eab6eff | ||
|
|
5a5dde70b3 | ||
|
|
d0963774a3 | ||
|
|
c83355235c | ||
|
|
33a216e4c1 | ||
|
|
09f2ac85bf | ||
|
|
32a152630f | ||
|
|
9153067d66 | ||
|
|
d246964bdf | ||
|
|
e700607554 | ||
|
|
5321550534 | ||
|
|
32e1cafb57 | ||
|
|
49bc4025dd | ||
|
|
51e36e41bc | ||
|
|
d011b92b9a | ||
|
|
f929576f0e | ||
|
|
b0f001a05a | ||
|
|
2291ba9d0d | ||
|
|
7f84f6b4ca | ||
|
|
5f61bd5252 | ||
|
|
bffa3ed558 | ||
|
|
7b5ef61aeb | ||
|
|
753336de8e | ||
|
|
65aea16ad1 | ||
|
|
796fee6364 | ||
|
|
adb0ba71d4 | ||
|
|
bfa6766f3c | ||
|
|
a2f457fa48 | ||
|
|
1879bc49ce | ||
|
|
678f6382b8 | ||
|
|
e27b0a46ba | ||
|
|
d35d0518f5 | ||
|
|
886511c0a6 | ||
|
|
d06bd6b216 | ||
|
|
2c85cd0991 | ||
|
|
1e45237a28 | ||
|
|
3a88d85538 | ||
|
|
b47ad24268 | ||
|
|
cab9a44e01 | ||
|
|
964afb2ce3 | ||
|
|
2f690d30bc | ||
|
|
331a88cee6 | ||
|
|
46353737e7 | ||
|
|
ffd73a64e7 | ||
|
|
1119d572ee | ||
|
|
20ca59114a | ||
|
|
3dd95a9ff1 | ||
|
|
be67eda117 | ||
|
|
e267a1922d | ||
|
|
9b456f48d9 | ||
|
|
0ca973a7eb | ||
|
|
df6acf0c2a | ||
|
|
98d23b8b29 | ||
|
|
85f8522536 | ||
|
|
d275341fb2 | ||
|
|
d735e5d527 | ||
|
|
4019198706 | ||
|
|
24dc85dc5e | ||
|
|
aaa5adb7fd | ||
|
|
5f68b3aaa1 | ||
|
|
2f69acbe9f | ||
|
|
7e12f5c39e | ||
|
|
462882b356 | ||
|
|
35687f1d18 | ||
|
|
e7dc3a1091 | ||
|
|
b594e240f9 | ||
|
|
605708dca6 | ||
|
|
cc40a4e7f0 | ||
|
|
4ae6c997ee | ||
|
|
0f44adf5f6 | ||
|
|
19d5565442 | ||
|
|
98e76d9bc6 | ||
|
|
b7f5bc95fe | ||
|
|
90ea2f6c5b | ||
|
|
837c3934d5 | ||
|
|
bcd42d11d9 | ||
|
|
8ad66d296f | ||
|
|
d9edc26e97 | ||
|
|
f0ac62151b | ||
|
|
214722a296 | ||
|
|
b8f8145b62 | ||
|
|
32d3e0e1f9 | ||
|
|
266919add6 | ||
|
|
c9ffd98b1e | ||
|
|
2d4bfa1485 | ||
|
|
b6ecfa251c | ||
|
|
3a81d7746a | ||
|
|
6e1a833825 | ||
|
|
3f80953dee | ||
|
|
4374f5be70 | ||
|
|
b23ab94cd5 | ||
|
|
40c77bd17e | ||
|
|
31b51004f2 | ||
|
|
9a4e4f2308 | ||
|
|
484fda7a23 | ||
|
|
45ba6e2652 | ||
|
|
e73b8a0cc9 | ||
|
|
209232befd | ||
|
|
f759e4d70f | ||
|
|
1fdff707b8 | ||
|
|
e97e8cb7fe | ||
|
|
2848df9b6c | ||
|
|
7a24ed7509 | ||
|
|
9c9760cfbb | ||
|
|
5ecd929b1a | ||
|
|
8ddebf4309 | ||
|
|
0df1882f21 | ||
|
|
4fa1b2b58c | ||
|
|
a4895fe364 | ||
|
|
7ab97f99c8 | ||
|
|
9bf3228f73 | ||
|
|
d41c60e21d | ||
|
|
934c596133 | ||
|
|
72e2209bd8 | ||
|
|
6a7092b9f7 | ||
|
|
1c893ef6d3 | ||
|
|
20e7598e8d | ||
|
|
3db92496f6 | ||
|
|
da47cfbd58 | ||
|
|
f5eadc74f1 | ||
|
|
175f9afea9 | ||
|
|
48eda3fe2f | ||
|
|
010c2223ca | ||
|
|
36b58d8fa8 | ||
|
|
c601f68c9f | ||
|
|
78ca103f3a | ||
|
|
7da00f3832 | ||
|
|
edba77e8df | ||
|
|
cf1afd2d90 | ||
|
|
8e58a9d8d4 | ||
|
|
4afef9391a | ||
|
|
b6bacc1ca3 | ||
|
|
955cd2836d | ||
|
|
70e4ed67ad | ||
|
|
699b9ab89e | ||
|
|
ba0921a01d | ||
|
|
367cb56e02 | ||
|
|
04d949c380 | ||
|
|
ed4359bb26 | ||
|
|
4142dc2fae | ||
|
|
468b037e4e | ||
|
|
b33ae3cdd6 | ||
|
|
4519ac8e17 | ||
|
|
92b93ee176 | ||
|
|
635149e3ab | ||
|
|
e28c350e31 | ||
|
|
1a122018a2 | ||
|
|
f3f8ee431e | ||
|
|
61c4047c6a | ||
|
|
3486a61a0f | ||
|
|
8291c3d462 | ||
|
|
1b6893b6f6 | ||
|
|
eca7f0760f | ||
|
|
89d969d518 | ||
|
|
16664189c1 | ||
|
|
6828870608 | ||
|
|
89d0cda2e2 | ||
|
|
8052a9b348 | ||
|
|
a5ac6c8b72 | ||
|
|
3a59526523 | ||
|
|
322a94f72f | ||
|
|
c03b23355b | ||
|
|
bc91bc5022 | ||
|
|
a7b053372f | ||
|
|
673fdb5cb3 | ||
|
|
97618746de | ||
|
|
d8f8b59df9 | ||
|
|
0745a9b88a | ||
|
|
a7f5ba28ba | ||
|
|
cc3b6af7a3 | ||
|
|
07e30ae923 | ||
|
|
69d90ac7ee | ||
|
|
d16032cf06 | ||
|
|
b479ce78d3 | ||
|
|
6e45e0ac61 | ||
|
|
82c5baa09b | ||
|
|
7dc6404726 | ||
|
|
2e88e29fe9 | ||
|
|
07203408de | ||
|
|
8bf2e9aeb0 | ||
|
|
cf184eb326 | ||
|
|
d822b721b4 | ||
|
|
c66b56c3fb | ||
|
|
89639ec48b | ||
|
|
2b1b0ac858 | ||
|
|
6b84c73f12 | ||
|
|
767b370c21 | ||
|
|
28010b5962 | ||
|
|
2e343f9e42 | ||
|
|
31f71dedee | ||
|
|
989262fe6e | ||
|
|
d91c775f36 | ||
|
|
96b556fbff | ||
|
|
fd36054ae4 | ||
|
|
89d6bfc5cb | ||
|
|
decc83e959 | ||
|
|
afcfefd7e3 | ||
|
|
c89b0b4c8c | ||
|
|
6b1e4ab0a3 | ||
|
|
4153cc4076 | ||
|
|
5304557d1d | ||
|
|
fdb2f61357 | ||
|
|
e964c4136c | ||
|
|
0490280d66 | ||
|
|
0f76470172 | ||
|
|
9352249c3e | ||
|
|
e5b2681513 | ||
|
|
629c9f0698 | ||
|
|
5d0d2a2e12 | ||
|
|
ad4693a18f | ||
|
|
c85bde3170 | ||
|
|
1e90e27720 | ||
|
|
f212a2ab9d | ||
|
|
84db325628 | ||
|
|
365efec24a | ||
|
|
491f15827b | ||
|
|
c17ecb53e7 | ||
|
|
6ba8e7621d | ||
|
|
39c95d6206 | ||
|
|
78b6bd4ca5 | ||
|
|
b3ff9e315c | ||
|
|
990186ec9d | ||
|
|
19da6ff59a | ||
|
|
36c817f0c3 | ||
|
|
35123f7ae4 | ||
|
|
54b7c1b10d | ||
|
|
e6fc3d209d | ||
|
|
f98b81e527 | ||
|
|
dc7202cfa9 | ||
|
|
f1a7caea3f | ||
|
|
b019532bc2 | ||
|
|
7fb579c667 | ||
|
|
dbefa67058 | ||
|
|
24d99c029e | ||
|
|
3fecd0154c | ||
|
|
0440343a11 | ||
|
|
3cf56b7bfa | ||
|
|
7153d8d6ce | ||
|
|
ecfe2f6a05 | ||
|
|
5753553ba3 | ||
|
|
04a4bfd455 | ||
|
|
b2f77dcebe | ||
|
|
85cf47de86 | ||
|
|
45bcea6c4f | ||
|
|
3fac7ce68c | ||
|
|
71c8f561e3 | ||
|
|
011ebf44eb | ||
|
|
c25ea78ec9 | ||
|
|
2e0cd292d2 | ||
|
|
4ebaf18c27 | ||
|
|
ab07733d66 | ||
|
|
d1f825ca13 | ||
|
|
4cf7d73b22 | ||
|
|
2cd854c2c7 | ||
|
|
ba05c39d65 | ||
|
|
64b8884911 | ||
|
|
d391e63418 | ||
|
|
8d8c895a1c |
@@ -1,19 +0,0 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
platform: x64
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
platform: x86
|
||||
|
||||
install:
|
||||
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init.exe -y --default-host %TARGET%
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --all --target %TARGET%
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-0-release-amd64
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 12.0
|
||||
env:
|
||||
LOOM_MAX_DURATION: 10
|
||||
setup_script:
|
||||
- pkg install -y curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y
|
||||
- . $HOME/.cargo/env
|
||||
- rustup target add i686-unknown-freebsd
|
||||
- |
|
||||
# Remove any existing patch statements
|
||||
mv Cargo.toml Cargo.toml.bck
|
||||
sed -n '/\[patch.crates-io\]/q;p' Cargo.toml.bck > Cargo.toml
|
||||
|
||||
# Patch all crates
|
||||
cat ci/patch.toml >> Cargo.toml
|
||||
|
||||
# Print `Cargo.toml` for debugging
|
||||
echo "~~~~ Cargo.toml ~~~~"
|
||||
cat Cargo.toml
|
||||
echo "~~~~~~~~~~~~~~~~~~~~"
|
||||
cargo_cache:
|
||||
folder: $HOME/.cargo/registry
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all --no-fail-fast
|
||||
- cargo doc --all
|
||||
i686_test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
cargo test --all --exclude tokio-tls --no-fail-fast --target i686-unknown-freebsd
|
||||
before_cache_script:
|
||||
- rm -rf $HOME/.cargo/registry/index
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
Thank you for reporting an issue.
|
||||
|
||||
Please fill in as much of the template below as you're able.
|
||||
-->
|
||||
|
||||
## Version
|
||||
|
||||
<!--
|
||||
List the versions of all `tokio` crates you are using. The easiest way to get
|
||||
this information is using `cargo-tree`.
|
||||
|
||||
`cargo install cargo-tree`
|
||||
(see install here: https://github.com/sfackler/cargo-tree)
|
||||
|
||||
Then:
|
||||
|
||||
`cargo tree | grep tokio`
|
||||
-->
|
||||
|
||||
## Platform
|
||||
|
||||
<!---
|
||||
Output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows)
|
||||
-->
|
||||
|
||||
## Subcrates
|
||||
|
||||
<!--
|
||||
If known, please specify the affected Tokio sub crates. Otherwise, delete this
|
||||
section.
|
||||
-->
|
||||
|
||||
## Description
|
||||
|
||||
<!--
|
||||
|
||||
Enter your issue details below this comment.
|
||||
|
||||
One way to structure the description:
|
||||
|
||||
<short summary of the bug>
|
||||
|
||||
I tried this code:
|
||||
|
||||
<code sample that causes the bug>
|
||||
|
||||
I expected to see this happen: <explanation>
|
||||
|
||||
Instead, this happened: <explanation>
|
||||
-->
|
||||
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
Thank you for your Pull Request. Please provide a description above and review
|
||||
the requirements below.
|
||||
|
||||
Bug fixes and new features should include tests.
|
||||
|
||||
Contributors guide: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
## Motivation
|
||||
|
||||
<!--
|
||||
Explain the context and why you're making that change. What is the problem
|
||||
you're trying to solve? In some cases there is not a problem and this can be
|
||||
thought of as being the motivation for your change.
|
||||
-->
|
||||
|
||||
## Solution
|
||||
|
||||
<!--
|
||||
Summarize the solution and provide any necessary context needed to understand
|
||||
the code change.
|
||||
-->
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
---
|
||||
language: rust
|
||||
sudo: false
|
||||
cache:
|
||||
- apt
|
||||
- cargo
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# to x-compile miniz-sys from sources
|
||||
- gcc-multilib
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# 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.
|
||||
- rust: 1.21.0
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
- os: osx
|
||||
- env: TARGET=x86_64-unknown-freebsd
|
||||
- env: TARGET=i686-unknown-freebsd
|
||||
- env: TARGET=i686-unknown-linux-gnu
|
||||
|
||||
script:
|
||||
- |
|
||||
set -e
|
||||
if [[ "$TRAVIS_RUST_VERSION" == nightly ]]
|
||||
then
|
||||
# Make sure the benchmarks compile
|
||||
cargo build --benches --all
|
||||
|
||||
export ASAN_OPTIONS="detect_odr_violation=0 detect_leaks=0"
|
||||
export TSAN_OPTIONS="suppressions=`pwd`/ci/tsan"
|
||||
|
||||
# === tokio-timer ====
|
||||
|
||||
# Run address sanitizer
|
||||
RUSTFLAGS="-Z sanitizer=address" \
|
||||
cargo test -p tokio-timer --test hammer --target x86_64-unknown-linux-gnu
|
||||
|
||||
# Run thread sanitizer
|
||||
RUSTFLAGS="-Z sanitizer=thread" \
|
||||
cargo test -p tokio-timer --test hammer --target x86_64-unknown-linux-gnu
|
||||
|
||||
# === tokio-threadpool ====
|
||||
|
||||
# Run address sanitizer
|
||||
RUSTFLAGS="-Z sanitizer=address" \
|
||||
cargo test -p tokio-threadpool --tests
|
||||
|
||||
# Run thread sanitizer
|
||||
RUSTFLAGS="-Z sanitizer=thread" \
|
||||
cargo test -p tokio-threadpool --tests
|
||||
fi
|
||||
- |
|
||||
set -e
|
||||
if [[ "$TARGET" ]]
|
||||
then
|
||||
rustup target add $TARGET
|
||||
cargo check --all --target $TARGET
|
||||
cargo check --tests --all --target $TARGET
|
||||
else
|
||||
cargo test --all
|
||||
# Disable these tests for now as they are buggy
|
||||
#
|
||||
# cargo test --features unstable-futures
|
||||
# cargo test --manifest-path tokio-threadpool/Cargo.toml --features unstable-futures
|
||||
# cargo test --manifest-path tokio-reactor/Cargo.toml --features unstable-futures
|
||||
fi
|
||||
|
||||
before_deploy:
|
||||
- cargo doc --all --no-deps
|
||||
|
||||
deploy:
|
||||
provider: pages
|
||||
skip_cleanup: true
|
||||
github_token: $GH_TOKEN
|
||||
target_branch: gh-pages
|
||||
local_dir: target/doc
|
||||
on:
|
||||
branch: master
|
||||
repo: tokio-rs/tokio
|
||||
rust: stable
|
||||
condition: $TRAVIS_OS_NAME = linux
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: iwlN1zfUCp/5BAAheqIRSFIqiM9zSwfIGcVDw/V7jHveqXyNzmCs7H58/cd90WLqonqpPX0t5GF66oTjms4v0DFjgXr/k4358qeSZaV082V3baNrVpCDHeCQV0SvKsfiYxDDJGSUL1WIUP+tqqDm4+ksZQP3LnwZojkABjWz5CBNt4kX+Wz5ZbYqtQoxyuZba5UyPY2CXJtubvCVPGMJULuUpklYxXZ4dWM2olzGgVJ8rE8udhSZ4ER4JgxB0KUx3/5TwHHzgyPEsWR4bKN6JzBjIczQofXUcUXXdoZBs23H/VhCpzKcn3/oJ8btVYPzwtdj5FmVB1aVR/gjPo2bSGi/sofq+LwL/1HJXkM+kjl8m2dLLcDBKqNYNERtVA1++LhkMWAFRgGYe8v8Ryxjiue1NF5LgAIA/fjK0uI1DELTzTf/TKrM+AtPDNTvhOft4/YD+hoImjwk6nv6PBb2TiTYnc79Qf4AZ65tv1qtsAUPuw4plLaccHQAO4ldYVXn4u9c+iisJwvovs6jo06bF3U3qtdI5gXsrI9+T25TrXvYb+IREo0MHzYEM0KlPFnscEArzC3eajuSd36ARFP3lDc+gp2RPs89iJjowms0eRyepp7Cu6XO3Cd2pfAX8AqvnmttZf4Nm51ONeiBPXPXItUkJm49MCpMJywU1IZcWZg=
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
@@ -1,48 +0,0 @@
|
||||
# 0.1.7 (June 6, 2018)
|
||||
|
||||
* Add `Runtime::block_on` for concurrent runtime (#391).
|
||||
* Provide handle to `current_thread::Runtime` that allows spawning tasks from
|
||||
other threads (#340).
|
||||
* Provide `clock::now()`, a configurable source of time (#381).
|
||||
|
||||
# 0.1.6 (May 2, 2018)
|
||||
|
||||
* Add asynchronous filesystem APIs (#323).
|
||||
* Add "current thread" runtime variant (#308).
|
||||
* `CurrentThread`: Expose inner `Park` instance.
|
||||
* Improve fairness of `CurrentThread` executor (#313).
|
||||
|
||||
# 0.1.5 (March 30, 2018)
|
||||
|
||||
* Provide timer API (#266)
|
||||
|
||||
# 0.1.4 (March 22, 2018)
|
||||
|
||||
* Fix build on FreeBSD (#218)
|
||||
* Shutdown the Runtime when the handle is dropped (#214)
|
||||
* Set Runtime thread name prefix for worker threads (#232)
|
||||
* Add builder for Runtime (#234)
|
||||
* Extract TCP and UDP types into separate crates (#224)
|
||||
* Optionally support futures 0.2.
|
||||
|
||||
# 0.1.3 (March 09, 2018)
|
||||
|
||||
* Fix `CurrentThread::turn` to block on idle (#212).
|
||||
|
||||
# 0.1.2 (March 09, 2018)
|
||||
|
||||
* Introduce Tokio Runtime (#141)
|
||||
* Provide `CurrentThread` for more flexible usage of current thread executor (#141).
|
||||
* Add Lio for platforms that support it (#142).
|
||||
* I/O resources now lazily bind to the reactor (#160).
|
||||
* Extract Reactor to dedicated crate (#169)
|
||||
* Add facade to sub crates and add prelude (#166).
|
||||
* Switch TCP/UDP fns to poll_ -> Poll<...> style (#175)
|
||||
|
||||
# 0.1.1 (February 09, 2018)
|
||||
|
||||
* Doc fixes
|
||||
|
||||
# 0.1.0 (February 07, 2018)
|
||||
|
||||
* Initial crate released based on [RFC](https://github.com/tokio-rs/tokio-rfcs/pull/3).
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
# Contributing to Tokio
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
you!
|
||||
|
||||
There are opportunities to contribute to Tokio at any level. It doesn't matter if
|
||||
you are just getting started with Rust or are the most weathered expert, we can
|
||||
use your help.
|
||||
|
||||
**No contribution is too small and all contributions are valued.**
|
||||
|
||||
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
|
||||
us!
|
||||
|
||||
[dev]: https://gitter.im/tokio-rs/dev
|
||||
|
||||
## Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
|
||||
the _minimum_ behavior expected from all contributors.
|
||||
|
||||
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
## Contributing in Issues
|
||||
|
||||
For any issue, there are fundamentally three ways an individual can contribute:
|
||||
|
||||
1. By opening the issue for discussion: For instance, if you believe that you
|
||||
have uncovered a bug in Tokio, creating a new issue in the tokio-rs/tokio
|
||||
issue tracker is the way to report it.
|
||||
|
||||
2. By helping to triage the issue: This can be done by providing
|
||||
supporting details (a test case that demonstrates a bug), providing
|
||||
suggestions on how to address the issue, or ensuring that the issue is tagged
|
||||
correctly.
|
||||
|
||||
3. By helping to resolve the issue: Typically this is done either in the form of
|
||||
demonstrating that the issue reported is not a problem after all, or more
|
||||
often, by opening a Pull Request that changes some bit of something in
|
||||
Tokio in a concrete and reviewable manner.
|
||||
|
||||
**Anybody can participate in any stage of contribution**. We urge you to
|
||||
participate in the discussion around bugs and participate in reviewing PRs.
|
||||
|
||||
### Asking for General Help
|
||||
|
||||
If you have reviewed existing documentation and still have questions or are
|
||||
having problems, you can open an issue asking for help.
|
||||
|
||||
In exchange for receiving help, we ask that you contribute back a documentation
|
||||
PR that helps others avoid the problems that you encountered.
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, users will be presented
|
||||
with a [basic template][template] that should be filled in. If you believe that you have
|
||||
uncovered a bug, please fill out this form, following the template to the best
|
||||
of your ability. Do not worry if you cannot answer every detail, just fill in
|
||||
what you can.
|
||||
|
||||
The two most important pieces of information we need in order to properly
|
||||
evaluate the report is a description of the behavior you are seeing and a simple
|
||||
test case we can use to recreate the problem on our own. If we cannot recreate
|
||||
the issue, it becomes impossible for us to fix.
|
||||
|
||||
In order to rule out the possibility of bugs introduced by userland code, test
|
||||
cases should be limited, as much as possible, to using only Tokio APIs.
|
||||
|
||||
See [How to create a Minimal, Complete, and Verifiable example][mcve].
|
||||
|
||||
[mcve]: https://stackoverflow.com/help/mcve
|
||||
[template]: .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Triaging a Bug Report
|
||||
|
||||
Once an issue has been opened, it is not uncommon for there to be discussion
|
||||
around it. Some contributors may have differing opinions about the issue,
|
||||
including whether the behavior being seen is a bug or a feature. This discussion
|
||||
is part of the process and should be kept focused, helpful, and professional.
|
||||
|
||||
Short, clipped responses—that provide neither additional context nor supporting
|
||||
detail—are not helpful or professional. To many, such responses are simply
|
||||
annoying and unfriendly.
|
||||
|
||||
Contributors are encouraged to help one another make forward progress as much as
|
||||
possible, empowering one another to solve issues collaboratively. If you choose
|
||||
to comment on an issue that you feel either is not a problem that needs to be
|
||||
fixed, or if you encounter information in an issue that you feel is incorrect,
|
||||
explain why you feel that way with additional supporting context, and be willing
|
||||
to be convinced that you may be wrong. By doing so, we can often reach the
|
||||
correct outcome much faster.
|
||||
|
||||
### Resolving a Bug Report
|
||||
|
||||
In the majority of cases, issues are resolved by opening a Pull Request. The
|
||||
process for opening and reviewing a Pull Request is similar to that of opening
|
||||
and triaging issues, but carries with it a necessary review and approval
|
||||
workflow that ensures that the proposed changes meet the minimal quality and
|
||||
functional guidelines of the Tokio project.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull Requests are the way concrete changes are made to the code, documentation,
|
||||
and dependencies in the Tokio repository.
|
||||
|
||||
Even tiny pull requests (e.g., one character pull request fixing a typo in API
|
||||
documentation) are greatly appreciated. Before making a large change, it is
|
||||
usually a good idea to first open an issue describing the change to solicit
|
||||
feedback and guidance. This will increase the likelihood of the PR getting
|
||||
merged.
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: integration tests and documentation tests
|
||||
(Tokio avoids unit tests as much as possible).
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Integration tests go in the same crate as the code they are testing. Each sub
|
||||
crate should have a `dev-dependency` on `tokio` itself. This makes all Tokio
|
||||
utilities available to use in tests, no matter the crate being tested.
|
||||
|
||||
The best strategy for writing a new integration test is to look at existing
|
||||
integration tests in the crate and follow the style.
|
||||
|
||||
#### Documentation tests
|
||||
|
||||
Ideally, every API has at least one [documentation test] that demonstrates how to
|
||||
use the API. Documentation tests are run with `cargo test --doc`. This ensures
|
||||
that the example is correct and provides additional test coverage.
|
||||
|
||||
The trick to documentation tests is striking a balance between being succinct
|
||||
for a reader to understand and actually testing the API.
|
||||
|
||||
Same as with integration tests, when writing a documentation test, the full
|
||||
`tokio` crate is available. This is especially useful for getting access to the
|
||||
runtime to run the example.
|
||||
|
||||
The documentation tests will be visible from both the crate specific
|
||||
documentation **and** the `tokio` facade documentation via the re-export. The
|
||||
example should be written from the point of view of a user that is using the
|
||||
`tokio` crate. As such, the example should use the API via the facade and not by
|
||||
directly referencing the crate.
|
||||
|
||||
The type level example for `tokio_timer::Timeout` provides a good example of a
|
||||
documentation test:
|
||||
|
||||
```
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// // import the `timeout` function, usually this is done
|
||||
/// // with `use tokio::prelude::*`
|
||||
/// use tokio::prelude::FutureExt;
|
||||
/// use futures::Stream;
|
||||
/// use futures::sync::mpsc;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let (tx, rx) = mpsc::unbounded();
|
||||
/// # tx.unbounded_send(()).unwrap();
|
||||
/// # drop(tx);
|
||||
///
|
||||
/// let process = rx.for_each(|item| {
|
||||
/// // do something with `item`
|
||||
/// # drop(item);
|
||||
/// # Ok(())
|
||||
/// });
|
||||
///
|
||||
/// # tokio::runtime::current_thread::block_on_all(
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// process.timeout(Duration::from_millis(10))
|
||||
/// # ).unwrap();
|
||||
/// # }
|
||||
```
|
||||
|
||||
Given that this is a *type* level documentation test and the primary way users
|
||||
of `tokio` will create an instance of `Timeout` is by using
|
||||
`FutureExt::timeout`, this is how the documentation test is structured.
|
||||
|
||||
Lines that start with `/// #` are removed when the documentation is generated.
|
||||
They are only there to get the test to run. The `block_on_all` function is the
|
||||
easiest way to execute a future from a test.
|
||||
|
||||
If this were a documentation test for the `Timeout::new` function, then the
|
||||
example would explicitly use `Timeout::new`. For example:
|
||||
|
||||
```
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// use tokio::timer::Timeout;
|
||||
/// use futures::Future;
|
||||
/// use futures::sync::oneshot;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
/// # tx.send(()).unwrap();
|
||||
///
|
||||
/// # tokio::runtime::current_thread::block_on_all(
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// Timeout::new(rx, Duration::from_millis(10))
|
||||
/// # ).unwrap();
|
||||
/// # }
|
||||
```
|
||||
|
||||
### Commits
|
||||
|
||||
It is a recommended best practice to keep your changes as logically grouped as
|
||||
possible within individual commits. There is no limit to the number of commits
|
||||
any single Pull Request may have, and many contributors find it easier to review
|
||||
changes that are split across multiple commits.
|
||||
|
||||
That said, if you have a number of commits that are "checkpoints" and don't
|
||||
represent a single logical change, please squash those together.
|
||||
|
||||
Note that multiple commits often get squashed when they are landed (see the
|
||||
notes about [commit squashing]).
|
||||
|
||||
#### Commit message guidelines
|
||||
|
||||
A good commit message should describe what changed and why.
|
||||
|
||||
1. The first line should:
|
||||
|
||||
* contain a short description of the change (preferably 50 characters or less,
|
||||
and no more than 72 characters)
|
||||
* be entirely in lowercase with the exception of proper nouns, acronyms, and
|
||||
the words that refer to code, like function/variable names
|
||||
* be prefixed with the name of the sub crate being changed (without the `tokio-`
|
||||
prefix) and start with an imperative verb. If modifying `tokio` proper,
|
||||
omit the crate prefix.
|
||||
|
||||
Examples:
|
||||
|
||||
* timer: introduce `Timeout` and deprecate `Deadline`
|
||||
* export `Encoder`, `Decoder`, `Framed*` from tokio_codec
|
||||
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns (except for long URLs).
|
||||
4. If your patch fixes an open issue, you can add a reference to it at the end
|
||||
of the log. Use the `Fixes: #` prefix and the issue number. For other
|
||||
references use `Refs: #`. `Refs` may include multiple issues, separated by a
|
||||
comma.
|
||||
|
||||
Examples:
|
||||
|
||||
- `Fixes: #1337`
|
||||
- `Refs: #1234`
|
||||
|
||||
Sample complete commit message:
|
||||
|
||||
```txt
|
||||
subcrate: explain the commit in one line
|
||||
|
||||
Body of commit message is a few lines of text, explaining things
|
||||
in more detail, possibly giving some background about the issue
|
||||
being fixed, etc.
|
||||
|
||||
The body of the commit message can be several paragraphs, and
|
||||
please do proper word-wrap and keep columns shorter than about
|
||||
72 characters or so. That way, `git log` will show things
|
||||
nicely even when it is indented.
|
||||
|
||||
Fixes: #1337
|
||||
Refs: #453, #154
|
||||
```
|
||||
|
||||
### Opening the Pull Request
|
||||
|
||||
From within GitHub, opening a new Pull Request will present you with a
|
||||
[template] that should be filled out. Please try to do your best at filling out
|
||||
the details, but feel free to skip parts if you're not sure what to put.
|
||||
|
||||
[template]: .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Discuss and update
|
||||
|
||||
You will probably get feedback or requests for changes to your Pull Request.
|
||||
This is a big part of the submission process so don't be discouraged! Some
|
||||
contributors may sign off on the Pull Request right away, others may have
|
||||
more detailed comments or feedback. This is a necessary part of the process
|
||||
in order to evaluate whether the changes are correct and necessary.
|
||||
|
||||
**Any community member can review a PR and you might get conflicting feedback**.
|
||||
Keep an eye out for comments from code owners to provide guidance on conflicting
|
||||
feedback.
|
||||
|
||||
**Once the PR is open, do not rebase the commits**. See [Commit Squashing] for
|
||||
more details.
|
||||
|
||||
### Commit Squashing
|
||||
|
||||
In most cases, **do not squash commits that you add to your Pull Request during
|
||||
the review process**. When the commits in your Pull Request land, they may be
|
||||
squashed into one commit per logical change. Metadata will be added to the
|
||||
commit message (including links to the Pull Request, links to relevant issues,
|
||||
and the names of the reviewers). The commit history of your Pull Request,
|
||||
however, will stay intact on the Pull Request page.
|
||||
|
||||
## Reviewing Pull Requests
|
||||
|
||||
**Any Tokio community member is welcome to review any pull request**.
|
||||
|
||||
All Tokio contributors who choose to review and provide feedback on Pull
|
||||
Requests have a responsibility to both the project and the individual making the
|
||||
contribution. Reviews and feedback must be helpful, insightful, and geared
|
||||
towards improving the contribution as opposed to simply blocking it. If there
|
||||
are reasons why you feel the PR should not land, explain what those are. Do not
|
||||
expect to be able to block a Pull Request from advancing simply because you say
|
||||
"No" without giving an explanation. Be open to having your mind changed. Be open
|
||||
to working with the contributor to make the Pull Request better.
|
||||
|
||||
Reviews that are dismissive or disrespectful of the contributor or any other
|
||||
reviewers are strictly counter to the Code of Conduct.
|
||||
|
||||
When reviewing a Pull Request, the primary goals are for the codebase to improve
|
||||
and for the person submitting the request to succeed. **Even if a Pull Request
|
||||
does not land, the submitters should come away from the experience feeling like
|
||||
their effort was not wasted or unappreciated**. Every Pull Request from a new
|
||||
contributor is an opportunity to grow the community.
|
||||
|
||||
### Review a bit at a time.
|
||||
|
||||
Do not overwhelm new contributors.
|
||||
|
||||
It is tempting to micro-optimize and make everything about relative performance,
|
||||
perfect grammar, or exact style matches. Do not succumb to that temptation.
|
||||
|
||||
Focus first on the most significant aspects of the change:
|
||||
|
||||
1. Does this change make sense for Tokio?
|
||||
2. Does this change make Tokio better, even if only incrementally?
|
||||
3. Are there clear bugs or larger scale issues that need attending to?
|
||||
4. Is the commit message readable and correct? If it contains a breaking change
|
||||
is it clear enough?
|
||||
|
||||
Note that only **incremental** improvement is needed to land a PR. This means
|
||||
that the PR does not need to be perfect, only better than the status quo. Follow
|
||||
up PRs may be opened to continue iterating.
|
||||
|
||||
When changes are necessary, *request* them, do not *demand* them, and **do not
|
||||
assume that the submitter already knows how to add a test or run a benchmark**.
|
||||
|
||||
Specific performance optimization techniques, coding styles and conventions
|
||||
change over time. The first impression you give to a new contributor never does.
|
||||
|
||||
Nits (requests for small changes that are not essential) are fine, but try to
|
||||
avoid stalling the Pull Request. Most nits can typically be fixed by the Tokio
|
||||
Collaborator landing the Pull Request but they can also be an opportunity for
|
||||
the contributor to learn a bit more about the project.
|
||||
|
||||
It is always good to clearly indicate nits when you comment: e.g.
|
||||
`Nit: change foo() to bar(). But this is not blocking.`
|
||||
|
||||
If your comments were addressed but were not folded automatically after new
|
||||
commits or if they proved to be mistaken, please, [hide them][hiding-a-comment]
|
||||
with the appropriate reason to keep the conversation flow concise and relevant.
|
||||
|
||||
### Be aware of the person behind the code
|
||||
|
||||
Be aware that *how* you communicate requests and reviews in your feedback can
|
||||
have a significant impact on the success of the Pull Request. Yes, we may land
|
||||
a particular change that makes Tokio better, but the individual might just not
|
||||
want to have anything to do with Tokio ever again. The goal is not just having
|
||||
good code.
|
||||
|
||||
### Abandoned or Stalled Pull Requests
|
||||
|
||||
If a Pull Request appears to be abandoned or stalled, it is polite to first
|
||||
check with the contributor to see if they intend to continue the work before
|
||||
checking if they would mind if you took it over (especially if it just has nits
|
||||
left). When doing so, it is courteous to give the original contributor credit
|
||||
for the work they started (either by preserving their name and email address in
|
||||
the commit log, or by using an `Author: ` meta-data tag in the commit.
|
||||
|
||||
_Adapted from the [Node.js contributing guide][node]_.
|
||||
|
||||
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
|
||||
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
|
||||
[documentation test]: https://doc.rust-lang.org/rustdoc/documentation-tests.html
|
||||
+9
-56
@@ -1,70 +1,23 @@
|
||||
[package]
|
||||
name = "tokio"
|
||||
|
||||
# When releasing to crates.io:
|
||||
# - Update html_root_url.
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.7"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio/0.1"
|
||||
description = """
|
||||
An event-driven, non-blocking I/O platform for writing asynchronous I/O
|
||||
backed applications.
|
||||
"""
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
keywords = ["io", "async", "non-blocking", "futures"]
|
||||
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"./",
|
||||
"tokio",
|
||||
"tokio-async-await",
|
||||
"tokio-buf",
|
||||
"tokio-codec",
|
||||
"tokio-current-thread",
|
||||
"tokio-executor",
|
||||
"tokio-fs",
|
||||
"tokio-io",
|
||||
"tokio-reactor",
|
||||
"tokio-signal",
|
||||
"tokio-sync",
|
||||
"tokio-threadpool",
|
||||
"tokio-timer",
|
||||
"tokio-tcp",
|
||||
"tokio-tls",
|
||||
"tokio-trace",
|
||||
"tokio-trace/tokio-trace-core",
|
||||
"tokio-udp",
|
||||
"tokio-uds",
|
||||
]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "tokio-rs/tokio" }
|
||||
appveyor = { repository = "carllerche/tokio", id = "s83yxhy9qeb58va7" }
|
||||
|
||||
[dependencies]
|
||||
tokio-codec = { version = "0.1.0", path = "tokio-codec" }
|
||||
tokio-io = { version = "0.1.6", path = "tokio-io" }
|
||||
tokio-executor = { version = "0.1.2", path = "tokio-executor" }
|
||||
tokio-reactor = { version = "0.1.1", path = "tokio-reactor" }
|
||||
tokio-threadpool = { version = "0.1.4", path = "tokio-threadpool" }
|
||||
tokio-tcp = { version = "0.1.0", path = "tokio-tcp" }
|
||||
tokio-udp = { version = "0.1.0", path = "tokio-udp" }
|
||||
tokio-timer = { version = "0.2.4", path = "tokio-timer" }
|
||||
tokio-fs = { version = "0.1.0", path = "tokio-fs" }
|
||||
|
||||
futures = "0.1.20"
|
||||
|
||||
# Needed until `reactor` is removed from `tokio`.
|
||||
mio = "0.6.14"
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = "0.4"
|
||||
env_logger = { version = "0.4", default-features = false }
|
||||
flate2 = { version = "1", features = ["tokio"] }
|
||||
futures-cpupool = "0.1"
|
||||
http = "0.1"
|
||||
httparse = "1.0"
|
||||
libc = "0.2"
|
||||
num_cpus = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 Tokio Contributors
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -14,29 +14,26 @@ the Rust programming language. It is:
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Travis Build Status][travis-badge]][travis-url]
|
||||
[![Appveyor Build Status][appveyor-badge]][appveyor-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Gitter chat][gitter-badge]][gitter-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: LICENSE-MIT
|
||||
[travis-badge]: https://travis-ci.org/tokio-rs/tokio.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/tokio-rs/tokio
|
||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/s83yxhy9qeb58va7/branch/master?svg=true
|
||||
[appveyor-url]: https://ci.appveyor.com/project/carllerche/tokio/branch/master
|
||||
[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
|
||||
|
||||
[Website](https://tokio.rs) |
|
||||
[Guides](https://tokio.rs/docs/getting-started/hello-world/) |
|
||||
[API Docs](https://docs.rs/tokio) |
|
||||
[API Docs](https://docs.rs/tokio/0.1.18/tokio) |
|
||||
[Chat](https://gitter.im/tokio-rs/tokio)
|
||||
|
||||
The API docs for the master branch are published [here][master-dox].
|
||||
|
||||
[master-dox]: https://tokio-rs.github.io/tokio/tokio/
|
||||
[master-dox]: https://tokio-rs.github.io/tokio/doc/tokio/
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -52,9 +49,9 @@ level, it provides a few major components:
|
||||
These components provide the runtime components necessary for building
|
||||
an asynchronous application.
|
||||
|
||||
[net]: https://docs.rs/tokio/0.1/tokio/net/index.html
|
||||
[reactor]: https://docs.rs/tokio/0.1.1/tokio/reactor/index.html
|
||||
[scheduler]: https://tokio-rs.github.io/tokio/tokio/runtime/index.html
|
||||
[net]: https://docs.rs/tokio/0.1.18/tokio/net/index.html
|
||||
[reactor]: https://docs.rs/tokio/0.1.18/tokio/reactor/index.html
|
||||
[scheduler]: https://docs.rs/tokio/0.1.18/tokio/runtime/index.html
|
||||
|
||||
## Example
|
||||
|
||||
@@ -103,6 +100,24 @@ fn main() {
|
||||
|
||||
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.
|
||||
|
||||
[chat]: https://gitter.im/tokio-rs/tokio
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues/new
|
||||
|
||||
## Contributing
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
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
|
||||
@@ -111,6 +126,13 @@ have greater guarantees of stability.
|
||||
|
||||
The crates included as part of Tokio are:
|
||||
|
||||
* [`tokio-async-await`]: Experimental `async` / `await` support.
|
||||
|
||||
* [`tokio-codec`]: Utilities for encoding and decoding protocol frames.
|
||||
|
||||
* [`tokio-current-thread`]: Schedule the execution of futures on the current
|
||||
thread.
|
||||
|
||||
* [`tokio-executor`]: Task execution related traits and utilities.
|
||||
|
||||
* [`tokio-fs`]: Filesystem (and standard in / out) APIs.
|
||||
@@ -132,6 +154,9 @@ The crates included as part of Tokio are:
|
||||
* [`tokio-uds`]: Unix Domain Socket bindings for use with `tokio-io` and
|
||||
`tokio-reactor`.
|
||||
|
||||
[`tokio-async-await`]: tokio-async-await
|
||||
[`tokio-codec`]: tokio-codec
|
||||
[`tokio-current-thread`]: tokio-current-thread
|
||||
[`tokio-executor`]: tokio-executor
|
||||
[`tokio-fs`]: tokio-fs
|
||||
[`tokio-io`]: tokio-io
|
||||
@@ -142,6 +167,14 @@ The crates included as part of Tokio are:
|
||||
[`tokio-udp`]: tokio-udp
|
||||
[`tokio-uds`]: tokio-uds
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
Tokio is built against the latest stable, nightly, and beta Rust releases. The
|
||||
minimum version supported is the stable release from three months before the
|
||||
current stable release version. For example, if the latest stable Rust is 1.29,
|
||||
the minimum version supported is 1.26. The current Tokio version is not
|
||||
guaranteed to build on Rust versions earlier than the minimum supported version.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
trigger: ["master"]
|
||||
pr: ["master"]
|
||||
|
||||
jobs:
|
||||
# Check formatting
|
||||
- template: ci/azure-rustfmt.yml
|
||||
parameters:
|
||||
name: rustfmt
|
||||
|
||||
# Test top level crate
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_tokio
|
||||
displayName: Test tokio
|
||||
cross: true
|
||||
crates:
|
||||
- tokio
|
||||
|
||||
# Test crates that are platform specific
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_sub_cross
|
||||
displayName: Test sub crates -
|
||||
cross: true
|
||||
crates:
|
||||
- tokio-fs
|
||||
- tokio-reactor
|
||||
- tokio-signal
|
||||
- tokio-tcp
|
||||
- tokio-tls
|
||||
- tokio-udp
|
||||
- tokio-uds
|
||||
|
||||
# Test crates that are NOT platform specific
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_linux
|
||||
displayName: Test sub crates -
|
||||
crates:
|
||||
- tokio-buf
|
||||
- tokio-codec
|
||||
- tokio-current-thread
|
||||
- tokio-executor
|
||||
- tokio-io
|
||||
- tokio-sync
|
||||
- tokio-threadpool
|
||||
- tokio-timer
|
||||
- tokio-trace
|
||||
- tokio-trace/tokio-trace-core
|
||||
|
||||
- template: ci/azure-cargo-check.yml
|
||||
parameters:
|
||||
name: features
|
||||
displayName: Check feature permtuations
|
||||
rust: stable
|
||||
crates:
|
||||
tokio:
|
||||
- codec
|
||||
- fs
|
||||
- io
|
||||
- reactor
|
||||
- rt-full
|
||||
- tcp
|
||||
- timer
|
||||
- udp
|
||||
- uds
|
||||
tokio-buf:
|
||||
- util
|
||||
|
||||
# Check async / await
|
||||
- template: ci/azure-cargo-check.yml
|
||||
parameters:
|
||||
name: async_await
|
||||
displayName: Async / Await
|
||||
rust: nightly-2019-02-28
|
||||
noDefaultFeatures: ''
|
||||
benches: true
|
||||
crates:
|
||||
tokio:
|
||||
- async-await-preview
|
||||
|
||||
# Try cross compiling
|
||||
- template: ci/azure-cross-compile.yml
|
||||
parameters:
|
||||
name: cross_32bit_linux
|
||||
target: i686-unknown-linux-gnu
|
||||
|
||||
# This represents the minimum Rust version supported by
|
||||
# Tokio. Updating this should be done in a dedicated PR and
|
||||
# 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.26.0
|
||||
|
||||
- template: ci/azure-tsan.yml
|
||||
parameters:
|
||||
name: tsan
|
||||
|
||||
- template: ci/azure-deploy-docs.yml
|
||||
parameters:
|
||||
dependsOn:
|
||||
- rustfmt
|
||||
- test_tokio
|
||||
- test_sub_cross
|
||||
- test_linux
|
||||
- features
|
||||
- async_await
|
||||
- cross_32bit_linux
|
||||
- minrust
|
||||
- tsan
|
||||
+1
-3
@@ -10,8 +10,8 @@ use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::thread;
|
||||
|
||||
use futures::sync::oneshot;
|
||||
use futures::sync::mpsc;
|
||||
use futures::sync::oneshot;
|
||||
use futures::{Future, Poll, Sink, Stream};
|
||||
use test::Bencher;
|
||||
use tokio::net::UdpSocket;
|
||||
@@ -57,7 +57,6 @@ fn udp_echo_latency(b: &mut Bencher) {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let child = thread::spawn(move || {
|
||||
|
||||
let socket = tokio::net::UdpSocket::bind(&any_addr).unwrap();
|
||||
tx.send(socket.local_addr().unwrap()).unwrap();
|
||||
|
||||
@@ -67,7 +66,6 @@ fn udp_echo_latency(b: &mut Bencher) {
|
||||
server.wait().unwrap();
|
||||
});
|
||||
|
||||
|
||||
let client = std::net::UdpSocket::bind(&any_addr).unwrap();
|
||||
|
||||
let server_addr = rx.wait().unwrap();
|
||||
|
||||
+8
-9
@@ -3,14 +3,13 @@
|
||||
#![feature(test)]
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate test;
|
||||
extern crate mio;
|
||||
extern crate test;
|
||||
|
||||
use test::Bencher;
|
||||
|
||||
use mio::tcp::TcpListener;
|
||||
use mio::{Token, Ready, PollOpt};
|
||||
|
||||
use mio::{PollOpt, Ready, Token};
|
||||
|
||||
#[bench]
|
||||
fn mio_register_deregister(b: &mut Bencher) {
|
||||
@@ -22,8 +21,8 @@ fn mio_register_deregister(b: &mut Bencher) {
|
||||
const CLIENT: Token = Token(1);
|
||||
|
||||
b.iter(|| {
|
||||
poll.register(&sock, CLIENT, Ready::readable(),
|
||||
PollOpt::edge()).unwrap();
|
||||
poll.register(&sock, CLIENT, Ready::readable(), PollOpt::edge())
|
||||
.unwrap();
|
||||
poll.deregister(&sock).unwrap();
|
||||
});
|
||||
}
|
||||
@@ -36,12 +35,12 @@ fn mio_reregister(b: &mut Bencher) {
|
||||
let poll = mio::Poll::new().unwrap();
|
||||
|
||||
const CLIENT: Token = Token(1);
|
||||
poll.register(&sock, CLIENT, Ready::readable(),
|
||||
PollOpt::edge()).unwrap();
|
||||
poll.register(&sock, CLIENT, Ready::readable(), PollOpt::edge())
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
poll.reregister(&sock, CLIENT, Ready::readable(),
|
||||
PollOpt::edge()).unwrap();
|
||||
poll.reregister(&sock, CLIENT, Ready::readable(), PollOpt::edge())
|
||||
.unwrap();
|
||||
});
|
||||
poll.deregister(&sock).unwrap();
|
||||
}
|
||||
|
||||
+62
-50
@@ -11,19 +11,18 @@ pub extern crate test;
|
||||
|
||||
mod prelude {
|
||||
pub use futures::*;
|
||||
pub use tokio::reactor::Reactor;
|
||||
pub use tokio::net::{TcpListener, TcpStream};
|
||||
pub use tokio::executor::current_thread;
|
||||
pub use tokio::reactor::Reactor;
|
||||
pub use tokio_io::io::read_to_end;
|
||||
|
||||
pub use test::{self, Bencher};
|
||||
pub use std::io::{self, Read, Write};
|
||||
pub use std::thread;
|
||||
pub use std::time::Duration;
|
||||
pub use std::io::{self, Read, Write};
|
||||
pub use test::{self, Bencher};
|
||||
}
|
||||
|
||||
mod connect_churn {
|
||||
use ::prelude::*;
|
||||
use prelude::*;
|
||||
|
||||
const NUM: usize = 300;
|
||||
const CONCURRENT: usize = 8;
|
||||
@@ -37,25 +36,29 @@ mod connect_churn {
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
// Spawn a single future that accepts & drops connections
|
||||
let serve_incomings = listener.incoming()
|
||||
let serve_incomings = listener
|
||||
.incoming()
|
||||
.map_err(|e| panic!("server err: {:?}", e))
|
||||
.for_each(|_| Ok(()));
|
||||
|
||||
let connects = stream::iter_result((0..NUM).map(|_| {
|
||||
Ok(TcpStream::connect(&addr)
|
||||
.and_then(|sock| {
|
||||
sock.set_linger(Some(Duration::from_secs(0))).unwrap();
|
||||
read_to_end(sock, vec![])
|
||||
}))
|
||||
Ok(TcpStream::connect(&addr).and_then(|sock| {
|
||||
sock.set_linger(Some(Duration::from_secs(0))).unwrap();
|
||||
read_to_end(sock, vec![])
|
||||
}))
|
||||
}));
|
||||
|
||||
let connects_concurrent = connects.buffer_unordered(CONCURRENT)
|
||||
let connects_concurrent = connects
|
||||
.buffer_unordered(CONCURRENT)
|
||||
.map_err(|e| panic!("client err: {:?}", e))
|
||||
.for_each(|_| Ok(()));
|
||||
|
||||
serve_incomings.select(connects_concurrent)
|
||||
.map(|_| ()).map_err(|_| ())
|
||||
.wait().unwrap();
|
||||
serve_incomings
|
||||
.select(connects_concurrent)
|
||||
.map(|_| ())
|
||||
.map_err(|_| ())
|
||||
.wait()
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,8 +69,7 @@ mod connect_churn {
|
||||
// Spawn reactor thread
|
||||
let server_thread = thread::spawn(move || {
|
||||
// Bind the TCP listener
|
||||
let listener = TcpListener::bind(
|
||||
&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
|
||||
// Get the address being listened on.
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -76,47 +78,56 @@ mod connect_churn {
|
||||
addr_tx.send(addr).unwrap();
|
||||
|
||||
// Spawn a single future that accepts & drops connections
|
||||
let serve_incomings = listener.incoming()
|
||||
let serve_incomings = listener
|
||||
.incoming()
|
||||
.map_err(|e| panic!("server err: {:?}", e))
|
||||
.for_each(|_| Ok(()));
|
||||
|
||||
// Run server
|
||||
serve_incomings.select(shutdown_rx)
|
||||
.map(|_| ()).map_err(|_| ())
|
||||
.wait().unwrap();
|
||||
serve_incomings
|
||||
.select(shutdown_rx)
|
||||
.map(|_| ())
|
||||
.map_err(|_| ())
|
||||
.wait()
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
// Get the bind addr of the server
|
||||
let addr = addr_rx.wait().unwrap();
|
||||
|
||||
b.iter(move || {
|
||||
use std::sync::{Barrier, Arc};
|
||||
use std::sync::{Arc, Barrier};
|
||||
|
||||
// Create a barrier to coordinate threads
|
||||
let barrier = Arc::new(Barrier::new(n + 1));
|
||||
|
||||
// Spawn worker threads
|
||||
let threads: Vec<_> = (0..n).map(|_| {
|
||||
let barrier = barrier.clone();
|
||||
let addr = addr.clone();
|
||||
let threads: Vec<_> = (0..n)
|
||||
.map(|_| {
|
||||
let barrier = barrier.clone();
|
||||
let addr = addr.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let connects = stream::iter_result((0..(NUM / n)).map(|_| {
|
||||
Ok(TcpStream::connect(&addr)
|
||||
.map_err(|e| panic!("connect err: {:?}", e))
|
||||
.and_then(|sock| {
|
||||
sock.set_linger(Some(Duration::from_secs(0))).unwrap();
|
||||
read_to_end(sock, vec![])
|
||||
}))
|
||||
}));
|
||||
thread::spawn(move || {
|
||||
let connects = stream::iter_result((0..(NUM / n)).map(|_| {
|
||||
Ok(TcpStream::connect(&addr)
|
||||
.map_err(|e| panic!("connect err: {:?}", e))
|
||||
.and_then(|sock| {
|
||||
sock.set_linger(Some(Duration::from_secs(0))).unwrap();
|
||||
read_to_end(sock, vec![])
|
||||
}))
|
||||
}));
|
||||
|
||||
barrier.wait();
|
||||
barrier.wait();
|
||||
|
||||
connects.buffer_unordered(CONCURRENT)
|
||||
.map_err(|e| panic!("client err: {:?}", e))
|
||||
.for_each(|_| Ok(())).wait().unwrap();
|
||||
connects
|
||||
.buffer_unordered(CONCURRENT)
|
||||
.map_err(|e| panic!("client err: {:?}", e))
|
||||
.for_each(|_| Ok(()))
|
||||
.wait()
|
||||
.unwrap();
|
||||
})
|
||||
})
|
||||
}).collect();
|
||||
.collect();
|
||||
|
||||
barrier.wait();
|
||||
|
||||
@@ -142,7 +153,7 @@ mod connect_churn {
|
||||
}
|
||||
|
||||
mod transfer {
|
||||
use ::prelude::*;
|
||||
use prelude::*;
|
||||
use std::{cmp, mem};
|
||||
|
||||
const MB: usize = 3 * 1024 * 1024;
|
||||
@@ -201,7 +212,8 @@ mod transfer {
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
// Spawn a single future that accepts 1 connection, Drain it and drops
|
||||
let server = listener.incoming()
|
||||
let server = listener
|
||||
.incoming()
|
||||
.into_future() // take the first connection
|
||||
.map_err(|(e, _other_incomings)| e)
|
||||
.map(|(connection, _other_incomings)| connection.unwrap())
|
||||
@@ -211,17 +223,17 @@ mod transfer {
|
||||
sock: sock,
|
||||
chunk: read_size,
|
||||
};
|
||||
drain.map(|_| ()).map_err(|e| panic!("server error: {:?}", e))
|
||||
drain
|
||||
.map(|_| ())
|
||||
.map_err(|e| panic!("server error: {:?}", e))
|
||||
})
|
||||
.map_err(|e| panic!("server err: {:?}", e));
|
||||
|
||||
let client = TcpStream::connect(&addr)
|
||||
.and_then(move |sock| {
|
||||
Transfer {
|
||||
sock: sock,
|
||||
rem: MB,
|
||||
chunk: write_size,
|
||||
}
|
||||
.and_then(move |sock| Transfer {
|
||||
sock: sock,
|
||||
rem: MB,
|
||||
chunk: write_size,
|
||||
})
|
||||
.map_err(|e| panic!("client err: {:?}", e));
|
||||
|
||||
@@ -230,7 +242,7 @@ mod transfer {
|
||||
}
|
||||
|
||||
mod small_chunks {
|
||||
use ::prelude::*;
|
||||
use prelude::*;
|
||||
|
||||
#[bench]
|
||||
fn one_thread(b: &mut Bencher) {
|
||||
@@ -239,7 +251,7 @@ mod transfer {
|
||||
}
|
||||
|
||||
mod big_chunks {
|
||||
use ::prelude::*;
|
||||
use prelude::*;
|
||||
|
||||
#[bench]
|
||||
fn one_thread(b: &mut Bencher) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
parameters:
|
||||
noDefaultFeatures: '--no-default-features'
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-is-release.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- ${{ each feature in crate.value }}:
|
||||
- script: cargo check ${{ parameters.noDefaultFeatures }} --features ${{ feature }}
|
||||
displayName: Check `${{ crate.key }}`, features = ${{ feature }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
condition: and(succeeded(), not(variables['isRelease']))
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- ${{ each feature in crate.value }}:
|
||||
- script: cargo check ${{ parameters.noDefaultFeatures }} --features ${{ feature }}
|
||||
displayName: Check `${{ crate.key }}`, features = ${{ feature }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate.key }}
|
||||
|
||||
- ${{ if parameters.benches }}:
|
||||
- script: cargo check --benches --all
|
||||
displayName: Check benchmarks
|
||||
@@ -0,0 +1,14 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Min supported Rust version
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust_version }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo check --all
|
||||
displayName: cargo check --all
|
||||
@@ -0,0 +1,24 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: stable
|
||||
|
||||
- script: sudo apt-get install gcc-multilib
|
||||
displayName: "Install gcc-multilib"
|
||||
|
||||
- script: rustup target add ${{ parameters.target }}
|
||||
displayName: "Add target"
|
||||
|
||||
# Always patch
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo check --all --exclude tokio-tls --target ${{ parameters.target }}
|
||||
displayName: Check source
|
||||
|
||||
- script: cargo check --tests --all --exclude tokio-tls --target ${{ parameters.target }}
|
||||
displayName: Check tests
|
||||
@@ -0,0 +1,38 @@
|
||||
parameters:
|
||||
dependsOn: []
|
||||
|
||||
jobs:
|
||||
- job: documentation
|
||||
displayName: 'Deploy API Documentation'
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
dependsOn:
|
||||
- ${{ parameters.dependsOn }}
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: stable
|
||||
- script: |
|
||||
cargo doc --all --no-deps
|
||||
cp -R target/doc '$(Build.BinariesDirectory)'
|
||||
displayName: 'Generate Documentation'
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
git --version
|
||||
ls -la
|
||||
git init
|
||||
git config user.name 'Deployment Bot (from Azure Pipelines)'
|
||||
git config user.email '[email protected]'
|
||||
git config --global credential.helper 'store --file ~/.my-credentials'
|
||||
printf "protocol=https\nhost=github.com\nusername=carllerche\npassword=%s\n\n" "$GITHUB_TOKEN" | git credential-store --file ~/.my-credentials store
|
||||
git remote add origin https://github.com/tokio-rs/tokio
|
||||
git checkout -b gh-pages
|
||||
git add .
|
||||
git commit -m 'Deploy Tokio API documentation'
|
||||
git push -f origin gh-pages
|
||||
env:
|
||||
GITHUB_TOKEN: $(githubPersonalToken)
|
||||
workingDirectory: '$(Build.BinariesDirectory)'
|
||||
displayName: 'Deploy Documentation'
|
||||
@@ -0,0 +1,27 @@
|
||||
steps:
|
||||
# Linux and macOS.
|
||||
- script: |
|
||||
set -e
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUSTUP_TOOLCHAIN
|
||||
echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin"
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: ${{parameters.rust_version}}
|
||||
displayName: "Install rust (*nix)"
|
||||
condition: not(eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Windows.
|
||||
- script: |
|
||||
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||
rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN%
|
||||
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: ${{parameters.rust_version}}
|
||||
displayName: "Install rust (windows)"
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
|
||||
# All platforms.
|
||||
- script: |
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
displayName: Query rust and cargo versions
|
||||
@@ -0,0 +1,9 @@
|
||||
steps:
|
||||
- bash: |
|
||||
set -e
|
||||
|
||||
if git log --no-merges -1 --format='%s' | grep -q '[ci-release]'; then
|
||||
echo "##vso[task.setvariable variable=isRelease]true"
|
||||
fi
|
||||
failOnStderr: true
|
||||
displayName: Check if release commit
|
||||
@@ -0,0 +1,16 @@
|
||||
steps:
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
# Remove any existing patch statements
|
||||
mv Cargo.toml Cargo.toml.bck
|
||||
sed -n '/\[patch.crates-io\]/q;p' Cargo.toml.bck > Cargo.toml
|
||||
|
||||
# Patch all crates
|
||||
cat ci/patch.toml >> Cargo.toml
|
||||
|
||||
# Print `Cargo.toml` for debugging
|
||||
echo "~~~~ Cargo.toml ~~~~"
|
||||
cat Cargo.toml
|
||||
echo "~~~~~~~~~~~~~~~~~~~~"
|
||||
displayName: Patch Cargo.toml
|
||||
@@ -0,0 +1,16 @@
|
||||
jobs:
|
||||
# Check formatting
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Check rustfmt
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: stable
|
||||
- script: |
|
||||
rustup component add rustfmt
|
||||
displayName: Install rustfmt
|
||||
- script: |
|
||||
cargo fmt --all -- --check
|
||||
displayName: Check formatting
|
||||
@@ -0,0 +1,41 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
${{ if parameters.cross }}:
|
||||
MacOS:
|
||||
vmImage: macOS-10.13
|
||||
Windows:
|
||||
vmImage: vs2017-win2016
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: stable
|
||||
|
||||
- template: azure-is-release.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- script: cargo test
|
||||
env:
|
||||
LOOM_MAX_DURATION: 10
|
||||
CI: 'True'
|
||||
displayName: cargo test -p ${{ crate }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
condition: and(succeeded(), not(variables['isRelease']))
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- script: cargo test
|
||||
env:
|
||||
LOOM_MAX_DURATION: 10
|
||||
CI: 'True'
|
||||
displayName: cargo test -p ${{ crate }} (PATCHED)
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
@@ -0,0 +1,36 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: TSAN
|
||||
strategy:
|
||||
matrix:
|
||||
Timer:
|
||||
cmd: cargo test -p tokio-timer --test hammer
|
||||
Threadpool:
|
||||
cmd: cargo test -p tokio-threadpool --tests
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: nightly-2018-11-18
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
# Make sure the benchmarks compile
|
||||
export ASAN_OPTIONS="detect_odr_violation=0 detect_leaks=0"
|
||||
export TSAN_OPTIONS="suppressions=`pwd`/ci/tsan"
|
||||
export RUST_BACKTRACE=1
|
||||
|
||||
# Run address sanitizer
|
||||
RUSTFLAGS="-Z sanitizer=address" \
|
||||
$(cmd) --target x86_64-unknown-linux-gnu
|
||||
|
||||
# Run thread sanitizer
|
||||
RUSTFLAGS="-Z sanitizer=thread" \
|
||||
$(cmd) --target x86_64-unknown-linux-gnu
|
||||
displayName: TSAN / MSAN
|
||||
env:
|
||||
TSAN: yes
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Patch dependencies to run all tests against versions of the crate in the
|
||||
# repository.
|
||||
[patch.crates-io]
|
||||
tokio = { path = "tokio" }
|
||||
tokio-async-await = { path = "tokio-async-await" }
|
||||
tokio-buf = { path = "tokio-buf" }
|
||||
tokio-codec = { path = "tokio-codec" }
|
||||
tokio-current-thread = { path = "tokio-current-thread" }
|
||||
tokio-executor = { path = "tokio-executor" }
|
||||
tokio-fs = { path = "tokio-fs" }
|
||||
tokio-io = { path = "tokio-io" }
|
||||
tokio-reactor = { path = "tokio-reactor" }
|
||||
tokio-signal = { path = "tokio-signal" }
|
||||
tokio-sync = { path = "tokio-sync" }
|
||||
tokio-threadpool = { path = "tokio-threadpool" }
|
||||
tokio-timer = { path = "tokio-timer" }
|
||||
tokio-tcp = { path = "tokio-tcp" }
|
||||
tokio-tls = { path = "tokio-tls" }
|
||||
tokio-trace = { path = "tokio-trace" }
|
||||
tokio-trace-core = { path = "tokio-trace/tokio-trace-core" }
|
||||
tokio-udp = { path = "tokio-udp" }
|
||||
tokio-uds = { path = "tokio-uds" }
|
||||
@@ -3,7 +3,7 @@
|
||||
# TSAN does not understand fences and `Arc::drop` is implemented using a fence.
|
||||
# This causes many false positives.
|
||||
race:Arc*drop
|
||||
race:arc*Weak*drop
|
||||
race:Weak*drop
|
||||
|
||||
# `std` mpsc is not used in any Tokio code base. This race is triggered by some
|
||||
# rust runtime logic.
|
||||
@@ -12,17 +12,16 @@ race:std*mpsc_queue
|
||||
# Probably more fences in std.
|
||||
race:__call_tls_dtors
|
||||
|
||||
# The crossbeam deque uses fences.
|
||||
race:crossbeam_deque
|
||||
# The epoch-based GC uses fences.
|
||||
race:crossbeam_epoch
|
||||
|
||||
# This is excluded as this race shows up due to using the stealing features of
|
||||
# the deque. Unfortunately, the implementation uses a fence, which makes tsan
|
||||
# unhappy.
|
||||
#
|
||||
# TODO: It would be nice to not have to filter this out.
|
||||
race:try_steal_task
|
||||
# Push and steal operations in crossbeam-deque may cause data races, but such
|
||||
# data races are safe. If a data race happens, the value read by `steal` is
|
||||
# forgotten and the steal operation is then retried.
|
||||
race:crossbeam_deque*push
|
||||
race:crossbeam_deque*steal
|
||||
|
||||
# This filters out expected data race in the treiber stack implementations.
|
||||
# This filters out expected data race in the Treiber stack implementations.
|
||||
# Treiber stacks are inherently racy. The pop operation will attempt to access
|
||||
# the "next" pointer on the node it is attempting to pop. However, at this
|
||||
# point it has not gained ownership of the node and another thread might beat
|
||||
@@ -30,4 +29,9 @@ race:try_steal_task
|
||||
# original pop operation will fail due to the ABA guard, but tsan still picks
|
||||
# up the access on the next pointer.
|
||||
race:Backup::next_sleeper
|
||||
race:Backup::set_next_sleeper
|
||||
race:WorkerEntry::set_next_sleeper
|
||||
|
||||
# This ignores a false positive caused by `thread::park()`/`thread::unpark()`.
|
||||
# See: https://github.com/rust-lang/rust/pull/54806#issuecomment-436193353
|
||||
race:pthread_cond_destroy
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
//! A chat server that broadcasts a message to all connections.
|
||||
//!
|
||||
//! This is a line-based server which accepts connections, reads lines from
|
||||
//! those connections, and broadcasts the lines to all other connected clients.
|
||||
//!
|
||||
//! This example is similar to chat.rs, but uses combinators and a much more
|
||||
//! functional style.
|
||||
//!
|
||||
//! Because we are here running the reactor/executor on the same thread instead
|
||||
//! of a threadpool, we can avoid full synchronization with Arc + Mutex and use
|
||||
//! Rc + RefCell instead. The max performance is however limited to a CPU HW
|
||||
//! thread.
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! cargo run --example chat-combinator-current-thread
|
||||
//!
|
||||
//! And then in another window run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! You can run the second command in multiple windows and then chat between the
|
||||
//! two, seeing the messages from the other client as they're received. For all
|
||||
//! connected clients they'll all join the same room and see everyone else's
|
||||
//! messages.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use tokio::runtime::current_thread::{Runtime, TaskExecutor};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::io::BufReader;
|
||||
use std::iter;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn main() -> Result<(), Box<std::error::Error>> {
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
|
||||
// Create the TCP listener we'll accept connections on.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = addr.parse()?;
|
||||
|
||||
let socket = TcpListener::bind(&addr)?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// This is running on the Tokio current_thread runtime, so it will be single-
|
||||
// threaded. The `Rc<RefCell<...>>` allows state to be shared across the tasks.
|
||||
let connections = Rc::new(RefCell::new(HashMap::new()));
|
||||
|
||||
// The server task asynchronously iterates over and processes each incoming
|
||||
// connection.
|
||||
let srv = socket
|
||||
.incoming()
|
||||
.map_err(|e| {
|
||||
println!("failed to accept socket; error = {:?}", e);
|
||||
e
|
||||
})
|
||||
.for_each(move |stream| {
|
||||
// The client's socket address
|
||||
let addr = stream.peer_addr()?;
|
||||
|
||||
println!("New Connection: {}", addr);
|
||||
|
||||
// Split the TcpStream into two separate handles. One handle for reading
|
||||
// and one handle for writing. This lets us use separate tasks for
|
||||
// reading and writing.
|
||||
let (reader, writer) = stream.split();
|
||||
|
||||
// Create a channel for our stream, which other sockets will use to
|
||||
// send us messages. Then register our address with the stream to send
|
||||
// data to us.
|
||||
let (tx, rx) = futures::sync::mpsc::unbounded();
|
||||
let mut conns = connections.borrow_mut();
|
||||
conns.insert(addr, tx);
|
||||
|
||||
// Define here what we do for the actual I/O. That is, read a bunch of
|
||||
// lines from the socket and dispatch them while we also write any lines
|
||||
// from other sockets.
|
||||
let connections_inner = connections.clone();
|
||||
let reader = BufReader::new(reader);
|
||||
|
||||
// Model the read portion of this socket by mapping an infinite
|
||||
// iterator to each line off the socket. This "loop" is then
|
||||
// terminated with an error once we hit EOF on the socket.
|
||||
let iter = stream::iter_ok::<_, io::Error>(iter::repeat(()));
|
||||
|
||||
let socket_reader = iter.fold(reader, move |reader, _| {
|
||||
// Read a line off the socket, failing if we're at EOF
|
||||
let line = io::read_until(reader, b'\n', Vec::new());
|
||||
let line = line.and_then(|(reader, vec)| {
|
||||
if vec.len() == 0 {
|
||||
Err(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe"))
|
||||
} else {
|
||||
Ok((reader, vec))
|
||||
}
|
||||
});
|
||||
|
||||
// Convert the bytes we read into a string, and then send that
|
||||
// string to all other connected clients.
|
||||
let line = line.map(|(reader, vec)| (reader, String::from_utf8(vec)));
|
||||
|
||||
// Move the connection state into the closure below.
|
||||
let connections = connections_inner.clone();
|
||||
|
||||
line.map(move |(reader, message)| {
|
||||
println!("{}: {:?}", addr, message);
|
||||
let mut conns = connections.borrow_mut();
|
||||
|
||||
if let Ok(msg) = message {
|
||||
// For each open connection except the sender, send the
|
||||
// string via the channel.
|
||||
let iter = conns
|
||||
.iter_mut()
|
||||
.filter(|&(&k, _)| k != addr)
|
||||
.map(|(_, v)| v);
|
||||
for tx in iter {
|
||||
tx.unbounded_send(format!("{}: {}", addr, msg)).unwrap();
|
||||
}
|
||||
} else {
|
||||
let tx = conns.get_mut(&addr).unwrap();
|
||||
tx.unbounded_send("You didn't send valid UTF-8.".to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
reader
|
||||
})
|
||||
});
|
||||
|
||||
// Whenever we receive a string on the Receiver, we write it to
|
||||
// `WriteHalf<TcpStream>`.
|
||||
let socket_writer = rx.fold(writer, |writer, msg| {
|
||||
let amt = io::write_all(writer, msg.into_bytes());
|
||||
let amt = amt.map(|(writer, _)| writer);
|
||||
amt.map_err(|_| ())
|
||||
});
|
||||
|
||||
// Now that we've got futures representing each half of the socket, we
|
||||
// use the `select` combinator to wait for either half to be done to
|
||||
// tear down the other. Then we spawn off the result.
|
||||
let connections = connections.clone();
|
||||
let socket_reader = socket_reader.map_err(|_| ());
|
||||
let connection = socket_reader.map(|_| ()).select(socket_writer.map(|_| ()));
|
||||
|
||||
// Spawn locally a task to process the connection
|
||||
TaskExecutor::current()
|
||||
.spawn_local(Box::new(connection.then(move |_| {
|
||||
let mut conns = connections.borrow_mut();
|
||||
conns.remove(&addr);
|
||||
println!("Connection {} closed.", addr);
|
||||
Ok(())
|
||||
})))
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| println!("error occurred: {:?}", err));
|
||||
|
||||
// Spawn srv itself
|
||||
runtime.spawn(srv);
|
||||
|
||||
// Execute server
|
||||
runtime.run().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
+23
-17
@@ -21,25 +21,25 @@
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::iter;
|
||||
use std::env;
|
||||
use std::io::{BufReader};
|
||||
use std::io::BufReader;
|
||||
use std::iter;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::Error>> {
|
||||
// Create the TCP listener we'll accept connections on.
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = addr.parse().unwrap();
|
||||
let addr = addr.parse()?;
|
||||
|
||||
let socket = TcpListener::bind(&addr).unwrap();
|
||||
let socket = TcpListener::bind(&addr)?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// This is running on the Tokio runtime, so it will be multi-threaded. The
|
||||
@@ -48,11 +48,15 @@ fn main() {
|
||||
|
||||
// The server task asynchronously iterates over and processes each incoming
|
||||
// connection.
|
||||
let srv = socket.incoming()
|
||||
.map_err(|e| println!("failed to accept socket; error = {:?}", e))
|
||||
let srv = socket
|
||||
.incoming()
|
||||
.map_err(|e| {
|
||||
println!("failed to accept socket; error = {:?}", e);
|
||||
e
|
||||
})
|
||||
.for_each(move |stream| {
|
||||
// The client's socket address
|
||||
let addr = stream.peer_addr().unwrap();
|
||||
let addr = stream.peer_addr()?;
|
||||
|
||||
println!("New Connection: {}", addr);
|
||||
|
||||
@@ -91,9 +95,7 @@ fn main() {
|
||||
|
||||
// Convert the bytes we read into a string, and then send that
|
||||
// string to all other connected clients.
|
||||
let line = line.map(|(reader, vec)| {
|
||||
(reader, String::from_utf8(vec))
|
||||
});
|
||||
let line = line.map(|(reader, vec)| (reader, String::from_utf8(vec)));
|
||||
|
||||
// Move the connection state into the closure below.
|
||||
let connections = connections_inner.clone();
|
||||
@@ -105,15 +107,17 @@ fn main() {
|
||||
if let Ok(msg) = message {
|
||||
// For each open connection except the sender, send the
|
||||
// string via the channel.
|
||||
let iter = conns.iter_mut()
|
||||
.filter(|&(&k, _)| k != addr)
|
||||
.map(|(_, v)| v);
|
||||
let iter = conns
|
||||
.iter_mut()
|
||||
.filter(|&(&k, _)| k != addr)
|
||||
.map(|(_, v)| v);
|
||||
for tx in iter {
|
||||
tx.unbounded_send(format!("{}: {}", addr, msg)).unwrap();
|
||||
}
|
||||
} else {
|
||||
let tx = conns.get_mut(&addr).unwrap();
|
||||
tx.unbounded_send("You didn't send valid UTF-8.".to_string()).unwrap();
|
||||
tx.unbounded_send("You didn't send valid UTF-8.".to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
reader
|
||||
@@ -143,8 +147,10 @@ fn main() {
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
});
|
||||
})
|
||||
.map_err(|err| println!("error occurred: {:?}", err));
|
||||
|
||||
// execute server
|
||||
tokio::run(srv);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+33
-34
@@ -31,12 +31,12 @@ extern crate tokio;
|
||||
extern crate futures;
|
||||
extern crate bytes;
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::future::{self, Either};
|
||||
use futures::sync::mpsc;
|
||||
use tokio::io;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
use futures::future::{self, Either};
|
||||
use bytes::{BytesMut, Bytes, BufMut};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
@@ -131,10 +131,7 @@ impl Shared {
|
||||
|
||||
impl Peer {
|
||||
/// Create a new instance of `Peer`.
|
||||
fn new(name: BytesMut,
|
||||
state: Arc<Mutex<Shared>>,
|
||||
lines: Lines) -> Peer
|
||||
{
|
||||
fn new(name: BytesMut, state: Arc<Mutex<Shared>>, lines: Lines) -> Peer {
|
||||
// Get the client socket address
|
||||
let addr = lines.socket.peer_addr().unwrap();
|
||||
|
||||
@@ -142,8 +139,7 @@ impl Peer {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
// Add an entry for this `Peer` in the shared state map.
|
||||
state.lock().unwrap()
|
||||
.peers.insert(addr, tx);
|
||||
state.lock().unwrap().peers.insert(addr, tx);
|
||||
|
||||
Peer {
|
||||
name,
|
||||
@@ -198,7 +194,7 @@ impl Future for Peer {
|
||||
// though there could still be lines to read. Because we did
|
||||
// not reach `Async::NotReady`, we have to notify ourselves
|
||||
// in order to tell the executor to schedule the task again.
|
||||
if i+1 == LINES_PER_TICK {
|
||||
if i + 1 == LINES_PER_TICK {
|
||||
task::current().notify();
|
||||
}
|
||||
}
|
||||
@@ -256,8 +252,7 @@ impl Future for Peer {
|
||||
|
||||
impl Drop for Peer {
|
||||
fn drop(&mut self) {
|
||||
self.state.lock().unwrap().peers
|
||||
.remove(&self.addr);
|
||||
self.state.lock().unwrap().peers.remove(&self.addr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +285,7 @@ impl Lines {
|
||||
fn poll_flush(&mut self) -> Poll<(), io::Error> {
|
||||
// As long as there is buffered data to write, try to write it.
|
||||
while !self.wr.is_empty() {
|
||||
// Try to read some bytes from the socket
|
||||
// Try to write some bytes to the socket
|
||||
let n = try_ready!(self.socket.poll_write(&self.wr));
|
||||
|
||||
// As long as the wr is not empty, a successful write should
|
||||
@@ -333,7 +328,10 @@ impl Stream for Lines {
|
||||
let sock_closed = self.fill_read_buf()?.is_ready();
|
||||
|
||||
// Now, try finding lines
|
||||
let pos = self.rd.windows(2).enumerate()
|
||||
let pos = self
|
||||
.rd
|
||||
.windows(2)
|
||||
.enumerate()
|
||||
.find(|&(_, bytes)| bytes == b"\r\n")
|
||||
.map(|(i, _)| i);
|
||||
|
||||
@@ -373,7 +371,8 @@ fn process(socket: TcpStream, state: Arc<Mutex<Shared>>) {
|
||||
// We use the `into_future` combinator to extract the first item from the
|
||||
// lines stream. `into_future` takes a `Stream` and converts it to a future
|
||||
// of `(first, rest)` where `rest` is the original stream instance.
|
||||
let connection = lines.into_future()
|
||||
let connection = lines
|
||||
.into_future()
|
||||
// `into_future` doesn't have the right error type, so map the error to
|
||||
// make it work.
|
||||
.map_err(|(e, _)| e)
|
||||
@@ -408,10 +407,7 @@ fn process(socket: TcpStream, state: Arc<Mutex<Shared>>) {
|
||||
//
|
||||
// This is also a future that processes the connection, only
|
||||
// completing when the socket closes.
|
||||
let peer = Peer::new(
|
||||
name,
|
||||
state,
|
||||
lines);
|
||||
let peer = Peer::new(name, state, lines);
|
||||
|
||||
// Wrap `peer` with `Either::B` to make the return type fit.
|
||||
Either::B(peer)
|
||||
@@ -426,7 +422,7 @@ fn process(socket: TcpStream, state: Arc<Mutex<Shared>>) {
|
||||
tokio::spawn(connection);
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
pub fn main() -> Result<(), Box<std::error::Error>> {
|
||||
// Create the shared state. This is how all the peers communicate.
|
||||
//
|
||||
// The server task will hold a handle to this. For every new client, the
|
||||
@@ -434,27 +430,29 @@ pub fn main() {
|
||||
// client connection.
|
||||
let state = Arc::new(Mutex::new(Shared::new()));
|
||||
|
||||
let addr = "127.0.0.1:6142".parse().unwrap();
|
||||
let addr = "127.0.0.1:6142".parse()?;
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
let listener = TcpListener::bind(&addr)?;
|
||||
|
||||
// The server task asynchronously iterates over and processes each
|
||||
// incoming connection.
|
||||
let server = listener.incoming().for_each(move |socket| {
|
||||
// Spawn a task to process the connection
|
||||
process(socket, state.clone());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
// All tasks must have an `Error` type of `()`. This forces error
|
||||
// handling and helps avoid silencing failures.
|
||||
//
|
||||
// In our example, we are only going to log the error to STDOUT.
|
||||
println!("accept error = {:?}", err);
|
||||
});
|
||||
let server = listener
|
||||
.incoming()
|
||||
.for_each(move |socket| {
|
||||
// Spawn a task to process the connection
|
||||
process(socket, state.clone());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
// All tasks must have an `Error` type of `()`. This forces error
|
||||
// handling and helps avoid silencing failures.
|
||||
//
|
||||
// In our example, we are only going to log the error to STDOUT.
|
||||
println!("accept error = {:?}", err);
|
||||
});
|
||||
|
||||
println!("server running on localhost:6142");
|
||||
|
||||
@@ -471,4 +469,5 @@ pub fn main() {
|
||||
// In our example, we have not defined a shutdown strategy, so this will
|
||||
// block until `ctrl-c` is pressed at the terminal.
|
||||
tokio::run(server);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+69
-58
@@ -16,21 +16,20 @@
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_io;
|
||||
extern crate futures;
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
|
||||
use std::env;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::SocketAddr;
|
||||
use std::thread;
|
||||
|
||||
use tokio::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
use tokio::prelude::*;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::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") {
|
||||
@@ -42,10 +41,11 @@ fn main() {
|
||||
};
|
||||
|
||||
// Parse what address we're going to connect to
|
||||
let addr = args.first().unwrap_or_else(|| {
|
||||
panic!("this program requires at least one argument")
|
||||
});
|
||||
let addr = addr.parse::<SocketAddr>().unwrap();
|
||||
let addr = match args.first() {
|
||||
Some(addr) => addr,
|
||||
None => Err("this program requires at least one argument")?,
|
||||
};
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
// Right now Tokio doesn't support a handle to stdin running on the event
|
||||
// loop, so we farm out that work to a separate thread. This thread will
|
||||
@@ -53,15 +53,15 @@ fn main() {
|
||||
// loop over a standard futures channel.
|
||||
let (stdin_tx, stdin_rx) = mpsc::channel(0);
|
||||
thread::spawn(|| read_stdin(stdin_tx));
|
||||
let stdin_rx = stdin_rx.map_err(|_| panic!()); // errors not possible on rx
|
||||
let stdin_rx = stdin_rx.map_err(|_| panic!("errors not possible on rx"));
|
||||
|
||||
// Now that we've got our stdin read we either set up our TCP connection or
|
||||
// our UDP connection to get a stream of bytes we're going to emit to
|
||||
// stdout.
|
||||
let stdout = if tcp {
|
||||
tcp::connect(&addr, Box::new(stdin_rx))
|
||||
tcp::connect(&addr, Box::new(stdin_rx))?
|
||||
} else {
|
||||
udp::connect(&addr, Box::new(stdin_rx))
|
||||
udp::connect(&addr, Box::new(stdin_rx))?
|
||||
};
|
||||
|
||||
// And now with our stream of bytes to write to stdout, we execute that in
|
||||
@@ -73,17 +73,16 @@ fn main() {
|
||||
|
||||
tokio::run({
|
||||
stdout
|
||||
.for_each(move |chunk| {
|
||||
out.write_all(&chunk)
|
||||
})
|
||||
.for_each(move |chunk| out.write_all(&chunk))
|
||||
.map_err(|e| println!("error reading stdout; error = {:?}", e))
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod codec {
|
||||
use std::io;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use tokio_codec::{Encoder, Decoder};
|
||||
use std::io;
|
||||
use tokio::codec::{Decoder, Encoder};
|
||||
|
||||
/// A simple `Codec` implementation that just ships bytes around.
|
||||
///
|
||||
@@ -121,20 +120,21 @@ mod codec {
|
||||
|
||||
mod tcp {
|
||||
use tokio;
|
||||
use tokio_codec::Decoder;
|
||||
use tokio::codec::Decoder;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use bytes::BytesMut;
|
||||
use codec::Bytes;
|
||||
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub fn connect(addr: &SocketAddr,
|
||||
stdin: Box<Stream<Item = Vec<u8>, Error = io::Error> + Send>)
|
||||
-> Box<Stream<Item = BytesMut, Error = io::Error> + Send>
|
||||
{
|
||||
pub fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: Box<Stream<Item = Vec<u8>, Error = io::Error> + Send>,
|
||||
) -> Result<Box<Stream<Item = BytesMut, Error = io::Error> + Send>, Box<Error>> {
|
||||
let tcp = TcpStream::connect(addr);
|
||||
|
||||
// After the TCP connection has been established, we set up our client
|
||||
@@ -152,45 +152,52 @@ mod tcp {
|
||||
// You'll also note that we *spawn* the work to read stdin and write it
|
||||
// to the TCP stream. This is done to ensure that happens concurrently
|
||||
// with us reading data from the stream.
|
||||
Box::new(tcp.map(move |stream| {
|
||||
let (sink, stream) = Bytes.framed(stream).split();
|
||||
let stream = Box::new(
|
||||
tcp.map(move |stream| {
|
||||
let (sink, stream) = Bytes.framed(stream).split();
|
||||
|
||||
tokio::spawn(stdin.forward(sink).then(|result| {
|
||||
if let Err(e) = result {
|
||||
panic!("failed to write to socket: {}", e)
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
tokio::spawn(stdin.forward(sink).then(|result| {
|
||||
if let Err(e) = result {
|
||||
println!("failed to write to socket: {}", e)
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
stream
|
||||
}).flatten_stream())
|
||||
stream
|
||||
})
|
||||
.flatten_stream(),
|
||||
);
|
||||
Ok(stream)
|
||||
}
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio;
|
||||
use tokio::net::{UdpSocket, UdpFramed};
|
||||
use tokio::prelude::*;
|
||||
use bytes::BytesMut;
|
||||
use tokio;
|
||||
use tokio::net::{UdpFramed, UdpSocket};
|
||||
use tokio::prelude::*;
|
||||
|
||||
use codec::Bytes;
|
||||
|
||||
pub fn connect(&addr: &SocketAddr,
|
||||
stdin: Box<Stream<Item = Vec<u8>, Error = io::Error> + Send>)
|
||||
-> Box<Stream<Item = BytesMut, Error = io::Error> + Send>
|
||||
{
|
||||
pub fn connect(
|
||||
&addr: &SocketAddr,
|
||||
stdin: Box<Stream<Item = Vec<u8>, Error = io::Error> + Send>,
|
||||
) -> Result<Box<Stream<Item = BytesMut, Error = io::Error> + Send>, Box<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 addr_to_bind = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0".parse().unwrap()
|
||||
"0.0.0.0:0".parse()?
|
||||
} else {
|
||||
"[::]:0".parse().unwrap()
|
||||
"[::]:0".parse()?
|
||||
};
|
||||
let udp = match UdpSocket::bind(&addr_to_bind) {
|
||||
Ok(udp) => udp,
|
||||
Err(_) => Err("failed to bind socket")?,
|
||||
};
|
||||
let udp = UdpSocket::bind(&addr_to_bind)
|
||||
.expect("failed to bind socket");
|
||||
|
||||
// Like above with TCP we use an instance of `Bytes` codec to transform
|
||||
// this UDP socket into a framed sink/stream which operates over
|
||||
@@ -200,14 +207,15 @@ mod udp {
|
||||
|
||||
// All bytes from `stdin` will go to the `addr` specified in our
|
||||
// argument list. Like with TCP this is spawned concurrently
|
||||
let forward_stdin = stdin.map(move |chunk| {
|
||||
(chunk, addr)
|
||||
}).forward(sink).then(|result| {
|
||||
if let Err(e) = result {
|
||||
panic!("failed to write to socket: {}", e)
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
let forward_stdin = stdin
|
||||
.map(move |chunk| (chunk, addr))
|
||||
.forward(sink)
|
||||
.then(|result| {
|
||||
if let Err(e) = result {
|
||||
println!("failed to write to socket: {}", e)
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// With UDP we could receive data from any source, so filter out
|
||||
// anything coming from a different address
|
||||
@@ -219,10 +227,14 @@ mod udp {
|
||||
}
|
||||
});
|
||||
|
||||
Box::new(future::lazy(|| {
|
||||
tokio::spawn(forward_stdin);
|
||||
future::ok(receive)
|
||||
}).flatten_stream())
|
||||
let stream = Box::new(
|
||||
future::lazy(|| {
|
||||
tokio::spawn(forward_stdin);
|
||||
future::ok(receive)
|
||||
})
|
||||
.flatten_stream(),
|
||||
);
|
||||
Ok(stream)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +245,7 @@ fn read_stdin(mut tx: mpsc::Sender<Vec<u8>>) {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = match stdin.read(&mut buf) {
|
||||
Err(_) |
|
||||
Ok(0) => break,
|
||||
Err(_) | Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
};
|
||||
buf.truncate(n);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! An UDP echo server that just sends back everything that it receives.
|
||||
//!
|
||||
//! If you're on unix you can test this out by in one terminal executing:
|
||||
//! If you're on Unix you can test this out by in one terminal executing:
|
||||
//!
|
||||
//! cargo run --example echo-udp
|
||||
//!
|
||||
@@ -16,11 +16,11 @@
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
|
||||
use std::{env, io};
|
||||
use std::net::SocketAddr;
|
||||
use std::{env, io};
|
||||
|
||||
use tokio::prelude::*;
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::prelude::*;
|
||||
|
||||
struct Server {
|
||||
socket: UdpSocket,
|
||||
@@ -50,12 +50,12 @@ impl Future for Server {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::Error>> {
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = addr.parse::<SocketAddr>().unwrap();
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let socket = UdpSocket::bind(&addr).unwrap();
|
||||
println!("Listening on: {}", socket.local_addr().unwrap());
|
||||
let socket = UdpSocket::bind(&addr)?;
|
||||
println!("Listening on: {}", socket.local_addr()?);
|
||||
|
||||
let server = Server {
|
||||
socket: socket,
|
||||
@@ -70,4 +70,5 @@ fn main() {
|
||||
//
|
||||
// `tokio::run` spawns the task on the Tokio runtime and starts running.
|
||||
tokio::run(server.map_err(|e| println!("server error = {:?}", e)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+6
-5
@@ -30,19 +30,19 @@ use tokio::prelude::*;
|
||||
use std::env;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<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 = addr.parse::<SocketAddr>().unwrap();
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop, so we pass in a handle
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let socket = TcpListener::bind(&addr).unwrap();
|
||||
let socket = TcpListener::bind(&addr)?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Here we convert the `TcpListener` to a stream of incoming connections
|
||||
@@ -54,7 +54,8 @@ fn main() {
|
||||
// connections made to the server). The return value of the `for_each`
|
||||
// method is itself a future representing processing the entire stream of
|
||||
// connections, and ends up being our server.
|
||||
let done = socket.incoming()
|
||||
let done = socket
|
||||
.incoming()
|
||||
.map_err(|e| println!("failed to accept socket; error = {:?}", e))
|
||||
.for_each(move |socket| {
|
||||
// Once we're inside this closure this represents an accepted client
|
||||
@@ -89,7 +90,6 @@ fn main() {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
|
||||
// And this is where much of the magic of this server happens. We
|
||||
// crucially want all clients to make progress concurrently, rather than
|
||||
// blocking one on completion of another. To achieve this we use the
|
||||
@@ -111,4 +111,5 @@ fn main() {
|
||||
// never completes (it just keeps accepting sockets), `tokio::run` blocks
|
||||
// forever (until ctrl-c is pressed).
|
||||
tokio::run(done);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+27
-39
@@ -1,58 +1,45 @@
|
||||
//! Hello world server.
|
||||
//!
|
||||
//! A simple server that accepts connections, writes "hello world\n", and closes
|
||||
//! A simple client that opens a TCP stream, writes "hello world\n", and closes
|
||||
//! the connection.
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! cargo run --example hello_world
|
||||
//! ncat -l 6142
|
||||
//!
|
||||
//! And then in another terminal run:
|
||||
//!
|
||||
//! telnet localhost 6142
|
||||
//!
|
||||
//! cargo run --example hello_world
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::prelude::*;
|
||||
|
||||
pub fn main() {
|
||||
let addr = "127.0.0.1:6142".parse().unwrap();
|
||||
pub fn main() -> Result<(), Box<std::error::Error>> {
|
||||
let addr = "127.0.0.1:6142".parse()?;
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
// Open a TCP stream to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
|
||||
// The server task asynchronously iterates over and processes each
|
||||
// incoming connection.
|
||||
let server = listener.incoming().for_each(|socket| {
|
||||
println!("accepted socket; addr={:?}", socket.peer_addr().unwrap());
|
||||
|
||||
let connection = io::write_all(socket, "hello world\n")
|
||||
.then(|res| {
|
||||
println!("wrote message; success={:?}", res.is_ok());
|
||||
// Note that this is the Tokio TcpStream, which is fully async.
|
||||
let client = TcpStream::connect(&addr)
|
||||
.and_then(|stream| {
|
||||
println!("created stream");
|
||||
io::write_all(stream, "hello world\n").then(|result| {
|
||||
println!("wrote to stream; success={:?}", result.is_ok());
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Spawn a new task that processes the socket:
|
||||
tokio::spawn(connection);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
// All tasks must have an `Error` type of `()`. This forces error
|
||||
// handling and helps avoid silencing failures.
|
||||
//
|
||||
// In our example, we are only going to log the error to STDOUT.
|
||||
println!("accept error = {:?}", err);
|
||||
});
|
||||
|
||||
println!("server running on localhost:6142");
|
||||
})
|
||||
})
|
||||
.map_err(|err| {
|
||||
// All tasks must have an `Error` type of `()`. This forces error
|
||||
// handling and helps avoid silencing failures.
|
||||
//
|
||||
// In our example, we are only going to log the error to STDOUT.
|
||||
println!("connection error = {:?}", err);
|
||||
});
|
||||
|
||||
// Start the Tokio runtime.
|
||||
//
|
||||
@@ -63,8 +50,9 @@ pub fn main() {
|
||||
// This function blocks until the runtime reaches an idle state. Idle is
|
||||
// defined as all spawned tasks have completed and all I/O resources (TCP
|
||||
// sockets in our case) have been dropped.
|
||||
//
|
||||
// In our example, we have not defined a shutdown strategy, so this will
|
||||
// block until `ctrl-c` is pressed at the terminal.
|
||||
tokio::run(server);
|
||||
println!("About to create the stream and write to it...");
|
||||
tokio::run(client);
|
||||
println!("Stream has been created and written to.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
extern crate tokio_current_thread;
|
||||
extern crate tokio_executor;
|
||||
extern crate tokio_reactor;
|
||||
extern crate tokio_timer;
|
||||
@@ -18,11 +19,11 @@ use std::io::Error as IoError;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use futures::{future, Future};
|
||||
use tokio::executor::current_thread::{self, CurrentThread};
|
||||
use tokio_current_thread::CurrentThread;
|
||||
use tokio_reactor::Reactor;
|
||||
use tokio_timer::timer::{self, Timer};
|
||||
|
||||
/// Creates a „runtime“.
|
||||
/// Creates a "runtime".
|
||||
///
|
||||
/// This is similar to running `tokio::runtime::current_thread::Runtime::new()`.
|
||||
fn run<F: Future<Item = (), Error = ()>>(f: F) -> Result<(), IoError> {
|
||||
@@ -46,7 +47,7 @@ fn run<F: Future<Item = (), Error = ()>>(f: F) -> Result<(), IoError> {
|
||||
// executor when used. This is a trick, because we need two mutable references to the
|
||||
// executor (one to run the provided future, another to install as the default one). We
|
||||
// use the fake one here as the default one.
|
||||
let mut default_executor = current_thread::TaskExecutor::current();
|
||||
let mut default_executor = tokio_current_thread::TaskExecutor::current();
|
||||
tokio_executor::with_default(&mut default_executor, enter, |enter| {
|
||||
let mut executor = executor.enter(enter);
|
||||
// Run the provided future
|
||||
@@ -59,9 +60,9 @@ fn run<F: Future<Item = (), Error = ()>>(f: F) -> Result<(), IoError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::Error>> {
|
||||
run(future::lazy(|| {
|
||||
// Here comes the application logic. It can spawn further tasks by current_thread::spawn().
|
||||
// Here comes the application logic. It can spawn further tasks by tokio_current_thread::spawn().
|
||||
// It also can use the default reactor and create timeouts.
|
||||
|
||||
// Connect somewhere. And then do nothing with it. Yes, useless.
|
||||
@@ -72,7 +73,7 @@ fn main() {
|
||||
.map_err(|e| println!("Failed to connect: {}", e));
|
||||
// We can spawn it without requiring Send. This would panic if we run it outside of the
|
||||
// `run` (or outside of anything else)
|
||||
current_thread::spawn(connect);
|
||||
tokio_current_thread::spawn(connect);
|
||||
|
||||
// We can also create timeouts.
|
||||
let deadline = tokio::timer::Delay::new(Instant::now() + Duration::from_secs(5))
|
||||
@@ -81,5 +82,6 @@ fn main() {
|
||||
// We can spawn on the default executor, which is also the local one.
|
||||
tokio::executor::spawn(deadline);
|
||||
Ok(())
|
||||
})).unwrap();
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -56,28 +56,28 @@
|
||||
|
||||
extern crate tokio;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_io;
|
||||
|
||||
use tokio_codec::{Decoder, BytesCodec};
|
||||
use tokio::codec::Decoder;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use tokio_codec::BytesCodec;
|
||||
|
||||
use std::env;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<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 = addr.parse::<SocketAddr>().unwrap();
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop, so we pass in a handle
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let socket = TcpListener::bind(&addr).unwrap();
|
||||
let socket = TcpListener::bind(&addr)?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Here we convert the `TcpListener` to a stream of incoming connections
|
||||
@@ -146,4 +146,5 @@ fn main() {
|
||||
// never completes (it just keeps accepting sockets), `tokio::run` blocks
|
||||
// forever (until ctrl-c is pressed).
|
||||
tokio::run(done);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+22
-20
@@ -24,28 +24,29 @@
|
||||
|
||||
extern crate tokio;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::env;
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use tokio::io::{copy, shutdown};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::prelude::*;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::Error>> {
|
||||
let listen_addr = env::args().nth(1).unwrap_or("127.0.0.1:8081".to_string());
|
||||
let listen_addr = listen_addr.parse::<SocketAddr>().unwrap();
|
||||
let listen_addr = listen_addr.parse::<SocketAddr>()?;
|
||||
|
||||
let server_addr = env::args().nth(2).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let server_addr = server_addr.parse::<SocketAddr>().unwrap();
|
||||
let server_addr = server_addr.parse::<SocketAddr>()?;
|
||||
|
||||
// Create a TCP listener which will listen for incoming connections.
|
||||
let socket = TcpListener::bind(&listen_addr).unwrap();
|
||||
let socket = TcpListener::bind(&listen_addr)?;
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
let done = socket.incoming()
|
||||
let done = socket
|
||||
.incoming()
|
||||
.map_err(|e| println!("error accepting socket; error = {:?}", e))
|
||||
.for_each(move |client| {
|
||||
let server = TcpStream::connect(&server_addr);
|
||||
@@ -68,25 +69,25 @@ fn main() {
|
||||
// After the copy is done we indicate to the remote side that we've
|
||||
// finished by shutting down the connection.
|
||||
let client_to_server = copy(client_reader, server_writer)
|
||||
.and_then(|(n, _, server_writer)| {
|
||||
shutdown(server_writer).map(move |_| n)
|
||||
});
|
||||
.and_then(|(n, _, server_writer)| shutdown(server_writer).map(move |_| n));
|
||||
|
||||
let server_to_client = copy(server_reader, client_writer)
|
||||
.and_then(|(n, _, client_writer)| {
|
||||
shutdown(client_writer).map(move |_| n)
|
||||
});
|
||||
.and_then(|(n, _, client_writer)| shutdown(client_writer).map(move |_| n));
|
||||
|
||||
client_to_server.join(server_to_client)
|
||||
});
|
||||
|
||||
let msg = amounts.map(move |(from_client, from_server)| {
|
||||
println!("client wrote {} bytes and received {} bytes",
|
||||
from_client, from_server);
|
||||
}).map_err(|e| {
|
||||
// Don't panic. Maybe the client just disconnected too soon.
|
||||
println!("error: {}", e);
|
||||
});
|
||||
let msg = amounts
|
||||
.map(move |(from_client, from_server)| {
|
||||
println!(
|
||||
"client wrote {} bytes and received {} bytes",
|
||||
from_client, from_server
|
||||
);
|
||||
})
|
||||
.map_err(|e| {
|
||||
// Don't panic. Maybe the client just disconnected too soon.
|
||||
println!("error: {}", e);
|
||||
});
|
||||
|
||||
tokio::spawn(msg);
|
||||
|
||||
@@ -94,6 +95,7 @@ fn main() {
|
||||
});
|
||||
|
||||
tokio::run(done);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This is a custom type used to have a custom implementation of the
|
||||
|
||||
+49
-28
@@ -44,8 +44,8 @@
|
||||
extern crate tokio;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::BufReader;
|
||||
use std::env;
|
||||
use std::io::BufReader;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -56,7 +56,7 @@ use tokio::prelude::*;
|
||||
/// The in-memory database shared amongst all clients.
|
||||
///
|
||||
/// This database will be shared via `Arc`, so to mutate the internal map we're
|
||||
/// also going to use a `RefCell` for interior mutability.
|
||||
/// going to use a `Mutex` for interior mutability.
|
||||
struct Database {
|
||||
map: Mutex<HashMap<String, String>>,
|
||||
}
|
||||
@@ -69,17 +69,26 @@ enum Request {
|
||||
|
||||
/// Responses to the `Request` commands above
|
||||
enum Response {
|
||||
Value { key: String, value: String },
|
||||
Set { key: String, value: String, previous: Option<String> },
|
||||
Error { msg: String },
|
||||
Value {
|
||||
key: String,
|
||||
value: String,
|
||||
},
|
||||
Set {
|
||||
key: String,
|
||||
value: String,
|
||||
previous: Option<String>,
|
||||
},
|
||||
Error {
|
||||
msg: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::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 = addr.parse::<SocketAddr>().unwrap();
|
||||
let listener = TcpListener::bind(&addr).expect("failed to bind");
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
let listener = TcpListener::bind(&addr).map_err(|_| "failed to bind")?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Create the shared state of this server that will be shared amongst all
|
||||
@@ -93,7 +102,8 @@ fn main() {
|
||||
map: Mutex::new(initial_db),
|
||||
});
|
||||
|
||||
let done = listener.incoming()
|
||||
let done = listener
|
||||
.incoming()
|
||||
.map_err(|e| println!("error accepting socket; error = {:?}", e))
|
||||
.for_each(move |socket| {
|
||||
// As with many other small examples, the first thing we'll do is
|
||||
@@ -124,15 +134,22 @@ fn main() {
|
||||
|
||||
let mut db = db.map.lock().unwrap();
|
||||
match request {
|
||||
Request::Get { key } => {
|
||||
match db.get(&key) {
|
||||
Some(value) => Response::Value { key, value: value.clone() },
|
||||
None => Response::Error { msg: format!("no key {}", key) },
|
||||
}
|
||||
}
|
||||
Request::Get { key } => match db.get(&key) {
|
||||
Some(value) => Response::Value {
|
||||
key,
|
||||
value: value.clone(),
|
||||
},
|
||||
None => Response::Error {
|
||||
msg: format!("no key {}", key),
|
||||
},
|
||||
},
|
||||
Request::Set { key, value } => {
|
||||
let previous = db.insert(key.clone(), value.clone());
|
||||
Response::Set { key, value, previous }
|
||||
Response::Set {
|
||||
key,
|
||||
value,
|
||||
previous,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -156,6 +173,7 @@ fn main() {
|
||||
});
|
||||
|
||||
tokio::run(done);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Request {
|
||||
@@ -168,9 +186,11 @@ impl Request {
|
||||
None => return Err(format!("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(format!("GET's key must not be followed by anything"));
|
||||
}
|
||||
Ok(Request::Get { key: key.to_string() })
|
||||
Ok(Request::Get {
|
||||
key: key.to_string(),
|
||||
})
|
||||
}
|
||||
Some("SET") => {
|
||||
let key = match parts.next() {
|
||||
@@ -181,7 +201,10 @@ impl Request {
|
||||
Some(value) => value,
|
||||
None => return Err(format!("SET needs a value")),
|
||||
};
|
||||
Ok(Request::Set { key: key.to_string(), value: value.to_string() })
|
||||
Ok(Request::Set {
|
||||
key: key.to_string(),
|
||||
value: value.to_string(),
|
||||
})
|
||||
}
|
||||
Some(cmd) => Err(format!("unknown command: {}", cmd)),
|
||||
None => Err(format!("empty input")),
|
||||
@@ -192,15 +215,13 @@ impl Request {
|
||||
impl Response {
|
||||
fn serialize(&self) -> String {
|
||||
match *self {
|
||||
Response::Value { ref key, ref value } => {
|
||||
format!("{} = {}", key, value)
|
||||
}
|
||||
Response::Set { ref key, ref value, ref previous } => {
|
||||
format!("set {} = `{}`, previous: {:?}", key, value, previous)
|
||||
}
|
||||
Response::Error { ref msg } => {
|
||||
format!("error: {}", msg)
|
||||
}
|
||||
Response::Value { ref key, ref value } => format!("{} = {}", key, value),
|
||||
Response::Set {
|
||||
ref key,
|
||||
ref value,
|
||||
ref previous,
|
||||
} => format!("set {} = `{}`, previous: {:?}", key, value, previous),
|
||||
Response::Error { ref msg } => format!("error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+74
-59
@@ -1,9 +1,9 @@
|
||||
//! A "tiny" example of HTTP request/response handling using just tokio-core
|
||||
//! A "tiny" example of HTTP request/response handling using transports.
|
||||
//!
|
||||
//! This example is intended for *learning purposes* to see how various pieces
|
||||
//! hook up together and how HTTP can get up and running. Note that this example
|
||||
//! is written with the restriction that it *can't* use any "big" library other
|
||||
//! than tokio-core, if you'd like a "real world" HTTP library you likely want a
|
||||
//! than Tokio, if you'd like a "real world" HTTP library you likely want a
|
||||
//! crate like Hyper.
|
||||
//!
|
||||
//! Code here is based on the `echo-threads` example and implements two paths,
|
||||
@@ -21,38 +21,38 @@ extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate time;
|
||||
extern crate tokio;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_io;
|
||||
|
||||
use std::{env, fmt, io};
|
||||
use std::net::SocketAddr;
|
||||
use std::{env, fmt, io};
|
||||
|
||||
use tokio::net::{TcpStream, TcpListener};
|
||||
use tokio::codec::{Decoder, Encoder};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::prelude::*;
|
||||
|
||||
use tokio_codec::{Encoder, Decoder};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use http::header::HeaderValue;
|
||||
use http::{Request, Response, StatusCode};
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::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 addr = addr.parse::<SocketAddr>().unwrap();
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let listener = TcpListener::bind(&addr).expect("failed to bind");
|
||||
let listener = TcpListener::bind(&addr)?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
tokio::run({
|
||||
listener.incoming()
|
||||
listener
|
||||
.incoming()
|
||||
.map_err(|e| println!("failed to accept socket; error = {:?}", e))
|
||||
.for_each(|socket| {
|
||||
process(socket);
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process(socket: TcpStream) {
|
||||
@@ -65,14 +65,13 @@ fn process(socket: TcpStream) {
|
||||
.split();
|
||||
|
||||
// Map all requests into responses and send them back to the client.
|
||||
let task = tx.send_all(rx.and_then(respond))
|
||||
.then(|res| {
|
||||
if let Err(e) = res {
|
||||
println!("failed to process connection; error = {:?}", e);
|
||||
}
|
||||
let task = tx.send_all(rx.and_then(respond)).then(|res| {
|
||||
if let Err(e) = res {
|
||||
println!("failed to process connection; error = {:?}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Spawn the task that handles the connection.
|
||||
tokio::spawn(task);
|
||||
@@ -83,31 +82,37 @@ fn process(socket: TcpStream) {
|
||||
/// This function is a map from and HTTP request to a future of a response and
|
||||
/// represents the various handling a server might do. Currently the contents
|
||||
/// here are pretty uninteresting.
|
||||
fn respond(req: Request<()>)
|
||||
-> Box<Future<Item = Response<String>, Error = io::Error> + Send>
|
||||
{
|
||||
let mut ret = Response::builder();
|
||||
let body = match req.uri().path() {
|
||||
"/plaintext" => {
|
||||
ret.header("Content-Type", "text/plain");
|
||||
"Hello, World!".to_string()
|
||||
}
|
||||
"/json" => {
|
||||
ret.header("Content-Type", "application/json");
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Message {
|
||||
message: &'static str,
|
||||
fn respond(req: Request<()>) -> Box<Future<Item = Response<String>, Error = io::Error> + Send> {
|
||||
let f = future::lazy(move || {
|
||||
let mut response = Response::builder();
|
||||
let body = match req.uri().path() {
|
||||
"/plaintext" => {
|
||||
response.header("Content-Type", "text/plain");
|
||||
"Hello, World!".to_string()
|
||||
}
|
||||
serde_json::to_string(&Message { message: "Hello, World!" })
|
||||
.unwrap()
|
||||
}
|
||||
_ => {
|
||||
ret.status(StatusCode::NOT_FOUND);
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
Box::new(future::ok(ret.body(body).unwrap()))
|
||||
"/json" => {
|
||||
response.header("Content-Type", "application/json");
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Message {
|
||||
message: &'static str,
|
||||
}
|
||||
serde_json::to_string(&Message {
|
||||
message: "Hello, World!",
|
||||
})?
|
||||
}
|
||||
_ => {
|
||||
response.status(StatusCode::NOT_FOUND);
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
let response = response
|
||||
.body(body)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||
Ok(response)
|
||||
});
|
||||
|
||||
Box::new(f)
|
||||
}
|
||||
|
||||
struct Http;
|
||||
@@ -121,12 +126,19 @@ impl Encoder for Http {
|
||||
fn encode(&mut self, item: Response<String>, dst: &mut BytesMut) -> io::Result<()> {
|
||||
use std::fmt::Write;
|
||||
|
||||
write!(BytesWrite(dst), "\
|
||||
HTTP/1.1 {}\r\n\
|
||||
Server: Example\r\n\
|
||||
Content-Length: {}\r\n\
|
||||
Date: {}\r\n\
|
||||
", item.status(), item.body().len(), date::now()).unwrap();
|
||||
write!(
|
||||
BytesWrite(dst),
|
||||
"\
|
||||
HTTP/1.1 {}\r\n\
|
||||
Server: Example\r\n\
|
||||
Content-Length: {}\r\n\
|
||||
Date: {}\r\n\
|
||||
",
|
||||
item.status(),
|
||||
item.body().len(),
|
||||
date::now()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (k, v) in item.headers() {
|
||||
dst.extend_from_slice(k.as_str().as_bytes());
|
||||
@@ -195,13 +207,18 @@ impl Decoder for Http {
|
||||
headers[i] = Some((k, v));
|
||||
}
|
||||
|
||||
(toslice(r.method.unwrap().as_bytes()),
|
||||
toslice(r.path.unwrap().as_bytes()),
|
||||
r.version.unwrap(),
|
||||
amt)
|
||||
(
|
||||
toslice(r.method.unwrap().as_bytes()),
|
||||
toslice(r.path.unwrap().as_bytes()),
|
||||
r.version.unwrap(),
|
||||
amt,
|
||||
)
|
||||
};
|
||||
if version != 1 {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "only HTTP/1.1 accepted"))
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"only HTTP/1.1 accepted",
|
||||
));
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
@@ -213,15 +230,13 @@ impl Decoder for Http {
|
||||
Some((ref k, ref v)) => (k, v),
|
||||
None => break,
|
||||
};
|
||||
let value = unsafe {
|
||||
HeaderValue::from_shared_unchecked(data.slice(v.0, v.1))
|
||||
};
|
||||
let value = unsafe { HeaderValue::from_shared_unchecked(data.slice(v.0, v.1)) };
|
||||
ret.header(&data[k.0..k.1], value);
|
||||
}
|
||||
|
||||
let req = ret.body(()).map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
})?;
|
||||
let req = ret
|
||||
.body(())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(Some(req))
|
||||
}
|
||||
}
|
||||
|
||||
+12
-16
@@ -35,29 +35,28 @@ use std::net::SocketAddr;
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::prelude::*;
|
||||
|
||||
fn get_stdin_data() -> Vec<u8> {
|
||||
fn get_stdin_data() -> Result<Vec<u8>, Box<std::error::Error>> {
|
||||
let mut buf = Vec::new();
|
||||
stdin().read_to_end(&mut buf).unwrap();
|
||||
buf
|
||||
stdin().read_to_end(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::Error>> {
|
||||
let remote_addr: SocketAddr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or("127.0.0.1:8080".into())
|
||||
.parse()
|
||||
.unwrap();
|
||||
.parse()?;
|
||||
// We use port 0 to let the operating system allocate an available port for us.
|
||||
let local_addr: SocketAddr = if remote_addr.is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
}.parse()
|
||||
.unwrap();
|
||||
let socket = UdpSocket::bind(&local_addr).unwrap();
|
||||
}
|
||||
.parse()?;
|
||||
let socket = UdpSocket::bind(&local_addr)?;
|
||||
const MAX_DATAGRAM_SIZE: usize = 65_507;
|
||||
let processing = socket
|
||||
.send_dgram(get_stdin_data(), &remote_addr)
|
||||
socket
|
||||
.send_dgram(get_stdin_data()?, &remote_addr)
|
||||
.and_then(|(socket, _)| socket.recv_dgram(vec![0u8; MAX_DATAGRAM_SIZE]))
|
||||
.map(|(_, data, len, _)| {
|
||||
println!(
|
||||
@@ -66,9 +65,6 @@ fn main() {
|
||||
String::from_utf8_lossy(&data[..len])
|
||||
)
|
||||
})
|
||||
.wait();
|
||||
match processing {
|
||||
Ok(_) => {}
|
||||
Err(e) => eprintln!("Encountered an error: {}", e),
|
||||
}
|
||||
.wait()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -8,26 +8,26 @@
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate tokio;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_io;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio::net::{UdpFramed, UdpSocket};
|
||||
use tokio::prelude::*;
|
||||
use tokio::net::{UdpSocket, UdpFramed};
|
||||
use tokio_codec::BytesCodec;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<std::error::Error>> {
|
||||
let _ = env_logger::init();
|
||||
|
||||
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let addr: SocketAddr = "127.0.0.1:0".parse()?;
|
||||
|
||||
// Bind both our sockets and then figure out what ports we got.
|
||||
let a = UdpSocket::bind(&addr).unwrap();
|
||||
let b = UdpSocket::bind(&addr).unwrap();
|
||||
let b_addr = b.local_addr().unwrap();
|
||||
let a = UdpSocket::bind(&addr)?;
|
||||
let b = UdpSocket::bind(&addr)?;
|
||||
let b_addr = b.local_addr()?;
|
||||
|
||||
// We're parsing each socket with the `BytesCodec` included in `tokio_io`, and then we
|
||||
// `split` each codec into the sink/stream halves.
|
||||
@@ -61,4 +61,5 @@ fn main() {
|
||||
.map(|_| ())
|
||||
.map_err(|e| println!("error = {:?}", e))
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
//! Task execution 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 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.
|
||||
//!
|
||||
//! The specific strategy used to manage the tasks is left up to the
|
||||
//! executor. There are two main flavors of executors: single-threaded and
|
||||
//! multithreaded. This module provides both.
|
||||
//!
|
||||
//! * **[`current_thread`]**: A single-threaded executor that support spawning
|
||||
//! tasks that are not `Send`. It guarantees that tasks will be executed on
|
||||
//! the same thread from which they are spawned.
|
||||
//!
|
||||
//! * **[`thread_pool`]**: A multi-threaded executor that maintains a pool of
|
||||
//! threads. Tasks are spawned to one of the threads in the pool and executed.
|
||||
//! The pool employs a [work-stealing] strategy for optimizing how tasks get
|
||||
//! spread across the available threads.
|
||||
//!
|
||||
//! # `Executor` trait.
|
||||
//!
|
||||
//! This module provides the [`Executor`] trait (re-exported from
|
||||
//! [`tokio-executor`]), which describes the API that all executors must
|
||||
//! implement.
|
||||
//!
|
||||
//! A free [`spawn`] function is provided that allows spawning futures onto the
|
||||
//! default executor (tracked via a thread-local variable) without referencing a
|
||||
//! handle. It is expected that all executors will set a value for the default
|
||||
//! executor. This value will often be set to the executor itself, but it is
|
||||
//! possible that the default executor might be set to a different executor.
|
||||
//!
|
||||
//! For example, the [`current_thread`] executor might set the default executor
|
||||
//! to a thread pool instead of itself, allowing futures to spawn new tasks onto
|
||||
//! the thread pool when those tasks are `Send`.
|
||||
//!
|
||||
//! [`Future::poll`]: https://docs.rs/futures/0.1/futures/future/trait.Future.html#tymethod.poll
|
||||
//! [notified]: https://docs.rs/futures/0.1/futures/executor/trait.Notify.html#tymethod.notify
|
||||
//! [`current_thread`]: current_thread/index.html
|
||||
//! [`thread_pool`]: thread_pool/index.html
|
||||
//! [work-stealing]: https://en.wikipedia.org/wiki/Work_stealing
|
||||
//! [`tokio-executor`]: #
|
||||
//! [`Executor`]: #
|
||||
//! [`spawn`]: #
|
||||
|
||||
pub mod current_thread;
|
||||
|
||||
pub mod thread_pool {
|
||||
//! Maintains a pool of threads across which the set of spawned tasks are
|
||||
//! executed.
|
||||
//!
|
||||
//! [`ThreadPool`] is an executor that uses a thread pool for executing
|
||||
//! tasks concurrently across multiple cores. It uses a thread pool that is
|
||||
//! optimized for use cases that involve multiplexing large number of
|
||||
//! independent tasks that perform short(ish) amounts of computation and are
|
||||
//! mainly waiting on I/O, i.e. the Tokio use case.
|
||||
//!
|
||||
//! Usually, users of [`ThreadPool`] will not create pool instances.
|
||||
//! Instead, they will create a [`Runtime`] instance, which comes with a
|
||||
//! pre-configured thread pool.
|
||||
//!
|
||||
//! At the core, [`ThreadPool`] uses a work-stealing based scheduling
|
||||
//! strategy. When spawning a task while *external* to the thread pool
|
||||
//! (i.e., from a thread that is not part of the thread pool), the task is
|
||||
//! randomly assigned to a worker thread. When spawning a task while
|
||||
//! *internal* to the thread pool, the task is assigned to the current
|
||||
//! worker.
|
||||
//!
|
||||
//! Each worker maintains its own queue and first focuses on processing all
|
||||
//! tasks in its queue. When the worker's queue is empty, the worker will
|
||||
//! attempt to *steal* tasks from other worker queues. This strategy helps
|
||||
//! ensure that work is evenly distributed across threads while minimizing
|
||||
//! synchronization between worker threads.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Thread pool instances are created using [`ThreadPool::new`] or
|
||||
//! [`Builder::new`]. The first option returns a thread pool with default
|
||||
//! configuration values. The second option allows configuring the thread
|
||||
//! pool before instantiating it.
|
||||
//!
|
||||
//! Once an instance is obtained, futures may be spawned onto it using the
|
||||
//! [`spawn`] function.
|
||||
//!
|
||||
//! A handle to the thread pool is obtained using [`ThreadPool::sender`].
|
||||
//! This handle is **only** able to spawn futures onto the thread pool. It
|
||||
//! is unable to affect the lifecycle of the thread pool in any way. This
|
||||
//! handle can be passed into functions or stored in structs as a way to
|
||||
//! grant the capability of spawning futures.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate tokio;
|
||||
//! # extern crate futures;
|
||||
//! # use tokio::executor::thread_pool::ThreadPool;
|
||||
//! use futures::future::{Future, lazy};
|
||||
//!
|
||||
//! # pub fn main() {
|
||||
//! // Create a thread pool with default configuration values
|
||||
//! let thread_pool = ThreadPool::new();
|
||||
//!
|
||||
//! thread_pool.spawn(lazy(|| {
|
||||
//! println!("called from a worker thread");
|
||||
//! Ok(())
|
||||
//! }));
|
||||
//!
|
||||
//! // Gracefully shutdown the threadpool
|
||||
//! thread_pool.shutdown().wait().unwrap();
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! [`ThreadPool`]: struct.ThreadPool.html
|
||||
//! [`ThreadPool::new`]: struct.ThreadPool.html#method.new
|
||||
//! [`ThreadPool::sender`]: struct.ThreadPool.html#method.sender
|
||||
//! [`spawn`]: struct.ThreadPool.html#method.spawn
|
||||
//! [`Builder::new`]: struct.Builder.html#method.new
|
||||
//! [`Runtime`]: ../../runtime/struct.Runtime.html
|
||||
|
||||
pub use tokio_threadpool::{
|
||||
Builder,
|
||||
Sender,
|
||||
Shutdown,
|
||||
ThreadPool,
|
||||
};
|
||||
}
|
||||
|
||||
pub use tokio_executor::{Executor, DefaultExecutor, SpawnError};
|
||||
|
||||
use futures::{Future, IntoFuture};
|
||||
use futures::future::{self, FutureResult};
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
use futures2;
|
||||
|
||||
/// Return value from the `spawn` function.
|
||||
///
|
||||
/// Currently this value doesn't actually provide any functionality. However, it
|
||||
/// provides a way to add functionality later without breaking backwards
|
||||
/// compatibility.
|
||||
///
|
||||
/// This also implements `IntoFuture` so that it can be used as the return value
|
||||
/// in a `for_each` loop.
|
||||
///
|
||||
/// See [`spawn`] for more details.
|
||||
///
|
||||
/// [`spawn`]: fn.spawn.html
|
||||
#[derive(Debug)]
|
||||
pub struct Spawn(());
|
||||
|
||||
/// Spawns a future on the default executor.
|
||||
///
|
||||
/// In order for a future to do work, it must be spawned on an executor. The
|
||||
/// `spawn` function is the easiest way to do this. It spawns a future on the
|
||||
/// [default executor] for the current execution context (tracked using a
|
||||
/// thread-local variable).
|
||||
///
|
||||
/// The default executor is **usually** a thread pool.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// In this example, a server is started and `spawn` is used to start a new task
|
||||
/// that processes each received connection.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::{Future, Stream};
|
||||
/// use tokio::net::TcpListener;
|
||||
///
|
||||
/// # fn process<T>(_: T) -> Box<Future<Item = (), Error = ()> + Send> {
|
||||
/// # unimplemented!();
|
||||
/// # }
|
||||
/// # fn dox() {
|
||||
/// # let addr = "127.0.0.1:8080".parse().unwrap();
|
||||
/// let listener = TcpListener::bind(&addr).unwrap();
|
||||
///
|
||||
/// let server = listener.incoming()
|
||||
/// .map_err(|e| println!("error = {:?}", e))
|
||||
/// .for_each(|socket| {
|
||||
/// tokio::spawn(process(socket))
|
||||
/// });
|
||||
///
|
||||
/// tokio::run(server);
|
||||
/// # }
|
||||
/// # pub fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// [default executor]: struct.DefaultExecutor.html
|
||||
///
|
||||
/// # 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
|
||||
/// [`DefaultExecutor`].
|
||||
///
|
||||
/// [`DefaultExecutor`]: struct.DefaultExecutor.html
|
||||
pub fn spawn<F>(f: F) -> Spawn
|
||||
where F: Future<Item = (), Error = ()> + 'static + Send
|
||||
{
|
||||
::tokio_executor::spawn(f);
|
||||
Spawn(())
|
||||
}
|
||||
|
||||
/// Like `spawn`, but compatible with futures 0.2
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
pub fn spawn2<F>(f: F) -> Spawn
|
||||
where F: futures2::Future<Item = (), Error = futures2::Never> + 'static + Send
|
||||
{
|
||||
::tokio_executor::spawn2(f);
|
||||
Spawn(())
|
||||
}
|
||||
|
||||
impl IntoFuture for Spawn {
|
||||
type Future = FutureResult<(), ()>;
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn into_future(self) -> Self::Future {
|
||||
future::ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
impl futures2::IntoFuture for Spawn {
|
||||
type Future = futures2::future::FutureResult<(), ()>;
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn into_future(self) -> Self::Future {
|
||||
futures2::future::ok(())
|
||||
}
|
||||
}
|
||||
-235
@@ -1,235 +0,0 @@
|
||||
//! A runtime for writing reliable, asynchronous, and slim applications.
|
||||
//!
|
||||
//! Tokio is an event-driven, non-blocking I/O platform for writing asynchronous
|
||||
//! applications with the Rust programming language. At a high level, it
|
||||
//! provides a few major components:
|
||||
//!
|
||||
//! * A multi threaded, work-stealing based task [scheduler][runtime].
|
||||
//! * A [reactor][reactor] backed by the operating system's event queue (epoll, kqueue,
|
||||
//! IOCP, etc...).
|
||||
//! * Asynchronous [TCP and UDP][net] sockets.
|
||||
//! * Asynchronous [filesystem][fs] operations.
|
||||
//! * [Timer][timer] API for scheduling work in the future.
|
||||
//!
|
||||
//! Tokio is built using [futures] as the abstraction for managing the
|
||||
//! complexity of asynchronous programming.
|
||||
//!
|
||||
//! Guide level documentation is found on the [website].
|
||||
//!
|
||||
//! [website]: https://tokio.rs/docs/getting-started/hello-world/
|
||||
//! [futures]: http://docs.rs/futures
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! A simple TCP echo server:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! extern crate tokio;
|
||||
//!
|
||||
//! use tokio::prelude::*;
|
||||
//! use tokio::io::copy;
|
||||
//! use tokio::net::TcpListener;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! // Bind the server's socket.
|
||||
//! let addr = "127.0.0.1:12345".parse().unwrap();
|
||||
//! let listener = TcpListener::bind(&addr)
|
||||
//! .expect("unable to bind TCP listener");
|
||||
//!
|
||||
//! // Pull out a stream of sockets for incoming connections
|
||||
//! let server = listener.incoming()
|
||||
//! .map_err(|e| eprintln!("accept failed = {:?}", e))
|
||||
//! .for_each(|sock| {
|
||||
//! // Split up the reading and writing parts of the
|
||||
//! // socket.
|
||||
//! let (reader, writer) = sock.split();
|
||||
//!
|
||||
//! // A future that echos the data and returns how
|
||||
//! // many bytes were copied...
|
||||
//! let bytes_copied = copy(reader, writer);
|
||||
//!
|
||||
//! // ... after which we'll print what happened.
|
||||
//! let handle_conn = bytes_copied.map(|amt| {
|
||||
//! println!("wrote {:?} bytes", amt)
|
||||
//! }).map_err(|err| {
|
||||
//! eprintln!("IO error {:?}", err)
|
||||
//! });
|
||||
//!
|
||||
//! // Spawn the future as a concurrent task.
|
||||
//! tokio::spawn(handle_conn)
|
||||
//! });
|
||||
//!
|
||||
//! // Start the Tokio runtime
|
||||
//! tokio::run(server);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![doc(html_root_url = "https://docs.rs/tokio/0.1.5")]
|
||||
#![deny(missing_docs, warnings, missing_debug_implementations)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate mio;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_executor;
|
||||
extern crate tokio_fs;
|
||||
extern crate tokio_reactor;
|
||||
extern crate tokio_threadpool;
|
||||
extern crate tokio_timer;
|
||||
extern crate tokio_tcp;
|
||||
extern crate tokio_udp;
|
||||
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
extern crate futures2;
|
||||
|
||||
pub mod clock;
|
||||
pub mod executor;
|
||||
pub mod fs;
|
||||
pub mod net;
|
||||
pub mod reactor;
|
||||
pub mod runtime;
|
||||
pub mod timer;
|
||||
pub mod util;
|
||||
|
||||
pub use executor::spawn;
|
||||
#[cfg(feature = "unstable-futures")]
|
||||
pub use executor::spawn2;
|
||||
|
||||
pub use runtime::run;
|
||||
|
||||
pub mod io {
|
||||
//! Asynchronous I/O.
|
||||
//!
|
||||
//! This module is the asynchronous version of `std::io`. Primarily, it
|
||||
//! defines two traits, [`AsyncRead`] and [`AsyncWrite`], which extend the
|
||||
//! `Read` and `Write` traits of the standard library.
|
||||
//!
|
||||
//! # AsyncRead and AsyncWrite
|
||||
//!
|
||||
//! [`AsyncRead`] and [`AsyncWrite`] must only be implemented for
|
||||
//! non-blocking I/O types that integrate with the futures type system. In
|
||||
//! other words, these types must never block the thread, and instead the
|
||||
//! current task is notified when the I/O resource is ready.
|
||||
//!
|
||||
//! # Standard input and output
|
||||
//!
|
||||
//! Tokio provides asynchronous APIs to standard [input], [output], and [error].
|
||||
//! These APIs are very similar to the ones provided by `std`, but they also
|
||||
//! implement [`AsyncRead`] and [`AsyncWrite`].
|
||||
//!
|
||||
//! Unlike *most* other Tokio APIs, the standard input / output APIs
|
||||
//! **must** be used from the context of the Tokio runtime as they require
|
||||
//! Tokio specific features to function.
|
||||
//!
|
||||
//! [input]: fn.stdin.html
|
||||
//! [output]: fn.stdout.html
|
||||
//! [error]: fn.stderr.html
|
||||
//!
|
||||
//! # Utility functions
|
||||
//!
|
||||
//! Utilities functions are provided for working with [`AsyncRead`] /
|
||||
//! [`AsyncWrite`] types. For example, [`copy`] asynchronously copies all
|
||||
//! data from a source to a destination.
|
||||
//!
|
||||
//! # `std` re-exports
|
||||
//!
|
||||
//! Additionally, [`Read`], [`Write`], [`Error`], [`ErrorKind`], and
|
||||
//! [`Result`] are re-exported from `std::io` for ease of use.
|
||||
//!
|
||||
//! [`AsyncRead`]: trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: trait.AsyncWrite.html
|
||||
//! [`copy`]: fn.copy.html
|
||||
//! [`Read`]: trait.Read.html
|
||||
//! [`Write`]: trait.Write.html
|
||||
//! [`Error`]: struct.Error.html
|
||||
//! [`ErrorKind`]: enum.ErrorKind.html
|
||||
//! [`Result`]: type.Result.html
|
||||
|
||||
pub use tokio_io::{
|
||||
AsyncRead,
|
||||
AsyncWrite,
|
||||
};
|
||||
|
||||
// standard input, output, and error
|
||||
pub use tokio_fs::{
|
||||
stdin,
|
||||
Stdin,
|
||||
stdout,
|
||||
Stdout,
|
||||
stderr,
|
||||
Stderr,
|
||||
};
|
||||
|
||||
// Utils
|
||||
pub use tokio_io::io::{
|
||||
copy,
|
||||
Copy,
|
||||
flush,
|
||||
Flush,
|
||||
lines,
|
||||
Lines,
|
||||
read_exact,
|
||||
ReadExact,
|
||||
read_to_end,
|
||||
ReadToEnd,
|
||||
read_until,
|
||||
ReadUntil,
|
||||
ReadHalf,
|
||||
shutdown,
|
||||
Shutdown,
|
||||
write_all,
|
||||
WriteAll,
|
||||
WriteHalf,
|
||||
};
|
||||
|
||||
// Re-export io::Error so that users don't have to deal
|
||||
// with conflicts when `use`ing `futures::io` and `std::io`.
|
||||
pub use ::std::io::{
|
||||
Error,
|
||||
ErrorKind,
|
||||
Result,
|
||||
Read,
|
||||
Write,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
//! A "prelude" for users of the `tokio` crate.
|
||||
//!
|
||||
//! This prelude is similar to the standard library's prelude in that you'll
|
||||
//! almost always want to import its entire contents, but unlike the standard
|
||||
//! library's prelude you'll have to do so manually:
|
||||
//!
|
||||
//! ```
|
||||
//! use tokio::prelude::*;
|
||||
//! ```
|
||||
//!
|
||||
//! The prelude may grow over time as additional items see ubiquitous use.
|
||||
|
||||
pub use tokio_io::{
|
||||
AsyncRead,
|
||||
AsyncWrite,
|
||||
};
|
||||
|
||||
pub use util::{
|
||||
FutureExt,
|
||||
};
|
||||
|
||||
pub use ::std::io::{
|
||||
Read,
|
||||
Write,
|
||||
};
|
||||
|
||||
pub use futures::{
|
||||
Future,
|
||||
future,
|
||||
Stream,
|
||||
stream,
|
||||
Sink,
|
||||
IntoFuture,
|
||||
Async,
|
||||
AsyncSink,
|
||||
Poll,
|
||||
task,
|
||||
};
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
//! TCP/UDP bindings for `tokio`.
|
||||
//!
|
||||
//! This module contains the TCP/UDP networking types, similar to the standard
|
||||
//! library, which can be used to implement networking protocols.
|
||||
//!
|
||||
//! # TCP
|
||||
//!
|
||||
//! Connecting to an address, via TCP, can be done using [`TcpStream`]'s
|
||||
//! [`connect`] method, which returns [`ConnectFuture`]. `ConnectFuture`
|
||||
//! implements a future which returns a `TcpStream`.
|
||||
//!
|
||||
//! To listen on an address [`TcpListener`] can be used. `TcpListener`'s
|
||||
//! [`incoming`][incoming_method] method can be used to accept new connections.
|
||||
//! It return the [`Incoming`] struct, which implements a stream which returns
|
||||
//! `TcpStream`s.
|
||||
//!
|
||||
//! [`TcpStream`]: struct.TcpStream.html
|
||||
//! [`connect`]: struct.TcpStream.html#method.connect
|
||||
//! [`ConnectFuture`]: struct.ConnectFuture.html
|
||||
//! [`TcpListener`]: struct.TcpListener.html
|
||||
//! [incoming_method]: struct.TcpListener.html#method.incoming
|
||||
//! [`Incoming`]: struct.Incoming.html
|
||||
//!
|
||||
//! # UDP
|
||||
//!
|
||||
//! The main struct for UDP is the [`UdpSocket`], which represents a UDP socket.
|
||||
//! Reading and writing to it can be done using futures, which return the
|
||||
//! [`RecvDgram`] and [`SendDgram`] structs respectively.
|
||||
//!
|
||||
//! For convenience it's also possible to convert raw datagrams into higher-level
|
||||
//! frames.
|
||||
//!
|
||||
//! [`UdpSocket`]: struct.UdpSocket.html
|
||||
//! [`RecvDgram`]: struct.RecvDgram.html
|
||||
//! [`SendDgram`]: struct.SendDgram.html
|
||||
//! [`UdpFramed`]: struct.UdpFramed.html
|
||||
//! [`framed`]: struct.UdpSocket.html#method.framed
|
||||
|
||||
pub use tokio_tcp::{TcpStream, ConnectFuture};
|
||||
pub use tokio_tcp::{TcpListener, Incoming};
|
||||
pub use tokio_udp::{UdpSocket, UdpFramed, SendDgram, RecvDgram};
|
||||
@@ -1,148 +0,0 @@
|
||||
use runtime::{Inner, Runtime};
|
||||
|
||||
use reactor::Reactor;
|
||||
|
||||
use std::io;
|
||||
|
||||
use tokio_reactor;
|
||||
use tokio_threadpool::Builder as ThreadPoolBuilder;
|
||||
use tokio_threadpool::park::DefaultPark;
|
||||
use tokio_timer::clock::{self, Clock};
|
||||
use tokio_timer::timer::{self, Timer};
|
||||
|
||||
/// Builds Tokio Runtime with custom configuration values.
|
||||
///
|
||||
/// Methods can be chained in order to set the configuration values. The
|
||||
/// Runtime 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
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate tokio_threadpool;
|
||||
/// # use tokio::runtime::Builder;
|
||||
///
|
||||
/// # pub fn main() {
|
||||
/// // create and configure ThreadPool
|
||||
/// let mut threadpool_builder = tokio_threadpool::Builder::new();
|
||||
/// threadpool_builder
|
||||
/// .name_prefix("my-runtime-worker-")
|
||||
/// .pool_size(4);
|
||||
///
|
||||
/// // build Runtime
|
||||
/// let runtime = Builder::new()
|
||||
/// .threadpool_builder(threadpool_builder)
|
||||
/// .build();
|
||||
/// // ... call runtime.run(...)
|
||||
/// # let _ = runtime;
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Builder {
|
||||
/// Thread pool specific builder
|
||||
threadpool_builder: ThreadPoolBuilder,
|
||||
|
||||
/// The clock to use
|
||||
clock: Clock,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Returns a new runtime builder initialized with default configuration
|
||||
/// values.
|
||||
///
|
||||
/// Configuration methods can be chained on the return value.
|
||||
pub fn new() -> Builder {
|
||||
let mut threadpool_builder = ThreadPoolBuilder::new();
|
||||
threadpool_builder.name_prefix("tokio-runtime-worker-");
|
||||
|
||||
Builder {
|
||||
threadpool_builder,
|
||||
clock: Clock::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `Clock` instance that will be used by the runtime.
|
||||
pub fn clock(&mut self, clock: Clock) -> &mut Self {
|
||||
self.clock = clock;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set builder to set up the thread pool instance.
|
||||
pub fn threadpool_builder(&mut self, val: ThreadPoolBuilder) -> &mut Self {
|
||||
self.threadpool_builder = val;
|
||||
self
|
||||
}
|
||||
|
||||
/// Create the configured `Runtime`.
|
||||
///
|
||||
/// The returned `ThreadPool` instance is ready to spawn tasks.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate tokio;
|
||||
/// # use tokio::runtime::Builder;
|
||||
/// # pub fn main() {
|
||||
/// let runtime = Builder::new().build().unwrap();
|
||||
/// // ... call runtime.run(...)
|
||||
/// # let _ = runtime;
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn build(&mut self) -> io::Result<Runtime> {
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// Get a handle to the clock for the runtime.
|
||||
let clock1 = self.clock.clone();
|
||||
let clock2 = clock1.clone();
|
||||
|
||||
let timers = Arc::new(Mutex::new(HashMap::<_, timer::Handle>::new()));
|
||||
let t1 = timers.clone();
|
||||
|
||||
// Spawn a reactor on a background thread.
|
||||
let reactor = Reactor::new()?.background()?;
|
||||
|
||||
// Get a handle to the reactor.
|
||||
let reactor_handle = reactor.handle().clone();
|
||||
|
||||
let pool = self.threadpool_builder
|
||||
.around_worker(move |w, enter| {
|
||||
let timer_handle = t1.lock().unwrap()
|
||||
.get(w.id()).unwrap()
|
||||
.clone();
|
||||
|
||||
tokio_reactor::with_default(&reactor_handle, enter, |enter| {
|
||||
clock::with_default(&clock1, enter, |enter| {
|
||||
timer::with_default(&timer_handle, enter, |_| {
|
||||
w.run();
|
||||
});
|
||||
})
|
||||
});
|
||||
})
|
||||
.custom_park(move |worker_id| {
|
||||
// Create a new timer
|
||||
let timer = Timer::new_with_now(DefaultPark::new(), clock2.clone());
|
||||
|
||||
timers.lock().unwrap()
|
||||
.insert(worker_id.clone(), timer.handle());
|
||||
|
||||
timer
|
||||
})
|
||||
.build();
|
||||
|
||||
Ok(Runtime {
|
||||
inner: Some(Inner {
|
||||
reactor,
|
||||
pool,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
use tokio_timer::Deadline;
|
||||
|
||||
use futures::Future;
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
|
||||
/// An extension trait for `Future` that provides a variety of convenient
|
||||
/// combinator functions.
|
||||
///
|
||||
/// Currently, there only is a [`deadline`] function, but this will increase
|
||||
/// over time.
|
||||
///
|
||||
/// Users are not expected to implement this trait. All types that implement
|
||||
/// `Future` already implement `FutureExt`.
|
||||
///
|
||||
/// This trait can be imported directly or via the Tokio prelude: `use
|
||||
/// tokio::prelude::*`.
|
||||
///
|
||||
/// [`deadline`]: #method.deadline
|
||||
pub trait FutureExt: Future {
|
||||
|
||||
/// Creates a new future which allows `self` until `deadline`.
|
||||
///
|
||||
/// This combinator creates a new future which wraps the receiving future
|
||||
/// with a deadline. The returned future is allowed to execute until it
|
||||
/// completes or `deadline` is reached, whichever happens first.
|
||||
///
|
||||
/// If the future completes before `deadline` then the future will resolve
|
||||
/// with that item. Otherwise the future will resolve to an error once
|
||||
/// `deadline` is reached.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate futures;
|
||||
/// use tokio::prelude::*;
|
||||
/// use std::time::{Duration, Instant};
|
||||
/// # use futures::future::{self, FutureResult};
|
||||
///
|
||||
/// # fn long_future() -> FutureResult<(), ()> {
|
||||
/// # future::ok(())
|
||||
/// # }
|
||||
/// #
|
||||
/// # fn main() {
|
||||
/// let future = long_future()
|
||||
/// .deadline(Instant::now() + Duration::from_secs(1))
|
||||
/// .map_err(|e| println!("error = {:?}", e));
|
||||
///
|
||||
/// tokio::run(future);
|
||||
/// # }
|
||||
/// ```
|
||||
fn deadline(self, deadline: Instant) -> Deadline<Self>
|
||||
where Self: Sized,
|
||||
{
|
||||
Deadline::new(self, deadline)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> FutureExt for T where T: Future {}
|
||||
@@ -1,9 +0,0 @@
|
||||
//! Utilities for working with Tokio.
|
||||
//!
|
||||
//! This module contains utilities that are useful for working with Tokio.
|
||||
//! Currently, this only includes [`FutureExt`][FutureExt]. However, this will
|
||||
//! include over time.
|
||||
|
||||
mod future;
|
||||
|
||||
pub use self::future::FutureExt;
|
||||
@@ -1,622 +0,0 @@
|
||||
#![cfg(not(feature = "unstable-futures"))]
|
||||
|
||||
extern crate tokio;
|
||||
extern crate tokio_executor;
|
||||
extern crate futures;
|
||||
|
||||
use tokio::executor::current_thread::{self, block_on_all, CurrentThread};
|
||||
|
||||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::task;
|
||||
use futures::future::{self, lazy};
|
||||
use futures::prelude::*;
|
||||
use futures::sync::oneshot;
|
||||
|
||||
#[test]
|
||||
fn spawn_from_block_on_all() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
let msg = current_thread::block_on_all(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
|
||||
// Spawn!
|
||||
current_thread::spawn(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
|
||||
Ok::<_, ()>("hello")
|
||||
})).unwrap();
|
||||
|
||||
assert_eq!(2, cnt.get());
|
||||
assert_eq!(msg, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_waits() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
thread::spawn(|| {
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let cnt2 = cnt.clone();
|
||||
|
||||
block_on_all(rx.then(move |_| {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok::<_, ()>(())
|
||||
})).unwrap();
|
||||
|
||||
assert_eq!(1, cnt2.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_many() {
|
||||
const ITER: usize = 200;
|
||||
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
for _ in 0..ITER {
|
||||
let cnt = cnt.clone();
|
||||
current_thread.spawn(lazy(move || {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
}
|
||||
|
||||
current_thread.run().unwrap();
|
||||
|
||||
assert_eq!(cnt.get(), ITER);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_set_global_executor_by_default() {
|
||||
use tokio_executor::Executor;
|
||||
|
||||
block_on_all(lazy(|| {
|
||||
tokio_executor::DefaultExecutor::current()
|
||||
.spawn(Box::new(lazy(|| ok())))
|
||||
.unwrap_err();
|
||||
|
||||
ok()
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_from_block_on_future() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
current_thread.block_on(lazy(|| {
|
||||
let cnt = cnt.clone();
|
||||
|
||||
current_thread::spawn(lazy(move || {
|
||||
cnt.set(1 + cnt.get());
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
Ok::<_, ()>(())
|
||||
})).unwrap();
|
||||
|
||||
current_thread.run().unwrap();
|
||||
|
||||
assert_eq!(1, cnt.get());
|
||||
}
|
||||
|
||||
struct Never(Rc<()>);
|
||||
|
||||
impl Future for Never {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outstanding_tasks_are_dropped_when_executor_is_dropped() {
|
||||
let mut rc = Rc::new(());
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
current_thread.spawn(Never(rc.clone()));
|
||||
|
||||
drop(current_thread);
|
||||
|
||||
// Ensure the daemon is dropped
|
||||
assert!(Rc::get_mut(&mut rc).is_some());
|
||||
|
||||
// Using the global spawn fn
|
||||
|
||||
let mut rc = Rc::new(());
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
current_thread.block_on(lazy(|| {
|
||||
current_thread::spawn(Never(rc.clone()));
|
||||
Ok::<_, ()>(())
|
||||
})).unwrap();
|
||||
|
||||
drop(current_thread);
|
||||
|
||||
// Ensure the daemon is dropped
|
||||
assert!(Rc::get_mut(&mut rc).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn nesting_run() {
|
||||
block_on_all(lazy(|| {
|
||||
block_on_all(lazy(|| {
|
||||
ok()
|
||||
})).unwrap();
|
||||
|
||||
ok()
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn run_in_future() {
|
||||
block_on_all(lazy(|| {
|
||||
current_thread::spawn(lazy(|| {
|
||||
block_on_all(lazy(|| {
|
||||
ok()
|
||||
})).unwrap();
|
||||
ok()
|
||||
}));
|
||||
ok()
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_on_infini_future() {
|
||||
let num = Rc::new(Cell::new(0));
|
||||
|
||||
struct Infini {
|
||||
num: Rc<Cell<usize>>,
|
||||
}
|
||||
|
||||
impl Future for Infini {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.num.set(1 + self.num.get());
|
||||
task::current().notify();
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
CurrentThread::new()
|
||||
.spawn(Infini {
|
||||
num: num.clone(),
|
||||
})
|
||||
.turn(None)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, num.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tasks_are_scheduled_fairly() {
|
||||
let state = Rc::new(RefCell::new([0, 0]));
|
||||
|
||||
struct Spin {
|
||||
state: Rc<RefCell<[i32; 2]>>,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl Future for Spin {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
if self.idx == 0 {
|
||||
let diff = state[0] - state[1];
|
||||
|
||||
assert!(diff.abs() <= 1);
|
||||
|
||||
if state[0] >= 50 {
|
||||
return Ok(().into());
|
||||
}
|
||||
}
|
||||
|
||||
state[self.idx] += 1;
|
||||
|
||||
if state[self.idx] >= 100 {
|
||||
return Ok(().into());
|
||||
}
|
||||
|
||||
task::current().notify();
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
block_on_all(lazy(|| {
|
||||
current_thread::spawn(Spin {
|
||||
state: state.clone(),
|
||||
idx: 0,
|
||||
});
|
||||
|
||||
current_thread::spawn(Spin {
|
||||
state: state,
|
||||
idx: 1,
|
||||
});
|
||||
|
||||
ok()
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_and_turn() {
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
// Spawn a basic task to get the executor to turn
|
||||
current_thread.spawn(lazy(move || {
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
// Turn once...
|
||||
current_thread.turn(None).unwrap();
|
||||
|
||||
current_thread.spawn(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
|
||||
// Spawn!
|
||||
current_thread::spawn(lazy(move || {
|
||||
c.set(1 + c.get());
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
// This does not run the newly spawned thread
|
||||
current_thread.turn(None).unwrap();
|
||||
assert_eq!(1, cnt.get());
|
||||
|
||||
// This runs the newly spawned thread
|
||||
current_thread.turn(None).unwrap();
|
||||
assert_eq!(2, cnt.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_in_drop() {
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
current_thread.spawn({
|
||||
struct OnDrop<F: FnOnce()>(Option<F>);
|
||||
|
||||
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.0.take().unwrap())();
|
||||
}
|
||||
}
|
||||
|
||||
struct MyFuture {
|
||||
_data: Box<Any>,
|
||||
}
|
||||
|
||||
impl Future for MyFuture {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
MyFuture {
|
||||
_data: Box::new(OnDrop(Some(move || {
|
||||
current_thread::spawn(lazy(move || {
|
||||
tx.send(()).unwrap();
|
||||
Ok(())
|
||||
}));
|
||||
}))),
|
||||
}
|
||||
});
|
||||
|
||||
current_thread.block_on(rx).unwrap();
|
||||
current_thread.run().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_turn() {
|
||||
use futures::sync::mpsc;
|
||||
|
||||
const ITER: usize = 100;
|
||||
const N: usize = 100;
|
||||
const THREADS: usize = 4;
|
||||
|
||||
for _ in 0..ITER {
|
||||
let mut ths = vec![];
|
||||
|
||||
// Add some jitter
|
||||
for _ in 0..THREADS {
|
||||
let th = thread::spawn(|| {
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
current_thread.spawn({
|
||||
let cnt = Rc::new(Cell::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
rx.for_each(move |_| {
|
||||
c.set(1 + c.get());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| panic!("err={:?}", e))
|
||||
.map(move |v| {
|
||||
assert_eq!(N, cnt.get());
|
||||
v
|
||||
})
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
for _ in 0..N {
|
||||
tx.unbounded_send(()).unwrap();
|
||||
thread::yield_now();
|
||||
}
|
||||
});
|
||||
|
||||
while !current_thread.is_idle() {
|
||||
current_thread.turn(None).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ths.push(th);
|
||||
}
|
||||
|
||||
for th in ths {
|
||||
th.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_has_polled() {
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
// Spawn oneshot receiver
|
||||
let (sender, receiver) = oneshot::channel::<()>();
|
||||
current_thread.spawn(receiver.then(|_| Ok(())));
|
||||
|
||||
// Turn once...
|
||||
let res = current_thread.turn(Some(Duration::from_millis(0))).unwrap();
|
||||
|
||||
// Should've polled the receiver once, but considered it not ready
|
||||
assert!(res.has_polled());
|
||||
|
||||
// Turn another time
|
||||
let res = current_thread.turn(Some(Duration::from_millis(0))).unwrap();
|
||||
|
||||
// Should've polled nothing, the receiver is not ready yet
|
||||
assert!(!res.has_polled());
|
||||
|
||||
// Make the receiver ready
|
||||
sender.send(()).unwrap();
|
||||
|
||||
// Turn another time
|
||||
let res = current_thread.turn(Some(Duration::from_millis(0))).unwrap();
|
||||
|
||||
// Should've polled the receiver, it's ready now
|
||||
assert!(res.has_polled());
|
||||
|
||||
// Now the executor should be empty
|
||||
assert!(current_thread.is_idle());
|
||||
let res = current_thread.turn(Some(Duration::from_millis(0))).unwrap();
|
||||
|
||||
// So should've polled nothing
|
||||
assert!(!res.has_polled());
|
||||
}
|
||||
|
||||
// Our own mock Park that is never really waiting and the only
|
||||
// thing it does is to send, on request, something (once) to a onshot
|
||||
// channel
|
||||
struct MyPark {
|
||||
sender: Option<oneshot::Sender<()>>,
|
||||
send_now: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
struct MyUnpark;
|
||||
|
||||
impl tokio_executor::park::Park for MyPark {
|
||||
type Unpark = MyUnpark;
|
||||
type Error = ();
|
||||
|
||||
fn unpark(&self) -> Self::Unpark {
|
||||
MyUnpark
|
||||
}
|
||||
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
// If called twice with send_now, this will intentionally panic
|
||||
if self.send_now.get() {
|
||||
self.sender.take().unwrap().send(()).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn park_timeout(&mut self, _duration: Duration) -> Result<(), Self::Error> {
|
||||
self.park()
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio_executor::park::Unpark for MyUnpark {
|
||||
fn unpark(&self) {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_fair() {
|
||||
let send_now = Rc::new(Cell::new(false));
|
||||
|
||||
let (sender, receiver) = oneshot::channel::<()>();
|
||||
let (sender_2, receiver_2) = oneshot::channel::<()>();
|
||||
let (sender_3, receiver_3) = oneshot::channel::<()>();
|
||||
|
||||
let my_park = MyPark {
|
||||
sender: Some(sender_3),
|
||||
send_now: send_now.clone(),
|
||||
};
|
||||
|
||||
let mut current_thread = CurrentThread::new_with_park(my_park);
|
||||
|
||||
let receiver_1_done = Rc::new(Cell::new(false));
|
||||
let receiver_1_done_clone = receiver_1_done.clone();
|
||||
|
||||
// Once an item is received on the oneshot channel, it will immediately
|
||||
// immediately make the second oneshot channel ready
|
||||
current_thread.spawn(receiver
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(move |_| {
|
||||
sender_2.send(()).unwrap();
|
||||
receiver_1_done_clone.set(true);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
);
|
||||
|
||||
let receiver_2_done = Rc::new(Cell::new(false));
|
||||
let receiver_2_done_clone = receiver_2_done.clone();
|
||||
|
||||
current_thread.spawn(receiver_2
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(move |_| {
|
||||
receiver_2_done_clone.set(true);
|
||||
Ok(())
|
||||
})
|
||||
);
|
||||
|
||||
// The third receiver is only woken up from our Park implementation, it simulates
|
||||
// e.g. a socket that first has to be polled to know if it is ready now
|
||||
let receiver_3_done = Rc::new(Cell::new(false));
|
||||
let receiver_3_done_clone = receiver_3_done.clone();
|
||||
|
||||
current_thread.spawn(receiver_3
|
||||
.map_err(|_| unreachable!())
|
||||
.and_then(move |_| {
|
||||
receiver_3_done_clone.set(true);
|
||||
Ok(())
|
||||
})
|
||||
);
|
||||
|
||||
// First turn should've polled both and considered them not ready
|
||||
let res = current_thread.turn(Some(Duration::from_millis(0))).unwrap();
|
||||
assert!(res.has_polled());
|
||||
|
||||
// Next turn should've polled nothing
|
||||
let res = current_thread.turn(Some(Duration::from_millis(0))).unwrap();
|
||||
assert!(!res.has_polled());
|
||||
|
||||
assert!(!receiver_1_done.get());
|
||||
assert!(!receiver_2_done.get());
|
||||
assert!(!receiver_3_done.get());
|
||||
|
||||
// After this the receiver future will wake up the second receiver future,
|
||||
// so there are pending futures again
|
||||
sender.send(()).unwrap();
|
||||
|
||||
// Now the first receiver should be done, the second receiver should be ready
|
||||
// to be polled again and the socket not yet
|
||||
let res = current_thread.turn(None).unwrap();
|
||||
assert!(res.has_polled());
|
||||
|
||||
assert!(receiver_1_done.get());
|
||||
assert!(!receiver_2_done.get());
|
||||
assert!(!receiver_3_done.get());
|
||||
|
||||
// Now let our park implementation know that it should send something to sender 3
|
||||
send_now.set(true);
|
||||
|
||||
// This should resolve the second receiver directly, but also poll the socket
|
||||
// and read the packet from it. If it didn't do both here, we would handle
|
||||
// futures that are woken up from the reactor and directly unfairly and would
|
||||
// favour the ones that are woken up directly.
|
||||
let res = current_thread.turn(None).unwrap();
|
||||
assert!(res.has_polled());
|
||||
|
||||
assert!(receiver_1_done.get());
|
||||
assert!(receiver_2_done.get());
|
||||
assert!(receiver_3_done.get());
|
||||
|
||||
// Don't send again
|
||||
send_now.set(false);
|
||||
|
||||
// Now we should be idle and turning should not poll anything
|
||||
assert!(current_thread.is_idle());
|
||||
let res = current_thread.turn(None).unwrap();
|
||||
assert!(!res.has_polled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_from_other_thread() {
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let handle = current_thread.handle();
|
||||
let (sender, receiver) = oneshot::channel::<()>();
|
||||
|
||||
thread::spawn(move || {
|
||||
handle.spawn(lazy(move || {
|
||||
sender.send(()).unwrap();
|
||||
Ok(())
|
||||
})).unwrap();
|
||||
});
|
||||
|
||||
let _ = current_thread.block_on(receiver).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_from_other_thread_unpark() {
|
||||
use std::sync::mpsc::channel as mpsc_channel;
|
||||
|
||||
let mut current_thread = CurrentThread::new();
|
||||
|
||||
let handle = current_thread.handle();
|
||||
let (sender_1, receiver_1) = oneshot::channel::<()>();
|
||||
let (sender_2, receiver_2) = mpsc_channel::<()>();
|
||||
|
||||
thread::spawn(move || {
|
||||
let _ = receiver_2.recv().unwrap();
|
||||
|
||||
handle.spawn(lazy(move || {
|
||||
sender_1.send(()).unwrap();
|
||||
Ok(())
|
||||
})).unwrap();
|
||||
});
|
||||
|
||||
// Ensure that unparking the executor works correctly. It will first
|
||||
// check if there are new futures (there are none), then execute the
|
||||
// lazy future below which will cause the future to be spawned from
|
||||
// the other thread. Then the executor will park but should be woken
|
||||
// up because *now* we have a new future to schedule
|
||||
let _ = current_thread.block_on(
|
||||
lazy(move || {
|
||||
sender_2.send(()).unwrap();
|
||||
Ok(())
|
||||
})
|
||||
.and_then(|_| receiver_1)
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
fn ok() -> future::FutureResult<(), ()> {
|
||||
future::ok(())
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#![cfg(feature = "unstable-futures")]
|
||||
|
||||
// This test is the same as `echo.rs`, but ported to futures 0.2
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate futures2;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::thread;
|
||||
|
||||
use futures2::prelude::*;
|
||||
use futures2::executor::block_on;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo_server() {
|
||||
drop(env_logger::init());
|
||||
|
||||
let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse())));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let msg = "foo bar baz";
|
||||
let t = thread::spawn(move || {
|
||||
let mut s = TcpStream::connect(&addr).unwrap();
|
||||
|
||||
for _i in 0..1024 {
|
||||
assert_eq!(t!(s.write(msg.as_bytes())), msg.len());
|
||||
let mut buf = [0; 1024];
|
||||
assert_eq!(t!(s.read(&mut buf)), msg.len());
|
||||
assert_eq!(&buf[..msg.len()], msg.as_bytes());
|
||||
}
|
||||
});
|
||||
|
||||
let clients = srv.incoming();
|
||||
let client = clients.next().map(|e| e.0.unwrap()).map_err(|e| e.0);
|
||||
let halves = client.map(|s| s.split());
|
||||
let copied = halves.and_then(|(a, b)| a.copy_into(b));
|
||||
|
||||
let (amt, _, _) = t!(block_on(copied));
|
||||
t.join().unwrap();
|
||||
|
||||
assert_eq!(amt, msg.len() as u64 * 1024);
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
#![cfg(feature = "unstable-futures")]
|
||||
|
||||
// This test is the same as `global.rs`, but ported to futures 0.2
|
||||
|
||||
extern crate futures;
|
||||
extern crate futures2;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::{io, thread};
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures2::prelude::*;
|
||||
use futures2::executor::block_on;
|
||||
use futures2::task;
|
||||
|
||||
use tokio::net::{TcpStream, TcpListener};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer() {
|
||||
let _ = env_logger::init();
|
||||
|
||||
let threads = (0..10).map(|_| {
|
||||
thread::spawn(|| {
|
||||
let srv = t!(TcpListener::bind(&"127.0.0.1:0".parse().unwrap()));
|
||||
let addr = t!(srv.local_addr());
|
||||
let mine = TcpStream::connect(&addr);
|
||||
let theirs = srv.incoming().next()
|
||||
.map(|(s, _)| s.unwrap())
|
||||
.map_err(|(s, _)| s);
|
||||
let (mine, theirs) = t!(block_on(mine.join(theirs)));
|
||||
|
||||
assert_eq!(t!(mine.local_addr()), t!(theirs.peer_addr()));
|
||||
assert_eq!(t!(theirs.local_addr()), t!(mine.peer_addr()));
|
||||
})
|
||||
}).collect::<Vec<_>>();
|
||||
for thread in threads {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct Rd(Arc<TcpStream>);
|
||||
struct Wr(Arc<TcpStream>);
|
||||
|
||||
impl AsyncRead for Rd {
|
||||
fn poll_read(&mut self, cx: &mut task::Context, dst: &mut [u8]) -> Poll<usize, io::Error> {
|
||||
<&TcpStream>::poll_read(&mut &*self.0, cx, dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Wr {
|
||||
fn poll_write(&mut self, cx: &mut task::Context, src: &[u8]) -> Poll<usize, io::Error> {
|
||||
<&TcpStream>::poll_write(&mut &*self.0, cx, src)
|
||||
}
|
||||
|
||||
fn poll_flush(&mut self, _cx: &mut task::Context) -> Poll<(), io::Error> {
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
fn poll_close(&mut self, _cx: &mut task::Context) -> Poll<(), io::Error> {
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_split() {
|
||||
const N: usize = 100;
|
||||
|
||||
let _ = env_logger::init();
|
||||
|
||||
let srv = t!(TcpListener::bind(&"127.0.0.1:0".parse().unwrap()));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
|
||||
fn split(socket: TcpStream) {
|
||||
let socket = Arc::new(socket);
|
||||
let rd = Rd(socket.clone());
|
||||
let wr = Wr(socket);
|
||||
|
||||
let rd = rd.read(vec![0; 1])
|
||||
.map(|_| ())
|
||||
.map_err(|e| panic!("read error = {:?}", e));
|
||||
|
||||
let wr = wr.write_all(b"1")
|
||||
.map(|_| ())
|
||||
.map_err(|e| panic!("write error = {:?}", e));
|
||||
|
||||
tokio::spawn2(rd);
|
||||
tokio::spawn2(wr);
|
||||
}
|
||||
|
||||
rt.spawn2({
|
||||
srv.incoming()
|
||||
.map_err(|e| panic!("accept error = {:?}", e))
|
||||
.take(N as u64)
|
||||
.for_each(|socket| {
|
||||
split(socket);
|
||||
Ok(())
|
||||
})
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
for _ in 0..N {
|
||||
rt.spawn2({
|
||||
TcpStream::connect(&addr)
|
||||
.map_err(|e| panic!("connect error = {:?}", e))
|
||||
.map(|socket| split(socket))
|
||||
});
|
||||
}
|
||||
|
||||
futures::Future::wait(rt.shutdown_on_idle()).unwrap();
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
extern crate tokio;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
|
||||
use futures::sync::oneshot;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use tokio::io;
|
||||
use tokio::net::{TcpStream, TcpListener};
|
||||
use tokio::prelude::future::lazy;
|
||||
use tokio::prelude::*;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_client_server_future() -> Box<Future<Item=(), Error=()> + Send> {
|
||||
let server = t!(TcpListener::bind(&"127.0.0.1:0".parse().unwrap()));
|
||||
let addr = t!(server.local_addr());
|
||||
let client = TcpStream::connect(&addr);
|
||||
|
||||
let server = server.incoming().take(1)
|
||||
.map_err(|e| panic!("accept err = {:?}", e))
|
||||
.for_each(|socket| {
|
||||
tokio::spawn({
|
||||
io::write_all(socket, b"hello")
|
||||
.map(|_| ())
|
||||
.map_err(|e| panic!("write err = {:?}", e))
|
||||
})
|
||||
})
|
||||
.map(|_| ());
|
||||
|
||||
let client = client
|
||||
.map_err(|e| panic!("connect err = {:?}", e))
|
||||
.and_then(|client| {
|
||||
// Read all
|
||||
io::read_to_end(client, vec![])
|
||||
.map(|_| ())
|
||||
.map_err(|e| panic!("read err = {:?}", e))
|
||||
});
|
||||
|
||||
let future = server.join(client)
|
||||
.map(|_| ());
|
||||
Box::new(future)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_tokio_run() {
|
||||
let _ = env_logger::init();
|
||||
|
||||
tokio::run(create_client_server_future());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_single_threaded() {
|
||||
let _ = env_logger::init();
|
||||
|
||||
let mut runtime = tokio::runtime::current_thread::Runtime::new()
|
||||
.unwrap();
|
||||
runtime.block_on(create_client_server_future()).unwrap();
|
||||
runtime.run().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_multi_threaded() {
|
||||
let _ = env_logger::init();
|
||||
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.spawn(create_client_server_future());
|
||||
runtime.shutdown_on_idle().wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_on_timer() {
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::timer::{Delay, Error};
|
||||
|
||||
fn after_1s<T>(x: T) -> Box<Future<Item = T, Error = Error> + Send>
|
||||
where
|
||||
T: Send + 'static,
|
||||
{
|
||||
Box::new(Delay::new(Instant::now() + Duration::from_millis(100)).map(move |_| x))
|
||||
}
|
||||
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
assert_eq!(runtime.block_on(after_1s(42)).unwrap(), 42);
|
||||
runtime.shutdown_on_idle().wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_from_block_on() {
|
||||
let cnt = Arc::new(Mutex::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
let msg = runtime
|
||||
.block_on(lazy(move || {
|
||||
{
|
||||
let mut x = c.lock().unwrap();
|
||||
*x = 1 + *x;
|
||||
}
|
||||
|
||||
// Spawn!
|
||||
tokio::spawn(lazy(move || {
|
||||
{
|
||||
let mut x = c.lock().unwrap();
|
||||
*x = 1 + *x;
|
||||
}
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
|
||||
Ok::<_, ()>("hello")
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
runtime.shutdown_on_idle().wait().unwrap();
|
||||
assert_eq!(2, *cnt.lock().unwrap());
|
||||
assert_eq!(msg, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_waits() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
thread::spawn(|| {
|
||||
use std::time::Duration;
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
let cnt = Arc::new(Mutex::new(0));
|
||||
let c = cnt.clone();
|
||||
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
runtime
|
||||
.block_on(rx.then(move |_| {
|
||||
{
|
||||
let mut x = c.lock().unwrap();
|
||||
*x = 1 + *x;
|
||||
}
|
||||
Ok::<_, ()>(())
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, *cnt.lock().unwrap());
|
||||
runtime.shutdown_on_idle().wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_many() {
|
||||
const ITER: usize = 200;
|
||||
|
||||
let cnt = Arc::new(Mutex::new(0));
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
|
||||
for _ in 0..ITER {
|
||||
let c = cnt.clone();
|
||||
runtime.spawn(lazy(move || {
|
||||
{
|
||||
let mut x = c.lock().unwrap();
|
||||
*x = 1 + *x;
|
||||
}
|
||||
Ok::<(), ()>(())
|
||||
}));
|
||||
}
|
||||
|
||||
runtime.shutdown_on_idle().wait().unwrap();
|
||||
assert_eq!(ITER, *cnt.lock().unwrap());
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
#![cfg(feature = "unstable-futures")]
|
||||
|
||||
// This test is the same as `tcp.rs`, but ported to futures 0.2
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate tokio;
|
||||
extern crate mio;
|
||||
extern crate futures2;
|
||||
|
||||
use std::{net, thread};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use futures2::executor::block_on;
|
||||
use futures2::prelude::*;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect() {
|
||||
drop(env_logger::init());
|
||||
let srv = t!(net::TcpListener::bind("127.0.0.1:0"));
|
||||
let addr = t!(srv.local_addr());
|
||||
let t = thread::spawn(move || {
|
||||
t!(srv.accept()).0
|
||||
});
|
||||
|
||||
let stream = TcpStream::connect(&addr);
|
||||
let mine = t!(block_on(stream));
|
||||
let theirs = t.join().unwrap();
|
||||
|
||||
assert_eq!(t!(mine.local_addr()), t!(theirs.peer_addr()));
|
||||
assert_eq!(t!(theirs.local_addr()), t!(mine.peer_addr()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accept() {
|
||||
drop(env_logger::init());
|
||||
let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse())));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let client = srv.incoming().map(move |t| {
|
||||
tx.send(()).unwrap();
|
||||
t
|
||||
}).next().map_err(|e| e.0);
|
||||
assert!(rx.try_recv().is_err());
|
||||
let t = thread::spawn(move || {
|
||||
net::TcpStream::connect(&addr).unwrap()
|
||||
});
|
||||
|
||||
let (mine, _remaining) = t!(block_on(client));
|
||||
let mine = mine.unwrap();
|
||||
let theirs = t.join().unwrap();
|
||||
|
||||
assert_eq!(t!(mine.local_addr()), t!(theirs.peer_addr()));
|
||||
assert_eq!(t!(theirs.local_addr()), t!(mine.peer_addr()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accept2() {
|
||||
drop(env_logger::init());
|
||||
let srv = t!(TcpListener::bind(&t!("127.0.0.1:0".parse())));
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let t = thread::spawn(move || {
|
||||
net::TcpStream::connect(&addr).unwrap()
|
||||
});
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let client = srv.incoming().map(move |t| {
|
||||
tx.send(()).unwrap();
|
||||
t
|
||||
}).next().map_err(|e| e.0);
|
||||
assert!(rx.try_recv().is_err());
|
||||
|
||||
let (mine, _remaining) = t!(block_on(client));
|
||||
mine.unwrap();
|
||||
t.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix {
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use env_logger;
|
||||
use futures2::future;
|
||||
use futures2::executor::block_on;
|
||||
use futures2::io::AsyncRead;
|
||||
use mio::unix::UnixReady;
|
||||
|
||||
use std::{net, thread};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn poll_hup() {
|
||||
drop(env_logger::init());
|
||||
|
||||
let srv = t!(net::TcpListener::bind("127.0.0.1:0"));
|
||||
let addr = t!(srv.local_addr());
|
||||
let t = thread::spawn(move || {
|
||||
let mut client = t!(srv.accept()).0;
|
||||
client.write(b"hello world").unwrap();
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
});
|
||||
|
||||
let mut stream = t!(block_on(TcpStream::connect(&addr)));
|
||||
|
||||
// Poll for HUP before reading.
|
||||
block_on(future::poll_fn(|cx| {
|
||||
stream.poll_read_ready2(cx, UnixReady::hup().into())
|
||||
})).unwrap();
|
||||
|
||||
// Same for write half
|
||||
block_on(future::poll_fn(|cx| {
|
||||
stream.poll_write_ready2(cx)
|
||||
})).unwrap();
|
||||
|
||||
let mut buf = vec![0; 11];
|
||||
|
||||
// Read the data
|
||||
block_on(future::poll_fn(|cx| {
|
||||
stream.poll_read(cx, &mut buf)
|
||||
})).unwrap();
|
||||
|
||||
assert_eq!(b"hello world", &buf[..]);
|
||||
|
||||
t.join().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "tokio-async-await"
|
||||
|
||||
# When releasing to crates.io:
|
||||
# - Update html_root_url.
|
||||
version = "0.1.6"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-async-await/0.1.6"
|
||||
description = """
|
||||
Experimental async/await support for Tokio
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[features]
|
||||
# This feature comes with no promise of stability. Things will
|
||||
# break with each patch release. Use at your own risk.
|
||||
async-await-preview = ["futures/nightly"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.23"
|
||||
tokio-io = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
bytes = "0.4.9"
|
||||
tokio = "0.1.8"
|
||||
hyper = "0.12.8"
|
||||
@@ -0,0 +1,52 @@
|
||||
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.
|
||||
|
||||
Copyright (c) 2016 futures-rs authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Tokio async/await preview
|
||||
|
||||
This crate provides a preview of Tokio with async / await support. It is a shim
|
||||
layer on top of `tokio`.
|
||||
|
||||
**This crate requires Rust nightly and does not provide API stability
|
||||
guarantees. You are living on the edge here.**
|
||||
|
||||
## Usage
|
||||
|
||||
To use this crate, you need to start with a Rust 2018 edition crate, with rustc
|
||||
1.34.0-nightly or later.
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
# In the `[packages]` section
|
||||
edition = "2018"
|
||||
|
||||
# In the `[dependencies]` section
|
||||
tokio = {version = "0.1.15", features = ["async-await-preview"]}
|
||||
```
|
||||
|
||||
Then, get started. In your application, add:
|
||||
|
||||
```rust
|
||||
// The nightly features that are commonly needed with async / await
|
||||
#![feature(await_macro, async_await, futures_api)]
|
||||
|
||||
// This pulls in the `tokio-async-await` crate. While Rust 2018 doesn't require
|
||||
// `extern crate`, we need to pull in the macros.
|
||||
#[macro_use]
|
||||
extern crate tokio;
|
||||
|
||||
fn main() {
|
||||
// And we are async...
|
||||
tokio::run_async(async {
|
||||
println!("Hello");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Because nightly is required, run the app with `cargo +nightly run`
|
||||
|
||||
Check the [examples](examples) directory for more.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target-dir = "../../target"
|
||||
@@ -0,0 +1,49 @@
|
||||
[package]
|
||||
name = "examples"
|
||||
edition = "2018"
|
||||
version = "0.1.0"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
license = "MIT"
|
||||
|
||||
# Break out of the parent workspace
|
||||
[workspace]
|
||||
|
||||
[[bin]]
|
||||
name = "chat"
|
||||
path = "src/chat.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "echo_client"
|
||||
path = "src/echo_client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "echo_server"
|
||||
path = "src/echo_server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "hyper"
|
||||
path = "src/hyper.rs"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.1.0", path = "../..", features = ["async-await-preview"] }
|
||||
futures = "0.1.23"
|
||||
bytes = "0.4.9"
|
||||
hyper = "0.12.8"
|
||||
|
||||
# Avoid using crates.io for Tokio dependencies
|
||||
[patch.crates-io]
|
||||
tokio = { path = "../.." }
|
||||
tokio-async-await = { path = "../" }
|
||||
tokio-codec = { path = "../../tokio-codec" }
|
||||
tokio-current-thread = { path = "../../tokio-current-thread" }
|
||||
tokio-executor = { path = "../../tokio-executor" }
|
||||
tokio-fs = { path = "../../tokio-fs" }
|
||||
tokio-io = { path = "../../tokio-io" }
|
||||
tokio-reactor = { path = "../../tokio-reactor" }
|
||||
tokio-signal = { path = "../../tokio-signal" }
|
||||
tokio-tcp = { path = "../../tokio-tcp" }
|
||||
tokio-threadpool = { path = "../../tokio-threadpool" }
|
||||
tokio-timer = { path = "../../tokio-timer" }
|
||||
tokio-tls = { path = "../../tokio-tls" }
|
||||
tokio-udp = { path = "../../tokio-udp" }
|
||||
tokio-uds = { path = "../../tokio-uds" }
|
||||
@@ -0,0 +1,5 @@
|
||||
# Tokio async/await examples
|
||||
|
||||
These are a separate crate in order to work around some cargo bugs. It also
|
||||
allows `[patch]` to be used in `Cargo.toml` to ensure the correct lib versions
|
||||
are being pulled in.
|
||||
@@ -0,0 +1,135 @@
|
||||
#![feature(await_macro, async_await, futures_api)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tokio;
|
||||
extern crate futures; // v0.1
|
||||
|
||||
use tokio::codec::{LinesCodec, Decoder};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::prelude::*;
|
||||
|
||||
use futures::sync::mpsc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Shorthand for the transmit half of the message channel.
|
||||
type Tx = mpsc::UnboundedSender<String>;
|
||||
|
||||
struct Shared {
|
||||
peers: HashMap<SocketAddr, Tx>,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
/// Create a new, empty, instance of `Shared`.
|
||||
fn new() -> Self {
|
||||
Shared {
|
||||
peers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn process(stream: TcpStream, state: Arc<Mutex<Shared>>) -> io::Result<()> {
|
||||
let addr = stream.peer_addr().unwrap();
|
||||
let mut lines = LinesCodec::new().framed(stream);
|
||||
|
||||
// Extract the peer's name
|
||||
let name = match await!(lines.next()) {
|
||||
Some(name) => name?,
|
||||
None => {
|
||||
// Disconnected early
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
println!("`{}` is joining the chat", name);
|
||||
|
||||
let (tx, mut rx) = mpsc::unbounded();
|
||||
|
||||
// Register the socket
|
||||
state.lock().unwrap()
|
||||
.peers.insert(addr, tx);
|
||||
|
||||
// Split the `lines` handle into send and recv handles. This allows spawning
|
||||
// separate tasks.
|
||||
let (mut lines_tx, mut lines_rx) = lines.split();
|
||||
|
||||
// Spawn a task that receives all lines broadcasted to us from other peers
|
||||
// and writes it to the client.
|
||||
tokio::spawn_async(async move {
|
||||
while let Some(line) = await!(rx.next()) {
|
||||
let line = line.unwrap();
|
||||
await!(lines_tx.send_async(line)).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// Use the current task to read lines from the socket and broadcast them to
|
||||
// other peers.
|
||||
while let Some(message) = await!(lines_rx.next()) {
|
||||
// TODO: Error handling
|
||||
let message = message.unwrap();
|
||||
|
||||
let mut line = name.clone();
|
||||
line.push_str(": ");
|
||||
line.push_str(&message);
|
||||
line.push_str("\r\n");
|
||||
|
||||
let state = state.lock().unwrap();
|
||||
|
||||
for (peer_addr, tx) in &state.peers {
|
||||
if *peer_addr != addr {
|
||||
// TODO: Error handling
|
||||
tx.unbounded_send(line.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the client from the shared state. Doing so will also result in the
|
||||
// tx task to terminate.
|
||||
state.lock().unwrap()
|
||||
.peers.remove(&addr)
|
||||
.expect("bug");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Create the shared state. This is how all the peers communicate.
|
||||
//
|
||||
// The server task will hold a handle to this. For every new client, the
|
||||
// `state` handle is cloned and passed into the task that processes the
|
||||
// client connection.
|
||||
let state = Arc::new(Mutex::new(Shared::new()));
|
||||
|
||||
let addr = "127.0.0.1:6142".parse().unwrap();
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
|
||||
println!("server running on localhost:6142");
|
||||
|
||||
// Start the Tokio runtime.
|
||||
tokio::run_async(async move {
|
||||
let mut incoming = listener.incoming();
|
||||
|
||||
while let Some(stream) = await!(incoming.next()) {
|
||||
let stream = match stream {
|
||||
Ok(stream) => stream,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let state = state.clone();
|
||||
|
||||
tokio::spawn_async(async move {
|
||||
if let Err(_) = await!(process(stream, state)) {
|
||||
eprintln!("failed to process connection");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#![feature(await_macro, async_await, futures_api)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::prelude::*;
|
||||
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
const MESSAGES: &[&str] = &[
|
||||
"hello",
|
||||
"world",
|
||||
"one two three",
|
||||
];
|
||||
|
||||
async fn run_client(addr: &SocketAddr) -> io::Result<()> {
|
||||
let mut stream = await!(TcpStream::connect(addr))?;
|
||||
|
||||
// Buffer to read into
|
||||
let mut buf = [0; 128];
|
||||
|
||||
for msg in MESSAGES {
|
||||
println!(" > write = {:?}", msg);
|
||||
|
||||
// Write the message to the server
|
||||
await!(stream.write_all_async(msg.as_bytes()))?;
|
||||
|
||||
// Read the message back from the server
|
||||
await!(stream.read_exact_async(&mut buf[..msg.len()]))?;
|
||||
|
||||
assert_eq!(&buf[..msg.len()], msg.as_bytes());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
use std::env;
|
||||
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = addr.parse::<SocketAddr>().unwrap();
|
||||
|
||||
// Connect to the echo serveer
|
||||
|
||||
tokio::run_async(async move {
|
||||
match await!(run_client(&addr)) {
|
||||
Ok(_) => println!("done."),
|
||||
Err(e) => eprintln!("echo client failed; error = {:?}", e),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#![feature(await_macro, async_await, futures_api)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tokio;
|
||||
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::prelude::*;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
fn handle(mut stream: TcpStream) {
|
||||
tokio::spawn_async(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
loop {
|
||||
match await!(stream.read_async(&mut buf)).unwrap() {
|
||||
0 => break, // Socket closed
|
||||
n => {
|
||||
// Send the data back
|
||||
await!(stream.write_all_async(&buf[0..n])).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn main() {
|
||||
use std::env;
|
||||
|
||||
let addr = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string());
|
||||
let addr = addr.parse::<SocketAddr>().unwrap();
|
||||
|
||||
// Bind the TCP listener
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
tokio::run_async(async {
|
||||
let mut incoming = listener.incoming();
|
||||
|
||||
while let Some(stream) = await!(incoming.next()) {
|
||||
let stream = stream.unwrap();
|
||||
handle(stream);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#![feature(await_macro, async_await, futures_api)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tokio;
|
||||
extern crate hyper;
|
||||
|
||||
use tokio::prelude::*;
|
||||
use hyper::Client;
|
||||
|
||||
use std::time::Duration;
|
||||
use std::str;
|
||||
|
||||
pub fn main() {
|
||||
tokio::run_async(async {
|
||||
let client = Client::new();
|
||||
|
||||
let uri = "http://httpbin.org/ip".parse().unwrap();
|
||||
|
||||
let response = await!({
|
||||
client.get(uri)
|
||||
.timeout(Duration::from_secs(10))
|
||||
}).unwrap();
|
||||
|
||||
println!("Response: {}", response.status());
|
||||
|
||||
let mut body = response.into_body();
|
||||
|
||||
while let Some(chunk) = await!(body.next()) {
|
||||
let chunk = chunk.unwrap();
|
||||
println!("chunk = {}", str::from_utf8(&chunk[..]).unwrap());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/// Wait for a future to complete.
|
||||
#[macro_export]
|
||||
macro_rules! await {
|
||||
($e:expr) => {{
|
||||
#[allow(unused_imports)]
|
||||
use $crate::compat::backward::IntoAwaitable as IntoAwaitableBackward;
|
||||
#[allow(unused_imports)]
|
||||
use $crate::compat::forward::IntoAwaitable as IntoAwaitableForward;
|
||||
use $crate::std_await;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut e = $e;
|
||||
let e = e.into_awaitable();
|
||||
std_await!(e)
|
||||
}};
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use futures::{Future, Poll};
|
||||
|
||||
use std::future::Future as StdFuture;
|
||||
use std::pin::Pin;
|
||||
use std::ptr;
|
||||
use std::task::{Poll as StdPoll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
/// Convert an 0.3 `Future` to an 0.1 `Future`.
|
||||
#[derive(Debug)]
|
||||
pub struct Compat<T>(Pin<Box<T>>);
|
||||
|
||||
impl<T> Compat<T> {
|
||||
/// Create a new `Compat` backed by `future`.
|
||||
pub fn new(future: T) -> Compat<T> {
|
||||
Compat(Box::pin(future))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a value into one that can be used with `await!`.
|
||||
pub trait IntoAwaitable {
|
||||
type Awaitable;
|
||||
|
||||
fn into_awaitable(self) -> Self::Awaitable;
|
||||
}
|
||||
|
||||
impl<T> IntoAwaitable for T
|
||||
where
|
||||
T: StdFuture,
|
||||
{
|
||||
type Awaitable = Self;
|
||||
|
||||
fn into_awaitable(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Item, Error> Future for Compat<T>
|
||||
where
|
||||
T: StdFuture<Output = Result<Item, Error>>,
|
||||
{
|
||||
type Item = Item;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Item, Error> {
|
||||
use futures::Async::*;
|
||||
|
||||
let waker = noop_waker();
|
||||
|
||||
let res = self.0.as_mut().poll(&waker);
|
||||
|
||||
match res {
|
||||
StdPoll::Ready(Ok(val)) => Ok(Ready(val)),
|
||||
StdPoll::Ready(Err(err)) => Err(err),
|
||||
StdPoll::Pending => Ok(NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== NoopWaker =====
|
||||
|
||||
fn noop_raw_waker() -> RawWaker {
|
||||
RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE)
|
||||
}
|
||||
|
||||
fn noop_waker() -> Waker {
|
||||
unsafe { Waker::new_unchecked(noop_raw_waker()) }
|
||||
}
|
||||
|
||||
unsafe fn clone_raw(_data: *const ()) -> RawWaker {
|
||||
noop_raw_waker()
|
||||
}
|
||||
|
||||
unsafe fn drop_raw(_data: *const ()) {}
|
||||
|
||||
unsafe fn wake(_data: *const ()) {
|
||||
unimplemented!("async-await-preview currently only supports futures 0.1. Use the compatibility layer of futures 0.3 instead, if you want to use futures 0.3.");
|
||||
}
|
||||
|
||||
const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable {
|
||||
clone: clone_raw,
|
||||
drop: drop_raw,
|
||||
wake,
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
use futures::{Async, Future};
|
||||
|
||||
use std::future::Future as StdFuture;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Poll as StdPoll, Waker};
|
||||
|
||||
/// Converts an 0.1 `Future` into an 0.3 `Future`.
|
||||
#[derive(Debug)]
|
||||
pub struct Compat<T>(T);
|
||||
|
||||
pub(crate) fn convert_poll<T, E>(poll: Result<Async<T>, E>) -> StdPoll<Result<T, E>> {
|
||||
use futures::Async::{NotReady, Ready};
|
||||
|
||||
match poll {
|
||||
Ok(Ready(val)) => StdPoll::Ready(Ok(val)),
|
||||
Ok(NotReady) => StdPoll::Pending,
|
||||
Err(err) => StdPoll::Ready(Err(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn convert_poll_stream<T, E>(
|
||||
poll: Result<Async<Option<T>>, E>,
|
||||
) -> StdPoll<Option<Result<T, E>>> {
|
||||
use futures::Async::{NotReady, Ready};
|
||||
|
||||
match poll {
|
||||
Ok(Ready(Some(val))) => StdPoll::Ready(Some(Ok(val))),
|
||||
Ok(Ready(None)) => StdPoll::Ready(None),
|
||||
Ok(NotReady) => StdPoll::Pending,
|
||||
Err(err) => StdPoll::Ready(Some(Err(err))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a value into one that can be used with `await!`.
|
||||
pub trait IntoAwaitable {
|
||||
type Awaitable;
|
||||
|
||||
/// Convert `self` into a value that can be used with `await!`.
|
||||
fn into_awaitable(self) -> Self::Awaitable;
|
||||
}
|
||||
|
||||
impl<T: Future + Unpin> IntoAwaitable for T {
|
||||
type Awaitable = Compat<T>;
|
||||
|
||||
fn into_awaitable(self) -> Self::Awaitable {
|
||||
Compat(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StdFuture for Compat<T>
|
||||
where
|
||||
T: Future + Unpin,
|
||||
{
|
||||
type Output = Result<T::Item, T::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _waker: &Waker) -> StdPoll<Self::Output> {
|
||||
use futures::Async::{NotReady, Ready};
|
||||
|
||||
// TODO: wire in cx
|
||||
|
||||
match self.0.poll() {
|
||||
Ok(Ready(val)) => StdPoll::Ready(Ok(val)),
|
||||
Ok(NotReady) => StdPoll::Pending,
|
||||
Err(e) => StdPoll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#![doc(hidden)]
|
||||
|
||||
pub mod backward;
|
||||
pub mod forward;
|
||||
@@ -0,0 +1,30 @@
|
||||
use tokio_io::AsyncWrite;
|
||||
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Poll, Waker};
|
||||
|
||||
/// A future used to fully flush an I/O object.
|
||||
#[derive(Debug)]
|
||||
pub struct Flush<'a, T: ?Sized + 'a> {
|
||||
writer: &'a mut T,
|
||||
}
|
||||
|
||||
// Pin is never projected to fields
|
||||
impl<'a, T: ?Sized> Unpin for Flush<'a, T> {}
|
||||
|
||||
impl<'a, T: AsyncWrite + ?Sized> Flush<'a, T> {
|
||||
pub(super) fn new(writer: &'a mut T) -> Flush<'a, T> {
|
||||
Flush { writer }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsyncWrite + ?Sized> Future for Flush<'a, T> {
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _wx: &Waker) -> Poll<Self::Output> {
|
||||
use crate::compat::forward::convert_poll;
|
||||
convert_poll(self.writer.poll_flush())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
//! Use I/O with `async` / `await`.
|
||||
|
||||
mod flush;
|
||||
mod read;
|
||||
mod read_exact;
|
||||
mod write;
|
||||
mod write_all;
|
||||
|
||||
pub use self::flush::Flush;
|
||||
pub use self::read::Read;
|
||||
pub use self::read_exact::ReadExact;
|
||||
pub use self::write::Write;
|
||||
pub use self::write_all::WriteAll;
|
||||
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
/// An extension trait which adds utility methods to `AsyncRead` types.
|
||||
pub trait AsyncReadExt: AsyncRead {
|
||||
/// Tries to read some bytes directly into the given `buf` in an
|
||||
/// asynchronous manner, returning a future.
|
||||
///
|
||||
/// The returned future will resolve to the number of bytes read once the read
|
||||
/// operation is completed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(async_await, await_macro, futures_api)]
|
||||
/// tokio::run_async(async {
|
||||
/// // The extension trait can also be imported with
|
||||
/// // `use tokio::prelude::*`.
|
||||
/// use tokio::prelude::AsyncReadExt;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// let mut reader = Cursor::new([1, 2, 3, 4]);
|
||||
/// let mut output = [0u8; 5];
|
||||
///
|
||||
/// let bytes = await!(reader.read_async(&mut output[..])).unwrap();
|
||||
///
|
||||
/// // This is only guaranteed to be 4 because `&[u8]` is a synchronous
|
||||
/// // reader. In a real system you could get anywhere from 1 to
|
||||
/// // `output.len()` bytes in a single read.
|
||||
/// assert_eq!(bytes, 4);
|
||||
/// assert_eq!(output, [1, 2, 3, 4, 0]);
|
||||
/// });
|
||||
/// ```
|
||||
fn read_async<'a>(&'a mut self, buf: &'a mut [u8]) -> Read<'a, Self> {
|
||||
Read::new(self, buf)
|
||||
}
|
||||
|
||||
/// Creates a future which will read exactly enough bytes to fill `buf`,
|
||||
/// returning an error if end of file (EOF) is hit sooner.
|
||||
///
|
||||
/// The returned future will resolve once the read operation is completed.
|
||||
///
|
||||
/// In the case of an error the buffer and the object will be discarded, with
|
||||
/// the error yielded.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(async_await, await_macro, futures_api)]
|
||||
/// tokio::run_async(async {
|
||||
/// // The extension trait can also be imported with
|
||||
/// // `use tokio::prelude::*`.
|
||||
/// use tokio::prelude::AsyncReadExt;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// let mut reader = Cursor::new([1, 2, 3, 4]);
|
||||
/// let mut output = [0u8; 4];
|
||||
///
|
||||
/// await!(reader.read_exact_async(&mut output)).unwrap();
|
||||
///
|
||||
/// assert_eq!(output, [1, 2, 3, 4]);
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// ## EOF is hit before `buf` is filled
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(async_await, await_macro, futures_api)]
|
||||
/// tokio::run_async(async {
|
||||
/// // The extension trait can also be imported with
|
||||
/// // `use tokio::prelude::*`.
|
||||
/// use tokio::prelude::AsyncReadExt;
|
||||
/// use std::io::{self, Cursor};
|
||||
///
|
||||
/// let mut reader = Cursor::new([1, 2, 3, 4]);
|
||||
/// let mut output = [0u8; 5];
|
||||
///
|
||||
/// let result = await!(reader.read_exact_async(&mut output));
|
||||
///
|
||||
/// assert_eq!(result.unwrap_err().kind(), io::ErrorKind::UnexpectedEof);
|
||||
/// });
|
||||
/// ```
|
||||
fn read_exact_async<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadExact<'a, Self> {
|
||||
ReadExact::new(self, buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// An extension trait which adds utility methods to `AsyncWrite` types.
|
||||
pub trait AsyncWriteExt: AsyncWrite {
|
||||
/// Write data into this object.
|
||||
///
|
||||
/// Creates a future that will write the entire contents of the buffer `buf` into
|
||||
/// this `AsyncWrite`.
|
||||
///
|
||||
/// The returned future will not complete until all the data has been written.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(async_await, await_macro, futures_api)]
|
||||
/// tokio::run_async(async {
|
||||
/// // The extension trait can also be imported with
|
||||
/// // `use tokio::prelude::*`.
|
||||
/// use tokio::prelude::AsyncWriteExt;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// let mut buf = [0u8; 5];
|
||||
/// let mut writer = Cursor::new(&mut buf[..]);
|
||||
///
|
||||
/// let n = await!(writer.write_async(&[1, 2, 3, 4])).unwrap();
|
||||
///
|
||||
/// assert_eq!(writer.into_inner()[..n], [1, 2, 3, 4, 0][..n]);
|
||||
/// });
|
||||
/// ```
|
||||
fn write_async<'a>(&'a mut self, buf: &'a [u8]) -> Write<'a, Self> {
|
||||
Write::new(self, buf)
|
||||
}
|
||||
|
||||
/// Write an entire buffer into this object.
|
||||
///
|
||||
/// Creates a future that will write the entire contents of the buffer `buf` into
|
||||
/// this `AsyncWrite`.
|
||||
///
|
||||
/// The returned future will not complete until all the data has been written.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(async_await, await_macro, futures_api)]
|
||||
/// tokio::run_async(async {
|
||||
/// // The extension trait can also be imported with
|
||||
/// // `use tokio::prelude::*`.
|
||||
/// use tokio::prelude::AsyncWriteExt;
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// let mut buf = [0u8; 5];
|
||||
/// let mut writer = Cursor::new(&mut buf[..]);
|
||||
///
|
||||
/// await!(writer.write_all_async(&[1, 2, 3, 4])).unwrap();
|
||||
///
|
||||
/// assert_eq!(writer.into_inner(), [1, 2, 3, 4, 0]);
|
||||
/// });
|
||||
/// ```
|
||||
fn write_all_async<'a>(&'a mut self, buf: &'a [u8]) -> WriteAll<'a, Self> {
|
||||
WriteAll::new(self, buf)
|
||||
}
|
||||
|
||||
/// Creates a future which will entirely flush this `AsyncWrite`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(async_await, await_macro, futures_api)]
|
||||
/// tokio::run_async(async {
|
||||
/// // The extension trait can also be imported with
|
||||
/// // `use tokio::prelude::*`.
|
||||
/// use tokio::prelude::AsyncWriteExt;
|
||||
/// use std::io::{BufWriter, Cursor};
|
||||
///
|
||||
/// let mut output = [0u8; 5];
|
||||
///
|
||||
/// {
|
||||
/// let mut writer = Cursor::new(&mut output[..]);
|
||||
/// let mut buffered = BufWriter::new(writer);
|
||||
/// await!(buffered.write_all_async(&[1, 2])).unwrap();
|
||||
/// await!(buffered.write_all_async(&[3, 4])).unwrap();
|
||||
/// await!(buffered.flush_async()).unwrap();
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(output, [1, 2, 3, 4, 0]);
|
||||
/// });
|
||||
/// ```
|
||||
fn flush_async<'a>(&mut self) -> Flush<Self> {
|
||||
Flush::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + ?Sized> AsyncReadExt for T {}
|
||||
impl<T: AsyncWrite + ?Sized> AsyncWriteExt for T {}
|
||||
@@ -0,0 +1,34 @@
|
||||
use tokio_io::AsyncRead;
|
||||
|
||||
use std::future::Future;
|
||||
use std::task::{self, Poll};
|
||||
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// A future which can be used to read bytes.
|
||||
#[derive(Debug)]
|
||||
pub struct Read<'a, T: ?Sized + 'a> {
|
||||
reader: &'a mut T,
|
||||
buf: &'a mut [u8],
|
||||
}
|
||||
|
||||
// Pinning is never projected to fields
|
||||
impl<'a, T: ?Sized> Unpin for Read<'a, T> {}
|
||||
|
||||
impl<'a, T: AsyncRead + ?Sized> Read<'a, T> {
|
||||
pub(super) fn new(reader: &'a mut T, buf: &'a mut [u8]) -> Read<'a, T> {
|
||||
Read { reader, buf }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsyncRead + ?Sized> Future for Read<'a, T> {
|
||||
type Output = io::Result<usize>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _waker: &task::Waker) -> Poll<Self::Output> {
|
||||
use crate::compat::forward::convert_poll;
|
||||
|
||||
let this = &mut *self;
|
||||
convert_poll(this.reader.poll_read(this.buf))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use tokio_io::AsyncRead;
|
||||
|
||||
use std::future::Future;
|
||||
use std::task::{self, Poll};
|
||||
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// A future which can be used to read exactly enough bytes to fill a buffer.
|
||||
#[derive(Debug)]
|
||||
pub struct ReadExact<'a, T: ?Sized + 'a> {
|
||||
reader: &'a mut T,
|
||||
buf: &'a mut [u8],
|
||||
}
|
||||
|
||||
// Pinning is never projected to fields
|
||||
impl<'a, T: ?Sized> Unpin for ReadExact<'a, T> {}
|
||||
|
||||
impl<'a, T: AsyncRead + ?Sized> ReadExact<'a, T> {
|
||||
pub(super) fn new(reader: &'a mut T, buf: &'a mut [u8]) -> ReadExact<'a, T> {
|
||||
ReadExact { reader, buf }
|
||||
}
|
||||
}
|
||||
|
||||
fn eof() -> io::Error {
|
||||
io::Error::new(io::ErrorKind::UnexpectedEof, "early eof")
|
||||
}
|
||||
|
||||
impl<'a, T: AsyncRead + ?Sized> Future for ReadExact<'a, T> {
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _waker: &task::Waker) -> Poll<Self::Output> {
|
||||
use crate::compat::forward::convert_poll;
|
||||
|
||||
let this = &mut *self;
|
||||
|
||||
while !this.buf.is_empty() {
|
||||
let n = try_ready!(convert_poll(this.reader.poll_read(this.buf)));
|
||||
|
||||
{
|
||||
let (_, rest) = mem::replace(&mut this.buf, &mut []).split_at_mut(n);
|
||||
this.buf = rest;
|
||||
}
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(eof()));
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use tokio_io::AsyncWrite;
|
||||
|
||||
use std::future::Future;
|
||||
use std::task::{self, Poll};
|
||||
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// A future used to write data.
|
||||
#[derive(Debug)]
|
||||
pub struct Write<'a, T: 'a + ?Sized> {
|
||||
writer: &'a mut T,
|
||||
buf: &'a [u8],
|
||||
}
|
||||
|
||||
// Pinning is never projected to fields
|
||||
impl<'a, T: ?Sized> Unpin for Write<'a, T> {}
|
||||
|
||||
impl<'a, T: AsyncWrite + ?Sized> Write<'a, T> {
|
||||
pub(super) fn new(writer: &'a mut T, buf: &'a [u8]) -> Write<'a, T> {
|
||||
Write { writer, buf }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsyncWrite + ?Sized> Future for Write<'a, T> {
|
||||
type Output = io::Result<usize>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _waker: &task::Waker) -> Poll<io::Result<usize>> {
|
||||
use crate::compat::forward::convert_poll;
|
||||
|
||||
let this = &mut *self;
|
||||
convert_poll(this.writer.poll_write(this.buf))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use tokio_io::AsyncWrite;
|
||||
|
||||
use std::future::Future;
|
||||
use std::task::{self, Poll};
|
||||
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// A future used to write the entire contents of a buffer.
|
||||
#[derive(Debug)]
|
||||
pub struct WriteAll<'a, T: ?Sized + 'a> {
|
||||
writer: &'a mut T,
|
||||
buf: &'a [u8],
|
||||
}
|
||||
|
||||
// Pinning is never projected to fields
|
||||
impl<'a, T: ?Sized> Unpin for WriteAll<'a, T> {}
|
||||
|
||||
impl<'a, T: AsyncWrite + ?Sized> WriteAll<'a, T> {
|
||||
pub(super) fn new(writer: &'a mut T, buf: &'a [u8]) -> WriteAll<'a, T> {
|
||||
WriteAll { writer, buf }
|
||||
}
|
||||
}
|
||||
|
||||
fn zero_write() -> io::Error {
|
||||
io::Error::new(io::ErrorKind::WriteZero, "zero-length write")
|
||||
}
|
||||
|
||||
impl<'a, T: AsyncWrite + ?Sized> Future for WriteAll<'a, T> {
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _waker: &task::Waker) -> Poll<io::Result<()>> {
|
||||
use crate::compat::forward::convert_poll;
|
||||
|
||||
let this = &mut *self;
|
||||
|
||||
while !this.buf.is_empty() {
|
||||
let n = try_ready!(convert_poll(this.writer.poll_write(this.buf)));
|
||||
|
||||
{
|
||||
let (_, rest) = mem::replace(&mut this.buf, &[]).split_at(n);
|
||||
this.buf = rest;
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(zero_write()));
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#![cfg(feature = "async-await-preview")]
|
||||
#![feature(rust_2018_preview, async_await, await_macro, futures_api)]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-async-await/0.1.6")]
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
|
||||
//! A preview of Tokio w/ `async` / `await` support.
|
||||
|
||||
extern crate futures;
|
||||
extern crate tokio_io;
|
||||
|
||||
/// Extracts the successful type of a `Poll<Result<T, E>>`.
|
||||
///
|
||||
/// This macro bakes in propagation of `Pending` and `Err` signals by returning early.
|
||||
macro_rules! try_ready {
|
||||
($x:expr) => {
|
||||
match $x {
|
||||
std::task::Poll::Ready(Ok(x)) => x,
|
||||
std::task::Poll::Ready(Err(e)) => return std::task::Poll::Ready(Err(e.into())),
|
||||
std::task::Poll::Pending => return std::task::Poll::Pending,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod await;
|
||||
pub mod compat;
|
||||
pub mod io;
|
||||
pub mod sink;
|
||||
pub mod stream;
|
||||
|
||||
// Rename the `await` macro in `std`. This is used by the redefined
|
||||
// `await` macro in this crate.
|
||||
#[doc(hidden)]
|
||||
pub use std::await as std_await;
|
||||
@@ -0,0 +1,24 @@
|
||||
//! Use sinks with `async` / `await`.
|
||||
|
||||
mod send;
|
||||
|
||||
pub use self::send::Send;
|
||||
|
||||
use futures::Sink;
|
||||
|
||||
/// An extension trait which adds utility methods to `Sink` types.
|
||||
pub trait SinkExt: Sink {
|
||||
/// Send an item into the sink.
|
||||
///
|
||||
/// Note that, **because of the flushing requirement, it is usually better
|
||||
/// to batch together items to send via `send_all`, rather than flushing
|
||||
/// between each item.**
|
||||
fn send_async(&mut self, item: Self::SinkItem) -> Send<Self>
|
||||
where
|
||||
Self: Sized + Unpin,
|
||||
{
|
||||
Send::new(self, item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sink> SinkExt for T {}
|
||||
@@ -0,0 +1,53 @@
|
||||
use futures::Sink;
|
||||
|
||||
use std::future::Future;
|
||||
use std::task::{self, Poll};
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
/// Future for the `SinkExt::send_async` combinator, which sends a value to a
|
||||
/// sink and then waits until the sink has fully flushed.
|
||||
#[derive(Debug)]
|
||||
pub struct Send<'a, T: Sink + 'a + ?Sized> {
|
||||
sink: &'a mut T,
|
||||
item: Option<T::SinkItem>,
|
||||
}
|
||||
|
||||
impl<T: Sink + Unpin + ?Sized> Unpin for Send<'_, T> {}
|
||||
|
||||
impl<'a, T: Sink + Unpin + ?Sized> Send<'a, T> {
|
||||
pub(super) fn new(sink: &'a mut T, item: T::SinkItem) -> Self {
|
||||
Send {
|
||||
sink,
|
||||
item: Some(item),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sink + Unpin + ?Sized> Future for Send<'_, T> {
|
||||
type Output = Result<(), T::SinkError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _waker: &task::Waker) -> Poll<Self::Output> {
|
||||
use crate::compat::forward::convert_poll;
|
||||
use futures::AsyncSink::{NotReady, Ready};
|
||||
|
||||
if let Some(item) = self.item.take() {
|
||||
match self.sink.start_send(item) {
|
||||
Ok(Ready) => {}
|
||||
Ok(NotReady(val)) => {
|
||||
self.item = Some(val);
|
||||
return Poll::Pending;
|
||||
}
|
||||
Err(err) => {
|
||||
return Poll::Ready(Err(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we're done sending the item, but want to block on flushing the
|
||||
// sink
|
||||
try_ready!(convert_poll(self.sink.poll_complete()));
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//! Use streams with `async` / `await`.
|
||||
|
||||
mod next;
|
||||
|
||||
pub use self::next::Next;
|
||||
|
||||
use futures::Stream;
|
||||
|
||||
/// An extension trait which adds utility methods to `Stream` types.
|
||||
pub trait StreamExt: Stream {
|
||||
/// Creates a future that resolves to the next item in the stream.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(await_macro, async_await)]
|
||||
/// tokio::run_async(async {
|
||||
/// // The extension trait can also be imported with
|
||||
/// // `use tokio::prelude::*`.
|
||||
/// use tokio::prelude::{stream, StreamExt};
|
||||
///
|
||||
/// let mut stream = stream::iter_ok::<_, ()>(1..3);
|
||||
///
|
||||
/// assert_eq!(await!(stream.next()), Some(Ok(1)));
|
||||
/// assert_eq!(await!(stream.next()), Some(Ok(2)));
|
||||
/// assert_eq!(await!(stream.next()), Some(Ok(3)));
|
||||
/// assert_eq!(await!(stream.next()), None);
|
||||
/// });
|
||||
/// ```
|
||||
fn next(&mut self) -> Next<Self>
|
||||
where
|
||||
Self: Sized + Unpin,
|
||||
{
|
||||
Next::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Stream> StreamExt for T {}
|
||||
@@ -0,0 +1,29 @@
|
||||
use futures::Stream;
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Poll, Waker};
|
||||
|
||||
/// A future of the next element of a stream.
|
||||
#[derive(Debug)]
|
||||
pub struct Next<'a, T: 'a> {
|
||||
stream: &'a mut T,
|
||||
}
|
||||
|
||||
impl<'a, T: Stream + Unpin> Unpin for Next<'a, T> {}
|
||||
|
||||
impl<'a, T: Stream + Unpin> Next<'a, T> {
|
||||
pub(super) fn new(stream: &'a mut T) -> Next<'a, T> {
|
||||
Next { stream }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Stream + Unpin> Future for Next<'a, T> {
|
||||
type Output = Option<Result<T::Item, T::Error>>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _waker: &Waker) -> Poll<Self::Output> {
|
||||
use crate::compat::forward::convert_poll_stream;
|
||||
|
||||
convert_poll_stream(self.stream.poll())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# 0.1.0 (February 23, 2019)
|
||||
|
||||
* Initial release
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "tokio-buf"
|
||||
|
||||
# When releasing to crates.io:
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.0"
|
||||
authors = ["Carl Lerche <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-buf/0.1.0/tokio_buf"
|
||||
description = """
|
||||
Asynchronous stream of byte buffers
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
bytes = "0.4.10"
|
||||
either = { version = "1.5", optional = true}
|
||||
futures = "0.1.23"
|
||||
|
||||
[features]
|
||||
default = ["util"]
|
||||
util = ["bytes/either", "either"]
|
||||
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,35 @@
|
||||
# tokio-buf
|
||||
|
||||
Asynchronous stream of byte buffers
|
||||
|
||||
[Documenation](https://docs.rs/tokio-buf)
|
||||
|
||||
## Usage
|
||||
|
||||
First, add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio-buf = "0.1.0"
|
||||
```
|
||||
|
||||
Next, add this to your crate:
|
||||
|
||||
```rust
|
||||
extern crate tokio_buf;
|
||||
```
|
||||
|
||||
You can find extensive documentation and examples about how to use this crate
|
||||
online at [https://tokio.rs](https://tokio.rs). The [API
|
||||
documentation](https://docs.rs/tokio-buf) is also a great place to get started
|
||||
for the nitty-gritty.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -0,0 +1,99 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-buf/0.1.0")]
|
||||
#![deny(missing_docs, missing_debug_implementations, unreachable_pub)]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
|
||||
//! Asynchronous stream of bytes.
|
||||
//!
|
||||
//! This crate contains the `BufStream` trait and a number of combinators for
|
||||
//! this trait. The trait is similar to `Stream` in the `futures` library, but
|
||||
//! instead of yielding arbitrary values, it only yields types that implement
|
||||
//! `Buf` (i.e, byte collections).
|
||||
|
||||
extern crate bytes;
|
||||
#[cfg(feature = "util")]
|
||||
extern crate either;
|
||||
#[allow(unused)]
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
|
||||
mod never;
|
||||
mod size_hint;
|
||||
mod str;
|
||||
mod u8;
|
||||
#[cfg(feature = "util")]
|
||||
pub mod util;
|
||||
|
||||
pub use self::size_hint::SizeHint;
|
||||
#[doc(inline)]
|
||||
#[cfg(feature = "util")]
|
||||
pub use util::BufStreamExt;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::Poll;
|
||||
|
||||
/// An asynchronous stream of bytes.
|
||||
///
|
||||
/// `BufStream` asynchronously yields values implementing `Buf`, i.e. byte
|
||||
/// buffers.
|
||||
pub trait BufStream {
|
||||
/// Values yielded by the `BufStream`.
|
||||
///
|
||||
/// Each item is a sequence of bytes representing a chunk of the total
|
||||
/// `ByteStream`.
|
||||
type Item: Buf;
|
||||
|
||||
/// The error type this `BufStream` might generate.
|
||||
type Error;
|
||||
|
||||
/// Attempt to pull out the next buffer of this stream, registering the
|
||||
/// current task for wakeup if the value is not yet available, and returning
|
||||
/// `None` if the stream is exhausted.
|
||||
///
|
||||
/// # Return value
|
||||
///
|
||||
/// There are several possible return values, each indicating a distinct
|
||||
/// stream state:
|
||||
///
|
||||
/// - `Ok(Async::NotReady)` means that this stream's next value is not ready
|
||||
/// yet. Implementations will ensure that the current task will be notified
|
||||
/// when the next value may be ready.
|
||||
///
|
||||
/// - `Ok(Async::Ready(Some(buf)))` means that the stream has successfully
|
||||
/// produced a value, `buf`, and may produce further values on subsequent
|
||||
/// `poll_buf` calls.
|
||||
///
|
||||
/// - `Ok(Async::Ready(None))` means that the stream has terminated, and
|
||||
/// `poll_buf` should not be invoked again.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Once a stream is finished, i.e. `Ready(None)` has been returned, further
|
||||
/// calls to `poll_buf` may result in a panic or other "bad behavior".
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error>;
|
||||
|
||||
/// Returns the bounds on the remaining length of the stream.
|
||||
///
|
||||
/// The size hint allows the caller to perform certain optimizations that
|
||||
/// are dependent on the byte stream size. For example, `collect` uses the
|
||||
/// size hint to pre-allocate enough capacity to store the entirety of the
|
||||
/// data received from the byte stream.
|
||||
///
|
||||
/// When `SizeHint::upper()` returns `Some` with a value equal to
|
||||
/// `SizeHint::lower()`, this represents the exact number of bytes that will
|
||||
/// be yielded by the `BufStream`.
|
||||
///
|
||||
/// # Implementation notes
|
||||
///
|
||||
/// While not enforced, implementations are expected to respect the values
|
||||
/// returned from `SizeHint`. Any deviation is considered an implementation
|
||||
/// bug. Consumers may rely on correctness in order to use the value as part
|
||||
/// of protocol impelmentations. For example, an HTTP library may use the
|
||||
/// size hint to set the `content-length` header.
|
||||
///
|
||||
/// However, `size_hint` must not be trusted to omit bounds checks in unsafe
|
||||
/// code. An incorrect implementation of `size_hint()` must not lead to
|
||||
/// memory safety violations.
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
SizeHint::default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use std::{error, fmt};
|
||||
|
||||
/// An error that can never occur
|
||||
pub enum Never {}
|
||||
|
||||
impl fmt::Debug for Never {
|
||||
fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Never {
|
||||
fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Never {
|
||||
fn description(&self) -> &str {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::u64;
|
||||
|
||||
/// A `BufStream` size hint
|
||||
///
|
||||
/// The default implementation returns:
|
||||
///
|
||||
/// * 0 for `available`
|
||||
/// * 0 for `lower`
|
||||
/// * `None` for `upper`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SizeHint {
|
||||
lower: u64,
|
||||
upper: Option<u64>,
|
||||
}
|
||||
|
||||
impl SizeHint {
|
||||
/// Returns a new `SizeHint` with default values
|
||||
pub fn new() -> SizeHint {
|
||||
SizeHint::default()
|
||||
}
|
||||
|
||||
/// Returns the lower bound of data that the `BufStream` will yield before
|
||||
/// completing.
|
||||
pub fn lower(&self) -> u64 {
|
||||
self.lower
|
||||
}
|
||||
|
||||
/// Set the value of the `lower` hint.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The function panics if `value` is less than `upper`.
|
||||
pub fn set_lower(&mut self, value: u64) {
|
||||
assert!(value <= self.upper.unwrap_or(u64::MAX));
|
||||
self.lower = value;
|
||||
}
|
||||
|
||||
/// Returns the upper bound of data the `BufStream` will yield before
|
||||
/// completing, or `None` if the value is unknown.
|
||||
pub fn upper(&self) -> Option<u64> {
|
||||
self.upper
|
||||
}
|
||||
|
||||
/// Set the value of the `upper` hint value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if `value` is less than `lower`.
|
||||
pub fn set_upper(&mut self, value: u64) {
|
||||
// There is no need to check `available` as that is guaranteed to be
|
||||
// less than or equal to `lower`.
|
||||
assert!(value >= self.lower, "`value` is less than than `lower`");
|
||||
|
||||
self.upper = Some(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use never::Never;
|
||||
use BufStream;
|
||||
|
||||
use futures::Poll;
|
||||
|
||||
use std::io;
|
||||
use std::mem;
|
||||
|
||||
impl BufStream for String {
|
||||
type Item = io::Cursor<Vec<u8>>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
let bytes = mem::replace(self, Default::default()).into_bytes();
|
||||
let buf = io::Cursor::new(bytes);
|
||||
|
||||
Ok(Some(buf).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl BufStream for &'static str {
|
||||
type Item = io::Cursor<&'static [u8]>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
let bytes = mem::replace(self, Default::default()).as_bytes();
|
||||
let buf = io::Cursor::new(bytes);
|
||||
|
||||
Ok(Some(buf).into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::Poll;
|
||||
use never::Never;
|
||||
use std::io;
|
||||
use BufStream;
|
||||
|
||||
impl BufStream for Vec<u8> {
|
||||
type Item = io::Cursor<Vec<u8>>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
poll_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufStream for &'static [u8] {
|
||||
type Item = io::Cursor<&'static [u8]>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
poll_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufStream for Bytes {
|
||||
type Item = io::Cursor<Bytes>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
poll_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufStream for BytesMut {
|
||||
type Item = io::Cursor<BytesMut>;
|
||||
type Error = Never;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.is_empty() {
|
||||
return Ok(None.into());
|
||||
}
|
||||
|
||||
poll_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_bytes<T: Default>(buf: &mut T) -> Poll<Option<io::Cursor<T>>, Never> {
|
||||
use std::mem;
|
||||
|
||||
let bytes = mem::replace(buf, Default::default());
|
||||
let buf = io::Cursor::new(bytes);
|
||||
|
||||
Ok(Some(buf).into())
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use BufStream;
|
||||
|
||||
use either::Either;
|
||||
use futures::Poll;
|
||||
|
||||
/// A buf stream that sequences two buf streams together.
|
||||
///
|
||||
/// `Chain` values are produced by the `chain` function on `BufStream`.
|
||||
#[derive(Debug)]
|
||||
pub struct Chain<T, U> {
|
||||
left: Option<T>,
|
||||
right: U,
|
||||
}
|
||||
|
||||
impl<T, U> Chain<T, U> {
|
||||
pub(crate) fn new(left: T, right: U) -> Chain<T, U> {
|
||||
Chain {
|
||||
left: Some(left),
|
||||
right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> BufStream for Chain<T, U>
|
||||
where
|
||||
T: BufStream,
|
||||
U: BufStream<Error = T::Error>,
|
||||
{
|
||||
type Item = Either<T::Item, U::Item>;
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if let Some(ref mut stream) = self.left {
|
||||
let res = try_ready!(stream.poll_buf());
|
||||
|
||||
if res.is_some() {
|
||||
return Ok(res.map(Either::Left).into());
|
||||
}
|
||||
}
|
||||
|
||||
self.left = None;
|
||||
|
||||
let res = try_ready!(self.right.poll_buf());
|
||||
Ok(res.map(Either::Right).into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use super::FromBufStream;
|
||||
use BufStream;
|
||||
|
||||
use futures::{Future, Poll};
|
||||
|
||||
/// Consumes a buf stream, collecting the data into a single byte container.
|
||||
///
|
||||
/// `Collect` values are produced by `BufStream::collect`.
|
||||
#[derive(Debug)]
|
||||
pub struct Collect<T, U>
|
||||
where
|
||||
T: BufStream,
|
||||
U: FromBufStream<T::Item>,
|
||||
{
|
||||
stream: T,
|
||||
builder: Option<U::Builder>,
|
||||
}
|
||||
|
||||
/// Errors returned from `Collect` future.
|
||||
#[derive(Debug)]
|
||||
pub struct CollectError<T, U> {
|
||||
inner: Error<T, U>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error<T, U> {
|
||||
Stream(T),
|
||||
Collect(U),
|
||||
}
|
||||
|
||||
impl<T, U> Collect<T, U>
|
||||
where
|
||||
T: BufStream,
|
||||
U: FromBufStream<T::Item>,
|
||||
{
|
||||
pub(crate) fn new(stream: T) -> Collect<T, U> {
|
||||
let builder = U::builder(&stream.size_hint());
|
||||
|
||||
Collect {
|
||||
stream,
|
||||
builder: Some(builder),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Future for Collect<T, U>
|
||||
where
|
||||
T: BufStream,
|
||||
U: FromBufStream<T::Item>,
|
||||
{
|
||||
type Item = U;
|
||||
type Error = CollectError<T::Error, U::Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
let res = self.stream.poll_buf().map_err(|err| {
|
||||
let inner = Error::Stream(err);
|
||||
CollectError { inner }
|
||||
});
|
||||
|
||||
match try_ready!(res) {
|
||||
Some(mut buf) => {
|
||||
let builder = self.builder.as_mut().expect("cannot poll after done");
|
||||
|
||||
U::extend(builder, &mut buf, &self.stream.size_hint()).map_err(|err| {
|
||||
let inner = Error::Collect(err);
|
||||
CollectError { inner }
|
||||
})?;
|
||||
}
|
||||
None => {
|
||||
let builder = self.builder.take().expect("cannot poll after done");
|
||||
let value = U::build(builder).map_err(|err| {
|
||||
let inner = Error::Collect(err);
|
||||
CollectError { inner }
|
||||
})?;
|
||||
return Ok(value.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl CollectError =====
|
||||
|
||||
impl<T, U> CollectError<T, U> {
|
||||
/// Returns `true` if the error was caused by polling the stream.
|
||||
pub fn is_stream_err(&self) -> bool {
|
||||
match self.inner {
|
||||
Error::Stream(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the error happened while collecting the data.
|
||||
pub fn is_collect_err(&self) -> bool {
|
||||
match self.inner {
|
||||
Error::Collect(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use SizeHint;
|
||||
|
||||
use bytes::{Buf, BufMut};
|
||||
|
||||
use std::usize;
|
||||
|
||||
/// Conversion from a `BufStream`.
|
||||
///
|
||||
/// By implementing `FromBufStream` for a type, you define how it will be
|
||||
/// created from a buf stream. This is common for types which describe byte
|
||||
/// storage of some kind.
|
||||
///
|
||||
/// `FromBufStream` is rarely called explicitly, and it is instead used through
|
||||
/// `BufStream`'s `collect` method.
|
||||
pub trait FromBufStream<T: Buf>: Sized {
|
||||
/// Type that is used to build `Self` while the `BufStream` is being
|
||||
/// consumed.
|
||||
type Builder;
|
||||
|
||||
/// Error that might happen on conversion.
|
||||
type Error;
|
||||
|
||||
/// Create a new, empty, builder. The provided `hint` can be used to inform
|
||||
/// reserving capacity.
|
||||
fn builder(hint: &SizeHint) -> Self::Builder;
|
||||
|
||||
/// Extend the builder with the `Buf`.
|
||||
///
|
||||
/// This method is called whenever a new `Buf` value is obtained from the
|
||||
/// buf stream.
|
||||
///
|
||||
/// The provided size hint represents the state of the stream **after**
|
||||
/// `buf` has been yielded. The lower bound represents the minimum amount of
|
||||
/// data that will be provided after this call to `extend` returns.
|
||||
fn extend(builder: &mut Self::Builder, buf: &mut T, hint: &SizeHint)
|
||||
-> Result<(), Self::Error>;
|
||||
|
||||
/// Finalize the building of `Self`.
|
||||
///
|
||||
/// Called once the buf stream is fully consumed.
|
||||
fn build(builder: Self::Builder) -> Result<Self, Self::Error>;
|
||||
}
|
||||
|
||||
/// Error returned from collecting into a `Vec<u8>`
|
||||
#[derive(Debug)]
|
||||
pub struct CollectVecError {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
impl<T: Buf> FromBufStream<T> for Vec<u8> {
|
||||
type Builder = Vec<u8>;
|
||||
type Error = CollectVecError;
|
||||
|
||||
fn builder(_hint: &SizeHint) -> Vec<u8> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn extend(builder: &mut Self, buf: &mut T, hint: &SizeHint) -> Result<(), Self::Error> {
|
||||
let lower = hint.lower();
|
||||
|
||||
// If the lower bound is greater than `usize::MAX` then we have a
|
||||
// problem
|
||||
if lower > usize::MAX as u64 {
|
||||
return Err(CollectVecError { _p: () });
|
||||
}
|
||||
|
||||
let mut reserve = lower as usize;
|
||||
|
||||
// If `upper` is set, use this value if it is less than or equal to 64.
|
||||
// This only really impacts the first iteration.
|
||||
match hint.upper() {
|
||||
Some(upper) if upper <= 64 => {
|
||||
reserve = upper as usize;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// hint.lower() represents the minimum amount of data that will be
|
||||
// received *after* this function call. We reserve this amount on top of
|
||||
// the amount of data in `buf`.
|
||||
reserve = match reserve.checked_add(buf.remaining()) {
|
||||
Some(n) => n,
|
||||
None => return Err(CollectVecError { _p: () }),
|
||||
};
|
||||
|
||||
// Always reserve 64 bytes the first time, unless `upper` is set and is
|
||||
// less than 64.
|
||||
if builder.is_empty() {
|
||||
reserve = reserve.max(match hint.upper() {
|
||||
Some(upper) if upper < 64 => upper as usize,
|
||||
_ => 64,
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure overflow won't happen when reserving
|
||||
if reserve.checked_add(builder.len()).is_none() {
|
||||
return Err(CollectVecError { _p: () });
|
||||
}
|
||||
|
||||
// Reserve space
|
||||
builder.reserve(reserve);
|
||||
|
||||
// Copy the data
|
||||
builder.put(buf);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(builder: Self) -> Result<Self, Self::Error> {
|
||||
Ok(builder)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use BufStream;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::Poll;
|
||||
|
||||
/// Limits the stream to a maximum amount of data.
|
||||
#[derive(Debug)]
|
||||
pub struct Limit<T> {
|
||||
stream: T,
|
||||
remaining: u64,
|
||||
}
|
||||
|
||||
/// Errors returned from `Limit`.
|
||||
#[derive(Debug)]
|
||||
pub struct LimitError<T> {
|
||||
/// When `None`, limit was reached
|
||||
inner: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Limit<T> {
|
||||
pub(crate) fn new(stream: T, amount: u64) -> Limit<T> {
|
||||
Limit {
|
||||
stream,
|
||||
remaining: amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BufStream for Limit<T>
|
||||
where
|
||||
T: BufStream,
|
||||
{
|
||||
type Item = T::Item;
|
||||
type Error = LimitError<T::Error>;
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
use futures::Async::Ready;
|
||||
|
||||
if self.stream.size_hint().lower() > self.remaining {
|
||||
return Err(LimitError { inner: None });
|
||||
}
|
||||
|
||||
let res = self
|
||||
.stream
|
||||
.poll_buf()
|
||||
.map_err(|err| LimitError { inner: Some(err) });
|
||||
|
||||
match res {
|
||||
Ok(Ready(Some(ref buf))) => {
|
||||
if buf.remaining() as u64 > self.remaining {
|
||||
self.remaining = 0;
|
||||
return Err(LimitError { inner: None });
|
||||
}
|
||||
|
||||
self.remaining -= buf.remaining() as u64;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl LimitError =====
|
||||
|
||||
impl<T> LimitError<T> {
|
||||
/// Returns `true` if the error was caused by polling the stream.
|
||||
pub fn is_stream_err(&self) -> bool {
|
||||
self.inner.is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if the stream reached its limit.
|
||||
pub fn is_limit_err(&self) -> bool {
|
||||
self.inner.is_none()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//! Types and utilities for working with `BufStream`.
|
||||
|
||||
mod chain;
|
||||
mod collect;
|
||||
mod from;
|
||||
mod limit;
|
||||
|
||||
pub use self::chain::Chain;
|
||||
pub use self::collect::Collect;
|
||||
pub use self::from::FromBufStream;
|
||||
pub use self::limit::Limit;
|
||||
|
||||
pub mod error {
|
||||
//! Error types
|
||||
|
||||
pub use super::collect::CollectError;
|
||||
pub use super::from::CollectVecError;
|
||||
pub use super::limit::LimitError;
|
||||
}
|
||||
|
||||
use BufStream;
|
||||
|
||||
impl<T> BufStreamExt for T where T: BufStream {}
|
||||
|
||||
/// An extension trait for `BufStream`'s that provides a variety of convenient
|
||||
/// adapters.
|
||||
pub trait BufStreamExt: BufStream {
|
||||
/// Takes two buf streams and creates a new buf stream over both in
|
||||
/// sequence.
|
||||
///
|
||||
/// `chain()` returns a new `BufStream` value which will first yield all
|
||||
/// data from `self` then all data from `other`.
|
||||
///
|
||||
/// In other words, it links two buf streams together, in a chain.
|
||||
fn chain<T>(self, other: T) -> Chain<Self, T>
|
||||
where
|
||||
Self: Sized,
|
||||
T: BufStream<Error = Self::Error>,
|
||||
{
|
||||
Chain::new(self, other)
|
||||
}
|
||||
|
||||
/// Consumes all data from `self`, storing it in byte storage of type `T`.
|
||||
///
|
||||
/// `collect()` returns a future that buffers all data yielded from `self`
|
||||
/// into storage of type of `T`. The future completes once `self` yield
|
||||
/// `None`, returning the buffered data.
|
||||
///
|
||||
/// The collect future will yield an error if `self` yields an error or if
|
||||
/// the collect operation errors. The collect error cases are dependent on
|
||||
/// the target storage type.
|
||||
fn collect<T>(self) -> Collect<Self, T>
|
||||
where
|
||||
Self: Sized,
|
||||
T: FromBufStream<Self::Item>,
|
||||
{
|
||||
Collect::new(self)
|
||||
}
|
||||
|
||||
/// Limit the number of bytes that the stream can yield.
|
||||
///
|
||||
/// `limit()` returns a new `BufStream` value which yields all the data from
|
||||
/// `self` while ensuring that at most `amount` bytes are yielded.
|
||||
///
|
||||
/// If `self` can yield greater than `amount` bytes, the returned stream
|
||||
/// will yield an error.
|
||||
fn limit(self, amount: u64) -> Limit<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Limit::new(self, amount)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::Async::*;
|
||||
use tokio_buf::{BufStream, SizeHint};
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
// ===== test `SizeHint` =====
|
||||
|
||||
#[test]
|
||||
fn size_hint() {
|
||||
let hint = SizeHint::new();
|
||||
assert_eq!(hint.lower(), 0);
|
||||
assert!(hint.upper().is_none());
|
||||
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_lower(100);
|
||||
assert_eq!(hint.lower(), 100);
|
||||
assert!(hint.upper().is_none());
|
||||
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_upper(200);
|
||||
assert_eq!(hint.lower(), 0);
|
||||
assert_eq!(hint.upper(), Some(200));
|
||||
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_lower(100);
|
||||
hint.set_upper(100);
|
||||
assert_eq!(hint.lower(), 100);
|
||||
assert_eq!(hint.upper(), Some(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn size_hint_lower_bigger_than_upper() {
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_upper(100);
|
||||
hint.set_lower(200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn size_hint_upper_less_than_lower() {
|
||||
let mut hint = SizeHint::new();
|
||||
hint.set_lower(200);
|
||||
hint.set_upper(100);
|
||||
}
|
||||
|
||||
// ===== BufStream impelmentations for misc types =====
|
||||
|
||||
#[test]
|
||||
fn str_buf_stream() {
|
||||
let mut bs = "hello world".to_string();
|
||||
assert_buf_eq!(bs.poll_buf(), "hello world");
|
||||
assert!(bs.is_empty());
|
||||
assert_none!(bs.poll_buf());
|
||||
|
||||
let mut bs = "hello world";
|
||||
assert_buf_eq!(bs.poll_buf(), "hello world");
|
||||
assert!(bs.is_empty());
|
||||
assert_none!(bs.poll_buf());
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
#![cfg(feature = "ext")]
|
||||
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::Async::*;
|
||||
use futures::Future;
|
||||
use tokio_buf::{BufStream, BufStreamExt};
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
|
||||
use support::*;
|
||||
|
||||
// ===== test `chain()` =====
|
||||
|
||||
#[test]
|
||||
fn chain() {
|
||||
// Chain one with one
|
||||
//
|
||||
let mut bs = one("hello").chain(one("world"));
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "hello");
|
||||
assert_buf_eq!(bs.poll_buf(), "world");
|
||||
assert_none!(bs.poll_buf());
|
||||
|
||||
// Chain multi with multi
|
||||
let mut bs = list(&["foo", "bar"]).chain(list(&["baz", "bok"]));
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "foo");
|
||||
assert_buf_eq!(bs.poll_buf(), "bar");
|
||||
assert_buf_eq!(bs.poll_buf(), "baz");
|
||||
assert_buf_eq!(bs.poll_buf(), "bok");
|
||||
assert_none!(bs.poll_buf());
|
||||
|
||||
// Chain includes a not ready call
|
||||
//
|
||||
let mut bs = new_mock(&[Ok(Ready("foo")), Ok(NotReady), Ok(Ready("bar"))]).chain(one("baz"));
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "foo");
|
||||
assert_not_ready!(bs.poll_buf());
|
||||
assert_buf_eq!(bs.poll_buf(), "bar");
|
||||
assert_buf_eq!(bs.poll_buf(), "baz");
|
||||
assert_none!(bs.poll_buf());
|
||||
}
|
||||
|
||||
// ===== Test `collect()` =====
|
||||
|
||||
#[test]
|
||||
fn collect_vec() {
|
||||
// While unfortunate, this test makes some assumptions on vec's resizing
|
||||
// behavior.
|
||||
//
|
||||
// Collect one
|
||||
//
|
||||
let bs = one("hello world");
|
||||
|
||||
let vec: Vec<u8> = bs.collect().wait().unwrap();
|
||||
|
||||
assert_eq!(vec, b"hello world");
|
||||
assert_eq!(vec.capacity(), 64);
|
||||
|
||||
// Collect one, with size hint
|
||||
//
|
||||
let mut bs = one("hello world");
|
||||
bs.size_hint.set_lower(11);
|
||||
|
||||
let vec: Vec<u8> = bs.collect().wait().unwrap();
|
||||
|
||||
assert_eq!(vec, b"hello world");
|
||||
assert_eq!(vec.capacity(), 64);
|
||||
|
||||
// Collect one, with size hint
|
||||
//
|
||||
let mut bs = one("hello world");
|
||||
bs.size_hint.set_lower(10);
|
||||
|
||||
let vec: Vec<u8> = bs.collect().wait().unwrap();
|
||||
|
||||
assert_eq!(vec, b"hello world");
|
||||
assert_eq!(vec.capacity(), 64);
|
||||
|
||||
// Collect many
|
||||
//
|
||||
let bs = list(&["hello", " ", "world", ", one two three"]);
|
||||
|
||||
let vec: Vec<u8> = bs.collect().wait().unwrap();
|
||||
|
||||
assert_eq!(vec, b"hello world, one two three");
|
||||
}
|
||||
|
||||
// ===== Test limit() =====
|
||||
|
||||
#[test]
|
||||
fn limit() {
|
||||
// Not limited
|
||||
|
||||
let res = one("hello world")
|
||||
.limit(100)
|
||||
.collect::<Vec<_>>()
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res, b"hello world");
|
||||
|
||||
let res = list(&["hello", " ", "world"])
|
||||
.limit(100)
|
||||
.collect::<Vec<_>>()
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res, b"hello world");
|
||||
|
||||
let res = list(&["hello", " ", "world"])
|
||||
.limit(11)
|
||||
.collect::<Vec<_>>()
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(res, b"hello world");
|
||||
|
||||
// Limited
|
||||
|
||||
let res = one("hello world").limit(5).collect::<Vec<_>>().wait();
|
||||
|
||||
assert!(res.is_err());
|
||||
|
||||
let res = one("hello world").limit(10).collect::<Vec<_>>().wait();
|
||||
|
||||
assert!(res.is_err());
|
||||
|
||||
let mut bs = list(&["hello", " ", "world"]).limit(9);
|
||||
|
||||
assert_buf_eq!(bs.poll_buf(), "hello");
|
||||
assert_buf_eq!(bs.poll_buf(), " ");
|
||||
assert!(bs.poll_buf().is_err());
|
||||
|
||||
let mut bs = list(&["hello", " ", "world"]);
|
||||
bs.size_hint.set_lower(11);
|
||||
let mut bs = bs.limit(9);
|
||||
|
||||
assert!(bs.poll_buf().is_err());
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#![allow(unused)]
|
||||
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
extern crate tokio_buf;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::Async::*;
|
||||
use futures::Poll;
|
||||
use tokio_buf::{BufStream, SizeHint};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::Cursor;
|
||||
|
||||
macro_rules! assert_buf_eq {
|
||||
($actual:expr, $expect:expr) => {{
|
||||
match $actual {
|
||||
Ok(Ready(Some(val))) => {
|
||||
assert_eq!(val.remaining(), val.bytes().len());
|
||||
assert_eq!(val.bytes(), $expect.as_bytes());
|
||||
}
|
||||
Ok(Ready(None)) => panic!("expected value; BufStream yielded None"),
|
||||
Ok(NotReady) => panic!("expected value; BufStream is not ready"),
|
||||
Err(e) => panic!("expected value; got error = {:?}", e),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_none {
|
||||
($actual:expr) => {
|
||||
match $actual {
|
||||
Ok(Ready(None)) => {}
|
||||
actual => panic!("expected None; actual = {:?}", actual),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_not_ready {
|
||||
($actual:expr) => {
|
||||
match $actual {
|
||||
Ok(NotReady) => {}
|
||||
actual => panic!("expected NotReady; actual = {:?}", actual),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ===== Test utils =====
|
||||
|
||||
pub fn one(buf: &'static str) -> Mock {
|
||||
list(&[buf])
|
||||
}
|
||||
|
||||
pub fn list(bufs: &[&'static str]) -> Mock {
|
||||
let mut polls = VecDeque::new();
|
||||
|
||||
for &buf in bufs {
|
||||
polls.push_back(Ok(Ready(buf.as_bytes())));
|
||||
}
|
||||
|
||||
Mock {
|
||||
polls,
|
||||
size_hint: SizeHint::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mock(values: &[Poll<&'static str, ()>]) -> Mock {
|
||||
let mut polls = VecDeque::new();
|
||||
|
||||
for &v in values {
|
||||
polls.push_back(match v {
|
||||
Ok(Ready(v)) => Ok(Ready(v.as_bytes())),
|
||||
Ok(NotReady) => Ok(NotReady),
|
||||
Err(e) => Err(e),
|
||||
});
|
||||
}
|
||||
|
||||
Mock {
|
||||
polls,
|
||||
size_hint: SizeHint::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mock {
|
||||
pub polls: VecDeque<Poll<&'static [u8], ()>>,
|
||||
pub size_hint: SizeHint,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockBuf {
|
||||
pub data: Cursor<&'static [u8]>,
|
||||
}
|
||||
|
||||
impl BufStream for Mock {
|
||||
type Item = MockBuf;
|
||||
type Error = ();
|
||||
|
||||
fn poll_buf(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
match self.polls.pop_front() {
|
||||
Some(Ok(Ready(value))) => Ok(Ready(Some(MockBuf::new(value)))),
|
||||
Some(Ok(NotReady)) => Ok(NotReady),
|
||||
Some(Err(e)) => Err(e),
|
||||
None => Ok(Ready(None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
self.size_hint.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl MockBuf {
|
||||
fn new(data: &'static [u8]) -> MockBuf {
|
||||
MockBuf {
|
||||
data: Cursor::new(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Buf for MockBuf {
|
||||
fn remaining(&self) -> usize {
|
||||
self.data.remaining()
|
||||
}
|
||||
|
||||
fn bytes(&self) -> &[u8] {
|
||||
self.data.bytes()
|
||||
}
|
||||
|
||||
fn advance(&mut self, cnt: usize) {
|
||||
self.data.advance(cnt)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
# Unreleased
|
||||
# 0.1.1 (September 26, 2018)
|
||||
|
||||
* Allow setting max line length with `LinesCodec` (#632)
|
||||
|
||||
# 0.1.0 (June 13, 2018)
|
||||
|
||||
* Initial release (#353)
|
||||
|
||||
@@ -3,20 +3,21 @@ name = "tokio-codec"
|
||||
|
||||
# When releasing to crates.io:
|
||||
# - Update html_root_url.
|
||||
# - Update doc URL.
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Carl Lerche <[email protected]>", "Bryan Burgers <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-codec/0.1"
|
||||
documentation = "https://docs.rs/tokio-codec/0.1.1/tokio_codec"
|
||||
description = """
|
||||
Utilities for encoding and decoding frames.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
tokio-io = { version = "0.1.6", path = "../tokio-io" }
|
||||
tokio-io = "0.1.7"
|
||||
bytes = "0.4.7"
|
||||
futures = "0.1.18"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 Tokio Contributors
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use bytes::{Bytes, BufMut, BytesMut};
|
||||
use tokio_io::_tokio_codec::{Encoder, Decoder};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use std::io;
|
||||
use tokio_io::_tokio_codec::{Decoder, Encoder};
|
||||
|
||||
/// A simple `Codec` implementation that just ships bytes around.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
@@ -8,7 +8,9 @@ pub struct BytesCodec(());
|
||||
|
||||
impl BytesCodec {
|
||||
/// Creates a new `BytesCodec` for shipping around raw bytes.
|
||||
pub fn new() -> BytesCodec { BytesCodec(()) }
|
||||
pub fn new() -> BytesCodec {
|
||||
BytesCodec(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder for BytesCodec {
|
||||
|
||||
+4
-11
@@ -1,3 +1,6 @@
|
||||
#![deny(missing_docs, missing_debug_implementations, warnings)]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-codec/0.1.1")]
|
||||
|
||||
//! Utilities for encoding and decoding frames.
|
||||
//!
|
||||
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and
|
||||
@@ -10,23 +13,13 @@
|
||||
//! [`Stream`]: #
|
||||
//! [transports]: #
|
||||
|
||||
#![deny(missing_docs, missing_debug_implementations, warnings)]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-codec/0.1.0")]
|
||||
|
||||
extern crate bytes;
|
||||
extern crate tokio_io;
|
||||
|
||||
mod bytes_codec;
|
||||
mod lines_codec;
|
||||
|
||||
pub use tokio_io::_tokio_codec::{
|
||||
Decoder,
|
||||
Encoder,
|
||||
Framed,
|
||||
FramedParts,
|
||||
FramedRead,
|
||||
FramedWrite,
|
||||
};
|
||||
pub use tokio_io::_tokio_codec::{Decoder, Encoder, Framed, FramedParts, FramedRead, FramedWrite};
|
||||
|
||||
pub use bytes_codec::BytesCodec;
|
||||
pub use lines_codec::LinesCodec;
|
||||
|
||||
+126
-20
@@ -1,6 +1,6 @@
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use tokio_io::_tokio_codec::{Encoder, Decoder};
|
||||
use std::{io, str};
|
||||
use std::{cmp, io, str, usize};
|
||||
use tokio_io::_tokio_codec::{Decoder, Encoder};
|
||||
|
||||
/// A simple `Codec` implementation that splits up data into lines.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
@@ -12,20 +12,99 @@ pub struct LinesCodec {
|
||||
// The next time `decode` is called with `abcde\n`, the method will
|
||||
// only look at `de\n` before returning.
|
||||
next_index: usize,
|
||||
|
||||
/// The maximum length for a given line. If `usize::MAX`, lines will be
|
||||
/// read until a `\n` character is reached.
|
||||
max_length: usize,
|
||||
|
||||
/// Are we currently discarding the remainder of a line which was over
|
||||
/// the length limit?
|
||||
is_discarding: bool,
|
||||
}
|
||||
|
||||
impl LinesCodec {
|
||||
/// Returns a `LinesCodec` for splitting up data into lines.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The returned `LinesCodec` will not have an upper bound on the length
|
||||
/// of a buffered line. See the documentation for [`new_with_max_length`]
|
||||
/// for information on why this could be a potential security risk.
|
||||
///
|
||||
/// [`new_with_max_length`]: #method.new_with_max_length
|
||||
pub fn new() -> LinesCodec {
|
||||
LinesCodec { next_index: 0 }
|
||||
LinesCodec {
|
||||
next_index: 0,
|
||||
max_length: usize::MAX,
|
||||
is_discarding: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `LinesCodec` with a maximum line length limit.
|
||||
///
|
||||
/// If this is set, calls to `LinesCodec::decode` will return a
|
||||
/// [`LengthError`] when a line exceeds the length limit. Subsequent calls
|
||||
/// will discard up to `limit` bytes from that line until a newline
|
||||
/// character is reached, returning `None` until the line over the limit
|
||||
/// has been fully discarded. After that point, calls to `decode` will
|
||||
/// function as normal.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Setting a length limit is highly recommended for any `LinesCodec` which
|
||||
/// will be exposed to untrusted input. Otherwise, the size of the buffer
|
||||
/// that holds the line currently being read is unbounded. An attacker could
|
||||
/// exploit this unbounded buffer by sending an unbounded amount of input
|
||||
/// without any `\n` characters, causing unbounded memory consumption.
|
||||
///
|
||||
/// [`LengthError`]: ../struct.LengthError
|
||||
pub fn new_with_max_length(max_length: usize) -> Self {
|
||||
LinesCodec {
|
||||
max_length,
|
||||
..LinesCodec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the maximum line length when decoding.
|
||||
///
|
||||
/// ```
|
||||
/// use std::usize;
|
||||
/// use tokio_codec::LinesCodec;
|
||||
///
|
||||
/// let codec = LinesCodec::new();
|
||||
/// assert_eq!(codec.max_length(), usize::MAX);
|
||||
/// ```
|
||||
/// ```
|
||||
/// use tokio_codec::LinesCodec;
|
||||
///
|
||||
/// let codec = LinesCodec::new_with_max_length(256);
|
||||
/// assert_eq!(codec.max_length(), 256);
|
||||
/// ```
|
||||
pub fn max_length(&self) -> usize {
|
||||
self.max_length
|
||||
}
|
||||
|
||||
fn discard(&mut self, newline_offset: Option<usize>, read_to: usize, buf: &mut BytesMut) {
|
||||
let discard_to = if let Some(offset) = newline_offset {
|
||||
// If we found a newline, discard up to that offset and
|
||||
// then stop discarding. On the next iteration, we'll try
|
||||
// to read a line normally.
|
||||
self.is_discarding = false;
|
||||
offset + self.next_index + 1
|
||||
} else {
|
||||
// Otherwise, we didn't find a newline, so we'll discard
|
||||
// everything we read. On the next iteration, we'll continue
|
||||
// discarding up to max_len bytes unless we find a newline.
|
||||
read_to
|
||||
};
|
||||
buf.advance(discard_to);
|
||||
self.next_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn utf8(buf: &[u8]) -> Result<&str, io::Error> {
|
||||
str::from_utf8(buf).map_err(|_|
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Unable to decode input as UTF8"))
|
||||
str::from_utf8(buf)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Unable to decode input as UTF8"))
|
||||
}
|
||||
|
||||
fn without_carriage_return(s: &[u8]) -> &[u8] {
|
||||
@@ -38,22 +117,49 @@ fn without_carriage_return(s: &[u8]) -> &[u8] {
|
||||
|
||||
impl Decoder for LinesCodec {
|
||||
type Item = String;
|
||||
// TODO: in the next breaking change, this should be changed to a custom
|
||||
// error type that indicates the "max length exceeded" condition better.
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<String>, io::Error> {
|
||||
if let Some(newline_offset) =
|
||||
buf[self.next_index..].iter().position(|b| *b == b'\n')
|
||||
{
|
||||
let newline_index = newline_offset + self.next_index;
|
||||
let line = buf.split_to(newline_index + 1);
|
||||
let line = &line[..line.len()-1];
|
||||
let line = without_carriage_return(line);
|
||||
let line = utf8(line)?;
|
||||
self.next_index = 0;
|
||||
Ok(Some(line.to_string()))
|
||||
} else {
|
||||
self.next_index = buf.len();
|
||||
Ok(None)
|
||||
loop {
|
||||
// Determine how far into the buffer we'll search for a newline. If
|
||||
// there's no max_length set, we'll read to the end of the buffer.
|
||||
let read_to = cmp::min(self.max_length.saturating_add(1), buf.len());
|
||||
|
||||
let newline_offset = buf[self.next_index..read_to]
|
||||
.iter()
|
||||
.position(|b| *b == b'\n');
|
||||
|
||||
if self.is_discarding {
|
||||
self.discard(newline_offset, read_to, buf);
|
||||
} else {
|
||||
return if let Some(offset) = newline_offset {
|
||||
// Found a line!
|
||||
let newline_index = offset + self.next_index;
|
||||
self.next_index = 0;
|
||||
let line = buf.split_to(newline_index + 1);
|
||||
let line = &line[..line.len() - 1];
|
||||
let line = without_carriage_return(line);
|
||||
let line = utf8(line)?;
|
||||
|
||||
Ok(Some(line.to_string()))
|
||||
} else if buf.len() > self.max_length {
|
||||
// Reached the maximum length without finding a
|
||||
// newline, return an error and start discarding on the
|
||||
// next call.
|
||||
self.is_discarding = true;
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"line length limit exceeded",
|
||||
))
|
||||
} else {
|
||||
// We didn't find a line or reach the length limit, so the next
|
||||
// call will resume searching at the current offset.
|
||||
self.next_index = read_to;
|
||||
Ok(None)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+144
-18
@@ -1,8 +1,8 @@
|
||||
extern crate tokio_codec;
|
||||
extern crate bytes;
|
||||
extern crate tokio_codec;
|
||||
|
||||
use bytes::{BytesMut, Bytes, BufMut};
|
||||
use tokio_codec::{BytesCodec, LinesCodec, Decoder, Encoder};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use tokio_codec::{BytesCodec, Decoder, Encoder, LinesCodec};
|
||||
|
||||
#[test]
|
||||
fn bytes_decoder() {
|
||||
@@ -27,13 +27,17 @@ fn bytes_encoder() {
|
||||
const INLINE_CAP: usize = 4 * 4 - 1;
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
codec.encode(Bytes::from_static(&[0; INLINE_CAP + 1]), &mut buf).unwrap();
|
||||
codec
|
||||
.encode(Bytes::from_static(&[0; INLINE_CAP + 1]), &mut buf)
|
||||
.unwrap();
|
||||
|
||||
// Default capacity of Framed Read
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
let mut buf = BytesMut::with_capacity(INITIAL_CAPACITY);
|
||||
codec.encode(Bytes::from_static(&[0; INITIAL_CAPACITY + 1]), &mut buf).unwrap();
|
||||
codec
|
||||
.encode(Bytes::from_static(&[0; INITIAL_CAPACITY + 1]), &mut buf)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -56,21 +60,143 @@ fn lines_decoder() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_encoder() {
|
||||
let mut codec = BytesCodec::new();
|
||||
fn lines_decoder_max_length() {
|
||||
const MAX_LENGTH: usize = 6;
|
||||
|
||||
// Default capacity of BytesMut
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const INLINE_CAP: usize = 4 * 8 - 1;
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const INLINE_CAP: usize = 4 * 4 - 1;
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
codec.encode(Bytes::from_static(&[b'a'; INLINE_CAP + 1]), &mut buf).unwrap();
|
||||
buf.reserve(200);
|
||||
buf.put("line 1 is too long\nline 2\nline 3\r\nline 4\n\r\n\r");
|
||||
|
||||
// Default capacity of Framed Read
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
assert!(codec.decode(buf).is_err());
|
||||
|
||||
let mut buf = BytesMut::with_capacity(INITIAL_CAPACITY);
|
||||
codec.encode(Bytes::from_static(&[b'a'; INITIAL_CAPACITY + 1]), &mut buf).unwrap();
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("line 2", line);
|
||||
|
||||
assert!(codec.decode(buf).is_err());
|
||||
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("line 4", line);
|
||||
|
||||
let line = codec.decode(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("", line);
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
buf.put("k");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
let line = codec.decode_eof(buf).unwrap().unwrap();
|
||||
assert!(
|
||||
line.len() <= MAX_LENGTH,
|
||||
"{:?}.len() <= {:?}",
|
||||
line,
|
||||
MAX_LENGTH
|
||||
);
|
||||
assert_eq!("\rk", line);
|
||||
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
assert_eq!(None, codec.decode_eof(buf).unwrap());
|
||||
|
||||
// Line that's one character too long. This could cause an out of bounds
|
||||
// error if we peek at the next characters using slice indexing.
|
||||
// buf.put("aaabbbc");
|
||||
// assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length_underrun() {
|
||||
const MAX_LENGTH: usize = 6;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("too l");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
buf.put("ong\n");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
buf.put("line 2");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("\n");
|
||||
assert_eq!("line 2", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length_bursts() {
|
||||
const MAX_LENGTH: usize = 10;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("too l");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("ong\n");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length_big_burst() {
|
||||
const MAX_LENGTH: usize = 10;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("line ");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
buf.put("too long!\n");
|
||||
assert!(codec.decode(buf).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_decoder_max_length_newline_between_decodes() {
|
||||
const MAX_LENGTH: usize = 5;
|
||||
|
||||
let mut codec = LinesCodec::new_with_max_length(MAX_LENGTH);
|
||||
let buf = &mut BytesMut::new();
|
||||
|
||||
buf.reserve(200);
|
||||
buf.put("hello");
|
||||
assert_eq!(None, codec.decode(buf).unwrap());
|
||||
|
||||
buf.put("\nworld");
|
||||
assert_eq!("hello", codec.decode(buf).unwrap().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lines_encoder() {
|
||||
let mut codec = LinesCodec::new();
|
||||
let mut buf = BytesMut::new();
|
||||
|
||||
codec.encode(String::from("line 1"), &mut buf).unwrap();
|
||||
assert_eq!("line 1\n", buf);
|
||||
|
||||
codec.encode(String::from("line 2"), &mut buf).unwrap();
|
||||
assert_eq!("line 1\nline 2\n", buf);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user