mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
435
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00500d1b35 | ||
|
|
647299866a | ||
|
|
a6051a61ec | ||
|
|
a8e0f0a919 | ||
|
|
7ae8135b62 | ||
|
|
353b0544a0 | ||
|
|
128495168d | ||
|
|
08548583b9 | ||
|
|
7707ba88ef | ||
|
|
72d6346c0d | ||
|
|
a85fdb884d | ||
|
|
c55d846f4b | ||
|
|
0acd06b42a | ||
|
|
4912943419 | ||
|
|
5e406a7a47 | ||
|
|
de33ee85ce | ||
|
|
874fc3320b | ||
|
|
ae67851f11 | ||
|
|
f927f01a34 | ||
|
|
49abfdb2ac | ||
|
|
479c545c20 | ||
|
|
34fcef258b | ||
|
|
7d11aa8668 | ||
|
|
0ea2307650 | ||
|
|
d0ebb41547 | ||
|
|
f5cb4c2042 | ||
|
|
4e39c9b818 | ||
|
|
97c2c4203c | ||
|
|
60366ca0fa | ||
|
|
850bfc9efa | ||
|
|
02b1117dca | ||
|
|
685da8dadd | ||
|
|
6a0e23c654 | ||
|
|
9d0c0dd22c | ||
|
|
6d5423f3e9 | ||
|
|
ebb8bab060 | ||
|
|
ce891a4df1 | ||
|
|
d869e16990 | ||
|
|
e1256d8ca4 | ||
|
|
a52f5071bf | ||
|
|
f1f8c3cde6 | ||
|
|
c2e843d928 | ||
|
|
a43ec09b55 | ||
|
|
90c2a510e2 | ||
|
|
f51ddc5958 | ||
|
|
47658a6da5 | ||
|
|
d7e3fcb9ee | ||
|
|
0b3918bce9 | ||
|
|
e309da0bee | ||
|
|
bbc8eb0f91 | ||
|
|
06c7e73e99 | ||
|
|
42de3bc7a4 | ||
|
|
20a2b9e263 | ||
|
|
ae4e8d7ad1 | ||
|
|
7a18ca2be0 | ||
|
|
4a7b7c52d1 | ||
|
|
fede3db76a | ||
|
|
2b23aa7389 | ||
|
|
382ee6bf5d | ||
|
|
24ed874e81 | ||
|
|
3965d91a5e | ||
|
|
a3ef4e4cf5 | ||
|
|
34eb47dde5 | ||
|
|
c8a484bbb2 | ||
|
|
9097ae548f | ||
|
|
d78655337a | ||
|
|
38605c5c85 | ||
|
|
fe2b997675 | ||
|
|
6d0ba19af5 | ||
|
|
cbb8fe6069 | ||
|
|
a9da220923 | ||
|
|
1c28c3b0a8 | ||
|
|
e31bd321ef | ||
|
|
c30ce1f65c | ||
|
|
a95378a850 | ||
|
|
ce173fdc91 | ||
|
|
e804f88d60 | ||
|
|
c153913211 | ||
|
|
358e4f9f80 | ||
|
|
adf822f5cc | ||
|
|
d14cbf9116 | ||
|
|
8bfb1c92ce | ||
|
|
b48fec9655 | ||
|
|
8dbc3c7937 | ||
|
|
7fbfa9b649 | ||
|
|
7d7b79e1d5 | ||
|
|
8f37544a79 | ||
|
|
43d0714898 | ||
|
|
16e272ea4b | ||
|
|
6d99e1c7de | ||
|
|
f73a2ad238 | ||
|
|
c793ead0c3 | ||
|
|
2696794771 | ||
|
|
cfd643d691 | ||
|
|
8d17261a4b | ||
|
|
423ecc187a | ||
|
|
fb28caa90c | ||
|
|
e88e64bcc0 | ||
|
|
3cc6ce7a99 | ||
|
|
81db03204d | ||
|
|
1644511bdf | ||
|
|
dc9742fbea | ||
|
|
12f1dffa2d | ||
|
|
871289b3e7 | ||
|
|
30b40ef518 | ||
|
|
22fa883296 | ||
|
|
00b6127f2e | ||
|
|
a249421abc | ||
|
|
1923350880 | ||
|
|
c90681bd8e | ||
|
|
24d0a0cfa8 | ||
|
|
07802b2c84 | ||
|
|
891de3271d | ||
|
|
8880222036 | ||
|
|
0893841f31 | ||
|
|
ec99e61945 | ||
|
|
f8c91f2ead | ||
|
|
b575082543 | ||
|
|
c4f620cb30 | ||
|
|
b047f647b7 | ||
|
|
2e05399f4b | ||
|
|
afe535283c | ||
|
|
ee597347c5 | ||
|
|
41ac1ae2bc | ||
|
|
60d81bbe10 | ||
|
|
b704c53b9c | ||
|
|
066965cd59 | ||
|
|
6259893094 | ||
|
|
43bd11bf2f | ||
|
|
d94ab62c54 | ||
|
|
a9a59ea90e | ||
|
|
c248167173 | ||
|
|
601a3ef93f | ||
|
|
fcdf9345bf | ||
|
|
4cf45c038b | ||
|
|
9730317e94 | ||
|
|
02311dcfa1 | ||
|
|
aa171f2aa9 | ||
|
|
c23c1ecbcb | ||
|
|
561a71ad63 | ||
|
|
242ea01189 | ||
|
|
1684e1c809 | ||
|
|
0ed4127d5c | ||
|
|
1e585ccb51 | ||
|
|
7ec6d88b21 | ||
|
|
13de30c53e | ||
|
|
496e889917 | ||
|
|
53ccfc1fd6 | ||
|
|
971ed2c6df | ||
|
|
dcb11118d2 | ||
|
|
3403be5e2e | ||
|
|
c6fc35aadf | ||
|
|
078d0a2ebc | ||
|
|
99d4061203 | ||
|
|
dfdfd61372 | ||
|
|
55d932a21f | ||
|
|
444660664b | ||
|
|
cf025ba45f | ||
|
|
4186b0aa38 | ||
|
|
760ae89401 | ||
|
|
56acde069f | ||
|
|
ffa5bdb22d | ||
|
|
a1d0681cd2 | ||
|
|
4dfbdbff7e | ||
|
|
c29f13b7a5 | ||
|
|
a0557840eb | ||
|
|
f25f12d576 | ||
|
|
0f70530ee7 | ||
|
|
3114d9e826 | ||
|
|
5467f0a573 | ||
|
|
555b74c7cd | ||
|
|
7ae5b7bd4f | ||
|
|
e09b90ea32 | ||
|
|
cb8f2ceb2e | ||
|
|
6866b24ca1 | ||
|
|
e7091fde78 | ||
|
|
2348f678e6 | ||
|
|
93f8cb8df2 | ||
|
|
c0c7124a4b | ||
|
|
1ac10fa80a | ||
|
|
2b96b1773d | ||
|
|
ba8680d667 | ||
|
|
111894fef9 | ||
|
|
68f7eff39e | ||
|
|
207320dbbb | ||
|
|
3fd043931e | ||
|
|
4c4699be00 | ||
|
|
8d2e3bc575 | ||
|
|
20ef286553 | ||
|
|
2bc9a48152 | ||
|
|
c5a9ede157 | ||
|
|
ce0af8f7a1 | ||
|
|
be7462e50f | ||
|
|
1550dda5cf | ||
|
|
cbb14a7bb9 | ||
|
|
a0a356152e | ||
|
|
ea79c95c67 | ||
|
|
37f405bd3b | ||
|
|
7c254eca44 | ||
|
|
f4d6ed03d9 | ||
|
|
38ec4845d1 | ||
|
|
842d5565bd | ||
|
|
d74eabc7d7 | ||
|
|
6260ed907b | ||
|
|
048174012d | ||
|
|
38cab93330 | ||
|
|
171cb57fa1 | ||
|
|
c9f5bc2915 | ||
|
|
5cdb6f8fd6 | ||
|
|
5a1a6dc90c | ||
|
|
827077409c | ||
|
|
d600ab9a8f | ||
|
|
d9d909cb4c | ||
|
|
262b19ae96 | ||
|
|
f0328f7810 | ||
|
|
347e18bc77 | ||
|
|
2e7e42bca7 | ||
|
|
0ccc09ac92 | ||
|
|
4e12299826 | ||
|
|
30d4ec0a20 | ||
|
|
9d58b70151 | ||
|
|
fde72bf047 | ||
|
|
138eef3526 | ||
|
|
c393236dfd | ||
|
|
71da06097b | ||
|
|
8feebab7cd | ||
|
|
674985d9fb | ||
|
|
77e6bb8d06 | ||
|
|
d8490c1626 | ||
|
|
e9adac288e | ||
|
|
27bfe52bba | ||
|
|
6ccefb77e2 | ||
|
|
1167c09ae8 | ||
|
|
7276d47072 | ||
|
|
9f0b6d3166 | ||
|
|
8fda719845 | ||
|
|
646fbae765 | ||
|
|
1562bb3144 | ||
|
|
0366a3e6d1 | ||
|
|
03b68f4e75 | ||
|
|
084fcd7954 | ||
|
|
cc2c358d25 | ||
|
|
51e7933c35 | ||
|
|
027351dd3a | ||
|
|
ff6130da65 | ||
|
|
018e345add | ||
|
|
e3e7cdeaff | ||
|
|
7f29acd964 | ||
|
|
2d97d5ad15 | ||
|
|
d1744bf260 | ||
|
|
de7b8914a9 | ||
|
|
9943acda81 | ||
|
|
4fca1974e9 | ||
|
|
08872c55d1 | ||
|
|
844d9c6acb | ||
|
|
ff7125ec7b | ||
|
|
7a60a0b362 | ||
|
|
94b64cd70d | ||
|
|
b5d2b0d05b | ||
|
|
0e090b7ae2 | ||
|
|
21f726041c | ||
|
|
c344aac925 | ||
|
|
cbb4abc8ae | ||
|
|
14723f9786 | ||
|
|
04a2826084 | ||
|
|
28a93e6044 | ||
|
|
dd28831e13 | ||
|
|
6dcce1901a | ||
|
|
32f46d7b88 | ||
|
|
356c81c977 | ||
|
|
d685bceb03 | ||
|
|
b094ee90e2 | ||
|
|
7e4edb8963 | ||
|
|
fc63fa2606 | ||
|
|
b9e3d2edde | ||
|
|
a23d2b2274 | ||
|
|
98e7831479 | ||
|
|
8411a6945f | ||
|
|
f69e5bfb87 | ||
|
|
2aa8751261 | ||
|
|
be02d36a86 | ||
|
|
cf2c05317c | ||
|
|
f0b2b708a7 | ||
|
|
f75e5a7ef4 | ||
|
|
0ab28627e2 | ||
|
|
a43ec11daf | ||
|
|
3db22e29d1 | ||
|
|
e2adf2612d | ||
|
|
2bc6bc14a8 | ||
|
|
d2f81b506a | ||
|
|
6b6e76080a | ||
|
|
68b4ca9f55 | ||
|
|
1769f65d37 | ||
|
|
1636910f0a | ||
|
|
adaa6849a5 | ||
|
|
0a422593f0 | ||
|
|
d22301967b | ||
|
|
4010335c84 | ||
|
|
be4577e22f | ||
|
|
e70a1b6d64 | ||
|
|
9264b837d8 | ||
|
|
db0d6d75b3 | ||
|
|
f2f30d4cf6 | ||
|
|
c624cb8ce3 | ||
|
|
f7574d9023 | ||
|
|
954f2b7304 | ||
|
|
d562e58871 | ||
|
|
9f63911adc | ||
|
|
1e54a35325 | ||
|
|
4f4f4807c3 | ||
|
|
8fda5f1984 | ||
|
|
7cb5e3460c | ||
|
|
9b81580be6 | ||
|
|
9b6744cc8e | ||
|
|
4563699838 | ||
|
|
f48065910e | ||
|
|
a5c1a7de03 | ||
|
|
a343b1d180 | ||
|
|
b44ab27359 | ||
|
|
02661ba30a | ||
|
|
fb7dfcf432 | ||
|
|
a32f918671 | ||
|
|
221f421464 | ||
|
|
1cc0168335 | ||
|
|
67220eac37 | ||
|
|
3ba818a177 | ||
|
|
6aeeeff6e8 | ||
|
|
a75fe38ba5 | ||
|
|
adce911b02 | ||
|
|
8565a98601 | ||
|
|
bff21aba6c | ||
|
|
07533a5255 | ||
|
|
4748b2571f | ||
|
|
66fef4a9bc | ||
|
|
13e2a366de | ||
|
|
cc8a662598 | ||
|
|
264ae3bdb2 | ||
|
|
187af2e6a3 | ||
|
|
31315b9463 | ||
|
|
20b5df9037 | ||
|
|
fa9743f0d4 | ||
|
|
7a89d66513 | ||
|
|
45773c5641 | ||
|
|
c52b78b792 | ||
|
|
1d28060836 | ||
|
|
947045b944 | ||
|
|
2c53bebe56 | ||
|
|
0f4287ac2b | ||
|
|
1bf1928088 | ||
|
|
a26d3aec96 | ||
|
|
a819584849 | ||
|
|
70ed3c7f04 | ||
|
|
ce9eabfdd1 | ||
|
|
894eb8b83f | ||
|
|
3572ba5a7b | ||
|
|
d8139fef7a | ||
|
|
9bcb50660e | ||
|
|
a3aab864d7 | ||
|
|
236629d1be | ||
|
|
f83f6388c4 | ||
|
|
13974068f9 | ||
|
|
6349efd237 | ||
|
|
2da15b5f24 | ||
|
|
7e88b56be5 | ||
|
|
43bbbf61a2 | ||
|
|
282b00cbe8 | ||
|
|
5a548044d7 | ||
|
|
a748da1031 | ||
|
|
6edc64afc7 | ||
|
|
8f3a265972 | ||
|
|
800574b4e0 | ||
|
|
19a87e090e | ||
|
|
6f00d7158b | ||
|
|
67c4cc0391 | ||
|
|
8381dff39b | ||
|
|
9553355c27 | ||
|
|
770d0ec452 | ||
|
|
5376f9181f | ||
|
|
4fc2adae4f | ||
|
|
f39c15334e | ||
|
|
060d22bd10 | ||
|
|
8118f8f117 | ||
|
|
1e679748ec | ||
|
|
3137c6f07d | ||
|
|
d294c992e7 | ||
|
|
58ba45a38c | ||
|
|
de8326a5a4 | ||
|
|
d65bf3805b | ||
|
|
7c1bc460f7 | ||
|
|
d883ac0fa0 | ||
|
|
1121a8eb23 | ||
|
|
6fa40b6e20 | ||
|
|
e10471dc3a | ||
|
|
0245515e4d | ||
|
|
03cb3b6ca1 | ||
|
|
3eaa1885c3 | ||
|
|
cf4cbc142b | ||
|
|
215d7d4c5f | ||
|
|
2a8d917d2c | ||
|
|
7fb1698e8d | ||
|
|
fa4fe9ef6f | ||
|
|
f01136b5c0 | ||
|
|
caa7e180e4 | ||
|
|
7b2438e744 | ||
|
|
00725f6876 | ||
|
|
5c71268bb8 | ||
|
|
8020b02bd0 | ||
|
|
11acfbbea4 | ||
|
|
f2005a78ca | ||
|
|
3fb213a861 | ||
|
|
6cf1a5b6b8 | ||
|
|
1cb1e291c1 | ||
|
|
186196b911 | ||
|
|
57ba37c978 | ||
|
|
acf8a7da7a | ||
|
|
2258de5147 | ||
|
|
dd27f1a259 | ||
|
|
5fd1b8f67c | ||
|
|
9e58b37ad5 | ||
|
|
c3b830110a | ||
|
|
602ad2e0ba | ||
|
|
06a4d895ec | ||
|
|
fce6845f2b | ||
|
|
826fc21abf | ||
|
|
bc8dcdeb58 | ||
|
|
a78b1c65cc | ||
|
|
5ede2e4d6b | ||
|
|
9d4d076189 | ||
|
|
1eb6131321 | ||
|
|
17a6f6fabf | ||
|
|
ecc23c084a | ||
|
|
937ae11f37 | ||
|
|
f0d18653a6 | ||
|
|
06bcbe8dcf | ||
|
|
bfdfb46fcd |
+7
-20
@@ -1,17 +1,17 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-0-release-amd64
|
||||
image: freebsd-12-2-release-amd64
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 12.0
|
||||
name: FreeBSD
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y curl
|
||||
- pkg install -y bash curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
- . $HOME/.cargo/env
|
||||
@@ -19,24 +19,11 @@ task:
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
|
||||
# Remove any existing patch statements
|
||||
mv Cargo.toml Cargo.toml.bck
|
||||
sed -n '/\[patch.crates-io\]/q;p' Cargo.toml.bck > Cargo.toml
|
||||
|
||||
# Patch all crates
|
||||
cat ci/patch.toml >> Cargo.toml
|
||||
|
||||
# Print `Cargo.toml` for debugging
|
||||
echo "~~~~ Cargo.toml ~~~~"
|
||||
cat Cargo.toml
|
||||
echo "~~~~~~~~~~~~~~~~~~~~"
|
||||
test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo test --all
|
||||
- cargo doc --all --no-deps
|
||||
# TODO: Re-enable
|
||||
# i686_test_script:
|
||||
# - . $HOME/.cargo/env
|
||||
# - |
|
||||
# cargo test --all --exclude tokio-tls --exclude tokio-macros --target i686-unknown-freebsd
|
||||
i686_test_script:
|
||||
- . $HOME/.cargo/env
|
||||
- |
|
||||
cargo test --all --target i686-unknown-freebsd
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<!--
|
||||
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,36 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: A-tokio, C-bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**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**
|
||||
The output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows)
|
||||
|
||||
**Description**
|
||||
Enter your issue details here.
|
||||
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,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: A-tokio, C-feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Question
|
||||
about: Please use the discussions tab for questions
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please post your question as a discussion here:
|
||||
https://github.com/tokio-rs/tokio/discussions
|
||||
|
||||
|
||||
You may also be able to find help here:
|
||||
https://discord.gg/tokio
|
||||
https://users.rust-lang.org/
|
||||
@@ -5,6 +5,9 @@ the requirements below.
|
||||
Bug fixes and new features should include tests.
|
||||
|
||||
Contributors guide: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
|
||||
|
||||
The contributors guide includes instructions for running rustfmt and building the
|
||||
documentation, which requires special commands beyond `cargo fmt` and `cargo doc`.
|
||||
-->
|
||||
|
||||
## Motivation
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Security Audit
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Audit Check
|
||||
uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Benchmark
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
bench:
|
||||
- rt_multi_threaded
|
||||
- sync_mpsc
|
||||
- sync_rwlock
|
||||
- sync_semaphore
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
# Run benchmark with `go test -bench` and stores the output to a file
|
||||
- name: Run benchmark
|
||||
run: cargo bench --bench ${{ matrix.bench }} | tee ../output.txt
|
||||
working-directory: benches
|
||||
|
||||
# Download previous benchmark result from cache (if exists)
|
||||
- name: Download previous benchmark data
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
|
||||
# Run `github-action-benchmark` action
|
||||
- name: Store benchmark result
|
||||
uses: rhysd/github-action-benchmark@v1
|
||||
with:
|
||||
name: ${{ matrix.bench }}
|
||||
# What benchmark tool the output.txt came from
|
||||
tool: 'cargo'
|
||||
# Where the output from the benchmark tool is stored
|
||||
output-file-path: output.txt
|
||||
# # Where the previous data file is stored
|
||||
# external-data-json-path: ./cache/benchmark-data.json
|
||||
# Workflow will fail when an alert happens
|
||||
fail-on-alert: true
|
||||
# GitHub API token to make a commit comment
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Enable alert commit comment
|
||||
comment-on-alert: true
|
||||
alert-comment-cc-users: '@tokio-rs/maintainers'
|
||||
auto-push: true
|
||||
|
||||
# Upload the updated cache file for the next job by actions/cache
|
||||
@@ -0,0 +1,275 @@
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
|
||||
name: CI
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
nightly: nightly-2020-09-21
|
||||
minrust: 1.45.2
|
||||
|
||||
jobs:
|
||||
# Depends on all action sthat are required for a "successful" CI run.
|
||||
tests-pass:
|
||||
name: all systems go
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
- test-unstable
|
||||
- miri
|
||||
- cross
|
||||
- features
|
||||
- minrust
|
||||
- fmt
|
||||
- clippy
|
||||
- docs
|
||||
- loom
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
test:
|
||||
name: test tokio full
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
|
||||
# Run `tokio` with `full` features. This excludes testing utilities which
|
||||
# can alter the runtime behavior of Tokio.
|
||||
- name: test tokio full
|
||||
run: cargo test --features full
|
||||
working-directory: tokio
|
||||
|
||||
# Test **all** crates in the workspace with all features.
|
||||
- name: test all --all-features
|
||||
run: cargo test --workspace --all-features
|
||||
|
||||
# Run integration tests for each feature
|
||||
- name: test tests-integration --each-feature
|
||||
run: cargo hack test --each-feature
|
||||
working-directory: tests-integration
|
||||
|
||||
# Run macro build tests
|
||||
- name: test tests-build --each-feature
|
||||
run: cargo hack test --each-feature
|
||||
working-directory: tests-build
|
||||
|
||||
test-unstable:
|
||||
name: test tokio full --unstable
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- windows-latest
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
# Run `tokio` with "unstable" cfg flag.
|
||||
- name: test tokio full --cfg unstable
|
||||
run: cargo test --features full
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
miri:
|
||||
name: miri
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- name: Install Miri
|
||||
run: |
|
||||
set -e
|
||||
rustup component add miri
|
||||
cargo miri setup
|
||||
rm -rf tokio/tests
|
||||
|
||||
- name: miri
|
||||
run: cargo miri test --features rt,rt-multi-thread,sync task
|
||||
working-directory: tokio
|
||||
san:
|
||||
name: san
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- name: asan
|
||||
run: cargo test --all-features --target x86_64-unknown-linux-gnu --lib -- --test-threads 1
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: -Z sanitizer=address
|
||||
ASAN_OPTIONS: detect_leaks=0
|
||||
|
||||
cross:
|
||||
name: cross
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- i686-unknown-linux-gnu
|
||||
- powerpc-unknown-linux-gnu
|
||||
- powerpc64-unknown-linux-gnu
|
||||
- mips-unknown-linux-gnu
|
||||
- arm-linux-androideabi
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: check
|
||||
args: --workspace --target ${{ matrix.target }}
|
||||
|
||||
features:
|
||||
name: features
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
|
||||
- name: check --each-feature
|
||||
run: cargo hack check --all --each-feature -Z avoid-dev-deps
|
||||
|
||||
# Try with unstable feature flags
|
||||
- name: check --each-feature --unstable
|
||||
run: cargo hack check --all --each-feature -Z avoid-dev-deps
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
|
||||
|
||||
minrust:
|
||||
name: minrust
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.minrust }}
|
||||
override: true
|
||||
|
||||
- name: "test --workspace --all-features"
|
||||
run: cargo check --workspace --all-features
|
||||
|
||||
minimal-versions:
|
||||
name: minimal-versions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
- name: Install cargo-hack
|
||||
run: cargo install cargo-hack
|
||||
- name: "check --all-features -Z minimal-versions"
|
||||
run: |
|
||||
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
|
||||
# from determining minimal versions based on dev-dependencies.
|
||||
cargo hack --remove-dev-deps --workspace
|
||||
# Update Cargo.lock to minimal version dependencies.
|
||||
cargo update -Z minimal-versions
|
||||
cargo check --all-features
|
||||
|
||||
fmt:
|
||||
name: fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
- name: Install rustfmt
|
||||
run: rustup component add rustfmt
|
||||
|
||||
# Check fmt
|
||||
- name: "rustfmt --check"
|
||||
# Workaround for rust-lang/cargo#7732
|
||||
run: |
|
||||
if ! rustfmt --check --edition 2018 $(find . -name '*.rs' -print); then
|
||||
printf "Please run \`rustfmt --edition 2018 \$(find . -name '*.rs' -print)\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update ${{ env.minrust }} && rustup default ${{ env.minrust }}
|
||||
- name: Install clippy
|
||||
run: rustup component add clippy
|
||||
|
||||
# Run clippy
|
||||
- name: "clippy --all"
|
||||
run: cargo clippy --all --tests
|
||||
|
||||
docs:
|
||||
name: docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.nightly }}
|
||||
override: true
|
||||
|
||||
- name: "doc --lib --all-features"
|
||||
run: cargo doc --lib --no-deps --all-features
|
||||
env:
|
||||
RUSTDOCFLAGS: --cfg docsrs
|
||||
|
||||
loom:
|
||||
name: loom
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
scope:
|
||||
- --skip loom_pool
|
||||
- loom_pool::group_a
|
||||
- loom_pool::group_b
|
||||
- loom_pool::group_c
|
||||
- loom_pool::group_d
|
||||
- time::driver
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Rust
|
||||
run: rustup update stable
|
||||
|
||||
- name: loom ${{ matrix.scope }}
|
||||
run: cargo test --lib --release --features full -- --nocapture $SCOPE
|
||||
working-directory: tokio
|
||||
env:
|
||||
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
SCOPE: ${{ matrix.scope }}
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Pull Request Security Audit
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
|
||||
jobs:
|
||||
security-audit:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, 'ci skip')"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install cargo-audit
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: cargo-audit
|
||||
|
||||
- name: Generate lockfile
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: generate-lockfile
|
||||
|
||||
- name: Audit dependencies
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: audit
|
||||
+130
-10
@@ -15,12 +15,14 @@ It should be considered a map to help you navigate the process.
|
||||
The [dev channel][dev] is available for any concerns not covered in this guide, please join
|
||||
us!
|
||||
|
||||
[dev]: https://discord.gg/6yGkFeN
|
||||
[dev]: https://discord.gg/tokio
|
||||
|
||||
## Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
|
||||
the _minimum_ behavior expected from all contributors. Instances of violations of the Code of Conduct can be reported by contacting the project team at [[email protected]](mailto:[email protected]).
|
||||
the _minimum_ behavior expected from all contributors. Instances of violations of the
|
||||
Code of Conduct can be reported by contacting the project team at
|
||||
[[email protected]](mailto:[email protected]).
|
||||
|
||||
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
@@ -29,8 +31,8 @@ the _minimum_ behavior expected from all contributors. Instances of violations o
|
||||
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.
|
||||
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
|
||||
issue tracker][issue] 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
|
||||
@@ -42,21 +44,25 @@ For any issue, there are fundamentally three ways an individual can contribute:
|
||||
often, by opening a Pull Request that changes some bit of something in
|
||||
Tokio in a concrete and reviewable manner.
|
||||
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues
|
||||
|
||||
**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.
|
||||
having problems, you can [open a discussion] 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.
|
||||
|
||||
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new
|
||||
|
||||
### 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
|
||||
When opening a new issue in the Tokio issue tracker, you will be presented
|
||||
with a basic 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.
|
||||
@@ -72,7 +78,6 @@ 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
|
||||
|
||||
@@ -112,6 +117,44 @@ 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.
|
||||
|
||||
### Cargo Commands
|
||||
|
||||
Due to the extensive use of features in Tokio, you will often need to add extra
|
||||
arguments to many common cargo commands. This section lists some commonly needed
|
||||
commands.
|
||||
|
||||
Some commands just need the `--all-features` argument:
|
||||
```
|
||||
cargo build --all-features
|
||||
cargo check --all-features
|
||||
cargo test --all-features
|
||||
```
|
||||
When building documentation normally, the markers that list the features
|
||||
required for various parts of Tokio are missing. To build the documentation
|
||||
correctly, use this command:
|
||||
```
|
||||
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
||||
```
|
||||
The `cargo fmt` command does not work on the Tokio codebase. You can use the
|
||||
command below instead:
|
||||
|
||||
```
|
||||
# Mac or Linux
|
||||
rustfmt --check --edition 2018 $(find . -name '*.rs' -print)
|
||||
|
||||
# Powershell
|
||||
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
|
||||
```
|
||||
The `--check` argument prints the things that need to be fixed. If you remove
|
||||
it, `rustfmt` will update your files locally instead.
|
||||
|
||||
You can run loom tests with
|
||||
```
|
||||
cd tokio # tokio crate in workspace
|
||||
LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \
|
||||
cargo test --lib --release --features full -- --test-threads=1 --nocapture
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
@@ -217,7 +260,7 @@ 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]).
|
||||
notes about [commit squashing](#commit-squashing)).
|
||||
|
||||
#### Commit message guidelines
|
||||
|
||||
@@ -288,7 +331,7 @@ in order to evaluate whether the changes are correct and necessary.
|
||||
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
|
||||
**Once the PR is open, do not rebase the commits**. See [Commit Squashing](#commit-squashing) for
|
||||
more details.
|
||||
|
||||
### Commit Squashing
|
||||
@@ -382,6 +425,83 @@ _Adapted from the [Node.js contributing guide][node]_.
|
||||
[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
|
||||
|
||||
## Keeping track of issues and PRs
|
||||
|
||||
The Tokio GitHub repository has a lot of issues and PRs, which is not easy to
|
||||
keep track of. This section explains the meaning of various labels, as well as
|
||||
our [GitHub project][project]. The section is primarily targeted at maintainers.
|
||||
|
||||
**Area.** The area label describes the crates relevant to this issue or PR.
|
||||
|
||||
- **A-tokio** This issue concerns the main Tokio crate.
|
||||
- **A-tokio-util** This issue concerns the `tokio-util` crate.
|
||||
- **A-tokio-tls** This issue concerns the `tokio-tls` crate. Only used for
|
||||
older issues, as the crate has been moved to another repository.
|
||||
- **A-tokio-test** The issue concerns the `tokio-test` crate.
|
||||
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
|
||||
be used for the procedural macros, and not `join!` or `select!`.
|
||||
- **A-ci** This issue concerns our GitHub Actions setup.
|
||||
|
||||
**Category.** The category label describes the category.
|
||||
|
||||
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
|
||||
- **C-enhancement** This is a PR that adds a new features.
|
||||
- **C-maintenance** This is an issue or PR about stuff such as documentation,
|
||||
GitHub Actions or code quality.
|
||||
- **C-feature-request** This is a feature request. Implementations of feature
|
||||
requests use `C-enhancement` instead.
|
||||
- **C-feature-accepted** If you submit a PR for this feature request, we wont
|
||||
close it with the reason "we don't want this". Issues with this label should
|
||||
also have the `C-feature-request` label.
|
||||
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
|
||||
world"
|
||||
- **C-proposal** A proposal of some kind, and a request for comments.
|
||||
- **C-question** A user question. Large overlap with GitHub discussions.
|
||||
- **C-request** A non-feature request, e.g. "please add deprecation notices to
|
||||
`-alpha.*` versions of crates"
|
||||
|
||||
**Call for participation.** I don't know why it's called `E-`. Many issues are
|
||||
missing a difficulty rating, and you should feel free to add one.
|
||||
|
||||
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
|
||||
or `C-feature-accepted`.
|
||||
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
|
||||
can do after reading the tutorial on our website.
|
||||
- **E-medium** This is not `E-easy` or `E-hard`.
|
||||
- **E-hard** This either involves very tricky code, is something we don't know
|
||||
how to solve, or is difficult for some other reason.
|
||||
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
|
||||
example.
|
||||
|
||||
**Module.** A more fine groaned categorization than area.
|
||||
|
||||
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
|
||||
- **M-codec** The `tokio_util::codec` module.
|
||||
- **M-compat** The `tokio_util::compat` module.
|
||||
- **M-coop** Things relevant to coop.
|
||||
- **M-fs** The `tokio::fs` module.
|
||||
- **M-io** The `tokio::io` module.
|
||||
- **M-macros** Issues about any kind of macro.
|
||||
- **M-net** The `tokio::net` module.
|
||||
- **M-process** The `tokio::process` module.
|
||||
- **M-runtime** The `tokio::runtime` module.
|
||||
- **M-signal** The `tokio::signal` module.
|
||||
- **M-stream** The `tokio::stream` module.
|
||||
- **M-sync** The `tokio::sync` module.
|
||||
- **M-task** The `tokio::task` module.
|
||||
- **M-time** The `tokio::time` module.
|
||||
- **M-tracing** Tracing support in Tokio.
|
||||
|
||||
**Topic.** Some extra information.
|
||||
|
||||
- **T-docs** This is about documentation.
|
||||
- **T-performance** This is about performance.
|
||||
- **T-v0.1.x** This is about old Tokio.
|
||||
|
||||
Any label not listed here is not in active use.
|
||||
|
||||
[project]: https://github.com/orgs/tokio-rs/projects/1
|
||||
|
||||
## Releasing
|
||||
|
||||
Since the Tokio project consists of a number of crates, many of which depend on
|
||||
|
||||
@@ -4,7 +4,6 @@ members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
"tokio-test",
|
||||
"tokio-tls",
|
||||
"tokio-util",
|
||||
|
||||
# Internal
|
||||
|
||||
@@ -14,20 +14,20 @@ the Rust programming language. It is:
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Build Status][actions-badge]][actions-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
|
||||
[crates-url]: https://crates.io/crates/tokio
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[mit-url]: LICENSE
|
||||
[azure-badge]: https://dev.azure.com/tokio-rs/Tokio/_apis/build/status/tokio-rs.tokio?branchName=master
|
||||
[azure-url]: https://dev.azure.com/tokio-rs/Tokio/_build/latest?definitionId=1&branchName=master
|
||||
[mit-url]: https://github.com/tokio-rs/tokio/blob/master/LICENSE
|
||||
[actions-badge]: https://github.com/tokio-rs/tokio/workflows/CI/badge.svg
|
||||
[actions-url]: https://github.com/tokio-rs/tokio/actions?query=workflow%3ACI+branch%3Amaster
|
||||
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/tokio
|
||||
|
||||
[Website](https://tokio.rs) |
|
||||
[Guides](https://tokio.rs/docs/overview/) |
|
||||
[Guides](https://tokio.rs/tokio/tutorial) |
|
||||
[API Docs](https://docs.rs/tokio/latest/tokio) |
|
||||
[Roadmap](https://github.com/tokio-rs/tokio/blob/master/ROADMAP.md) |
|
||||
[Chat](https://discord.gg/tokio)
|
||||
@@ -90,19 +90,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
```
|
||||
|
||||
More examples can be found [here](examples).
|
||||
More examples can be found [here][examples]. For a larger "real world" example, see the
|
||||
[mini-redis] repository.
|
||||
|
||||
[examples]: https://github.com/tokio-rs/tokio/tree/master/examples
|
||||
[mini-redis]: https://github.com/tokio-rs/mini-redis/
|
||||
|
||||
To see a list of the available features flags that can be enabled, check our
|
||||
[docs][feature-flag-docs].
|
||||
|
||||
## Getting Help
|
||||
|
||||
First, see if the answer to your question can be found in the [Guides] or the
|
||||
[API documentation]. If the answer is not there, there is an active community in
|
||||
the [Tokio Discord server][chat]. We would be happy to try to answer your
|
||||
question. Last, if that doesn't work, try opening an [issue] with the question.
|
||||
question. You can also ask your question on [the discussions page][discussions].
|
||||
|
||||
[Guides]: https://tokio.rs/docs/
|
||||
[Guides]: https://tokio.rs/tokio/tutorial
|
||||
[API documentation]: https://docs.rs/tokio/latest/tokio
|
||||
[chat]: https://discord.gg/tokio
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues/new
|
||||
[discussions]: https://github.com/tokio-rs/tokio/discussions
|
||||
[feature-flag-docs]: https://docs.rs/tokio/#feature-flags
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -127,7 +135,7 @@ several other libraries, including:
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level
|
||||
tracing and async-aware diagnostics.
|
||||
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
@@ -149,15 +157,15 @@ several other libraries, including:
|
||||
|
||||
## 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.
|
||||
Tokio is built against the latest stable release. The minimum supported version is 1.45.
|
||||
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).
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
[MIT license]: https://github.com/tokio-rs/tokio/blob/master/LICENSE
|
||||
|
||||
### Contribution
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
## Report a security issue
|
||||
|
||||
The Tokio project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [[email protected]](mailto:[email protected]). Security issues should not be reported via the public Github Issue tracker.
|
||||
|
||||
## Vulnerability coordination
|
||||
|
||||
Remediation of security vulnerabilities is prioritized by the project team. The project team coordinates remediation with third-party project stakeholders via [Github Security Advisories](https://help.github.com/en/github/managing-security-vulnerabilities/about-github-security-advisories). Third-party stakeholders may include the reporter of the issue, affected direct or indirect users of Tokio, and maintainers of upstream dependencies if applicable.
|
||||
|
||||
Downstream project maintainers and Tokio users can request participation in coordination of applicable security issues by sending your contact email address, Github username(s) and any other salient information to [[email protected]](mailto:[email protected]). Participation in security issue coordination processes is at the discretion of the Tokio team.
|
||||
|
||||
## Security advisories
|
||||
|
||||
The project team is committed to transparency in the security issue disclosure process. The Tokio team announces security issues via [project Github Release notes](https://github.com/tokio-rs/tokio/releases) and the [RustSec advisory database](https://github.com/RustSec/advisory-db) (i.e. `cargo-audit`).
|
||||
@@ -1,115 +0,0 @@
|
||||
trigger: ["master"]
|
||||
pr: ["master"]
|
||||
|
||||
variables:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
nightly: nightly-2020-01-25
|
||||
|
||||
jobs:
|
||||
# Test top level crate
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_tokio
|
||||
rust: stable
|
||||
displayName: Test tokio
|
||||
cross: true
|
||||
crates:
|
||||
- tokio
|
||||
- tests-integration
|
||||
|
||||
# Test sub crates
|
||||
- template: ci/azure-test-stable.yml
|
||||
parameters:
|
||||
name: test_linux
|
||||
displayName: Test sub crates -
|
||||
rust: stable
|
||||
crates:
|
||||
- tokio-macros
|
||||
- tokio-test
|
||||
- tokio-tls
|
||||
- tokio-util
|
||||
- examples
|
||||
|
||||
# Run integration tests
|
||||
- template: ci/azure-test-integration.yml
|
||||
parameters:
|
||||
name: test_integration
|
||||
displayName: Integration tests
|
||||
rust: stable
|
||||
|
||||
# Run tests from `tests-build`. This requires a different process
|
||||
- template: ci/azure-test-build.yml
|
||||
parameters:
|
||||
name: test_build
|
||||
displayName: Test build permutations
|
||||
rust: stable
|
||||
|
||||
# Run loom tests
|
||||
- template: ci/azure-loom.yml
|
||||
parameters:
|
||||
name: loom
|
||||
rust: stable
|
||||
crates:
|
||||
- tokio
|
||||
|
||||
# Try cross compiling
|
||||
- template: ci/azure-cross-compile.yml
|
||||
parameters:
|
||||
name: cross
|
||||
rust: stable
|
||||
|
||||
# Check each feature works properly
|
||||
- template: ci/azure-check-features.yml
|
||||
parameters:
|
||||
rust: $(nightly)
|
||||
name: check_features
|
||||
|
||||
# This represents the minimum Rust version supported by
|
||||
# Tokio. Updating this should be done in a dedicated PR and
|
||||
# cannot be greater than two 0.x releases prior to the
|
||||
# current stable.
|
||||
#
|
||||
# Tests are not run as tests may require newer versions of
|
||||
# rust.
|
||||
- template: ci/azure-check-minrust.yml
|
||||
parameters:
|
||||
name: minrust
|
||||
rust: 1.39.0
|
||||
|
||||
# Check formatting
|
||||
- template: ci/azure-rustfmt.yml
|
||||
parameters:
|
||||
rust: stable
|
||||
name: rustfmt
|
||||
|
||||
# Apply clippy lints to all crates
|
||||
- template: ci/azure-clippy.yml
|
||||
parameters:
|
||||
rust: stable
|
||||
name: clippy
|
||||
|
||||
# Check doc generation
|
||||
- template: ci/azure-check-docs.yml
|
||||
parameters:
|
||||
rust: $(nightly)
|
||||
name: docs
|
||||
|
||||
# - template: ci/azure-tsan.yml
|
||||
# parameters:
|
||||
# name: tsan
|
||||
# rust: stable
|
||||
|
||||
- template: ci/azure-deploy-docs.yml
|
||||
parameters:
|
||||
rust: stable
|
||||
dependsOn:
|
||||
- rustfmt
|
||||
- clippy
|
||||
- test_tokio
|
||||
- test_linux
|
||||
- test_build
|
||||
- loom
|
||||
- cross
|
||||
- minrust
|
||||
- check_features
|
||||
# - tsan
|
||||
+27
-3
@@ -5,15 +5,39 @@ publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.42"
|
||||
|
||||
[[bench]]
|
||||
name = "spawn"
|
||||
path = "spawn.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "mpsc"
|
||||
path = "mpsc.rs"
|
||||
name = "sync_mpsc"
|
||||
path = "sync_mpsc.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "rt_multi_threaded"
|
||||
path = "rt_multi_threaded.rs"
|
||||
harness = false
|
||||
|
||||
|
||||
[[bench]]
|
||||
name = "sync_rwlock"
|
||||
path = "sync_rwlock.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "sync_semaphore"
|
||||
path = "sync_semaphore.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "signal"
|
||||
path = "signal.rs"
|
||||
harness = false
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
//! Benchmark implementation details of the theaded scheduler. These benches are
|
||||
//! intended to be used as a form of regression testing and not as a general
|
||||
//! purpose benchmark demonstrating real-world performance.
|
||||
|
||||
use tokio::runtime::{self, Runtime};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
fn spawn_many(b: &mut Bencher) {
|
||||
const NUM_SPAWN: usize = 10_000;
|
||||
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(|| {
|
||||
rem.store(NUM_SPAWN, Relaxed);
|
||||
|
||||
rt.block_on(async {
|
||||
for _ in 0..NUM_SPAWN {
|
||||
let tx = tx.clone();
|
||||
let rem = rem.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let _ = rx.recv().unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn yield_many(b: &mut Bencher) {
|
||||
const NUM_YIELD: usize = 1_000;
|
||||
const TASKS: usize = 200;
|
||||
|
||||
let rt = rt();
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel(TASKS);
|
||||
|
||||
b.iter(move || {
|
||||
for _ in 0..TASKS {
|
||||
let tx = tx.clone();
|
||||
|
||||
rt.spawn(async move {
|
||||
for _ in 0..NUM_YIELD {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..TASKS {
|
||||
let _ = rx.recv().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn ping_pong(b: &mut Bencher) {
|
||||
const NUM_PINGS: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
let rem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
b.iter(|| {
|
||||
let done_tx = done_tx.clone();
|
||||
let rem = rem.clone();
|
||||
rem.store(NUM_PINGS, Relaxed);
|
||||
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..NUM_PINGS {
|
||||
let rem = rem.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
rx1.await.unwrap();
|
||||
tx2.send(()).unwrap();
|
||||
});
|
||||
|
||||
tx1.send(()).unwrap();
|
||||
rx2.await.unwrap();
|
||||
|
||||
if 1 == rem.fetch_sub(1, Relaxed) {
|
||||
done_tx.send(()).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn chained_spawn(b: &mut Bencher) {
|
||||
const ITER: usize = 1_000;
|
||||
|
||||
let rt = rt();
|
||||
|
||||
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
|
||||
if n == 0 {
|
||||
done_tx.send(()).unwrap();
|
||||
} else {
|
||||
tokio::spawn(async move {
|
||||
iter(done_tx, n - 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let (done_tx, done_rx) = mpsc::sync_channel(1000);
|
||||
|
||||
b.iter(move || {
|
||||
let done_tx = done_tx.clone();
|
||||
|
||||
rt.block_on(async {
|
||||
tokio::spawn(async move {
|
||||
iter(done_tx, ITER);
|
||||
});
|
||||
|
||||
done_rx.recv().unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn rt() -> Runtime {
|
||||
runtime::Builder::new_multi_thread()
|
||||
.worker_threads(4)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
benchmark_group!(scheduler, spawn_many, ping_pong, yield_many, chained_spawn,);
|
||||
|
||||
benchmark_main!(scheduler);
|
||||
@@ -0,0 +1,95 @@
|
||||
//! Benchmark the delay in propagating OS signals to any listeners.
|
||||
#![cfg(unix)]
|
||||
|
||||
use bencher::{benchmark_group, benchmark_main, Bencher};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::runtime;
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
struct Spinner {
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Future for Spinner {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if self.count > 3 {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
self.count += 1;
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spinner {
|
||||
fn new() -> Self {
|
||||
Self { count: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_signal(signal: libc::c_int) {
|
||||
use libc::{getpid, kill};
|
||||
|
||||
unsafe {
|
||||
assert_eq!(kill(getpid(), signal), 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn many_signals(bench: &mut Bencher) {
|
||||
let num_signals = 10;
|
||||
let (tx, mut rx) = mpsc::channel(num_signals);
|
||||
|
||||
// Intentionally single threaded to measure delays in propagating wakes
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let spawn_signal = |kind| {
|
||||
let tx = tx.clone();
|
||||
rt.spawn(async move {
|
||||
let mut signal = signal(kind).expect("failed to create signal");
|
||||
|
||||
while signal.recv().await.is_some() {
|
||||
if tx.send(()).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
for _ in 0..num_signals {
|
||||
// Pick some random signals which don't terminate the test harness
|
||||
spawn_signal(SignalKind::child());
|
||||
spawn_signal(SignalKind::io());
|
||||
}
|
||||
drop(tx);
|
||||
|
||||
// Turn the runtime for a while to ensure that all the spawned
|
||||
// tasks have been polled at least once
|
||||
rt.block_on(Spinner::new());
|
||||
|
||||
bench.iter(|| {
|
||||
rt.block_on(async {
|
||||
send_signal(libc::SIGCHLD);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
|
||||
send_signal(libc::SIGIO);
|
||||
for _ in 0..num_signals {
|
||||
rx.recv().await.expect("channel closed");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
benchmark_group!(signal_group, many_signals,);
|
||||
|
||||
benchmark_main!(signal_group);
|
||||
+8
-14
@@ -10,8 +10,7 @@ async fn work() -> usize {
|
||||
}
|
||||
|
||||
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
@@ -23,8 +22,7 @@ fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
@@ -36,25 +34,21 @@ fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn basic_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
let h = runtime.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap();
|
||||
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
let h = runtime.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ use tokio::sync::mpsc;
|
||||
type Medium = [usize; 64];
|
||||
type Large = [Medium; 64];
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create_1_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(1));
|
||||
@@ -24,7 +31,7 @@ fn create_100_000_medium(b: &mut Bencher) {
|
||||
|
||||
fn send_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
let (tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
|
||||
let _ = tx.try_send([0; 64]);
|
||||
|
||||
@@ -34,7 +41,7 @@ fn send_medium(b: &mut Bencher) {
|
||||
|
||||
fn send_large(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
let (tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
let _ = tx.try_send([[0; 64]; 64]);
|
||||
|
||||
@@ -43,18 +50,14 @@ fn send_large(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contention_bounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut tx = tx.clone();
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -70,18 +73,14 @@ fn contention_bounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contention_bounded_full(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut tx = tx.clone();
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -97,11 +96,7 @@ fn contention_bounded_full(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn contention_unbounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
@@ -124,15 +119,11 @@ fn contention_unbounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn uncontented_bounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (mut tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
@@ -146,11 +137,7 @@ fn uncontented_bounded(b: &mut Bencher) {
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let rt = rt();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
@@ -0,0 +1,142 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use std::sync::Arc;
|
||||
use tokio::{sync::RwLock, task};
|
||||
|
||||
fn read_uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
async fn task(lock: Arc<RwLock<()>>) {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
async fn task(lock: Arc<RwLock<()>>) {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone())
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_contended_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
async fn task(lock: Arc<RwLock<()>>) {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
let j = tokio::try_join! {
|
||||
async move { drop(write); Ok(()) },
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
task::spawn(task(lock.clone())),
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn read_concurrent_contended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
async fn task(lock: Arc<RwLock<()>>) {
|
||||
let read = lock.read().await;
|
||||
black_box(read);
|
||||
}
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
b.iter(|| {
|
||||
let lock = lock.clone();
|
||||
rt.block_on(async move {
|
||||
let write = lock.write().await;
|
||||
tokio::join! {
|
||||
async move { drop(write) },
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
task(lock.clone()),
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
sync_rwlock,
|
||||
read_uncontended,
|
||||
read_concurrent_uncontended,
|
||||
read_concurrent_uncontended_multi,
|
||||
read_concurrent_contended,
|
||||
read_concurrent_contended_multi
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(sync_rwlock);
|
||||
@@ -0,0 +1,125 @@
|
||||
use bencher::Bencher;
|
||||
use std::sync::Arc;
|
||||
use tokio::{sync::Semaphore, task};
|
||||
|
||||
fn uncontended(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
for _ in 0..6 {
|
||||
let permit = s.acquire().await;
|
||||
drop(permit);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async fn task(s: Arc<Semaphore>) {
|
||||
let permit = s.acquire().await;
|
||||
drop(permit);
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(10));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contended_concurrent_multi(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(6)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(5));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
let j = tokio::try_join! {
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone())),
|
||||
task::spawn(task(s.clone()))
|
||||
};
|
||||
j.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contended_concurrent_single(b: &mut Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let s = Arc::new(Semaphore::new(5));
|
||||
b.iter(|| {
|
||||
let s = s.clone();
|
||||
rt.block_on(async move {
|
||||
tokio::join! {
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone()),
|
||||
task(s.clone())
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
sync_semaphore,
|
||||
uncontended,
|
||||
uncontended_concurrent_multi,
|
||||
uncontended_concurrent_single,
|
||||
contended_concurrent_multi,
|
||||
contended_concurrent_single
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(sync_semaphore);
|
||||
@@ -1,29 +0,0 @@
|
||||
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 }}
|
||||
@@ -1,15 +0,0 @@
|
||||
jobs:
|
||||
# Check docs
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Check docs
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: |
|
||||
RUSTDOCFLAGS="--cfg docsrs" cargo doc --lib --no-deps --all-features
|
||||
displayName: Check docs
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Check features
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: ubuntu-16.04
|
||||
MacOS:
|
||||
vmImage: macOS-10.13
|
||||
Windows:
|
||||
vmImage: vs2017-win2016
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo install cargo-hack
|
||||
displayName: Install cargo-hack
|
||||
|
||||
# Check each feature works properly
|
||||
# * --each-feature
|
||||
# run for each feature which includes --no-default-features and default features of package
|
||||
# * -Z avoid-dev-deps
|
||||
# build without dev-dependencies to avoid https://github.com/rust-lang/cargo/issues/4866
|
||||
# tracking-issue: https://github.com/rust-lang/cargo/issues/5133
|
||||
- script: cargo hack check --all --each-feature -Z avoid-dev-deps
|
||||
displayName: cargo hack check --all --each-feature
|
||||
@@ -1,14 +0,0 @@
|
||||
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 }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo check --all
|
||||
displayName: cargo check --all
|
||||
@@ -1,16 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Clippy
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
- script: |
|
||||
rustup component add clippy
|
||||
cargo clippy --version
|
||||
displayName: Install clippy
|
||||
- script: |
|
||||
cargo clippy --all --all-features
|
||||
displayName: cargo clippy --all
|
||||
@@ -1,44 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
strategy:
|
||||
matrix:
|
||||
i686:
|
||||
vmImage: ubuntu-16.04
|
||||
target: i686-unknown-linux-gnu
|
||||
powerpc:
|
||||
vmImage: ubuntu-16.04
|
||||
target: powerpc-unknown-linux-gnu
|
||||
powerpc64:
|
||||
vmImage: ubuntu-16.04
|
||||
target: powerpc64-unknown-linux-gnu
|
||||
mips:
|
||||
vmImage: ubuntu-16.04
|
||||
target: mips-unknown-linux-gnu
|
||||
arm:
|
||||
vmImage: ubuntu-16.04
|
||||
target: arm-linux-androideabi
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: sudo apt-get update
|
||||
displayName: apt-get update
|
||||
|
||||
- script: sudo apt-get install gcc-multilib
|
||||
displayName: Install gcc-multilib
|
||||
|
||||
- script: cargo install cross
|
||||
displayName: Install cross
|
||||
|
||||
# Always patch
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cross check --all --exclude tokio-tls --target $(target)
|
||||
displayName: Check source
|
||||
|
||||
# - script: cross check --tests --all --exclude tokio-tls --target $(target)
|
||||
# displayName: Check tests
|
||||
@@ -1,39 +0,0 @@
|
||||
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
|
||||
rust_version: ${{ parameters.rust }}
|
||||
- script: |
|
||||
cargo doc --all --no-deps --all-features
|
||||
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'
|
||||
@@ -1,33 +0,0 @@
|
||||
steps:
|
||||
# Linux and macOS.
|
||||
- script: |
|
||||
set -e
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain none
|
||||
export PATH=$PATH:$HOME/.cargo/bin
|
||||
rustup toolchain install $RUSTUP_TOOLCHAIN
|
||||
rustup default $RUSTUP_TOOLCHAIN
|
||||
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 --profile minimal --default-toolchain none
|
||||
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
rustup toolchain install %RUSTUP_TOOLCHAIN%
|
||||
rustup default %RUSTUP_TOOLCHAIN%
|
||||
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: ${{parameters.rust_version}}
|
||||
displayName: "Install rust (windows)"
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
|
||||
# All platforms.
|
||||
- script: |
|
||||
rustup toolchain list
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
displayName: Query rust and cargo versions
|
||||
@@ -1,9 +0,0 @@
|
||||
steps:
|
||||
- bash: |
|
||||
set -e
|
||||
|
||||
if git log --no-merges -1 --format='%B' | grep -qF '[ci-release]'; then
|
||||
echo "##vso[task.setvariable variable=isRelease]true"
|
||||
fi
|
||||
failOnStderr: true
|
||||
displayName: Check if release commit
|
||||
@@ -1,18 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Loom tests
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
- script: RUSTFLAGS="--cfg loom" cargo test --lib --release --features "full" -- --test-threads=1 --nocapture
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 1
|
||||
CI: 'True'
|
||||
displayName: test ${{ crate }}
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
@@ -1,18 +0,0 @@
|
||||
jobs:
|
||||
# Check formatting
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: Check rustfmt
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
- script: |
|
||||
rustup component add rustfmt
|
||||
cargo fmt --version
|
||||
displayName: Install rustfmt
|
||||
- script: |
|
||||
# Workaround for rust-lang/cargo#7732
|
||||
rustfmt --check --edition 2018 $(find . -name '*.rs' -print)
|
||||
displayName: Check formatting
|
||||
@@ -1,17 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: cargo install cargo-hack
|
||||
displayName: Install cargo-hack
|
||||
|
||||
- script: cargo hack test --each-feature
|
||||
displayName: cargo hack test --each-feature
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests-build
|
||||
@@ -1,28 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: ubuntu-16.04
|
||||
MacOS:
|
||||
vmImage: macOS-10.13
|
||||
Windows:
|
||||
vmImage: vs2017-win2016
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- script: cargo install cargo-hack
|
||||
displayName: Install cargo-hack
|
||||
|
||||
# Run with all crate features
|
||||
- script: cargo hack test --each-feature
|
||||
env:
|
||||
CI: 'True'
|
||||
displayName: cargo hack test --each-feature
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests-integration
|
||||
@@ -1,19 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- script: cargo check --all
|
||||
displayName: cargo check --all
|
||||
|
||||
# Check benches
|
||||
- script: cargo check --benches --all
|
||||
displayName: Check benchmarks
|
||||
@@ -1,47 +0,0 @@
|
||||
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: ${{ parameters.rust }}
|
||||
|
||||
- template: azure-is-release.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
# Run with all crate features
|
||||
- script: cargo test --all-features
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate }} - cargo test --all-features
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
|
||||
# Check benches
|
||||
- script: cargo check --all-features --benches
|
||||
displayName: ${{ crate }} - cargo check --benches
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
|
||||
- template: azure-patch-crates.yml
|
||||
|
||||
- ${{ each crate in parameters.crates }}:
|
||||
# Run with all crate features
|
||||
- script: cargo test --all-features
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
CI: 'True'
|
||||
displayName: ${{ crate }} - cargo test --all-features
|
||||
workingDirectory: $(Build.SourcesDirectory)/${{ crate }}
|
||||
@@ -1,34 +0,0 @@
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: TSAN
|
||||
strategy:
|
||||
matrix:
|
||||
Timer:
|
||||
cmd: cargo test -p tokio-timer --test hammer
|
||||
pool:
|
||||
vmImage: ubuntu-16.04
|
||||
steps:
|
||||
- template: azure-install-rust.yml
|
||||
parameters:
|
||||
rust_version: ${{ parameters.rust }}
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# Patch dependencies to run all tests against versions of the crate in the
|
||||
# repository.
|
||||
[patch.crates-io]
|
||||
tokio = { path = "tokio" }
|
||||
tokio-macros = { path = "tokio-macros" }
|
||||
tokio-test = { path = "tokio-test" }
|
||||
tokio-tls = { path = "tokio-tls" }
|
||||
tokio-util = { path = "tokio-util" }
|
||||
@@ -1,39 +0,0 @@
|
||||
# TSAN suppressions file for Tokio
|
||||
|
||||
# TSAN does not understand fences and `Arc::drop` is implemented using a fence.
|
||||
# This causes many false positives.
|
||||
race:Arc*drop
|
||||
race:Weak*drop
|
||||
|
||||
# `std` mpsc is not used in any Tokio code base. This race is triggered by some
|
||||
# rust runtime logic.
|
||||
race:std*mpsc_queue
|
||||
race:std*lang_start
|
||||
race:drop*std::thread*
|
||||
|
||||
# Probably more fences in std.
|
||||
race:__call_tls_dtors
|
||||
|
||||
# The epoch-based GC uses fences.
|
||||
race:crossbeam_epoch
|
||||
|
||||
# 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.
|
||||
# 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
|
||||
# it and take ownership of the node first (touching the next pointer). The
|
||||
# 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
|
||||
+7
-3
@@ -4,10 +4,14 @@ version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
# If you copy one of the examples into a new project, you should be using
|
||||
# [dependencies] instead.
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio-util = { version = "0.2.0", path = "../tokio-util", features = ["full"] }
|
||||
bytes = "0.5"
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full", "tracing"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
|
||||
tokio-util = { version = "0.5.0", path = "../tokio-util", features = ["full"] }
|
||||
bytes = "0.6"
|
||||
futures = "0.3.0"
|
||||
http = "0.2"
|
||||
serde = "1.0"
|
||||
|
||||
+4
-1
@@ -13,8 +13,11 @@ A good starting point for the examples would be [`hello_world`](hello_world.rs)
|
||||
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
|
||||
additional guides for some of the examples.
|
||||
|
||||
For a larger "real world" example, see the [`mini-redis`][redis] repository.
|
||||
|
||||
If you've got an example you'd like to see here, please feel free to open an
|
||||
issue. Otherwise if you've got an example you'd like to add, please feel free
|
||||
to make a PR!
|
||||
|
||||
[tokioweb]: https://tokio.rs/docs/overview/
|
||||
[tokioweb]: https://tokio.rs/tokio/tutorial
|
||||
[redis]: https://github.com/tokio-rs/mini-redis
|
||||
|
||||
+32
-12
@@ -43,6 +43,26 @@ use std::task::{Context, Poll};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter};
|
||||
// Configure a `tracing` subscriber that logs traces emitted by the chat
|
||||
// server.
|
||||
tracing_subscriber::fmt()
|
||||
// Filter what traces are displayed based on the RUST_LOG environment
|
||||
// variable.
|
||||
//
|
||||
// Traces emitted by the example code will always be displayed. You
|
||||
// can set `RUST_LOG=tokio=trace` to enable additional traces emitted by
|
||||
// Tokio itself.
|
||||
.with_env_filter(EnvFilter::from_default_env().add_directive("chat=info".parse()?))
|
||||
// Log events when `tracing` spans are created, entered, exited, or
|
||||
// closed. When Tokio's internal tracing support is enabled (as
|
||||
// described above), this can be used to track the lifecycle of spawned
|
||||
// tasks on the Tokio runtime.
|
||||
.with_span_events(FmtSpan::FULL)
|
||||
// Set this subscriber as the default, to collect all traces emitted by
|
||||
// the program.
|
||||
.init();
|
||||
|
||||
// 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
|
||||
@@ -57,9 +77,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
println!("server running on {}", addr);
|
||||
tracing::info!("server running on {}", addr);
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound TcpStream.
|
||||
@@ -70,8 +90,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// Spawn our handler to be run asynchronously.
|
||||
tokio::spawn(async move {
|
||||
tracing::debug!("accepted connection");
|
||||
if let Err(e) = process(state, stream, addr).await {
|
||||
println!("an error occurred; error = {:?}", e);
|
||||
tracing::info!("an error occurred; error = {:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -193,16 +214,14 @@ async fn process(
|
||||
let mut lines = Framed::new(stream, LinesCodec::new());
|
||||
|
||||
// Send a prompt to the client to enter their username.
|
||||
lines
|
||||
.send(String::from("Please enter your username:"))
|
||||
.await?;
|
||||
lines.send("Please enter your username:").await?;
|
||||
|
||||
// Read the first line from the `LineCodec` stream to get the username.
|
||||
let username = match lines.next().await {
|
||||
Some(Ok(line)) => line,
|
||||
// We didn't get a line so we return early here.
|
||||
_ => {
|
||||
println!("Failed to get username from {}. Client disconnected.", addr);
|
||||
tracing::error!("Failed to get username from {}. Client disconnected.", addr);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
@@ -214,7 +233,7 @@ async fn process(
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{} has joined the chat", username);
|
||||
println!("{}", msg);
|
||||
tracing::info!("{}", msg);
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
@@ -232,12 +251,13 @@ async fn process(
|
||||
// A message was received from a peer. Send it to the
|
||||
// current user.
|
||||
Ok(Message::Received(msg)) => {
|
||||
peer.lines.send(msg).await?;
|
||||
peer.lines.send(&msg).await?;
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
tracing::error!(
|
||||
"an error occurred while processing messages for {}; error = {:?}",
|
||||
username, e
|
||||
username,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -250,7 +270,7 @@ async fn process(
|
||||
state.peers.remove(&addr);
|
||||
|
||||
let msg = format!("{} has left the chat", username);
|
||||
println!("{}", msg);
|
||||
tracing::info!("{}", msg);
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -96,7 +96,6 @@ mod udp {
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::udp::{RecvHalf, SendHalf};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn connect(
|
||||
@@ -114,16 +113,15 @@ mod udp {
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
let (mut r, mut w) = socket.split();
|
||||
|
||||
future::try_join(send(stdin, &mut w), recv(stdout, &mut r)).await?;
|
||||
future::try_join(send(stdin, &socket), recv(stdout, &socket)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
writer: &mut SendHalf,
|
||||
writer: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
@@ -135,7 +133,7 @@ mod udp {
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
reader: &mut RecvHalf,
|
||||
reader: &UdpSocket,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
use std::{env, io};
|
||||
use tokio;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
struct Server {
|
||||
@@ -27,7 +26,7 @@ struct Server {
|
||||
impl Server {
|
||||
async fn run(self) -> Result<(), io::Error> {
|
||||
let Server {
|
||||
mut socket,
|
||||
socket,
|
||||
mut buf,
|
||||
mut to_send,
|
||||
} = self;
|
||||
|
||||
+1
-2
@@ -21,7 +21,6 @@
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
@@ -40,7 +39,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
|
||||
@@ -74,7 +74,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// above and must be associated with an event loop, so we pass in a handle
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
|
||||
+11
-3
@@ -23,6 +23,7 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use futures::future::try_join;
|
||||
@@ -42,7 +43,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
let mut listener = TcpListener::bind(listen_addr).await?;
|
||||
let listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
|
||||
@@ -63,8 +64,15 @@ async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<
|
||||
let (mut ri, mut wi) = inbound.split();
|
||||
let (mut ro, mut wo) = outbound.split();
|
||||
|
||||
let client_to_server = io::copy(&mut ri, &mut wo);
|
||||
let server_to_client = io::copy(&mut ro, &mut wi);
|
||||
let client_to_server = async {
|
||||
io::copy(&mut ri, &mut wo).await?;
|
||||
wo.shutdown().await
|
||||
};
|
||||
|
||||
let server_to_client = async {
|
||||
io::copy(&mut ro, &mut wi).await?;
|
||||
wi.shutdown().await
|
||||
};
|
||||
|
||||
try_join(client_to_server, server_to_client).await?;
|
||||
|
||||
|
||||
+2
-2
@@ -89,7 +89,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Create the shared state of this server that will be shared amongst all
|
||||
@@ -130,7 +130,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let response = response.serialize();
|
||||
|
||||
if let Err(e) = lines.send(response).await {
|
||||
if let Err(e) = lines.send(response.as_str()).await {
|
||||
println!("error on sending response; error = {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ use futures::SinkExt;
|
||||
use http::{header::HeaderValue, Request, Response, StatusCode};
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
use serde_json;
|
||||
use std::{env, error::Error, fmt, io};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::StreamExt;
|
||||
@@ -31,19 +30,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let mut server = TcpListener::bind(&addr).await?;
|
||||
let mut incoming = server.incoming();
|
||||
let server = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
while let Some(Ok(stream)) = incoming.next().await {
|
||||
loop {
|
||||
let (stream, _) = server.accept().await?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process(stream).await {
|
||||
println!("failed to process connection; error = {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process(stream: TcpStream) -> Result<(), Box<dyn Error>> {
|
||||
@@ -96,8 +93,7 @@ struct Http;
|
||||
|
||||
/// Implementation of encoding an HTTP response into a `BytesMut`, basically
|
||||
/// just writing out an HTTP/1.1 response.
|
||||
impl Encoder for Http {
|
||||
type Item = Response<String>;
|
||||
impl Encoder<Response<String>> for Http {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: Response<String>, dst: &mut BytesMut) -> io::Result<()> {
|
||||
|
||||
@@ -55,7 +55,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
.parse()?;
|
||||
|
||||
let mut socket = UdpSocket::bind(local_addr).await?;
|
||||
let socket = UdpSocket::bind(local_addr).await?;
|
||||
const MAX_DATAGRAM_SIZE: usize = 65_507;
|
||||
socket.connect(&remote_addr).await?;
|
||||
let data = get_stdin_data()?;
|
||||
|
||||
@@ -7,6 +7,7 @@ publish = false
|
||||
|
||||
[features]
|
||||
full = ["tokio/full"]
|
||||
rt = ["tokio/rt", "tokio/macros"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", optional = true }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
async fn my_fn() {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,7 @@
|
||||
error: The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled.
|
||||
--> $DIR/macros_core_no_default.rs:3:1
|
||||
|
|
||||
3 | #[tokio::main]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
@@ -4,7 +4,7 @@ error: the async keyword is missing from the function declaration
|
||||
4 | fn main_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`
|
||||
--> $DIR/macros_invalid_input.rs:6:15
|
||||
|
|
||||
6 | #[tokio::main(foo)]
|
||||
@@ -28,7 +28,7 @@ error: the test function cannot accept arguments
|
||||
16 | async fn test_fn_has_args(_x: u8) {}
|
||||
| ^^^^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`
|
||||
--> $DIR/macros_invalid_input.rs:18:15
|
||||
|
|
||||
18 | #[tokio::test(foo)]
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#[test]
|
||||
fn compile_fail() {
|
||||
fn compile_fail_full() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
#[cfg(all(feature = "rt", not(feature = "full")))]
|
||||
t.compile_fail("tests/fail/macros_core_no_default.rs");
|
||||
|
||||
drop(t);
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@ publish = false
|
||||
[features]
|
||||
full = [
|
||||
"macros",
|
||||
"rt-core",
|
||||
"rt-threaded",
|
||||
"rt",
|
||||
"rt-multi-thread",
|
||||
|
||||
"tokio/full",
|
||||
"tokio-test"
|
||||
]
|
||||
macros = ["tokio/macros"]
|
||||
rt-core = ["tokio/rt-core"]
|
||||
rt-threaded = ["rt-core", "tokio/rt-threaded"]
|
||||
sync = ["tokio/sync"]
|
||||
rt = ["tokio/rt"]
|
||||
rt-multi-thread = ["rt", "tokio/rt-multi-thread"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg(feature = "macros")]
|
||||
#![cfg(all(feature = "macros", feature = "rt"))]
|
||||
|
||||
#[tokio::main]
|
||||
async fn basic_main() -> usize {
|
||||
@@ -10,18 +10,15 @@ async fn generic_fun<T: Default>() -> T {
|
||||
T::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "rt-core")]
|
||||
mod spawn {
|
||||
#[tokio::main]
|
||||
async fn spawning() -> usize {
|
||||
let join = tokio::spawn(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
#[tokio::main]
|
||||
async fn spawning() -> usize {
|
||||
let join = tokio::spawn(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
}
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -72,7 +72,7 @@ async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
};
|
||||
|
||||
// Compose reading and writing concurrently.
|
||||
future::join3(write, read, cat)
|
||||
future::join3(write, read, cat.wait())
|
||||
.map(|(_, _, status)| status)
|
||||
.await
|
||||
}
|
||||
@@ -125,3 +125,26 @@ async fn status_closes_any_pipes() {
|
||||
|
||||
assert_ok!(child.await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn try_wait() {
|
||||
let mut child = cat().spawn().unwrap();
|
||||
|
||||
let id = child.id().expect("missing id");
|
||||
assert!(id > 0);
|
||||
|
||||
assert_eq!(None, assert_ok!(child.try_wait()));
|
||||
|
||||
// Drop the child's stdio handles so it can terminate
|
||||
drop(child.stdin.take());
|
||||
drop(child.stderr.take());
|
||||
drop(child.stdout.take());
|
||||
|
||||
assert_ok!(child.wait().await);
|
||||
|
||||
// test that the `.try_wait()` method is fused just like the stdlib
|
||||
assert!(assert_ok!(child.try_wait()).unwrap().success());
|
||||
|
||||
// Can't get id after process has exited
|
||||
assert_eq!(child.id(), None);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
# 0.3.1 (October 25, 2020)
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix incorrect docs regarding `max_threads` option ([#3038])
|
||||
|
||||
# 0.3.0 (October 15, 2020)
|
||||
|
||||
- Track `tokio` 0.3 release.
|
||||
|
||||
### Changed
|
||||
- options are renamed to track `tokio` runtime builder fn names.
|
||||
- `#[tokio::main]` macro requires `rt-multi-thread` when no `flavor` is specified.
|
||||
|
||||
# 0.2.5 (February 27, 2019)
|
||||
|
||||
### Fixed
|
||||
- doc improvements (#2225).
|
||||
- doc improvements ([#2225]).
|
||||
|
||||
# 0.2.4 (January 27, 2019)
|
||||
|
||||
### Fixed
|
||||
- generics on `#[tokio::main]` function (#2177).
|
||||
- generics on `#[tokio::main]` function ([#2177]).
|
||||
|
||||
### Added
|
||||
- support for `tokio::select!` (#2152).
|
||||
- support for `tokio::select!` ([#2152]).
|
||||
|
||||
# 0.2.3 (January 7, 2019)
|
||||
|
||||
@@ -19,13 +33,21 @@
|
||||
# 0.2.2 (January 7, 2019)
|
||||
|
||||
### Added
|
||||
- General refactoring and inclusion of additional runtime options (#2022 and #2038)
|
||||
- General refactoring and inclusion of additional runtime options ([#2022] and [#2038])
|
||||
|
||||
# 0.2.1 (December 18, 2019)
|
||||
|
||||
### Fixes
|
||||
- inherit visibility when wrapping async fn (#1954).
|
||||
- inherit visibility when wrapping async fn ([#1954]).
|
||||
|
||||
# 0.2.0 (November 26, 2019)
|
||||
|
||||
- Initial release
|
||||
|
||||
[#1954]: https://github.com/tokio-rs/tokio/pull/1954
|
||||
[#2022]: https://github.com/tokio-rs/tokio/pull/2022
|
||||
[#2038]: https://github.com/tokio-rs/tokio/pull/2038
|
||||
[#2152]: https://github.com/tokio-rs/tokio/pull/2152
|
||||
[#2177]: https://github.com/tokio-rs/tokio/pull/2177
|
||||
[#2225]: https://github.com/tokio-rs/tokio/pull/2225
|
||||
[#3038]: https://github.com/tokio-rs/tokio/pull/3038
|
||||
|
||||
@@ -6,14 +6,14 @@ name = "tokio-macros"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.2.5"
|
||||
# - Create "v0.3.x" git tag.
|
||||
version = "0.3.1"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-macros/0.2.5/tokio_macros"
|
||||
documentation = "https://docs.rs/tokio-macros/0.3.1/tokio_macros"
|
||||
description = """
|
||||
Tokio's proc macros.
|
||||
"""
|
||||
@@ -30,7 +30,7 @@ quote = "1"
|
||||
syn = { version = "1.0.3", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
+183
-242
@@ -1,18 +1,144 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use std::num::NonZeroUsize;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Runtime {
|
||||
Basic,
|
||||
enum RuntimeFlavor {
|
||||
CurrentThread,
|
||||
Threaded,
|
||||
}
|
||||
|
||||
impl RuntimeFlavor {
|
||||
fn from_str(s: &str) -> Result<RuntimeFlavor, String> {
|
||||
match s {
|
||||
"current_thread" => Ok(RuntimeFlavor::CurrentThread),
|
||||
"multi_thread" => Ok(RuntimeFlavor::Threaded),
|
||||
"single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()),
|
||||
"basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()),
|
||||
"threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()),
|
||||
_ => Err(format!("No such runtime flavor `{}`. The runtime flavors are `current_thread` and `multi_thread`.", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FinalConfig {
|
||||
flavor: RuntimeFlavor,
|
||||
worker_threads: Option<usize>,
|
||||
}
|
||||
|
||||
struct Configuration {
|
||||
rt_multi_thread_available: bool,
|
||||
default_flavor: RuntimeFlavor,
|
||||
flavor: Option<RuntimeFlavor>,
|
||||
worker_threads: Option<(usize, Span)>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
fn new(is_test: bool, rt_multi_thread: bool) -> Self {
|
||||
Configuration {
|
||||
rt_multi_thread_available: rt_multi_thread,
|
||||
default_flavor: match is_test {
|
||||
true => RuntimeFlavor::CurrentThread,
|
||||
false => RuntimeFlavor::Threaded,
|
||||
},
|
||||
flavor: None,
|
||||
worker_threads: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_flavor(&mut self, runtime: syn::Lit, span: Span) -> Result<(), syn::Error> {
|
||||
if self.flavor.is_some() {
|
||||
return Err(syn::Error::new(span, "`flavor` set multiple times."));
|
||||
}
|
||||
|
||||
let runtime_str = parse_string(runtime, span, "flavor")?;
|
||||
let runtime =
|
||||
RuntimeFlavor::from_str(&runtime_str).map_err(|err| syn::Error::new(span, err))?;
|
||||
self.flavor = Some(runtime);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_worker_threads(
|
||||
&mut self,
|
||||
worker_threads: syn::Lit,
|
||||
span: Span,
|
||||
) -> Result<(), syn::Error> {
|
||||
if self.worker_threads.is_some() {
|
||||
return Err(syn::Error::new(
|
||||
span,
|
||||
"`worker_threads` set multiple times.",
|
||||
));
|
||||
}
|
||||
|
||||
let worker_threads = parse_int(worker_threads, span, "worker_threads")?;
|
||||
if worker_threads == 0 {
|
||||
return Err(syn::Error::new(span, "`worker_threads` may not be 0."));
|
||||
}
|
||||
self.worker_threads = Some((worker_threads, span));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build(&self) -> Result<FinalConfig, syn::Error> {
|
||||
let flavor = self.flavor.unwrap_or(self.default_flavor);
|
||||
use RuntimeFlavor::*;
|
||||
match (flavor, self.worker_threads) {
|
||||
(CurrentThread, Some((_, worker_threads_span))) => Err(syn::Error::new(
|
||||
worker_threads_span,
|
||||
"The `worker_threads` option requires the `multi_thread` runtime flavor.",
|
||||
)),
|
||||
(CurrentThread, None) => Ok(FinalConfig {
|
||||
flavor,
|
||||
worker_threads: None,
|
||||
}),
|
||||
(Threaded, worker_threads) if self.rt_multi_thread_available => Ok(FinalConfig {
|
||||
flavor,
|
||||
worker_threads: worker_threads.map(|(val, _span)| val),
|
||||
}),
|
||||
(Threaded, _) => {
|
||||
let msg = if self.flavor.is_none() {
|
||||
"The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled."
|
||||
} else {
|
||||
"The runtime flavor `multi_thread` requires the `rt-multi-thread` feature."
|
||||
};
|
||||
Err(syn::Error::new(Span::call_site(), msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error> {
|
||||
match int {
|
||||
syn::Lit::Int(lit) => match lit.base10_parse::<usize>() {
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as integer: {}", field, e),
|
||||
)),
|
||||
},
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as integer.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::Error> {
|
||||
match int {
|
||||
syn::Lit::Str(s) => Ok(s.value()),
|
||||
syn::Lit::Verbatim(s) => Ok(s.to_string()),
|
||||
_ => Err(syn::Error::new(
|
||||
span,
|
||||
format!("Failed to parse {} as string.", field),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_knobs(
|
||||
mut input: syn::ItemFn,
|
||||
args: syn::AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_threaded: bool,
|
||||
rt_multi_thread: bool,
|
||||
) -> Result<TokenStream, syn::Error> {
|
||||
let sig = &mut input.sig;
|
||||
let body = &input.block;
|
||||
@@ -26,9 +152,12 @@ fn parse_knobs(
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let mut runtime = None;
|
||||
let mut core_threads = None;
|
||||
let mut max_threads = None;
|
||||
let macro_name = if is_test {
|
||||
"tokio::test"
|
||||
} else {
|
||||
"tokio::main"
|
||||
};
|
||||
let mut config = Configuration::new(is_test, rt_multi_thread);
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
@@ -39,65 +168,18 @@ fn parse_knobs(
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"core_threads" => {
|
||||
if rt_threaded {
|
||||
match &namevalue.lit {
|
||||
syn::Lit::Int(expr) => {
|
||||
let num = expr.base10_parse::<NonZeroUsize>().unwrap();
|
||||
if num.get() > 1 {
|
||||
runtime = Some(Runtime::Threaded);
|
||||
} else {
|
||||
runtime = Some(Runtime::Basic);
|
||||
}
|
||||
|
||||
if let Some(v) = max_threads {
|
||||
if v < num {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads cannot be less than core_threads",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
core_threads = Some(num);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"core_threads argument must be an int",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"core_threads can only be set with rt-threaded feature flag enabled",
|
||||
));
|
||||
}
|
||||
"worker_threads" => {
|
||||
config.set_worker_threads(namevalue.lit.clone(), namevalue.span())?;
|
||||
}
|
||||
"flavor" => {
|
||||
config.set_flavor(namevalue.lit.clone(), namevalue.span())?;
|
||||
}
|
||||
"core_threads" => {
|
||||
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
"max_threads" => match &namevalue.lit {
|
||||
syn::Lit::Int(expr) => {
|
||||
let num = expr.base10_parse::<NonZeroUsize>().unwrap();
|
||||
|
||||
if let Some(v) = core_threads {
|
||||
if num < v {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads cannot be less than core_threads",
|
||||
));
|
||||
}
|
||||
}
|
||||
max_threads = Some(num);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads argument must be an int",
|
||||
))
|
||||
}
|
||||
},
|
||||
name => {
|
||||
let msg = format!("Unknown attribute pair {} is specified; expected one of: `core_threads`, `max_threads`", name);
|
||||
let msg = format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
}
|
||||
@@ -108,16 +190,28 @@ fn parse_knobs(
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => {
|
||||
runtime = Some(runtime.unwrap_or_else(|| Runtime::Threaded))
|
||||
let name = ident.unwrap().to_string().to_lowercase();
|
||||
let msg = match name.as_str() {
|
||||
"threaded_scheduler" | "multi_thread" => {
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"multi_thread\")].",
|
||||
macro_name
|
||||
)
|
||||
}
|
||||
"basic_scheduler" | "current_thread" | "single_threaded" => {
|
||||
format!(
|
||||
"Set the runtime flavor with #[{}(flavor = \"current_thread\")].",
|
||||
macro_name
|
||||
)
|
||||
}
|
||||
"flavor" | "worker_threads" => {
|
||||
format!("The `{}` attribute requires an argument.", name)
|
||||
}
|
||||
"basic_scheduler" => runtime = Some(runtime.unwrap_or_else(|| Runtime::Basic)),
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name)
|
||||
}
|
||||
}
|
||||
};
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
other => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
@@ -128,21 +222,24 @@ fn parse_knobs(
|
||||
}
|
||||
}
|
||||
|
||||
let mut rt = quote! { tokio::runtime::Builder::new().basic_scheduler() };
|
||||
if rt_threaded && (runtime == Some(Runtime::Threaded) || (runtime.is_none() && !is_test)) {
|
||||
rt = quote! { #rt.threaded_scheduler() };
|
||||
}
|
||||
if let Some(v) = core_threads.map(|v| v.get()) {
|
||||
rt = quote! { #rt.core_threads(#v) };
|
||||
}
|
||||
if let Some(v) = max_threads.map(|v| v.get()) {
|
||||
rt = quote! { #rt.max_threads(#v) };
|
||||
let config = config.build()?;
|
||||
|
||||
let mut rt = match config.flavor {
|
||||
RuntimeFlavor::CurrentThread => quote! {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
},
|
||||
RuntimeFlavor::Threaded => quote! {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
},
|
||||
};
|
||||
if let Some(v) = config.worker_threads {
|
||||
rt = quote! { #rt.worker_threads(#v) };
|
||||
}
|
||||
|
||||
let header = {
|
||||
if is_test {
|
||||
quote! {
|
||||
#[test]
|
||||
#[::core::prelude::v1::test]
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
@@ -165,21 +262,21 @@ fn parse_knobs(
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
return syn::Error::new_spanned(&input.sig.ident, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, false, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
parse_knobs(input, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
@@ -199,161 +296,5 @@ pub(crate) fn test(args: TokenStream, item: TokenStream, rt_threaded: bool) -> T
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, true, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) mod old {
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
enum Runtime {
|
||||
Basic,
|
||||
Threaded,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let sig = &mut input.sig;
|
||||
let name = &sig.ident;
|
||||
let inputs = &sig.inputs;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
if sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if name == "main" && !inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded | Runtime::Auto => quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic => quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let ret = &input.sig.output;
|
||||
let name = &input.sig.ident;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
if input.sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(&input.sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if !input.sig.inputs.is_empty() {
|
||||
let msg = "the test function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded => quote! {
|
||||
#[test]
|
||||
#(#attrs)*
|
||||
#vis fn #name() #ret {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic | Runtime::Auto => quote! {
|
||||
#[test]
|
||||
#(#attrs)*
|
||||
#vis fn #name() #ret {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
+163
-81
@@ -1,4 +1,4 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.2.5")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.3.1")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
@@ -6,7 +6,7 @@
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
# or
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html) directly.
|
||||
///
|
||||
/// ## Options:
|
||||
/// Note: This macro is designed to be simplistic and targets applications that
|
||||
/// do not require a complex setup. If the provided functionality is not
|
||||
/// sufficient, you may be interested in using
|
||||
/// [Builder](../tokio/runtime/struct.Builder.html), which provides a more
|
||||
/// powerful interface.
|
||||
///
|
||||
/// Note: This macro can be used on any function and not just the `main`
|
||||
/// function. Using it on a non-main function makes the function behave
|
||||
/// as if it was synchronous by starting a new runtime each time it is called.
|
||||
/// If the function is called often, it is preferable to create the runtime using
|
||||
/// the runtime builder so the runtime can be reused across calls.
|
||||
///
|
||||
/// - `core_threads=n` - Sets core threads to `n` (requires `rt-threaded` feature).
|
||||
/// - `max_threads=n` - Sets max threads to `n` (requires `rt-core` or `rt-threaded` feature).
|
||||
/// # Multi-threaded runtime
|
||||
///
|
||||
/// To use the multi-threaded runtime, the macro can be configured using
|
||||
///
|
||||
/// ```
|
||||
/// #[tokio::main(flavor = "multi_thread", worker_threads = 10)]
|
||||
/// # async fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// The `worker_threads` option configures the number of worker threads, and
|
||||
/// defaults to the number of cpus on the system. This is the default flavor.
|
||||
///
|
||||
/// # Current thread runtime
|
||||
///
|
||||
/// To use the single-threaded runtime known as the `current_thread` runtime,
|
||||
/// the macro can be configured using
|
||||
///
|
||||
/// ```
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
@@ -38,7 +69,7 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using default
|
||||
/// ### Using the multi-thread runtime
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
@@ -47,61 +78,84 @@ use proc_macro::TokenStream;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Set number of core threads
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(core_threads = 1)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime.
|
||||
///
|
||||
/// ## Options:
|
||||
/// ### Using current thread runtime
|
||||
///
|
||||
/// - `basic_scheduler` - All tasks are executed on the current thread.
|
||||
/// - `threaded_scheduler` - Uses the multi-threaded scheduler. Used by default (requires `rt-threaded` feature).
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using default
|
||||
/// The basic scheduler is single-threaded.
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Select runtime
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(basic_scheduler)]
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Set number of worker threads
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(worker_threads = 2)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_multi_thread()
|
||||
/// .worker_threads(2)
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro will not work.
|
||||
/// If you must rename the 0.3 version of tokio because you're also using the
|
||||
/// 0.1 version of tokio, you _must_ make the tokio 0.3 crate available as
|
||||
/// `tokio` in the module where this macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::old::main(args, item)
|
||||
entry::main(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime.
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
/// Marks async function to be executed by selected runtime. This macro helps set up a `Runtime`
|
||||
/// without requiring the user to use [Runtime](../tokio/runtime/struct.Runtime.html) or
|
||||
/// [Builder](../tokio/runtime/struct.builder.html) directly.
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
@@ -112,30 +166,47 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// #[tokio::main(flavor = "current_thread")]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Equivalent code not using `#[tokio::main]`
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// tokio::runtime::Builder::new_current_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap()
|
||||
/// .block_on(async {
|
||||
/// println!("Hello world");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.3 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.3 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, false)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `core_threads=n` - Sets core threads to `n` (requires `rt-threaded` feature).
|
||||
/// - `max_threads=n` - Sets max threads to `n` (requires `rt-core` or `rt-threaded` feature).
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Select runtime
|
||||
/// ### Multi-thread runtime
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(core_threads = 1)]
|
||||
/// #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
@@ -143,65 +214,76 @@ pub fn main_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// The default test runtime is single-threaded.
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.3 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.3 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `basic_scheduler` - All tasks are executed on the current thread. Used by default.
|
||||
/// - `threaded_scheduler` - Use multi-threaded scheduler (requires `rt-threaded` feature).
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Select runtime
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(threaded_scheduler)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### NOTE:
|
||||
///
|
||||
/// If you rename the tokio crate in your dependencies this macro
|
||||
/// will not work. If you must rename the 0.3 version of tokio because
|
||||
/// you're also using the 0.1 version of tokio, you _must_ make the
|
||||
/// tokio 0.3 crate available as `tokio` in the module where this
|
||||
/// macro is expanded.
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::old::test(args, item)
|
||||
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// Always fails with the error message below.
|
||||
/// ```text
|
||||
/// The #[tokio::main] macro requires rt or rt-multi-thread.
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
pub fn main_fail(_args: TokenStream, _item: TokenStream) -> TokenStream {
|
||||
syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"The #[tokio::main] macro requires rt or rt-multi-thread.",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Always fails with the error message below.
|
||||
/// ```text
|
||||
/// The #[tokio::test] macro requires rt or rt-multi-thread.
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_fail(_args: TokenStream, _item: TokenStream) -> TokenStream {
|
||||
syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
"The #[tokio::test] macro requires rt or rt-multi-thread.",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Implementation detail of the `select!` macro. This macro is **not** intended
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# 0.3.0 (October 15, 2020)
|
||||
|
||||
- Track `tokio` 0.3 release.
|
||||
|
||||
# 0.2.1 (April 17, 2020)
|
||||
|
||||
- Add `Future` and `Stream` implementations for `task::Spawn<T>`.
|
||||
|
||||
# 0.2.0 (November 25, 2019)
|
||||
|
||||
- Initial release
|
||||
|
||||
@@ -6,27 +6,27 @@ name = "tokio-test"
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0"
|
||||
# - Create "v0.3.x" git tag.
|
||||
version = "0.3.0"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-test/0.2.0/tokio_test"
|
||||
documentation = "https://docs.rs/tokio-test/0.3.0/tokio_test"
|
||||
description = """
|
||||
Testing utilities for Tokio- and futures-based code
|
||||
"""
|
||||
categories = ["asynchronous", "testing"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["rt-core", "stream", "sync", "time", "test-util"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["rt", "stream", "sync", "time", "test-util"] }
|
||||
|
||||
bytes = "0.5.0"
|
||||
bytes = "0.6.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
+111
-38
@@ -12,21 +12,21 @@
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Attempting to write data that the mock isn't expected will result in a
|
||||
//! Attempting to write data that the mock isn't expecting will result in a
|
||||
//! panic.
|
||||
//!
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{self, Delay, Duration, Instant};
|
||||
use tokio::time::{self, Duration, Instant, Sleep};
|
||||
|
||||
use bytes::Buf;
|
||||
use futures_core::ready;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{self, Poll, Waker};
|
||||
use std::{cmp, io};
|
||||
|
||||
@@ -57,13 +57,17 @@ enum Action {
|
||||
Read(Vec<u8>),
|
||||
Write(Vec<u8>),
|
||||
Wait(Duration),
|
||||
// Wrapped in Arc so that Builder can be cloned and Send.
|
||||
// Mock is not cloned as does not need to check Rc for ref counts.
|
||||
ReadError(Option<Arc<io::Error>>),
|
||||
WriteError(Option<Arc<io::Error>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
actions: VecDeque<Action>,
|
||||
waiting: Option<Instant>,
|
||||
sleep: Option<Delay>,
|
||||
sleep: Option<Sleep>,
|
||||
read_wait: Option<Waker>,
|
||||
rx: mpsc::UnboundedReceiver<Action>,
|
||||
}
|
||||
@@ -83,6 +87,16 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a `read` operation that produces an error.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `read` call
|
||||
/// and return `error`.
|
||||
pub fn read_error(&mut self, error: io::Error) -> &mut Self {
|
||||
let error = Some(error.into());
|
||||
self.actions.push_back(Action::ReadError(error));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a `write` operation.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `write`
|
||||
@@ -92,6 +106,16 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a `write` operation that produces an error.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `write`
|
||||
/// call that provides `error`.
|
||||
pub fn write_error(&mut self, error: io::Error) -> &mut Self {
|
||||
let error = Some(error.into());
|
||||
self.actions.push_back(Action::WriteError(error));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a wait.
|
||||
///
|
||||
/// The next operation in the mock's script will be to wait without doing so
|
||||
@@ -128,6 +152,16 @@ impl Handle {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a `read` operation error.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `read` call
|
||||
/// and return `error`.
|
||||
pub fn read_error(&mut self, error: io::Error) -> &mut Self {
|
||||
let error = Some(error.into());
|
||||
self.tx.send(Action::ReadError(error)).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a `write` operation.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `write`
|
||||
@@ -136,6 +170,16 @@ impl Handle {
|
||||
self.tx.send(Action::Write(buf.into())).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a `write` operation error.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `write`
|
||||
/// call error.
|
||||
pub fn write_error(&mut self, error: io::Error) -> &mut Self {
|
||||
let error = Some(error.into());
|
||||
self.tx.send(Action::WriteError(error)).unwrap();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
@@ -156,29 +200,36 @@ impl Inner {
|
||||
}
|
||||
|
||||
fn poll_action(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Action>> {
|
||||
self.rx.poll_recv(cx)
|
||||
use futures_core::stream::Stream;
|
||||
|
||||
Pin::new(&mut self.rx).poll_next(cx)
|
||||
}
|
||||
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
fn read(&mut self, dst: &mut ReadBuf<'_>) -> io::Result<()> {
|
||||
match self.action() {
|
||||
Some(&mut Action::Read(ref mut data)) => {
|
||||
// Figure out how much to copy
|
||||
let n = cmp::min(dst.len(), data.len());
|
||||
let n = cmp::min(dst.remaining(), data.len());
|
||||
|
||||
// Copy the data into the `dst` slice
|
||||
(&mut dst[..n]).copy_from_slice(&data[..n]);
|
||||
dst.put_slice(&data[..n]);
|
||||
|
||||
// Drain the data from the source
|
||||
data.drain(..n);
|
||||
|
||||
// Return the number of bytes read
|
||||
Ok(n)
|
||||
Ok(())
|
||||
}
|
||||
Some(&mut Action::ReadError(ref mut err)) => {
|
||||
// As the
|
||||
let err = err.take().expect("Should have been removed from actions.");
|
||||
let err = Arc::try_unwrap(err).expect("There are no other references.");
|
||||
Err(err)
|
||||
}
|
||||
Some(_) => {
|
||||
// Either waiting or expecting a write
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
None => Ok(0),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +244,12 @@ impl Inner {
|
||||
return Err(io::ErrorKind::WouldBlock.into());
|
||||
}
|
||||
|
||||
if let Some(&mut Action::WriteError(ref mut err)) = self.action() {
|
||||
let err = err.take().expect("Should have been removed from actions.");
|
||||
let err = Arc::try_unwrap(err).expect("There are no other references.");
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
for i in 0..self.actions.len() {
|
||||
match self.actions[i] {
|
||||
Action::Write(ref mut expect) => {
|
||||
@@ -210,7 +267,7 @@ impl Inner {
|
||||
return Ok(ret);
|
||||
}
|
||||
}
|
||||
Action::Wait(..) => {
|
||||
Action::Wait(..) | Action::WriteError(..) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
@@ -258,6 +315,11 @@ impl Inner {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Action::ReadError(ref mut error) | Action::WriteError(ref mut error) => {
|
||||
if error.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _action = self.actions.pop_front();
|
||||
@@ -272,7 +334,7 @@ impl Inner {
|
||||
impl Mock {
|
||||
fn maybe_wakeup_reader(&mut self) {
|
||||
match self.inner.action() {
|
||||
Some(&mut Action::Read(_)) | None => {
|
||||
Some(&mut Action::Read(_)) | Some(&mut Action::ReadError(_)) | None => {
|
||||
if let Some(waker) = self.inner.read_wait.take() {
|
||||
waker.wake();
|
||||
}
|
||||
@@ -286,8 +348,8 @@ impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
if let Some(ref mut sleep) = self.inner.sleep {
|
||||
ready!(Pin::new(sleep).poll(cx));
|
||||
@@ -296,29 +358,35 @@ impl AsyncRead for Mock {
|
||||
// If a sleep is set, it has already fired
|
||||
self.inner.sleep = None;
|
||||
|
||||
// Capture 'filled' to monitor if it changed
|
||||
let filled = buf.filled().len();
|
||||
|
||||
match self.inner.read(buf) {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if let Some(rem) = self.inner.remaining_wait() {
|
||||
let until = Instant::now() + rem;
|
||||
self.inner.sleep = Some(time::delay_until(until));
|
||||
self.inner.sleep = Some(time::sleep_until(until));
|
||||
} else {
|
||||
self.inner.read_wait = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
Ok(0) => {
|
||||
// TODO: Extract
|
||||
match ready!(self.inner.poll_action(cx)) {
|
||||
Some(action) => {
|
||||
self.inner.actions.push_back(action);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
return Poll::Ready(Ok(0));
|
||||
Ok(()) => {
|
||||
if buf.filled().len() == filled {
|
||||
match ready!(self.inner.poll_action(cx)) {
|
||||
Some(action) => {
|
||||
self.inner.actions.push_back(action);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
ret => return Poll::Ready(ret),
|
||||
Err(e) => return Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,7 +410,7 @@ impl AsyncWrite for Mock {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if let Some(rem) = self.inner.remaining_wait() {
|
||||
let until = Instant::now() + rem;
|
||||
self.inner.sleep = Some(time::delay_until(until));
|
||||
self.inner.sleep = Some(time::sleep_until(until));
|
||||
} else {
|
||||
panic!("unexpected WouldBlock");
|
||||
}
|
||||
@@ -372,16 +440,6 @@ impl AsyncWrite for Mock {
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_write_buf<B: Buf>(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut B,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let n = ready!(self.poll_write(cx, buf.bytes()))?;
|
||||
buf.advance(n);
|
||||
Poll::Ready(Ok(n))
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
@@ -391,6 +449,21 @@ impl AsyncWrite for Mock {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that Mock isn't dropped with data "inside".
|
||||
impl Drop for Mock {
|
||||
fn drop(&mut self) {
|
||||
// Avoid double panicking, since makes debugging much harder.
|
||||
if std::thread::panicking() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.inner.actions.iter().for_each(|a| match a {
|
||||
Action::Read(data) => assert!(data.is_empty(), "There is still data left to read."),
|
||||
Action::Write(data) => assert!(data.is_empty(), "There is still data left to write."),
|
||||
_ => (),
|
||||
})
|
||||
}
|
||||
}
|
||||
/*
|
||||
/// Returns `true` if called from the context of a futures-rs Task
|
||||
fn is_task_ctx() -> bool {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.2.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.3.0")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
@@ -28,8 +28,7 @@ pub mod task;
|
||||
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
use tokio::runtime;
|
||||
|
||||
let mut rt = runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
+19
-13
@@ -11,7 +11,7 @@ use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use tokio::stream::Stream;
|
||||
|
||||
/// TOOD: dox
|
||||
/// TODO: dox
|
||||
pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
Spawn {
|
||||
task: MockTask::new(),
|
||||
@@ -46,21 +46,11 @@ const SLEEP: usize = 2;
|
||||
|
||||
impl<T> Spawn<T> {
|
||||
/// Consumes `self` returning the inner value
|
||||
pub fn into_inner(mut self) -> T
|
||||
pub fn into_inner(self) -> T
|
||||
where
|
||||
T: Unpin,
|
||||
{
|
||||
drop(self.task);
|
||||
|
||||
// Pin::into_inner is unstable, so we work around it
|
||||
//
|
||||
// Safety: `T` is bound by `Unpin`.
|
||||
unsafe {
|
||||
let ptr = Pin::get_mut(self.future.as_mut()) as *mut T;
|
||||
let future = Box::from_raw(ptr);
|
||||
mem::forget(self.future);
|
||||
*future
|
||||
}
|
||||
*Pin::into_inner(self.future)
|
||||
}
|
||||
|
||||
/// Returns `true` if the inner future has received a wake notification
|
||||
@@ -116,6 +106,22 @@ impl<T: Stream> Spawn<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Future> Future for Spawn<T> {
|
||||
type Output = T::Output;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.future.as_mut().poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Stream> Stream for Spawn<T> {
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.future.as_mut().poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl MockTask {
|
||||
/// Creates new mock task
|
||||
fn new() -> Self {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::time::{delay_until, Duration, Instant};
|
||||
use tokio::time::{sleep_until, Duration, Instant};
|
||||
use tokio_test::block_on;
|
||||
|
||||
#[test]
|
||||
@@ -18,10 +18,10 @@ fn async_fn() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delay() {
|
||||
fn test_sleep() {
|
||||
let deadline = Instant::now() + Duration::from_millis(100);
|
||||
|
||||
block_on(async {
|
||||
delay_until(deadline).await;
|
||||
sleep_until(deadline).await;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::io;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio_test::io::Builder;
|
||||
|
||||
@@ -16,6 +17,31 @@ async fn read() {
|
||||
assert_eq!(&buf[..n], b"world!");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_error() {
|
||||
let error = io::Error::new(io::ErrorKind::Other, "cruel");
|
||||
let mut mock = Builder::new()
|
||||
.read(b"hello ")
|
||||
.read_error(error)
|
||||
.read(b"world!")
|
||||
.build();
|
||||
let mut buf = [0; 256];
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 1");
|
||||
assert_eq!(&buf[..n], b"hello ");
|
||||
|
||||
match mock.read(&mut buf).await {
|
||||
Err(error) => {
|
||||
assert_eq!(error.kind(), io::ErrorKind::Other);
|
||||
assert_eq!("cruel", format!("{}", error));
|
||||
}
|
||||
Ok(_) => panic!("error not received"),
|
||||
}
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 1");
|
||||
assert_eq!(&buf[..n], b"world!");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write() {
|
||||
let mut mock = Builder::new().write(b"hello ").write(b"world!").build();
|
||||
@@ -23,3 +49,38 @@ async fn write() {
|
||||
mock.write_all(b"hello ").await.expect("write 1");
|
||||
mock.write_all(b"world!").await.expect("write 2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_error() {
|
||||
let error = io::Error::new(io::ErrorKind::Other, "cruel");
|
||||
let mut mock = Builder::new()
|
||||
.write(b"hello ")
|
||||
.write_error(error)
|
||||
.write(b"world!")
|
||||
.build();
|
||||
mock.write_all(b"hello ").await.expect("write 1");
|
||||
|
||||
match mock.write_all(b"whoa").await {
|
||||
Err(error) => {
|
||||
assert_eq!(error.kind(), io::ErrorKind::Other);
|
||||
assert_eq!("cruel", format!("{}", error));
|
||||
}
|
||||
Ok(_) => panic!("error not received"),
|
||||
}
|
||||
|
||||
mock.write_all(b"world!").await.expect("write 2");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn mock_panics_read_data_left() {
|
||||
use tokio_test::io::Builder;
|
||||
Builder::new().read(b"read").build();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn mock_panics_write_data_left() {
|
||||
use tokio_test::io::Builder;
|
||||
Builder::new().write(b"write").build();
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# 0.3.0 (November 26, 2019)
|
||||
|
||||
- Updates for tokio 0.2 release
|
||||
|
||||
# 0.3.0-alpha.6 (September 30, 2019)
|
||||
|
||||
- Move to `futures-*-preview 0.3.0-alpha.19`
|
||||
- Move to `pin-project 0.4`
|
||||
|
||||
# 0.3.0-alpha.5 (September 19, 2019)
|
||||
|
||||
### Added
|
||||
- `TlsStream::get_ref` and `TlsStream::get_mut` (#1537).
|
||||
|
||||
# 0.3.0-alpha.4 (August 30, 2019)
|
||||
|
||||
### Changed
|
||||
- Track `tokio` 0.2.0-alpha.4
|
||||
|
||||
# 0.3.0-alpha.2 (August 17, 2019)
|
||||
|
||||
### Changed
|
||||
- Update `futures` dependency to 0.3.0-alpha.18.
|
||||
|
||||
# 0.3.0-alpha.1 (August 8, 2019)
|
||||
|
||||
### Changed
|
||||
- Switch to `async`, `await`, and `std::future`.
|
||||
|
||||
# 0.2.1 (January 6, 2019)
|
||||
|
||||
* Implement `Clone` for `TlsConnector` and `TlsAcceptor` (#777)
|
||||
|
||||
# 0.2.0 (August 8, 2018)
|
||||
|
||||
* Initial release with `tokio` support.
|
||||
@@ -1,63 +0,0 @@
|
||||
[package]
|
||||
name = "tokio-tls"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.3.x" git tag.
|
||||
version = "0.3.0"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-tls/0.3.0-alpha.6/tokio_tls/"
|
||||
description = """
|
||||
An implementation of TLS/SSL streams for Tokio giving an implementation of TLS
|
||||
for nonblocking I/O streams.
|
||||
"""
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "tokio-rs/tokio-tls" }
|
||||
|
||||
[dependencies]
|
||||
native-tls = "0.2"
|
||||
tokio = { version = "0.2.0", path = "../tokio" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["macros", "stream", "rt-core", "io-util", "net"] }
|
||||
tokio-util = { version = "0.2.0", path = "../tokio-util", features = ["full"] }
|
||||
|
||||
cfg-if = "0.1"
|
||||
env_logger = { version = "0.6", default-features = false }
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
|
||||
[target.'cfg(all(not(target_os = "macos"), not(windows), not(target_os = "ios")))'.dev-dependencies]
|
||||
openssl = "0.10"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dev-dependencies]
|
||||
security-framework = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies]
|
||||
schannel = "0.1"
|
||||
|
||||
[target.'cfg(windows)'.dev-dependencies.winapi]
|
||||
version = "0.3"
|
||||
features = [
|
||||
"lmcons",
|
||||
"basetsd",
|
||||
"minwinbase",
|
||||
"minwindef",
|
||||
"ntdef",
|
||||
"sysinfoapi",
|
||||
"timezoneapi",
|
||||
"wincrypt",
|
||||
"winerror",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -1,25 +0,0 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,14 +0,0 @@
|
||||
# tokio-tls
|
||||
|
||||
An implementation of TLS/SSL streams for Tokio built on top of the [`native-tls`
|
||||
crate]
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](./LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -1,40 +0,0 @@
|
||||
// #![warn(rust_2018_idioms)]
|
||||
|
||||
use native_tls::TlsConnector;
|
||||
use std::error::Error;
|
||||
use std::net::ToSocketAddrs;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tls;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let addr = "www.rust-lang.org:443"
|
||||
.to_socket_addrs()?
|
||||
.next()
|
||||
.ok_or("failed to resolve www.rust-lang.org")?;
|
||||
|
||||
let socket = TcpStream::connect(&addr).await?;
|
||||
let cx = TlsConnector::builder().build()?;
|
||||
let cx = tokio_tls::TlsConnector::from(cx);
|
||||
|
||||
let mut socket = cx.connect("www.rust-lang.org", socket).await?;
|
||||
|
||||
socket
|
||||
.write_all(
|
||||
"\
|
||||
GET / HTTP/1.0\r\n\
|
||||
Host: www.rust-lang.org\r\n\
|
||||
\r\n\
|
||||
"
|
||||
.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
socket.read_to_end(&mut data).await?;
|
||||
|
||||
// println!("data: {:?}", &data);
|
||||
println!("{}", String::from_utf8_lossy(&data[..]));
|
||||
Ok(())
|
||||
}
|
||||
Binary file not shown.
@@ -1,55 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
// A tiny async TLS echo server with Tokio
|
||||
use native_tls;
|
||||
use native_tls::Identity;
|
||||
use tokio;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_tls;
|
||||
|
||||
/**
|
||||
an example to setup a tls server.
|
||||
how to test:
|
||||
wget https://127.0.0.1:12345 --no-check-certificate
|
||||
*/
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Bind the server's socket
|
||||
let addr = "127.0.0.1:12345".to_string();
|
||||
let mut tcp: TcpListener = TcpListener::bind(&addr).await?;
|
||||
|
||||
// Create the TLS acceptor.
|
||||
let der = include_bytes!("identity.p12");
|
||||
let cert = Identity::from_pkcs12(der, "mypass")?;
|
||||
let tls_acceptor =
|
||||
tokio_tls::TlsAcceptor::from(native_tls::TlsAcceptor::builder(cert).build()?);
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
let (socket, remote_addr) = tcp.accept().await?;
|
||||
let tls_acceptor = tls_acceptor.clone();
|
||||
println!("accept connection from {}", remote_addr);
|
||||
tokio::spawn(async move {
|
||||
// Accept the TLS connection.
|
||||
let mut tls_stream = tls_acceptor.accept(socket).await.expect("accept error");
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
|
||||
let mut buf = [0; 1024];
|
||||
let n = tls_stream
|
||||
.read(&mut buf)
|
||||
.await
|
||||
.expect("failed to read data from socket");
|
||||
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
println!("read={}", unsafe {
|
||||
String::from_utf8_unchecked(buf[0..n].into())
|
||||
});
|
||||
tls_stream
|
||||
.write_all(&buf[0..n])
|
||||
.await
|
||||
.expect("failed to write data to socket");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-tls/0.3.0")]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
))]
|
||||
|
||||
//! Async TLS streams
|
||||
//!
|
||||
//! This library is an implementation of TLS streams using the most appropriate
|
||||
//! system library by default for negotiating the connection. That is, on
|
||||
//! Windows this library uses SChannel, on OSX it uses SecureTransport, and on
|
||||
//! other platforms it uses OpenSSL.
|
||||
//!
|
||||
//! Each TLS stream implements the `Read` and `Write` traits to interact and
|
||||
//! interoperate with the rest of the futures I/O ecosystem. Client connections
|
||||
//! initiated from this crate verify hostnames automatically and by default.
|
||||
//!
|
||||
//! This crate primarily exports this ability through two newtypes,
|
||||
//! `TlsConnector` and `TlsAcceptor`. These newtypes augment the
|
||||
//! functionality provided by the `native-tls` crate, on which this crate is
|
||||
//! built. Configuration of TLS parameters is still primarily done through the
|
||||
//! `native-tls` crate.
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use native_tls::{Error, HandshakeError, MidHandshakeTlsStream};
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::marker::Unpin;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::null_mut;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AllowStd<S> {
|
||||
inner: S,
|
||||
context: *mut (),
|
||||
}
|
||||
|
||||
/// A wrapper around an underlying raw stream which implements the TLS or SSL
|
||||
/// protocol.
|
||||
///
|
||||
/// A `TlsStream<S>` represents a handshake that has been completed successfully
|
||||
/// and both the server and the client are ready for receiving and sending
|
||||
/// data. Bytes read from a `TlsStream` are decrypted from `S` and bytes written
|
||||
/// to a `TlsStream` are encrypted when passing through to `S`.
|
||||
#[derive(Debug)]
|
||||
pub struct TlsStream<S>(native_tls::TlsStream<AllowStd<S>>);
|
||||
|
||||
/// A wrapper around a `native_tls::TlsConnector`, providing an async `connect`
|
||||
/// method.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConnector(native_tls::TlsConnector);
|
||||
|
||||
/// A wrapper around a `native_tls::TlsAcceptor`, providing an async `accept`
|
||||
/// method.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsAcceptor(native_tls::TlsAcceptor);
|
||||
|
||||
struct MidHandshake<S>(Option<MidHandshakeTlsStream<AllowStd<S>>>);
|
||||
|
||||
enum StartedHandshake<S> {
|
||||
Done(TlsStream<S>),
|
||||
Mid(MidHandshakeTlsStream<AllowStd<S>>),
|
||||
}
|
||||
|
||||
struct StartedHandshakeFuture<F, S>(Option<StartedHandshakeFutureInner<F, S>>);
|
||||
struct StartedHandshakeFutureInner<F, S> {
|
||||
f: F,
|
||||
stream: S,
|
||||
}
|
||||
|
||||
struct Guard<'a, S>(&'a mut TlsStream<S>)
|
||||
where
|
||||
AllowStd<S>: Read + Write;
|
||||
|
||||
impl<S> Drop for Guard<'_, S>
|
||||
where
|
||||
AllowStd<S>: Read + Write,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
(self.0).0.get_mut().context = null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
// *mut () context is neither Send nor Sync
|
||||
unsafe impl<S: Send> Send for AllowStd<S> {}
|
||||
unsafe impl<S: Sync> Sync for AllowStd<S> {}
|
||||
|
||||
impl<S> AllowStd<S>
|
||||
where
|
||||
S: Unpin,
|
||||
{
|
||||
fn with_context<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Context<'_>, Pin<&mut S>) -> R,
|
||||
{
|
||||
unsafe {
|
||||
assert!(!self.context.is_null());
|
||||
let waker = &mut *(self.context as *mut _);
|
||||
f(waker, Pin::new(&mut self.inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Read for AllowStd<S>
|
||||
where
|
||||
S: AsyncRead + Unpin,
|
||||
{
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match self.with_context(|ctx, stream| stream.poll_read(ctx, buf)) {
|
||||
Poll::Ready(r) => r,
|
||||
Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Write for AllowStd<S>
|
||||
where
|
||||
S: AsyncWrite + Unpin,
|
||||
{
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self.with_context(|ctx, stream| stream.poll_write(ctx, buf)) {
|
||||
Poll::Ready(r) => r,
|
||||
Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
match self.with_context(|ctx, stream| stream.poll_flush(ctx)) {
|
||||
Poll::Ready(r) => r,
|
||||
Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt<T>(r: io::Result<T>) -> Poll<io::Result<T>> {
|
||||
match r {
|
||||
Ok(v) => Poll::Ready(Ok(v)),
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Poll::Pending,
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> TlsStream<S> {
|
||||
fn with_context<F, R>(&mut self, ctx: &mut Context<'_>, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut native_tls::TlsStream<AllowStd<S>>) -> R,
|
||||
AllowStd<S>: Read + Write,
|
||||
{
|
||||
self.0.get_mut().context = ctx as *mut _ as *mut ();
|
||||
let g = Guard(self);
|
||||
f(&mut (g.0).0)
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the inner stream.
|
||||
pub fn get_ref(&self) -> &S
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
&self.0.get_ref().inner
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner stream.
|
||||
pub fn get_mut(&mut self) -> &mut S
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
&mut self.0.get_mut().inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> AsyncRead for TlsStream<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
unsafe fn prepare_uninitialized_buffer(&self, _: &mut [MaybeUninit<u8>]) -> bool {
|
||||
// Note that this does not forward to `S` because the buffer is
|
||||
// unconditionally filled in by OpenSSL, not the actual object `S`.
|
||||
// We're decrypting bytes from `S` into the buffer above!
|
||||
false
|
||||
}
|
||||
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
self.with_context(ctx, |s| cvt(s.read(buf)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> AsyncWrite for TlsStream<S>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
self.with_context(ctx, |s| cvt(s.write(buf)))
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.with_context(ctx, |s| cvt(s.flush()))
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
match self.with_context(ctx, |s| s.shutdown()) {
|
||||
Ok(()) => Poll::Ready(Ok(())),
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Poll::Pending,
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handshake<F, S>(f: F, stream: S) -> Result<TlsStream<S>, Error>
|
||||
where
|
||||
F: FnOnce(
|
||||
AllowStd<S>,
|
||||
) -> Result<native_tls::TlsStream<AllowStd<S>>, HandshakeError<AllowStd<S>>>
|
||||
+ Unpin,
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
let start = StartedHandshakeFuture(Some(StartedHandshakeFutureInner { f, stream }));
|
||||
|
||||
match start.await {
|
||||
Err(e) => Err(e),
|
||||
Ok(StartedHandshake::Done(s)) => Ok(s),
|
||||
Ok(StartedHandshake::Mid(s)) => MidHandshake(Some(s)).await,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, S> Future for StartedHandshakeFuture<F, S>
|
||||
where
|
||||
F: FnOnce(
|
||||
AllowStd<S>,
|
||||
) -> Result<native_tls::TlsStream<AllowStd<S>>, HandshakeError<AllowStd<S>>>
|
||||
+ Unpin,
|
||||
S: Unpin,
|
||||
AllowStd<S>: Read + Write,
|
||||
{
|
||||
type Output = Result<StartedHandshake<S>, Error>;
|
||||
|
||||
fn poll(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context<'_>,
|
||||
) -> Poll<Result<StartedHandshake<S>, Error>> {
|
||||
let inner = self.0.take().expect("future polled after completion");
|
||||
let stream = AllowStd {
|
||||
inner: inner.stream,
|
||||
context: ctx as *mut _ as *mut (),
|
||||
};
|
||||
|
||||
match (inner.f)(stream) {
|
||||
Ok(mut s) => {
|
||||
s.get_mut().context = null_mut();
|
||||
Poll::Ready(Ok(StartedHandshake::Done(TlsStream(s))))
|
||||
}
|
||||
Err(HandshakeError::WouldBlock(mut s)) => {
|
||||
s.get_mut().context = null_mut();
|
||||
Poll::Ready(Ok(StartedHandshake::Mid(s)))
|
||||
}
|
||||
Err(HandshakeError::Failure(e)) => Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsConnector {
|
||||
/// Connects the provided stream with this connector, assuming the provided
|
||||
/// domain.
|
||||
///
|
||||
/// This function will internally call `TlsConnector::connect` to connect
|
||||
/// the stream and returns a future representing the resolution of the
|
||||
/// connection operation. The returned future will resolve to either
|
||||
/// `TlsStream<S>` or `Error` depending if it's successful or not.
|
||||
///
|
||||
/// This is typically used for clients who have already established, for
|
||||
/// example, a TCP connection to a remote server. That stream is then
|
||||
/// provided here to perform the client half of a connection to a
|
||||
/// TLS-powered server.
|
||||
pub async fn connect<S>(&self, domain: &str, stream: S) -> Result<TlsStream<S>, Error>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
handshake(move |s| self.0.connect(domain, s), stream).await
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TlsConnector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TlsConnector").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<native_tls::TlsConnector> for TlsConnector {
|
||||
fn from(inner: native_tls::TlsConnector) -> TlsConnector {
|
||||
TlsConnector(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsAcceptor {
|
||||
/// Accepts a new client connection with the provided stream.
|
||||
///
|
||||
/// This function will internally call `TlsAcceptor::accept` to connect
|
||||
/// the stream and returns a future representing the resolution of the
|
||||
/// connection operation. The returned future will resolve to either
|
||||
/// `TlsStream<S>` or `Error` depending if it's successful or not.
|
||||
///
|
||||
/// This is typically used after a new socket has been accepted from a
|
||||
/// `TcpListener`. That socket is then passed to this function to perform
|
||||
/// the server half of accepting a client connection.
|
||||
pub async fn accept<S>(&self, stream: S) -> Result<TlsStream<S>, Error>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
handshake(move |s| self.0.accept(s), stream).await
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TlsAcceptor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TlsAcceptor").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<native_tls::TlsAcceptor> for TlsAcceptor {
|
||||
fn from(inner: native_tls::TlsAcceptor) -> TlsAcceptor {
|
||||
TlsAcceptor(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: AsyncRead + AsyncWrite + Unpin> Future for MidHandshake<S> {
|
||||
type Output = Result<TlsStream<S>, Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut_self = self.get_mut();
|
||||
let mut s = mut_self.0.take().expect("future polled after completion");
|
||||
|
||||
s.get_mut().context = cx as *mut _ as *mut ();
|
||||
match s.handshake() {
|
||||
Ok(stream) => Poll::Ready(Ok(TlsStream(stream))),
|
||||
Err(HandshakeError::Failure(e)) => Poll::Ready(Err(e)),
|
||||
Err(HandshakeError::WouldBlock(mut s)) => {
|
||||
s.get_mut().context = null_mut();
|
||||
mut_self.0 = Some(s);
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use env_logger;
|
||||
use native_tls::TlsConnector;
|
||||
use std::io::{self, Error};
|
||||
use std::net::ToSocketAddrs;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tls;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "force-rustls")] {
|
||||
fn verify_failed(err: &Error, s: &str) {
|
||||
let err = err.to_string();
|
||||
assert!(err.contains(s), "bad error: {}", err);
|
||||
}
|
||||
|
||||
fn assert_expired_error(err: &Error) {
|
||||
verify_failed(err, "CertExpired");
|
||||
}
|
||||
|
||||
fn assert_wrong_host(err: &Error) {
|
||||
verify_failed(err, "CertNotValidForName");
|
||||
}
|
||||
|
||||
fn assert_self_signed(err: &Error) {
|
||||
verify_failed(err, "UnknownIssuer");
|
||||
}
|
||||
|
||||
fn assert_untrusted_root(err: &Error) {
|
||||
verify_failed(err, "UnknownIssuer");
|
||||
}
|
||||
} else if #[cfg(any(feature = "force-openssl",
|
||||
all(not(target_os = "macos"),
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "ios"))))] {
|
||||
fn verify_failed(err: &Error) {
|
||||
assert!(format!("{}", err).contains("certificate verify failed"))
|
||||
}
|
||||
|
||||
use verify_failed as assert_expired_error;
|
||||
use verify_failed as assert_wrong_host;
|
||||
use verify_failed as assert_self_signed;
|
||||
use verify_failed as assert_untrusted_root;
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "ios"))] {
|
||||
|
||||
fn assert_invalid_cert_chain(err: &Error) {
|
||||
assert!(format!("{}", err).contains("was not trusted."))
|
||||
}
|
||||
|
||||
use crate::assert_invalid_cert_chain as assert_expired_error;
|
||||
use crate::assert_invalid_cert_chain as assert_wrong_host;
|
||||
use crate::assert_invalid_cert_chain as assert_self_signed;
|
||||
use crate::assert_invalid_cert_chain as assert_untrusted_root;
|
||||
} else {
|
||||
fn assert_expired_error(err: &Error) {
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("system clock"), "error = {:?}", s);
|
||||
}
|
||||
|
||||
fn assert_wrong_host(err: &Error) {
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("CN name"), "error = {:?}", s);
|
||||
}
|
||||
|
||||
fn assert_self_signed(err: &Error) {
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("root certificate which is not trusted"), "error = {:?}", s);
|
||||
}
|
||||
|
||||
use assert_self_signed as assert_untrusted_root;
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_host(host: &'static str) -> Error {
|
||||
drop(env_logger::try_init());
|
||||
|
||||
let addr = format!("{}:443", host);
|
||||
let addr = t!(addr.to_socket_addrs()).next().unwrap();
|
||||
|
||||
let socket = t!(TcpStream::connect(&addr).await);
|
||||
let builder = TlsConnector::builder();
|
||||
let cx = t!(builder.build());
|
||||
let cx = tokio_tls::TlsConnector::from(cx);
|
||||
let res = cx
|
||||
.connect(host, socket)
|
||||
.await
|
||||
.map_err(|e| Error::new(io::ErrorKind::Other, e));
|
||||
|
||||
assert!(res.is_err());
|
||||
res.err().unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn expired() {
|
||||
assert_expired_error(&get_host("expired.badssl.com").await)
|
||||
}
|
||||
|
||||
// TODO: the OSX builders on Travis apparently fail this tests spuriously?
|
||||
// passes locally though? Seems... bad!
|
||||
#[tokio::test]
|
||||
#[cfg_attr(all(target_os = "macos", feature = "force-openssl"), ignore)]
|
||||
async fn wrong_host() {
|
||||
assert_wrong_host(&get_host("wrong.host.badssl.com").await)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn self_signed() {
|
||||
assert_self_signed(&get_host("self-signed.badssl.com").await)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn untrusted_root() {
|
||||
assert_untrusted_root(&get_host("untrusted-root.badssl.com").await)
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use env_logger;
|
||||
use native_tls;
|
||||
use native_tls::TlsConnector;
|
||||
use std::io;
|
||||
use std::net::ToSocketAddrs;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tls;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "force-rustls")] {
|
||||
fn assert_bad_hostname_error(err: &io::Error) {
|
||||
let err = err.to_string();
|
||||
assert!(err.contains("CertNotValidForName"), "bad error: {}", err);
|
||||
}
|
||||
} else if #[cfg(any(feature = "force-openssl",
|
||||
all(not(target_os = "macos"),
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "ios"))))] {
|
||||
fn assert_bad_hostname_error(err: &io::Error) {
|
||||
let err = err.get_ref().unwrap();
|
||||
let err = err.downcast_ref::<native_tls::Error>().unwrap();
|
||||
assert!(format!("{}", err).contains("certificate verify failed"));
|
||||
}
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "ios"))] {
|
||||
fn assert_bad_hostname_error(err: &io::Error) {
|
||||
let err = err.get_ref().unwrap();
|
||||
let err = err.downcast_ref::<native_tls::Error>().unwrap();
|
||||
assert!(format!("{}", err).contains("was not trusted."));
|
||||
}
|
||||
} else {
|
||||
fn assert_bad_hostname_error(err: &io::Error) {
|
||||
let err = err.get_ref().unwrap();
|
||||
let err = err.downcast_ref::<native_tls::Error>().unwrap();
|
||||
assert!(format!("{}", err).contains("CN name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fetch_google() {
|
||||
drop(env_logger::try_init());
|
||||
|
||||
// First up, resolve google.com
|
||||
let addr = t!("google.com:443".to_socket_addrs()).next().unwrap();
|
||||
|
||||
let socket = TcpStream::connect(&addr).await.unwrap();
|
||||
|
||||
// Send off the request by first negotiating an SSL handshake, then writing
|
||||
// of our request, then flushing, then finally read off the response.
|
||||
let builder = TlsConnector::builder();
|
||||
let connector = t!(builder.build());
|
||||
let connector = tokio_tls::TlsConnector::from(connector);
|
||||
let mut socket = t!(connector.connect("google.com", socket).await);
|
||||
t!(socket.write_all(b"GET / HTTP/1.0\r\n\r\n").await);
|
||||
let mut data = Vec::new();
|
||||
t!(socket.read_to_end(&mut data).await);
|
||||
|
||||
// any response code is fine
|
||||
assert!(data.starts_with(b"HTTP/1.0 "));
|
||||
|
||||
let data = String::from_utf8_lossy(&data);
|
||||
let data = data.trim_end();
|
||||
assert!(data.ends_with("</html>") || data.ends_with("</HTML>"));
|
||||
}
|
||||
|
||||
fn native2io(e: native_tls::Error) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
|
||||
// see comment in bad.rs for ignore reason
|
||||
#[cfg_attr(all(target_os = "macos", feature = "force-openssl"), ignore)]
|
||||
#[tokio::test]
|
||||
async fn wrong_hostname_error() {
|
||||
drop(env_logger::try_init());
|
||||
|
||||
let addr = t!("google.com:443".to_socket_addrs()).next().unwrap();
|
||||
|
||||
let socket = t!(TcpStream::connect(&addr).await);
|
||||
let builder = TlsConnector::builder();
|
||||
let connector = t!(builder.build());
|
||||
let connector = tokio_tls::TlsConnector::from(connector);
|
||||
let res = connector
|
||||
.connect("rust-lang.org", socket)
|
||||
.await
|
||||
.map_err(native2io);
|
||||
|
||||
assert!(res.is_err());
|
||||
assert_bad_hostname_error(&res.err().unwrap());
|
||||
}
|
||||
@@ -1,629 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use env_logger;
|
||||
use futures::join;
|
||||
use native_tls;
|
||||
use native_tls::{Identity, TlsAcceptor, TlsConnector};
|
||||
use std::io::Write;
|
||||
use std::marker::Unpin;
|
||||
use std::process::Command;
|
||||
use std::ptr;
|
||||
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt, Error, ErrorKind};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_tls;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct Keys {
|
||||
cert_der: Vec<u8>,
|
||||
pkey_der: Vec<u8>,
|
||||
pkcs12_der: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn openssl_keys() -> &'static Keys {
|
||||
static INIT: Once = Once::new();
|
||||
static mut KEYS: *mut Keys = ptr::null_mut();
|
||||
|
||||
INIT.call_once(|| {
|
||||
let path = t!(env::current_exe());
|
||||
let path = path.parent().unwrap();
|
||||
let keyfile = path.join("test.key");
|
||||
let certfile = path.join("test.crt");
|
||||
let config = path.join("openssl.config");
|
||||
|
||||
File::create(&config)
|
||||
.unwrap()
|
||||
.write_all(
|
||||
b"\
|
||||
[req]\n\
|
||||
distinguished_name=dn\n\
|
||||
[ dn ]\n\
|
||||
CN=localhost\n\
|
||||
[ ext ]\n\
|
||||
basicConstraints=CA:FALSE,pathlen:0\n\
|
||||
subjectAltName = @alt_names
|
||||
extendedKeyUsage=serverAuth,clientAuth
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let subj = "/C=US/ST=Denial/L=Sprintfield/O=Dis/CN=localhost";
|
||||
let output = t!(Command::new("openssl")
|
||||
.arg("req")
|
||||
.arg("-nodes")
|
||||
.arg("-x509")
|
||||
.arg("-newkey")
|
||||
.arg("rsa:2048")
|
||||
.arg("-config")
|
||||
.arg(&config)
|
||||
.arg("-extensions")
|
||||
.arg("ext")
|
||||
.arg("-subj")
|
||||
.arg(subj)
|
||||
.arg("-keyout")
|
||||
.arg(&keyfile)
|
||||
.arg("-out")
|
||||
.arg(&certfile)
|
||||
.arg("-days")
|
||||
.arg("1")
|
||||
.output());
|
||||
assert!(output.status.success());
|
||||
|
||||
let crtout = t!(Command::new("openssl")
|
||||
.arg("x509")
|
||||
.arg("-outform")
|
||||
.arg("der")
|
||||
.arg("-in")
|
||||
.arg(&certfile)
|
||||
.output());
|
||||
assert!(crtout.status.success());
|
||||
let keyout = t!(Command::new("openssl")
|
||||
.arg("rsa")
|
||||
.arg("-outform")
|
||||
.arg("der")
|
||||
.arg("-in")
|
||||
.arg(&keyfile)
|
||||
.output());
|
||||
assert!(keyout.status.success());
|
||||
|
||||
let pkcs12out = t!(Command::new("openssl")
|
||||
.arg("pkcs12")
|
||||
.arg("-export")
|
||||
.arg("-nodes")
|
||||
.arg("-inkey")
|
||||
.arg(&keyfile)
|
||||
.arg("-in")
|
||||
.arg(&certfile)
|
||||
.arg("-password")
|
||||
.arg("pass:foobar")
|
||||
.output());
|
||||
assert!(pkcs12out.status.success());
|
||||
|
||||
let keys = Box::new(Keys {
|
||||
cert_der: crtout.stdout,
|
||||
pkey_der: keyout.stdout,
|
||||
pkcs12_der: pkcs12out.stdout,
|
||||
});
|
||||
unsafe {
|
||||
KEYS = Box::into_raw(keys);
|
||||
}
|
||||
});
|
||||
unsafe { &*KEYS }
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "rustls")] {
|
||||
use webpki;
|
||||
use untrusted;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::process::Command;
|
||||
use std::sync::Once;
|
||||
|
||||
use untrusted::Input;
|
||||
use webpki::trust_anchor_util;
|
||||
|
||||
fn server_cx() -> io::Result<ServerContext> {
|
||||
let mut cx = ServerContext::new();
|
||||
|
||||
let (cert, key) = keys();
|
||||
cx.config_mut()
|
||||
.set_single_cert(vec![cert.to_vec()], key.to_vec());
|
||||
|
||||
Ok(cx)
|
||||
}
|
||||
|
||||
fn configure_client(cx: &mut ClientContext) {
|
||||
let (cert, _key) = keys();
|
||||
let cert = Input::from(cert);
|
||||
let anchor = trust_anchor_util::cert_der_as_trust_anchor(cert).unwrap();
|
||||
cx.config_mut().root_store.add_trust_anchors(&[anchor]);
|
||||
}
|
||||
|
||||
// Like OpenSSL we generate certificates on the fly, but for OSX we
|
||||
// also have to put them into a specific keychain. We put both the
|
||||
// certificates and the keychain next to our binary.
|
||||
//
|
||||
// Right now I don't know of a way to programmatically create a
|
||||
// self-signed certificate, so we just fork out to the `openssl` binary.
|
||||
fn keys() -> (&'static [u8], &'static [u8]) {
|
||||
static INIT: Once = Once::new();
|
||||
static mut KEYS: *mut (Vec<u8>, Vec<u8>) = ptr::null_mut();
|
||||
|
||||
INIT.call_once(|| {
|
||||
let (key, cert) = openssl_keys();
|
||||
let path = t!(env::current_exe());
|
||||
let path = path.parent().unwrap();
|
||||
let keyfile = path.join("test.key");
|
||||
let certfile = path.join("test.crt");
|
||||
let config = path.join("openssl.config");
|
||||
|
||||
File::create(&config).unwrap().write_all(b"\
|
||||
[req]\n\
|
||||
distinguished_name=dn\n\
|
||||
[ dn ]\n\
|
||||
CN=localhost\n\
|
||||
[ ext ]\n\
|
||||
basicConstraints=CA:FALSE,pathlen:0\n\
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
").unwrap();
|
||||
|
||||
let subj = "/C=US/ST=Denial/L=Sprintfield/O=Dis/CN=localhost";
|
||||
let output = t!(Command::new("openssl")
|
||||
.arg("req")
|
||||
.arg("-nodes")
|
||||
.arg("-x509")
|
||||
.arg("-newkey").arg("rsa:2048")
|
||||
.arg("-config").arg(&config)
|
||||
.arg("-extensions").arg("ext")
|
||||
.arg("-subj").arg(subj)
|
||||
.arg("-keyout").arg(&keyfile)
|
||||
.arg("-out").arg(&certfile)
|
||||
.arg("-days").arg("1")
|
||||
.output());
|
||||
assert!(output.status.success());
|
||||
|
||||
let crtout = t!(Command::new("openssl")
|
||||
.arg("x509")
|
||||
.arg("-outform").arg("der")
|
||||
.arg("-in").arg(&certfile)
|
||||
.output());
|
||||
assert!(crtout.status.success());
|
||||
let keyout = t!(Command::new("openssl")
|
||||
.arg("rsa")
|
||||
.arg("-outform").arg("der")
|
||||
.arg("-in").arg(&keyfile)
|
||||
.output());
|
||||
assert!(keyout.status.success());
|
||||
|
||||
let cert = crtout.stdout;
|
||||
let key = keyout.stdout;
|
||||
unsafe {
|
||||
KEYS = Box::into_raw(Box::new((cert, key)));
|
||||
}
|
||||
});
|
||||
unsafe {
|
||||
(&(*KEYS).0, &(*KEYS).1)
|
||||
}
|
||||
}
|
||||
} else if #[cfg(any(feature = "force-openssl",
|
||||
all(not(target_os = "macos"),
|
||||
not(target_os = "windows"),
|
||||
not(target_os = "ios"))))] {
|
||||
use std::fs::File;
|
||||
use std::env;
|
||||
use std::sync::Once;
|
||||
|
||||
fn contexts() -> (tokio_tls::TlsAcceptor, tokio_tls::TlsConnector) {
|
||||
let keys = openssl_keys();
|
||||
|
||||
let pkcs12 = t!(Identity::from_pkcs12(&keys.pkcs12_der, "foobar"));
|
||||
let srv = TlsAcceptor::builder(pkcs12);
|
||||
|
||||
let cert = t!(native_tls::Certificate::from_der(&keys.cert_der));
|
||||
|
||||
let mut client = TlsConnector::builder();
|
||||
t!(client.add_root_certificate(cert).build());
|
||||
|
||||
(t!(srv.build()).into(), t!(client.build()).into())
|
||||
}
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "ios"))] {
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::sync::Once;
|
||||
|
||||
fn contexts() -> (tokio_tls::TlsAcceptor, tokio_tls::TlsConnector) {
|
||||
let keys = openssl_keys();
|
||||
|
||||
let pkcs12 = t!(Identity::from_pkcs12(&keys.pkcs12_der, "foobar"));
|
||||
let srv = TlsAcceptor::builder(pkcs12);
|
||||
|
||||
let cert = native_tls::Certificate::from_der(&keys.cert_der).unwrap();
|
||||
let mut client = TlsConnector::builder();
|
||||
client.add_root_certificate(cert);
|
||||
|
||||
(t!(srv.build()).into(), t!(client.build()).into())
|
||||
}
|
||||
} else {
|
||||
use schannel;
|
||||
use winapi;
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::sync::Once;
|
||||
|
||||
use schannel::cert_context::CertContext;
|
||||
use schannel::cert_store::{CertStore, CertAdd, Memory};
|
||||
use winapi::shared::basetsd::*;
|
||||
use winapi::shared::lmcons::*;
|
||||
use winapi::shared::minwindef::*;
|
||||
use winapi::shared::ntdef::WCHAR;
|
||||
use winapi::um::minwinbase::*;
|
||||
use winapi::um::sysinfoapi::*;
|
||||
use winapi::um::timezoneapi::*;
|
||||
use winapi::um::wincrypt::*;
|
||||
|
||||
const FRIENDLY_NAME: &str = "tokio-tls localhost testing cert";
|
||||
|
||||
fn contexts() -> (tokio_tls::TlsAcceptor, tokio_tls::TlsConnector) {
|
||||
let cert = localhost_cert();
|
||||
let mut store = t!(Memory::new()).into_store();
|
||||
t!(store.add_cert(&cert, CertAdd::Always));
|
||||
let pkcs12_der = t!(store.export_pkcs12("foobar"));
|
||||
let pkcs12 = t!(Identity::from_pkcs12(&pkcs12_der, "foobar"));
|
||||
|
||||
let srv = TlsAcceptor::builder(pkcs12);
|
||||
let client = TlsConnector::builder();
|
||||
(t!(srv.build()).into(), t!(client.build()).into())
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Magic!
|
||||
//
|
||||
// Lots of magic is happening here to wrangle certificates for running
|
||||
// these tests on Windows. For more information see the test suite
|
||||
// in the schannel-rs crate as this is just coyping that.
|
||||
//
|
||||
// The general gist of this though is that the only way to add custom
|
||||
// trusted certificates is to add it to the system store of trust. To
|
||||
// do that we go through the whole rigamarole here to generate a new
|
||||
// self-signed certificate and then insert that into the system store.
|
||||
//
|
||||
// This generates some dialogs, so we print what we're doing sometimes,
|
||||
// and otherwise we just manage the ephemeral certificates. Because
|
||||
// they're in the system store we always ensure that they're only valid
|
||||
// for a small period of time (e.g. 1 day).
|
||||
|
||||
fn localhost_cert() -> CertContext {
|
||||
static INIT: Once = Once::new();
|
||||
INIT.call_once(|| {
|
||||
for cert in local_root_store().certs() {
|
||||
let name = match cert.friendly_name() {
|
||||
Ok(name) => name,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if name != FRIENDLY_NAME {
|
||||
continue
|
||||
}
|
||||
if !cert.is_time_valid().unwrap() {
|
||||
io::stdout().write_all(br#"
|
||||
|
||||
The tokio-tls test suite is about to delete an old copy of one of its
|
||||
certificates from your root trust store. This certificate was only valid for one
|
||||
day and it is no longer needed. The host should be "localhost" and the
|
||||
description should mention "tokio-tls".
|
||||
|
||||
"#).unwrap();
|
||||
cert.delete().unwrap();
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
install_certificate().unwrap();
|
||||
});
|
||||
|
||||
for cert in local_root_store().certs() {
|
||||
let name = match cert.friendly_name() {
|
||||
Ok(name) => name,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if name == FRIENDLY_NAME {
|
||||
return cert
|
||||
}
|
||||
}
|
||||
|
||||
panic!("couldn't find a cert");
|
||||
}
|
||||
|
||||
fn local_root_store() -> CertStore {
|
||||
if env::var("CI").is_ok() {
|
||||
CertStore::open_local_machine("Root").unwrap()
|
||||
} else {
|
||||
CertStore::open_current_user("Root").unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn install_certificate() -> io::Result<CertContext> {
|
||||
unsafe {
|
||||
let mut provider = 0;
|
||||
let mut hkey = 0;
|
||||
|
||||
let mut buffer = "tokio-tls test suite".encode_utf16()
|
||||
.chain(Some(0))
|
||||
.collect::<Vec<_>>();
|
||||
let res = CryptAcquireContextW(&mut provider,
|
||||
buffer.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
PROV_RSA_FULL,
|
||||
CRYPT_MACHINE_KEYSET);
|
||||
if res != TRUE {
|
||||
// create a new key container (since it does not exist)
|
||||
let res = CryptAcquireContextW(&mut provider,
|
||||
buffer.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
PROV_RSA_FULL,
|
||||
CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET);
|
||||
if res != TRUE {
|
||||
return Err(Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
// create a new keypair (RSA-2048)
|
||||
let res = CryptGenKey(provider,
|
||||
AT_SIGNATURE,
|
||||
0x0800<<16 | CRYPT_EXPORTABLE,
|
||||
&mut hkey);
|
||||
if res != TRUE {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
|
||||
// start creating the certificate
|
||||
let name = "CN=localhost,O=tokio-tls,OU=tokio-tls,\
|
||||
G=tokio_tls".encode_utf16()
|
||||
.chain(Some(0))
|
||||
.collect::<Vec<_>>();
|
||||
let mut cname_buffer: [WCHAR; UNLEN as usize + 1] = mem::zeroed();
|
||||
let mut cname_len = cname_buffer.len() as DWORD;
|
||||
let res = CertStrToNameW(X509_ASN_ENCODING,
|
||||
name.as_ptr(),
|
||||
CERT_X500_NAME_STR,
|
||||
ptr::null_mut(),
|
||||
cname_buffer.as_mut_ptr() as *mut u8,
|
||||
&mut cname_len,
|
||||
ptr::null_mut());
|
||||
if res != TRUE {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
|
||||
let mut subject_issuer = CERT_NAME_BLOB {
|
||||
cbData: cname_len,
|
||||
pbData: cname_buffer.as_ptr() as *mut u8,
|
||||
};
|
||||
let mut key_provider = CRYPT_KEY_PROV_INFO {
|
||||
pwszContainerName: buffer.as_mut_ptr(),
|
||||
pwszProvName: ptr::null_mut(),
|
||||
dwProvType: PROV_RSA_FULL,
|
||||
dwFlags: CRYPT_MACHINE_KEYSET,
|
||||
cProvParam: 0,
|
||||
rgProvParam: ptr::null_mut(),
|
||||
dwKeySpec: AT_SIGNATURE,
|
||||
};
|
||||
let mut sig_algorithm = CRYPT_ALGORITHM_IDENTIFIER {
|
||||
pszObjId: szOID_RSA_SHA256RSA.as_ptr() as *mut _,
|
||||
Parameters: mem::zeroed(),
|
||||
};
|
||||
let mut expiration_date: SYSTEMTIME = mem::zeroed();
|
||||
GetSystemTime(&mut expiration_date);
|
||||
let mut file_time: FILETIME = mem::zeroed();
|
||||
let res = SystemTimeToFileTime(&expiration_date,
|
||||
&mut file_time);
|
||||
if res != TRUE {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
let mut timestamp: u64 = file_time.dwLowDateTime as u64 |
|
||||
(file_time.dwHighDateTime as u64) << 32;
|
||||
// one day, timestamp unit is in 100 nanosecond intervals
|
||||
timestamp += (1E9 as u64) / 100 * (60 * 60 * 24);
|
||||
file_time.dwLowDateTime = timestamp as u32;
|
||||
file_time.dwHighDateTime = (timestamp >> 32) as u32;
|
||||
let res = FileTimeToSystemTime(&file_time,
|
||||
&mut expiration_date);
|
||||
if res != TRUE {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
|
||||
// create a self signed certificate
|
||||
let cert_context = CertCreateSelfSignCertificate(
|
||||
0 as ULONG_PTR,
|
||||
&mut subject_issuer,
|
||||
0,
|
||||
&mut key_provider,
|
||||
&mut sig_algorithm,
|
||||
ptr::null_mut(),
|
||||
&mut expiration_date,
|
||||
ptr::null_mut());
|
||||
if cert_context.is_null() {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
|
||||
// TODO: this is.. a terrible hack. Right now `schannel`
|
||||
// doesn't provide a public method to go from a raw
|
||||
// cert context pointer to the `CertContext` structure it
|
||||
// has, so we just fake it here with a transmute. This'll
|
||||
// probably break at some point, but hopefully by then
|
||||
// it'll have a method to do this!
|
||||
struct MyCertContext<T>(T);
|
||||
impl<T> Drop for MyCertContext<T> {
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
let cert_context = MyCertContext(cert_context);
|
||||
let cert_context: CertContext = mem::transmute(cert_context);
|
||||
|
||||
cert_context.set_friendly_name(FRIENDLY_NAME)?;
|
||||
|
||||
// install the certificate to the machine's local store
|
||||
io::stdout().write_all(br#"
|
||||
|
||||
The tokio-tls test suite is about to add a certificate to your set of root
|
||||
and trusted certificates. This certificate should be for the domain "localhost"
|
||||
with the description related to "tokio-tls". This certificate is only valid
|
||||
for one day and will be automatically deleted if you re-run the tokio-tls
|
||||
test suite later.
|
||||
|
||||
"#).unwrap();
|
||||
local_root_store().add_cert(&cert_context,
|
||||
CertAdd::ReplaceExisting)?;
|
||||
Ok(cert_context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const AMT: usize = 128 * 1024;
|
||||
|
||||
async fn copy_data<W: AsyncWrite + Unpin>(mut w: W) -> Result<usize, Error> {
|
||||
let mut data = vec![9; AMT as usize];
|
||||
let mut amt = 0;
|
||||
while !data.is_empty() {
|
||||
let written = w.write(&data).await?;
|
||||
if written <= data.len() {
|
||||
amt += written;
|
||||
data.resize(data.len() - written, 0);
|
||||
} else {
|
||||
w.write_all(&data).await?;
|
||||
amt += data.len();
|
||||
break;
|
||||
}
|
||||
|
||||
println!("remaining: {}", data.len());
|
||||
}
|
||||
Ok(amt)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_to_server() {
|
||||
drop(env_logger::try_init());
|
||||
|
||||
// Create a server listening on a port, then figure out what that port is
|
||||
let mut srv = t!(TcpListener::bind("127.0.0.1:0").await);
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let (server_cx, client_cx) = contexts();
|
||||
|
||||
// Create a future to accept one socket, connect the ssl stream, and then
|
||||
// read all the data from it.
|
||||
let server = async move {
|
||||
let mut incoming = srv.incoming();
|
||||
let socket = t!(incoming.next().await.unwrap());
|
||||
let mut socket = t!(server_cx.accept(socket).await);
|
||||
let mut data = Vec::new();
|
||||
t!(socket.read_to_end(&mut data).await);
|
||||
data
|
||||
};
|
||||
|
||||
// Create a future to connect to our server, connect the ssl stream, and
|
||||
// then write a bunch of data to it.
|
||||
let client = async move {
|
||||
let socket = t!(TcpStream::connect(&addr).await);
|
||||
let socket = t!(client_cx.connect("localhost", socket).await);
|
||||
copy_data(socket).await
|
||||
};
|
||||
|
||||
// Finally, run everything!
|
||||
let (data, _) = join!(server, client);
|
||||
// assert_eq!(amt, AMT);
|
||||
assert!(data == vec![9; AMT]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn server_to_client() {
|
||||
drop(env_logger::try_init());
|
||||
|
||||
// Create a server listening on a port, then figure out what that port is
|
||||
let mut srv = t!(TcpListener::bind("127.0.0.1:0").await);
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let (server_cx, client_cx) = contexts();
|
||||
|
||||
let server = async move {
|
||||
let mut incoming = srv.incoming();
|
||||
let socket = t!(incoming.next().await.unwrap());
|
||||
let socket = t!(server_cx.accept(socket).await);
|
||||
copy_data(socket).await
|
||||
};
|
||||
|
||||
let client = async move {
|
||||
let socket = t!(TcpStream::connect(&addr).await);
|
||||
let mut socket = t!(client_cx.connect("localhost", socket).await);
|
||||
let mut data = Vec::new();
|
||||
t!(socket.read_to_end(&mut data).await);
|
||||
data
|
||||
};
|
||||
|
||||
// Finally, run everything!
|
||||
let (_, data) = join!(server, client);
|
||||
// assert_eq!(amt, AMT);
|
||||
assert!(data == vec![9; AMT]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn one_byte_at_a_time() {
|
||||
const AMT: usize = 1024;
|
||||
drop(env_logger::try_init());
|
||||
|
||||
let mut srv = t!(TcpListener::bind("127.0.0.1:0").await);
|
||||
let addr = t!(srv.local_addr());
|
||||
|
||||
let (server_cx, client_cx) = contexts();
|
||||
|
||||
let server = async move {
|
||||
let mut incoming = srv.incoming();
|
||||
let socket = t!(incoming.next().await.unwrap());
|
||||
let mut socket = t!(server_cx.accept(socket).await);
|
||||
let mut amt = 0;
|
||||
for b in std::iter::repeat(9).take(AMT) {
|
||||
let data = [b as u8];
|
||||
t!(socket.write_all(&data).await);
|
||||
amt += 1;
|
||||
}
|
||||
amt
|
||||
};
|
||||
|
||||
let client = async move {
|
||||
let socket = t!(TcpStream::connect(&addr).await);
|
||||
let mut socket = t!(client_cx.connect("localhost", socket).await);
|
||||
let mut data = Vec::new();
|
||||
loop {
|
||||
let mut buf = [0; 1];
|
||||
match socket.read_exact(&mut buf).await {
|
||||
Ok(_) => data.extend_from_slice(&buf),
|
||||
Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => break,
|
||||
Err(err) => panic!(err),
|
||||
}
|
||||
}
|
||||
data
|
||||
};
|
||||
|
||||
let (amt, data) = join!(server, client);
|
||||
assert_eq!(amt, AMT);
|
||||
assert!(data == vec![9; AMT as usize]);
|
||||
}
|
||||
@@ -1,3 +1,52 @@
|
||||
# 0.5.1 (December 3, 2020)
|
||||
|
||||
### Added
|
||||
- io: `poll_read_buf` util fn (#2972).
|
||||
- io: `poll_write_buf` util fn with vectored write support (#3156).
|
||||
|
||||
# 0.5.0 (October 30, 2020)
|
||||
|
||||
### Changed
|
||||
- io: update `bytes` to 0.6 (#3071).
|
||||
|
||||
# 0.4.0 (October 15, 2020)
|
||||
|
||||
### Added
|
||||
- sync: `CancellationToken` for coordinating task cancellation (#2747).
|
||||
- rt: `TokioContext` sets the Tokio runtime for the duration of a future (#2791)
|
||||
- io: `StreamReader`/`ReaderStream` map between `AsyncRead` values and `Stream`
|
||||
of bytes (#2788).
|
||||
- time: `DelayQueue` to manage many delays (#2897).
|
||||
|
||||
# 0.3.1 (March 18, 2020)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Adjust minimum-supported Tokio version to v0.2.5 to account for an internal
|
||||
dependency on features in that version of Tokio. ([#2326])
|
||||
|
||||
# 0.3.0 (March 4, 2020)
|
||||
|
||||
### Changed
|
||||
|
||||
- **Breaking Change**: Change `Encoder` trait to take a generic `Item` parameter, which allows
|
||||
codec writers to pass references into `Framed` and `FramedWrite` types. ([#1746])
|
||||
|
||||
### Added
|
||||
|
||||
- Add futures-io/tokio::io compatibility layer. ([#2117])
|
||||
- Add `Framed::with_capacity`. ([#2215])
|
||||
|
||||
### Fixed
|
||||
|
||||
- Use advance over split_to when data is not needed. ([#2198])
|
||||
|
||||
# 0.2.0 (November 26, 2019)
|
||||
|
||||
- Initial release
|
||||
|
||||
[#2326]: https://github.com/tokio-rs/tokio/pull/2326
|
||||
[#2215]: https://github.com/tokio-rs/tokio/pull/2215
|
||||
[#2198]: https://github.com/tokio-rs/tokio/pull/2198
|
||||
[#2117]: https://github.com/tokio-rs/tokio/pull/2117
|
||||
[#1746]: https://github.com/tokio-rs/tokio/pull/1746
|
||||
|
||||
+14
-9
@@ -7,13 +7,13 @@ name = "tokio-util"
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0"
|
||||
version = "0.5.1"
|
||||
edition = "2018"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-util/0.2.0/tokio_util"
|
||||
documentation = "https://docs.rs/tokio-util/0.5.1/tokio_util"
|
||||
description = """
|
||||
Additional utilities for working with Tokio.
|
||||
"""
|
||||
@@ -24,27 +24,32 @@ categories = ["asynchronous"]
|
||||
default = []
|
||||
|
||||
# Shorthand for enabling everything
|
||||
full = ["codec", "udp", "compat"]
|
||||
full = ["codec", "compat", "io", "time", "net", "rt"]
|
||||
|
||||
net = ["tokio/net"]
|
||||
compat = ["futures-io",]
|
||||
codec = ["tokio/stream"]
|
||||
udp = ["tokio/udp"]
|
||||
time = ["tokio/time","slab"]
|
||||
io = []
|
||||
rt = ["tokio/rt"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio" }
|
||||
tokio = { version = "0.3.4", path = "../tokio" }
|
||||
|
||||
bytes = "0.5.0"
|
||||
bytes = "0.6.0"
|
||||
futures-core = "0.3.0"
|
||||
futures-sink = "0.3.0"
|
||||
futures-io = { version = "0.3.0", optional = true }
|
||||
log = "0.4"
|
||||
pin-project-lite = "0.1.1"
|
||||
pin-project-lite = "0.2.0"
|
||||
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.2.0", path = "../tokio-test" }
|
||||
tokio = { version = "0.3.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.3.0", path = "../tokio-test" }
|
||||
|
||||
futures = "0.3.0"
|
||||
futures-test = "0.3.5"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
+23
-3
@@ -18,11 +18,31 @@ macro_rules! cfg_compat {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_udp {
|
||||
macro_rules! cfg_net {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(all(feature = "udp", feature = "codec"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "udp", feature = "codec"))))]
|
||||
#[cfg(all(feature = "net", feature = "codec"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "net", feature = "codec"))))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_io {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "io")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "io")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! cfg_rt {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(feature = "rt")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
|
||||
$item
|
||||
)*
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ use std::io;
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # #[tokio::main(core_threads = 1)]
|
||||
/// # #[tokio::main(flavor = "current_thread")]
|
||||
/// # async fn main() -> Result<(), std::io::Error> {
|
||||
/// let my_async_read = File::open("filename.txt").await?;
|
||||
/// let my_stream_of_bytes = FramedRead::new(my_async_read, BytesCodec::new());
|
||||
@@ -65,8 +65,7 @@ impl Decoder for BytesCodec {
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for BytesCodec {
|
||||
type Item = Bytes;
|
||||
impl Encoder<Bytes> for BytesCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, data: Bytes, buf: &mut BytesMut) -> Result<(), io::Error> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::codec::encoder::Encoder;
|
||||
use crate::codec::Framed;
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
@@ -159,7 +158,7 @@ pub trait Decoder {
|
||||
/// [`Framed`]: crate::codec::Framed
|
||||
fn framed<T: AsyncRead + AsyncWrite + Sized>(self, io: T) -> Framed<T, Self>
|
||||
where
|
||||
Self: Encoder + Sized,
|
||||
Self: Sized,
|
||||
{
|
||||
Framed::new(io, self)
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@ use std::io;
|
||||
/// [`FramedWrite`].
|
||||
///
|
||||
/// [`FramedWrite`]: crate::codec::FramedWrite
|
||||
pub trait Encoder {
|
||||
/// The type of items consumed by the `Encoder`
|
||||
type Item;
|
||||
|
||||
pub trait Encoder<Item> {
|
||||
/// The type of encoding errors.
|
||||
///
|
||||
/// [`FramedWrite`] requires `Encoder`s errors to implement `From<io::Error>`
|
||||
@@ -24,5 +21,5 @@ pub trait Encoder {
|
||||
/// will be written out when possible.
|
||||
///
|
||||
/// [`FramedWrite`]: crate::codec::FramedWrite
|
||||
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error>;
|
||||
fn encode(&mut self, item: Item, dst: &mut BytesMut) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
+88
-162
@@ -1,10 +1,9 @@
|
||||
use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
use crate::codec::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2};
|
||||
use crate::codec::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2};
|
||||
use crate::codec::framed_impl::{FramedImpl, RWFrames, ReadFrame, WriteFrame};
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncBufRead, AsyncRead, AsyncWrite},
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
|
||||
@@ -12,8 +11,7 @@ use bytes::BytesMut;
|
||||
use futures_sink::Sink;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
use std::io::{self, BufRead, Read, Write};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
@@ -30,44 +28,13 @@ pin_project! {
|
||||
/// [`Decoder::framed`]: crate::codec::Decoder::framed()
|
||||
pub struct Framed<T, U> {
|
||||
#[pin]
|
||||
inner: FramedRead2<FramedWrite2<Fuse<T, U>>>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct Fuse<T, U> {
|
||||
#[pin]
|
||||
pub(crate) io: T,
|
||||
pub(crate) codec: U,
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstracts over `FramedRead2` being either `FramedRead2<FramedWrite2<Fuse<T, U>>>` or
|
||||
/// `FramedRead2<Fuse<T, U>>` and lets the io and codec parts be extracted in either case.
|
||||
pub(crate) trait ProjectFuse {
|
||||
type Io;
|
||||
type Codec;
|
||||
|
||||
fn project(self: Pin<&mut Self>) -> Fuse<Pin<&mut Self::Io>, &mut Self::Codec>;
|
||||
}
|
||||
|
||||
impl<T, U> ProjectFuse for Fuse<T, U> {
|
||||
type Io = T;
|
||||
type Codec = U;
|
||||
|
||||
fn project(self: Pin<&mut Self>) -> Fuse<Pin<&mut Self::Io>, &mut Self::Codec> {
|
||||
let self_ = self.project();
|
||||
Fuse {
|
||||
io: self_.io,
|
||||
codec: self_.codec,
|
||||
}
|
||||
inner: FramedImpl<T, U, RWFrames>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Framed<T, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
U: Decoder + Encoder,
|
||||
{
|
||||
/// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this
|
||||
/// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data.
|
||||
@@ -94,7 +61,52 @@ where
|
||||
/// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
|
||||
pub fn new(inner: T, codec: U) -> Framed<T, U> {
|
||||
Framed {
|
||||
inner: framed_read2(framed_write2(Fuse { io: inner, codec })),
|
||||
inner: FramedImpl {
|
||||
inner,
|
||||
codec,
|
||||
state: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this
|
||||
/// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data,
|
||||
/// with a specific read buffer initial capacity.
|
||||
///
|
||||
/// Raw I/O objects work with byte sequences, but higher-level code usually
|
||||
/// wants to batch these into meaningful chunks, called "frames". This
|
||||
/// method layers framing on top of an I/O object, by using the codec
|
||||
/// traits to handle encoding and decoding of messages frames. Note that
|
||||
/// the incoming and outgoing frame types may be distinct.
|
||||
///
|
||||
/// This function returns a *single* object that is both [`Stream`] and
|
||||
/// [`Sink`]; grouping this into a single object is often useful for layering
|
||||
/// things like gzip or TLS, which require both read and write access to the
|
||||
/// underlying object.
|
||||
///
|
||||
/// If you want to work more directly with the streams and sink, consider
|
||||
/// calling [`split`] on the `Framed` returned by this method, which will
|
||||
/// break them into separate objects, allowing them to interact more easily.
|
||||
///
|
||||
/// [`Stream`]: tokio::stream::Stream
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
/// [`Decode`]: crate::codec::Decoder
|
||||
/// [`Encoder`]: crate::codec::Encoder
|
||||
/// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
|
||||
pub fn with_capacity(inner: T, codec: U, capacity: usize) -> Framed<T, U> {
|
||||
Framed {
|
||||
inner: FramedImpl {
|
||||
inner,
|
||||
codec,
|
||||
state: RWFrames {
|
||||
read: ReadFrame {
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(capacity),
|
||||
},
|
||||
write: WriteFrame::default(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,16 +141,14 @@ impl<T, U> Framed<T, U> {
|
||||
/// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
|
||||
pub fn from_parts(parts: FramedParts<T, U>) -> Framed<T, U> {
|
||||
Framed {
|
||||
inner: framed_read2_with_buffer(
|
||||
framed_write2_with_buffer(
|
||||
Fuse {
|
||||
io: parts.io,
|
||||
codec: parts.codec,
|
||||
},
|
||||
parts.write_buf,
|
||||
),
|
||||
parts.read_buf,
|
||||
),
|
||||
inner: FramedImpl {
|
||||
inner: parts.io,
|
||||
codec: parts.codec,
|
||||
state: RWFrames {
|
||||
read: parts.read_buf.into(),
|
||||
write: parts.write_buf.into(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +159,7 @@ impl<T, U> Framed<T, U> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner.get_ref().get_ref().io
|
||||
&self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
@@ -159,7 +169,7 @@ impl<T, U> Framed<T, U> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.get_mut().get_mut().io
|
||||
&mut self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying codec wrapped by
|
||||
@@ -168,7 +178,7 @@ impl<T, U> Framed<T, U> {
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec(&self) -> &U {
|
||||
&self.inner.get_ref().get_ref().codec
|
||||
&self.inner.codec
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying codec wrapped by
|
||||
@@ -177,12 +187,17 @@ impl<T, U> Framed<T, U> {
|
||||
/// Note that care should be taken to not tamper with the underlying codec
|
||||
/// as it may corrupt the stream of frames otherwise being worked with.
|
||||
pub fn codec_mut(&mut self) -> &mut U {
|
||||
&mut self.inner.get_mut().get_mut().codec
|
||||
&mut self.inner.codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the read buffer.
|
||||
pub fn read_buffer(&self) -> &BytesMut {
|
||||
self.inner.buffer()
|
||||
&self.inner.state.read.buffer
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the read buffer.
|
||||
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.inner.state.read.buffer
|
||||
}
|
||||
|
||||
/// Consumes the `Framed`, returning its underlying I/O stream.
|
||||
@@ -191,7 +206,7 @@ impl<T, U> Framed<T, U> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.into_inner().into_inner().io
|
||||
self.inner.inner
|
||||
}
|
||||
|
||||
/// Consumes the `Framed`, returning its underlying I/O stream, the buffer
|
||||
@@ -201,19 +216,17 @@ impl<T, U> Framed<T, U> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_parts(self) -> FramedParts<T, U> {
|
||||
let (inner, read_buf) = self.inner.into_parts();
|
||||
let (inner, write_buf) = inner.into_parts();
|
||||
|
||||
FramedParts {
|
||||
io: inner.io,
|
||||
codec: inner.codec,
|
||||
read_buf,
|
||||
write_buf,
|
||||
io: self.inner.inner,
|
||||
codec: self.inner.codec,
|
||||
read_buf: self.inner.state.read.buffer,
|
||||
write_buf: self.inner.state.write.buffer,
|
||||
_priv: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
impl<T, U> Stream for Framed<T, U>
|
||||
where
|
||||
T: AsyncRead,
|
||||
@@ -226,28 +239,29 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
impl<T, I, U> Sink<I> for Framed<T, U>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
U: Encoder<Item = I>,
|
||||
U: Encoder<I>,
|
||||
U::Error: From<io::Error>,
|
||||
{
|
||||
type Error = U::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.get_pin_mut().poll_ready(cx)
|
||||
self.project().inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
self.project().inner.get_pin_mut().start_send(item)
|
||||
self.project().inner.start_send(item)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.get_pin_mut().poll_flush(cx)
|
||||
self.project().inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project().inner.get_pin_mut().poll_close(cx)
|
||||
self.project().inner.poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,110 +272,19 @@ where
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Framed")
|
||||
.field("io", &self.inner.get_ref().get_ref().io)
|
||||
.field("codec", &self.inner.get_ref().get_ref().codec)
|
||||
.field("io", self.get_ref())
|
||||
.field("codec", self.codec())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Fuse =====
|
||||
|
||||
impl<T: Read, U> Read for Fuse<T, U> {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
self.io.read(dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BufRead, U> BufRead for Fuse<T, U> {
|
||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||
self.io.fill_buf()
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.io.consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead, U> AsyncRead for Fuse<T, U> {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [MaybeUninit<u8>]) -> bool {
|
||||
self.io.prepare_uninitialized_buffer(buf)
|
||||
}
|
||||
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
self.project().io.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncBufRead, U> AsyncBufRead for Fuse<T, U> {
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
self.project().io.poll_fill_buf(cx)
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
self.project().io.consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write, U> Write for Fuse<T, U> {
|
||||
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
|
||||
self.io.write(src)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.io.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite, U> AsyncWrite for Fuse<T, U> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
self.project().io.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
self.project().io.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
self.project().io.poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: Decoder> Decoder for Fuse<T, U> {
|
||||
type Item = U::Item;
|
||||
type Error = U::Error;
|
||||
|
||||
fn decode(&mut self, buffer: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
self.codec.decode(buffer)
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, buffer: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
self.codec.decode_eof(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: Encoder> Encoder for Fuse<T, U> {
|
||||
type Item = U::Item;
|
||||
type Error = U::Error;
|
||||
|
||||
fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> {
|
||||
self.codec.encode(item, dst)
|
||||
}
|
||||
}
|
||||
|
||||
/// `FramedParts` contains an export of the data of a Framed transport.
|
||||
/// It can be used to construct a new [`Framed`] with a different codec.
|
||||
/// It contains all current buffers and the inner transport.
|
||||
///
|
||||
/// [`Framed`]: crate::codec::Framed
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::manual_non_exhaustive)]
|
||||
pub struct FramedParts<T, U> {
|
||||
/// The inner transport used to read bytes to and write bytes to
|
||||
pub io: T,
|
||||
@@ -381,8 +304,11 @@ pub struct FramedParts<T, U> {
|
||||
}
|
||||
|
||||
impl<T, U> FramedParts<T, U> {
|
||||
/// Create a new, default, `FramedParts`.
|
||||
pub fn new(io: T, codec: U) -> FramedParts<T, U> {
|
||||
/// Create a new, default, `FramedParts`
|
||||
pub fn new<I>(io: T, codec: U) -> FramedParts<T, U>
|
||||
where
|
||||
U: Encoder<I>,
|
||||
{
|
||||
FramedParts {
|
||||
io,
|
||||
codec,
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FramedImpl<T, U, State> {
|
||||
#[pin]
|
||||
pub(crate) inner: T,
|
||||
pub(crate) state: State,
|
||||
pub(crate) codec: U,
|
||||
}
|
||||
}
|
||||
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY;
|
||||
|
||||
pub(crate) struct ReadFrame {
|
||||
pub(crate) eof: bool,
|
||||
pub(crate) is_readable: bool,
|
||||
pub(crate) buffer: BytesMut,
|
||||
}
|
||||
|
||||
pub(crate) struct WriteFrame {
|
||||
pub(crate) buffer: BytesMut,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct RWFrames {
|
||||
pub(crate) read: ReadFrame,
|
||||
pub(crate) write: WriteFrame,
|
||||
}
|
||||
|
||||
impl Default for ReadFrame {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WriteFrame {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesMut> for ReadFrame {
|
||||
fn from(mut buffer: BytesMut) -> Self {
|
||||
let size = buffer.capacity();
|
||||
if size < INITIAL_CAPACITY {
|
||||
buffer.reserve(INITIAL_CAPACITY - size);
|
||||
}
|
||||
|
||||
Self {
|
||||
buffer,
|
||||
is_readable: size > 0,
|
||||
eof: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesMut> for WriteFrame {
|
||||
fn from(mut buffer: BytesMut) -> Self {
|
||||
let size = buffer.capacity();
|
||||
if size < INITIAL_CAPACITY {
|
||||
buffer.reserve(INITIAL_CAPACITY - size);
|
||||
}
|
||||
|
||||
Self { buffer }
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<ReadFrame> for RWFrames {
|
||||
fn borrow(&self) -> &ReadFrame {
|
||||
&self.read
|
||||
}
|
||||
}
|
||||
impl BorrowMut<ReadFrame> for RWFrames {
|
||||
fn borrow_mut(&mut self) -> &mut ReadFrame {
|
||||
&mut self.read
|
||||
}
|
||||
}
|
||||
impl Borrow<WriteFrame> for RWFrames {
|
||||
fn borrow(&self) -> &WriteFrame {
|
||||
&self.write
|
||||
}
|
||||
}
|
||||
impl BorrowMut<WriteFrame> for RWFrames {
|
||||
fn borrow_mut(&mut self) -> &mut WriteFrame {
|
||||
&mut self.write
|
||||
}
|
||||
}
|
||||
impl<T, U, R> Stream for FramedImpl<T, U, R>
|
||||
where
|
||||
T: AsyncRead,
|
||||
U: Decoder,
|
||||
R: BorrowMut<ReadFrame>,
|
||||
{
|
||||
type Item = Result<U::Item, U::Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
use crate::util::poll_read_buf;
|
||||
|
||||
let mut pinned = self.project();
|
||||
let state: &mut ReadFrame = pinned.state.borrow_mut();
|
||||
loop {
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
||||
// "readable". Readable is defined as not having returned `None`. If
|
||||
// the upstream has returned EOF, and the decoder is no longer
|
||||
// readable, it can be assumed that the decoder will never become
|
||||
// readable again, at which point the stream is terminated.
|
||||
if state.is_readable {
|
||||
if state.eof {
|
||||
let frame = pinned.codec.decode_eof(&mut state.buffer)?;
|
||||
return Poll::Ready(frame.map(Ok));
|
||||
}
|
||||
|
||||
trace!("attempting to decode a frame");
|
||||
|
||||
if let Some(frame) = pinned.codec.decode(&mut state.buffer)? {
|
||||
trace!("frame decoded from buffer");
|
||||
return Poll::Ready(Some(Ok(frame)));
|
||||
}
|
||||
|
||||
state.is_readable = false;
|
||||
}
|
||||
|
||||
assert!(!state.eof);
|
||||
|
||||
// Otherwise, try to read more data and try again. Make sure we've
|
||||
// got room for at least one byte to read to ensure that we don't
|
||||
// get a spurious 0 that looks like EOF
|
||||
state.buffer.reserve(1);
|
||||
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer)? {
|
||||
Poll::Ready(ct) => ct,
|
||||
Poll::Pending => return Poll::Pending,
|
||||
};
|
||||
if bytect == 0 {
|
||||
state.eof = true;
|
||||
}
|
||||
|
||||
state.is_readable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I, U, W> Sink<I> for FramedImpl<T, U, W>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
U: Encoder<I>,
|
||||
U::Error: From<io::Error>,
|
||||
W: BorrowMut<WriteFrame>,
|
||||
{
|
||||
type Error = U::Error;
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if self.state.borrow().buffer.len() >= BACKPRESSURE_BOUNDARY {
|
||||
self.as_mut().poll_flush(cx)
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
let pinned = self.project();
|
||||
pinned
|
||||
.codec
|
||||
.encode(item, &mut pinned.state.borrow_mut().buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
use crate::util::poll_write_buf;
|
||||
trace!("flushing framed transport");
|
||||
let mut pinned = self.project();
|
||||
|
||||
while !pinned.state.borrow_mut().buffer.is_empty() {
|
||||
let WriteFrame { buffer } = pinned.state.borrow_mut();
|
||||
trace!("writing; remaining={}", buffer.len());
|
||||
|
||||
let n = ready!(poll_write_buf(pinned.inner.as_mut(), cx, buffer))?;
|
||||
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::WriteZero,
|
||||
"failed to \
|
||||
write frame to transport",
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
}
|
||||
|
||||
// Try flushing the underlying IO
|
||||
ready!(pinned.inner.poll_flush(cx))?;
|
||||
|
||||
trace!("framed transport flushed");
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(self.as_mut().poll_flush(cx))?;
|
||||
ready!(self.project().inner.poll_shutdown(cx))?;
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::codec::framed::{Fuse, ProjectFuse};
|
||||
use crate::codec::framed_impl::{FramedImpl, ReadFrame};
|
||||
use crate::codec::Decoder;
|
||||
|
||||
use tokio::{io::AsyncRead, stream::Stream};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
@@ -18,22 +17,10 @@ pin_project! {
|
||||
/// [`AsyncRead`]: tokio::io::AsyncRead
|
||||
pub struct FramedRead<T, D> {
|
||||
#[pin]
|
||||
inner: FramedRead2<Fuse<T, D>>,
|
||||
inner: FramedImpl<T, D, ReadFrame>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct FramedRead2<T> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
eof: bool,
|
||||
is_readable: bool,
|
||||
buffer: BytesMut,
|
||||
}
|
||||
}
|
||||
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
|
||||
// ===== impl FramedRead =====
|
||||
|
||||
impl<T, D> FramedRead<T, D>
|
||||
@@ -44,10 +31,27 @@ where
|
||||
/// Creates a new `FramedRead` with the given `decoder`.
|
||||
pub fn new(inner: T, decoder: D) -> FramedRead<T, D> {
|
||||
FramedRead {
|
||||
inner: framed_read2(Fuse {
|
||||
io: inner,
|
||||
inner: FramedImpl {
|
||||
inner,
|
||||
codec: decoder,
|
||||
}),
|
||||
state: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `FramedRead` with the given `decoder` and a buffer of `capacity`
|
||||
/// initial size.
|
||||
pub fn with_capacity(inner: T, decoder: D, capacity: usize) -> FramedRead<T, D> {
|
||||
FramedRead {
|
||||
inner: FramedImpl {
|
||||
inner,
|
||||
codec: decoder,
|
||||
state: ReadFrame {
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(capacity),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +64,7 @@ impl<T, D> FramedRead<T, D> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner.inner.io
|
||||
&self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
@@ -70,7 +74,7 @@ impl<T, D> FramedRead<T, D> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.inner.io
|
||||
&mut self.inner.inner
|
||||
}
|
||||
|
||||
/// Consumes the `FramedRead`, returning its underlying I/O stream.
|
||||
@@ -79,25 +83,31 @@ impl<T, D> FramedRead<T, D> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.inner.io
|
||||
self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying decoder.
|
||||
pub fn decoder(&self) -> &D {
|
||||
&self.inner.inner.codec
|
||||
&self.inner.codec
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying decoder.
|
||||
pub fn decoder_mut(&mut self) -> &mut D {
|
||||
&mut self.inner.inner.codec
|
||||
&mut self.inner.codec
|
||||
}
|
||||
|
||||
/// Returns a reference to the read buffer.
|
||||
pub fn read_buffer(&self) -> &BytesMut {
|
||||
&self.inner.buffer
|
||||
&self.inner.state.buffer
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the read buffer.
|
||||
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
|
||||
&mut self.inner.state.buffer
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
impl<T, D> Stream for FramedRead<T, D>
|
||||
where
|
||||
T: AsyncRead,
|
||||
@@ -118,43 +128,19 @@ where
|
||||
type Error = T::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project()
|
||||
.inner
|
||||
.project()
|
||||
.inner
|
||||
.project()
|
||||
.io
|
||||
.poll_ready(cx)
|
||||
self.project().inner.project().inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
self.project()
|
||||
.inner
|
||||
.project()
|
||||
.inner
|
||||
.project()
|
||||
.io
|
||||
.start_send(item)
|
||||
self.project().inner.project().inner.start_send(item)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project()
|
||||
.inner
|
||||
.project()
|
||||
.inner
|
||||
.project()
|
||||
.io
|
||||
.poll_flush(cx)
|
||||
self.project().inner.project().inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.project()
|
||||
.inner
|
||||
.project()
|
||||
.inner
|
||||
.project()
|
||||
.io
|
||||
.poll_close(cx)
|
||||
self.project().inner.project().inner.poll_close(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,126 +151,11 @@ where
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("FramedRead")
|
||||
.field("inner", &self.inner.inner.io)
|
||||
.field("decoder", &self.inner.inner.codec)
|
||||
.field("eof", &self.inner.eof)
|
||||
.field("is_readable", &self.inner.is_readable)
|
||||
.field("buffer", &self.inner.buffer)
|
||||
.field("inner", &self.get_ref())
|
||||
.field("decoder", &self.decoder())
|
||||
.field("eof", &self.inner.state.eof)
|
||||
.field("is_readable", &self.inner.state.is_readable)
|
||||
.field("buffer", &self.read_buffer())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FramedRead2 =====
|
||||
|
||||
pub(crate) fn framed_read2<T>(inner: T) -> FramedRead2<T> {
|
||||
FramedRead2 {
|
||||
inner,
|
||||
eof: false,
|
||||
is_readable: false,
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn framed_read2_with_buffer<T>(inner: T, mut buf: BytesMut) -> FramedRead2<T> {
|
||||
if buf.capacity() < INITIAL_CAPACITY {
|
||||
let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity();
|
||||
buf.reserve(bytes_to_reserve);
|
||||
}
|
||||
FramedRead2 {
|
||||
inner,
|
||||
eof: false,
|
||||
is_readable: !buf.is_empty(),
|
||||
buffer: buf,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FramedRead2<T> {
|
||||
pub(crate) fn get_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn into_parts(self) -> (T, BytesMut) {
|
||||
(self.inner, self.buffer)
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
|
||||
self.project().inner
|
||||
}
|
||||
|
||||
pub(crate) fn buffer(&self) -> &BytesMut {
|
||||
&self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Stream for FramedRead2<T>
|
||||
where
|
||||
T: ProjectFuse + AsyncRead,
|
||||
T::Codec: Decoder,
|
||||
{
|
||||
type Item = Result<<T::Codec as Decoder>::Item, <T::Codec as Decoder>::Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut pinned = self.project();
|
||||
loop {
|
||||
// Repeatedly call `decode` or `decode_eof` as long as it is
|
||||
// "readable". Readable is defined as not having returned `None`. If
|
||||
// the upstream has returned EOF, and the decoder is no longer
|
||||
// readable, it can be assumed that the decoder will never become
|
||||
// readable again, at which point the stream is terminated.
|
||||
if *pinned.is_readable {
|
||||
if *pinned.eof {
|
||||
let frame = pinned
|
||||
.inner
|
||||
.as_mut()
|
||||
.project()
|
||||
.codec
|
||||
.decode_eof(&mut pinned.buffer)?;
|
||||
return Poll::Ready(frame.map(Ok));
|
||||
}
|
||||
|
||||
trace!("attempting to decode a frame");
|
||||
|
||||
if let Some(frame) = pinned
|
||||
.inner
|
||||
.as_mut()
|
||||
.project()
|
||||
.codec
|
||||
.decode(&mut pinned.buffer)?
|
||||
{
|
||||
trace!("frame decoded from buffer");
|
||||
return Poll::Ready(Some(Ok(frame)));
|
||||
}
|
||||
|
||||
*pinned.is_readable = false;
|
||||
}
|
||||
|
||||
assert!(!*pinned.eof);
|
||||
|
||||
// Otherwise, try to read more data and try again. Make sure we've
|
||||
// got room for at least one byte to read to ensure that we don't
|
||||
// get a spurious 0 that looks like EOF
|
||||
pinned.buffer.reserve(1);
|
||||
let bytect = match pinned
|
||||
.inner
|
||||
.as_mut()
|
||||
.poll_read_buf(cx, &mut pinned.buffer)?
|
||||
{
|
||||
Poll::Ready(ct) => ct,
|
||||
Poll::Pending => return Poll::Pending,
|
||||
};
|
||||
if bytect == 0 {
|
||||
*pinned.eof = true;
|
||||
}
|
||||
|
||||
*pinned.is_readable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
use crate::codec::decoder::Decoder;
|
||||
use crate::codec::encoder::Encoder;
|
||||
use crate::codec::framed::{Fuse, ProjectFuse};
|
||||
use crate::codec::framed_impl::{FramedImpl, WriteFrame};
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncBufRead, AsyncRead, AsyncWrite},
|
||||
stream::Stream,
|
||||
};
|
||||
use tokio::{io::AsyncWrite, stream::Stream};
|
||||
|
||||
use bytes::{Buf, BytesMut};
|
||||
use futures_core::ready;
|
||||
use futures_sink::Sink;
|
||||
use log::trace;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::fmt;
|
||||
use std::io::{self, BufRead, Read};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
@@ -24,33 +16,22 @@ pin_project! {
|
||||
/// [`Sink`]: futures_sink::Sink
|
||||
pub struct FramedWrite<T, E> {
|
||||
#[pin]
|
||||
inner: FramedWrite2<Fuse<T, E>>,
|
||||
inner: FramedImpl<T, E, WriteFrame>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct FramedWrite2<T> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
buffer: BytesMut,
|
||||
}
|
||||
}
|
||||
|
||||
const INITIAL_CAPACITY: usize = 8 * 1024;
|
||||
const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY;
|
||||
|
||||
impl<T, E> FramedWrite<T, E>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
E: Encoder,
|
||||
{
|
||||
/// Creates a new `FramedWrite` with the given `encoder`.
|
||||
pub fn new(inner: T, encoder: E) -> FramedWrite<T, E> {
|
||||
FramedWrite {
|
||||
inner: framed_write2(Fuse {
|
||||
io: inner,
|
||||
inner: FramedImpl {
|
||||
inner,
|
||||
codec: encoder,
|
||||
}),
|
||||
state: WriteFrame::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +44,7 @@ impl<T, E> FramedWrite<T, E> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_ref(&self) -> &T {
|
||||
&self.inner.inner.io
|
||||
&self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying I/O stream wrapped by
|
||||
@@ -73,7 +54,7 @@ impl<T, E> FramedWrite<T, E> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner.inner.io
|
||||
&mut self.inner.inner
|
||||
}
|
||||
|
||||
/// Consumes the `FramedWrite`, returning its underlying I/O stream.
|
||||
@@ -82,25 +63,25 @@ impl<T, E> FramedWrite<T, E> {
|
||||
/// of data coming in as it may corrupt the stream of frames otherwise
|
||||
/// being worked with.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner.inner.io
|
||||
self.inner.inner
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying decoder.
|
||||
/// Returns a reference to the underlying encoder.
|
||||
pub fn encoder(&self) -> &E {
|
||||
&self.inner.inner.codec
|
||||
&self.inner.codec
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying decoder.
|
||||
/// Returns a mutable reference to the underlying encoder.
|
||||
pub fn encoder_mut(&mut self) -> &mut E {
|
||||
&mut self.inner.inner.codec
|
||||
&mut self.inner.codec
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying FramedWrite2
|
||||
// This impl just defers to the underlying FramedImpl
|
||||
impl<T, I, E> Sink<I> for FramedWrite<T, E>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
E: Encoder<Item = I>,
|
||||
E: Encoder<I>,
|
||||
E::Error: From<io::Error>,
|
||||
{
|
||||
type Error = E::Error;
|
||||
@@ -122,6 +103,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// This impl just defers to the underlying T: Stream
|
||||
impl<T, D> Stream for FramedWrite<T, D>
|
||||
where
|
||||
T: Stream,
|
||||
@@ -129,13 +111,7 @@ where
|
||||
type Item = T::Item;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.project()
|
||||
.inner
|
||||
.project()
|
||||
.inner
|
||||
.project()
|
||||
.io
|
||||
.poll_next(cx)
|
||||
self.project().inner.project().inner.poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,180 +122,9 @@ where
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("FramedWrite")
|
||||
.field("inner", &self.inner.get_ref().io)
|
||||
.field("encoder", &self.inner.get_ref().codec)
|
||||
.field("buffer", &self.inner.buffer)
|
||||
.field("inner", &self.get_ref())
|
||||
.field("encoder", &self.encoder())
|
||||
.field("buffer", &self.inner.state.buffer)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FramedWrite2 =====
|
||||
|
||||
pub(crate) fn framed_write2<T>(inner: T) -> FramedWrite2<T> {
|
||||
FramedWrite2 {
|
||||
inner,
|
||||
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn framed_write2_with_buffer<T>(inner: T, mut buf: BytesMut) -> FramedWrite2<T> {
|
||||
if buf.capacity() < INITIAL_CAPACITY {
|
||||
let bytes_to_reserve = INITIAL_CAPACITY - buf.capacity();
|
||||
buf.reserve(bytes_to_reserve);
|
||||
}
|
||||
FramedWrite2 { inner, buffer: buf }
|
||||
}
|
||||
|
||||
impl<T> FramedWrite2<T> {
|
||||
pub(crate) fn get_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn into_parts(self) -> (T, BytesMut) {
|
||||
(self.inner, self.buffer)
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> Sink<I> for FramedWrite2<T>
|
||||
where
|
||||
T: ProjectFuse + AsyncWrite,
|
||||
T::Codec: Encoder<Item = I>,
|
||||
{
|
||||
type Error = <T::Codec as Encoder>::Error;
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// If the buffer is already over 8KiB, then attempt to flush it. If after flushing it's
|
||||
// *still* over 8KiB, then apply backpressure (reject the send).
|
||||
if self.buffer.len() >= BACKPRESSURE_BOUNDARY {
|
||||
match self.as_mut().poll_flush(cx) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
|
||||
Poll::Ready(Ok(())) => (),
|
||||
};
|
||||
|
||||
if self.buffer.len() >= BACKPRESSURE_BOUNDARY {
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
|
||||
let mut pinned = self.project();
|
||||
pinned
|
||||
.inner
|
||||
.project()
|
||||
.codec
|
||||
.encode(item, &mut pinned.buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
trace!("flushing framed transport");
|
||||
let mut pinned = self.project();
|
||||
|
||||
while !pinned.buffer.is_empty() {
|
||||
trace!("writing; remaining={}", pinned.buffer.len());
|
||||
|
||||
let buf = &pinned.buffer;
|
||||
let n = ready!(pinned.inner.as_mut().poll_write(cx, &buf))?;
|
||||
|
||||
if n == 0 {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::WriteZero,
|
||||
"failed to \
|
||||
write frame to transport",
|
||||
)
|
||||
.into()));
|
||||
}
|
||||
|
||||
pinned.buffer.advance(n);
|
||||
}
|
||||
|
||||
// Try flushing the underlying IO
|
||||
ready!(pinned.inner.poll_flush(cx))?;
|
||||
|
||||
trace!("framed transport flushed");
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
ready!(self.as_mut().poll_flush(cx))?;
|
||||
ready!(self.project().inner.poll_shutdown(cx))?;
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decoder> Decoder for FramedWrite2<T> {
|
||||
type Item = T::Item;
|
||||
type Error = T::Error;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<T::Item>, T::Error> {
|
||||
self.inner.decode(src)
|
||||
}
|
||||
|
||||
fn decode_eof(&mut self, src: &mut BytesMut) -> Result<Option<T::Item>, T::Error> {
|
||||
self.inner.decode_eof(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read> Read for FramedWrite2<T> {
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
self.inner.read(dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BufRead> BufRead for FramedWrite2<T> {
|
||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||
self.inner.fill_buf()
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.inner.consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead> AsyncRead for FramedWrite2<T> {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [MaybeUninit<u8>]) -> bool {
|
||||
self.inner.prepare_uninitialized_buffer(buf)
|
||||
}
|
||||
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<Result<usize, io::Error>> {
|
||||
self.project().inner.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncBufRead> AsyncBufRead for FramedWrite2<T> {
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
self.project().inner.poll_fill_buf(cx)
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
self.project().inner.consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ProjectFuse for FramedWrite2<T>
|
||||
where
|
||||
T: ProjectFuse,
|
||||
{
|
||||
type Io = T::Io;
|
||||
type Codec = T::Codec;
|
||||
|
||||
fn project(self: Pin<&mut Self>) -> Fuse<Pin<&mut Self::Io>, &mut Self::Codec> {
|
||||
self.project().inner.project()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +302,37 @@
|
||||
//! anywhere because it already is factored into the total frame length that
|
||||
//! is read from the byte stream.
|
||||
//!
|
||||
//! ## Example 7
|
||||
//!
|
||||
//! The following will parse a 3 byte length field at offset 0 in a 4 byte
|
||||
//! frame head, excluding the 4th byte from the yielded `BytesMut`.
|
||||
//!
|
||||
//! ```
|
||||
//! # use tokio::io::AsyncRead;
|
||||
//! # use tokio_util::codec::LengthDelimitedCodec;
|
||||
//! # fn bind_read<T: AsyncRead>(io: T) {
|
||||
//! LengthDelimitedCodec::builder()
|
||||
//! .length_field_offset(0) // default value
|
||||
//! .length_field_length(3)
|
||||
//! .length_adjustment(0) // default value
|
||||
//! .num_skip(4) // skip the first 4 bytes
|
||||
//! .new_read(io);
|
||||
//! # }
|
||||
//! # pub fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! The following frame will be decoded as such:
|
||||
//!
|
||||
//! ```text
|
||||
//! INPUT DECODED
|
||||
//! +------- len ------+--- Payload ---+ +--- Payload ---+
|
||||
//! | \x00\x00\x0B\xFF | Hello world | => | Hello world |
|
||||
//! +------------------+---------------+ +---------------+
|
||||
//! ```
|
||||
//!
|
||||
//! A simple example where there are unused bytes between the length field
|
||||
//! and the payload.
|
||||
//!
|
||||
//! # Encoding
|
||||
//!
|
||||
//! [`FramedWrite`] adapts an [`AsyncWrite`] into a `Sink` of [`BytesMut`],
|
||||
@@ -333,13 +364,13 @@
|
||||
//! +------------+--------------+
|
||||
//! ```
|
||||
//!
|
||||
//! [`LengthDelimitedCodec::new()`]: struct.LengthDelimitedCodec.html#method.new
|
||||
//! [`FramedRead`]: struct.FramedRead.html
|
||||
//! [`FramedWrite`]: struct.FramedWrite.html
|
||||
//! [`AsyncRead`]: ../../trait.AsyncRead.html
|
||||
//! [`AsyncWrite`]: ../../trait.AsyncWrite.html
|
||||
//! [`Encoder`]: ../trait.Encoder.html
|
||||
//! [`BytesMut`]: https://docs.rs/bytes/0.4/bytes/struct.BytesMut.html
|
||||
//! [`LengthDelimitedCodec::new()`]: method@LengthDelimitedCodec::new
|
||||
//! [`FramedRead`]: struct@FramedRead
|
||||
//! [`FramedWrite`]: struct@FramedWrite
|
||||
//! [`AsyncRead`]: trait@tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: trait@tokio::io::AsyncWrite
|
||||
//! [`Encoder`]: trait@Encoder
|
||||
//! [`BytesMut`]: bytes::BytesMut
|
||||
|
||||
use crate::codec::{Decoder, Encoder, Framed, FramedRead, FramedWrite};
|
||||
|
||||
@@ -546,12 +577,11 @@ impl Decoder for LengthDelimitedCodec {
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for LengthDelimitedCodec {
|
||||
type Item = Bytes;
|
||||
impl Encoder<Bytes> for LengthDelimitedCodec {
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, data: Bytes, dst: &mut BytesMut) -> Result<(), io::Error> {
|
||||
let n = (&data).remaining();
|
||||
let n = data.len();
|
||||
|
||||
if n > self.builder.max_frame_len {
|
||||
return Err(io::Error::new(
|
||||
|
||||
@@ -182,11 +182,14 @@ impl Decoder for LinesCodec {
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder for LinesCodec {
|
||||
type Item = String;
|
||||
impl<T> Encoder<T> for LinesCodec
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
type Error = LinesCodecError;
|
||||
|
||||
fn encode(&mut self, line: String, buf: &mut BytesMut) -> Result<(), LinesCodecError> {
|
||||
fn encode(&mut self, line: T, buf: &mut BytesMut) -> Result<(), LinesCodecError> {
|
||||
let line = line.as_ref();
|
||||
buf.reserve(line.len() + 1);
|
||||
buf.put(line.as_bytes());
|
||||
buf.put_u8(b'\n');
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
//! Utilities for encoding and decoding frames.
|
||||
//! Adaptors from AsyncRead/AsyncWrite to Stream/Sink
|
||||
//!
|
||||
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and
|
||||
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
|
||||
//! Framed streams are also known as transports.
|
||||
//! Raw I/O objects work with byte sequences, but higher-level code
|
||||
//! usually wants to batch these into meaningful chunks, called
|
||||
//! "frames".
|
||||
//!
|
||||
//! This module contains adapters to go from streams of bytes,
|
||||
//! [`AsyncRead`] and [`AsyncWrite`], to framed streams implementing
|
||||
//! [`Sink`] and [`Stream`]. Framed streams are also known as
|
||||
//! transports.
|
||||
//!
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
@@ -18,6 +23,10 @@ pub use self::decoder::Decoder;
|
||||
mod encoder;
|
||||
pub use self::encoder::Encoder;
|
||||
|
||||
mod framed_impl;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use self::framed_impl::{FramedImpl, RWFrames, ReadFrame, WriteFrame};
|
||||
|
||||
mod framed;
|
||||
pub use self::framed::{Framed, FramedParts};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Compatibility between the `tokio::io` and `futures-io` versions of the
|
||||
//! `AsyncRead` and `AsyncWrite` traits.
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
@@ -19,7 +20,7 @@ pin_project! {
|
||||
/// `futures_io::AsyncRead` to implement `tokio::io::AsyncRead`.
|
||||
pub trait FuturesAsyncReadCompatExt: futures_io::AsyncRead {
|
||||
/// Wraps `self` with a compatibility layer that implements
|
||||
/// `tokio_io::AsyncWrite`.
|
||||
/// `tokio_io::AsyncRead`.
|
||||
fn compat(self) -> Compat<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -107,9 +108,18 @@ where
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
futures_io::AsyncRead::poll_read(self.project().inner, cx, buf)
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
// We can't trust the inner type to not peak at the bytes,
|
||||
// so we must defensively initialize the buffer.
|
||||
let slice = buf.initialize_unfilled();
|
||||
let n = ready!(futures_io::AsyncRead::poll_read(
|
||||
self.project().inner,
|
||||
cx,
|
||||
slice
|
||||
))?;
|
||||
buf.advance(n);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +130,15 @@ where
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
slice: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
tokio::io::AsyncRead::poll_read(self.project().inner, cx, buf)
|
||||
let mut buf = tokio::io::ReadBuf::new(slice);
|
||||
ready!(tokio::io::AsyncRead::poll_read(
|
||||
self.project().inner,
|
||||
cx,
|
||||
&mut buf
|
||||
))?;
|
||||
Poll::Ready(Ok(buf.filled().len()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Tokio context aware futures utilities.
|
||||
//!
|
||||
//! This module includes utilities around integrating tokio with other runtimes
|
||||
//! by allowing the context to be attached to futures. This allows spawning
|
||||
//! futures on other executors while still using tokio to drive them. This
|
||||
//! can be useful if you need to use a tokio based library in an executor/runtime
|
||||
//! that does not provide a tokio context.
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
pin_project! {
|
||||
/// `TokioContext` allows connecting a custom executor with the tokio runtime.
|
||||
///
|
||||
/// It contains a `Handle` to the runtime. A handle to the runtime can be
|
||||
/// obtain by calling the `Runtime::handle()` method.
|
||||
pub struct TokioContext<'a, F> {
|
||||
#[pin]
|
||||
inner: F,
|
||||
handle: &'a Runtime,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for TokioContext<'_, F> {
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let me = self.project();
|
||||
let handle = me.handle;
|
||||
let fut = me.inner;
|
||||
|
||||
let _enter = handle.enter();
|
||||
fut.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait extension that simplifies bundling a `Handle` with a `Future`.
|
||||
pub trait RuntimeExt {
|
||||
/// Convenience method that takes a Future and returns a `TokioContext`.
|
||||
///
|
||||
/// # Example: calling Tokio Runtime from a custom ThreadPool
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tokio_util::context::RuntimeExt;
|
||||
/// use tokio::time::{sleep, Duration};
|
||||
///
|
||||
/// let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .enable_all()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let rt2 = tokio::runtime::Builder::new_multi_thread()
|
||||
/// .build()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let fut = sleep(Duration::from_millis(2));
|
||||
///
|
||||
/// rt.block_on(
|
||||
/// rt2
|
||||
/// .wrap(async { sleep(Duration::from_millis(2)).await }),
|
||||
/// );
|
||||
///```
|
||||
fn wrap<F: Future>(&self, fut: F) -> TokioContext<'_, F>;
|
||||
}
|
||||
|
||||
impl RuntimeExt for Runtime {
|
||||
fn wrap<F: Future>(&self, fut: F) -> TokioContext<'_, F> {
|
||||
TokioContext {
|
||||
inner: fut,
|
||||
handle: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
//! Module defining an Either type.
|
||||
use std::{
|
||||
future::Future,
|
||||
io::SeekFrom,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf, Result};
|
||||
|
||||
/// Combines two different futures, streams, or sinks having the same associated types into a single type.
|
||||
///
|
||||
/// This type implements common asynchronous traits such as [`Future`] and those in Tokio.
|
||||
///
|
||||
/// [`Future`]: std::future::Future
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// The following code will not work:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # fn some_condition() -> bool { true }
|
||||
/// # async fn some_async_function() -> u32 { 10 }
|
||||
/// # async fn other_async_function() -> u32 { 20 }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let result = if some_condition() {
|
||||
/// some_async_function()
|
||||
/// } else {
|
||||
/// other_async_function() // <- Will print: "`if` and `else` have incompatible types"
|
||||
/// };
|
||||
///
|
||||
/// println!("Result is {}", result.await);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
// This is because although the output types for both futures is the same, the exact future
|
||||
// types are different, but the compiler must be able to choose a single type for the
|
||||
// `result` variable.
|
||||
///
|
||||
/// When the output type is the same, we can wrap each future in `Either` to avoid the
|
||||
/// issue:
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_util::either::Either;
|
||||
/// # fn some_condition() -> bool { true }
|
||||
/// # async fn some_async_function() -> u32 { 10 }
|
||||
/// # async fn other_async_function() -> u32 { 20 }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let result = if some_condition() {
|
||||
/// Either::Left(some_async_function())
|
||||
/// } else {
|
||||
/// Either::Right(other_async_function())
|
||||
/// };
|
||||
///
|
||||
/// let value = result.await;
|
||||
/// println!("Result is {}", value);
|
||||
/// # assert_eq!(value, 10);
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(missing_docs)] // Doc-comments for variants in this particular case don't make much sense.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Either<L, R> {
|
||||
Left(L),
|
||||
Right(R),
|
||||
}
|
||||
|
||||
/// A small helper macro which reduces amount of boilerplate in the actual trait method implementation.
|
||||
/// It takes an invokation of method as an argument (e.g. `self.poll(cx)`), and redirects it to either
|
||||
/// enum variant held in `self`.
|
||||
macro_rules! delegate_call {
|
||||
($self:ident.$method:ident($($args:ident),+)) => {
|
||||
unsafe {
|
||||
match $self.get_unchecked_mut() {
|
||||
Self::Left(l) => Pin::new_unchecked(l).$method($($args),+),
|
||||
Self::Right(r) => Pin::new_unchecked(r).$method($($args),+),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R, O> Future for Either<L, R>
|
||||
where
|
||||
L: Future<Output = O>,
|
||||
R: Future<Output = O>,
|
||||
{
|
||||
type Output = O;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
delegate_call!(self.poll(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> AsyncRead for Either<L, R>
|
||||
where
|
||||
L: AsyncRead,
|
||||
R: AsyncRead,
|
||||
{
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<Result<()>> {
|
||||
delegate_call!(self.poll_read(cx, buf))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> AsyncBufRead for Either<L, R>
|
||||
where
|
||||
L: AsyncBufRead,
|
||||
R: AsyncBufRead,
|
||||
{
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<&[u8]>> {
|
||||
delegate_call!(self.poll_fill_buf(cx))
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
delegate_call!(self.consume(amt))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> AsyncSeek for Either<L, R>
|
||||
where
|
||||
L: AsyncSeek,
|
||||
R: AsyncSeek,
|
||||
{
|
||||
fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> Result<()> {
|
||||
delegate_call!(self.start_seek(position))
|
||||
}
|
||||
|
||||
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<u64>> {
|
||||
delegate_call!(self.poll_complete(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> AsyncWrite for Either<L, R>
|
||||
where
|
||||
L: AsyncWrite,
|
||||
R: AsyncWrite,
|
||||
{
|
||||
fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
|
||||
delegate_call!(self.poll_write(cx, buf))
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<tokio::io::Result<()>> {
|
||||
delegate_call!(self.poll_flush(cx))
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<tokio::io::Result<()>> {
|
||||
delegate_call!(self.poll_shutdown(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> futures_core::stream::Stream for Either<L, R>
|
||||
where
|
||||
L: futures_core::stream::Stream,
|
||||
R: futures_core::stream::Stream<Item = L::Item>,
|
||||
{
|
||||
type Item = L::Item;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
delegate_call!(self.poll_next(cx))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tokio::{
|
||||
io::{repeat, AsyncReadExt, Repeat},
|
||||
stream::{once, Once, StreamExt},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn either_is_stream() {
|
||||
let mut either: Either<Once<u32>, Once<u32>> = Either::Left(once(1));
|
||||
|
||||
assert_eq!(Some(1u32), either.next().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn either_is_async_read() {
|
||||
let mut buffer = [0; 3];
|
||||
let mut either: Either<Repeat, Repeat> = Either::Right(repeat(0b101));
|
||||
|
||||
either.read_exact(&mut buffer).await.unwrap();
|
||||
assert_eq!(buffer, [0b101, 0b101, 0b101]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//! Helpers for IO related tasks.
|
||||
//!
|
||||
//! These types are often used in combination with hyper or reqwest, as they
|
||||
//! allow converting between a hyper [`Body`] and [`AsyncRead`].
|
||||
//!
|
||||
//! [`Body`]: https://docs.rs/hyper/0.13/hyper/struct.Body.html
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
|
||||
mod read_buf;
|
||||
mod reader_stream;
|
||||
mod stream_reader;
|
||||
|
||||
pub use self::read_buf::read_buf;
|
||||
pub use self::reader_stream::ReaderStream;
|
||||
pub use self::stream_reader::StreamReader;
|
||||
pub use crate::util::{poll_read_buf, poll_write_buf};
|
||||
@@ -0,0 +1,65 @@
|
||||
use bytes::BufMut;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
/// Read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
|
||||
///
|
||||
/// [`BufMut`]: bytes::BufMut
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bytes::{Bytes, BytesMut};
|
||||
/// use tokio::stream;
|
||||
/// use tokio::io::Result;
|
||||
/// use tokio_util::io::{StreamReader, read_buf};
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() -> std::io::Result<()> {
|
||||
///
|
||||
/// // Create a reader from an iterator. This particular reader will always be
|
||||
/// // ready.
|
||||
/// let mut read = StreamReader::new(stream::iter(vec![Result::Ok(Bytes::from_static(&[0, 1, 2, 3]))]));
|
||||
///
|
||||
/// let mut buf = BytesMut::new();
|
||||
/// let mut reads = 0;
|
||||
///
|
||||
/// loop {
|
||||
/// reads += 1;
|
||||
/// let n = read_buf(&mut read, &mut buf).await?;
|
||||
///
|
||||
/// if n == 0 {
|
||||
/// break;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // one or more reads might be necessary.
|
||||
/// assert!(reads >= 1);
|
||||
/// assert_eq!(&buf[..], &[0, 1, 2, 3]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn read_buf<R, B>(read: &mut R, buf: &mut B) -> io::Result<usize>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
B: BufMut,
|
||||
{
|
||||
return ReadBufFn(read, buf).await;
|
||||
|
||||
struct ReadBufFn<'a, R, B>(&'a mut R, &'a mut B);
|
||||
|
||||
impl<'a, R, B> Future for ReadBufFn<'a, R, B>
|
||||
where
|
||||
R: AsyncRead + Unpin,
|
||||
B: BufMut,
|
||||
{
|
||||
type Output = io::Result<usize>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = &mut *self;
|
||||
crate::util::poll_read_buf(Pin::new(this.0), cx, this.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user