mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf9398ef61 | ||
|
|
b726610e7f | ||
|
|
338567ca80 | ||
|
|
3181ebfda6 | ||
|
|
424be889a4 | ||
|
|
72283f178a | ||
|
|
202220034e | ||
|
|
0c0c0bb177 | ||
|
|
69ced1b6da | ||
|
|
17481a3a54 | ||
|
|
93e7d9759a | ||
|
|
85d6d43d18 | ||
|
|
e74b728ecf | ||
|
|
9acd70b7e3 | ||
|
|
a531721b1c | ||
|
|
06153d0f28 | ||
|
|
50973e0734 |
-42
@@ -1,42 +0,0 @@
|
||||
freebsd_instance:
|
||||
image: freebsd-12-0-release-amd64
|
||||
|
||||
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
|
||||
# same VM. The binary will be built in 32-bit mode, but will execute on a
|
||||
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
|
||||
# the system's binaries, so the environment shouldn't matter.
|
||||
task:
|
||||
name: FreeBSD 12.0
|
||||
env:
|
||||
LOOM_MAX_PREEMPTIONS: 2
|
||||
RUSTFLAGS: -Dwarnings
|
||||
setup_script:
|
||||
- pkg install -y curl
|
||||
- curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
- sh rustup.sh -y --profile minimal --default-toolchain stable
|
||||
- . $HOME/.cargo/env
|
||||
- rustup target add i686-unknown-freebsd
|
||||
- |
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
|
||||
# Remove any existing patch statements
|
||||
mv Cargo.toml Cargo.toml.bck
|
||||
sed -n '/\[patch.crates-io\]/q;p' Cargo.toml.bck > Cargo.toml
|
||||
|
||||
# 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
|
||||
@@ -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>
|
||||
-->
|
||||
@@ -1,23 +0,0 @@
|
||||
<!--
|
||||
Thank you for your Pull Request. Please provide a description above and review
|
||||
the requirements below.
|
||||
|
||||
Bug fixes and new features should include tests.
|
||||
|
||||
Contributors guide: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
## Motivation
|
||||
|
||||
<!--
|
||||
Explain the context and why you're making that change. What is the problem
|
||||
you're trying to solve? In some cases there is not a problem and this can be
|
||||
thought of as being the motivation for your change.
|
||||
-->
|
||||
|
||||
## Solution
|
||||
|
||||
<!--
|
||||
Summarize the solution and provide any necessary context needed to understand
|
||||
the code change.
|
||||
-->
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
sudo: false
|
||||
before_script:
|
||||
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
|
||||
script:
|
||||
- cargo build
|
||||
- cargo test
|
||||
- cargo doc --no-deps
|
||||
after_success:
|
||||
- travis-cargo --only nightly doc-upload
|
||||
env:
|
||||
global:
|
||||
- secure: "SXXK7Znvm1s5WWQ94l9IP25mXA0uGIQ7ghBuumZz3nSAfxhhJQnYi5hCAbl2/cOfSbpgEtE137dgk6Nd9UDx7rIkLCSe8TWyYjzraX/vvX3xNtLh/fjsayYYRK9a6qU2HIJegZdxPgyF5h2DeBgeLks0Ue8drrFQ1s9bYZVUO0yeuZ3aLkL1FkIG6RXGItUFpb6srEYL1NLizYLxXFEG3cL+kKoFIWc2qPx3EwOqv/eii134nQsuObhWZvPqfTo7zfNP8W/6TnoiggpRH1nrZc3DI3CynTICIOJ2Ogn9gFX9LftYKuJysSwUNVN3WF5aOuLP/XjRSBLYc+PW3v0iqiGzMX3n1VpcyhcbsSNA7ZckGn1HZsWYwspAxkN3idSuVie9Mezm7IV4005juiYKEWEr6hlkv1lzd49QZkWOvLCFCMRiwOOGp4NyzilG1Q1Zs3G1wrcvstmasNpK+QUFNdOFvT2sm34rI4x2rQUvjC/OyqbAK+PjYmTHL47YKON5ymfUL3mAcwgUfBUSd4Wpx8G3VKg3gMcmQm27ah1knOGJWH6XulYTnfGfx6bLo5t2NGx+vZk0naqajD3auWnseobMDsFjhUIRrt6GlnfPqeFoJSm0unu3riAX+RDF/iqZdDfjhX4evETIw3SaTl8EQtVLwz7kJTnxSbTU4XTi+0M="
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
@@ -1,7 +0,0 @@
|
||||
# Code of Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). This describes the minimum behavior expected from all contributors.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of violations of the Code of Conduct can be reported by contacting the project team at [[email protected]](mailto:[email protected]).
|
||||
-443
@@ -1,443 +0,0 @@
|
||||
# Contributing to Tokio
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
you!
|
||||
|
||||
There are opportunities to contribute to Tokio at any level. It doesn't matter if
|
||||
you are just getting started with Rust or are the most weathered expert, we can
|
||||
use your help.
|
||||
|
||||
**No contribution is too small and all contributions are valued.**
|
||||
|
||||
This guide will help you get started. **Do not let this guide intimidate you**.
|
||||
It should be considered a map to help you navigate the process.
|
||||
|
||||
The [dev channel][dev] is available for any concerns not covered in this guide, please join
|
||||
us!
|
||||
|
||||
[dev]: https://discord.gg/6yGkFeN
|
||||
|
||||
## Conduct
|
||||
|
||||
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
|
||||
the _minimum_ behavior expected from all contributors. 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
|
||||
|
||||
## Contributing in Issues
|
||||
|
||||
For any issue, there are fundamentally three ways an individual can contribute:
|
||||
|
||||
1. By opening the issue for discussion: For instance, if you believe that you
|
||||
have uncovered a bug in Tokio, creating a new issue in the tokio-rs/tokio
|
||||
issue tracker is the way to report it.
|
||||
|
||||
2. By helping to triage the issue: This can be done by providing
|
||||
supporting details (a test case that demonstrates a bug), providing
|
||||
suggestions on how to address the issue, or ensuring that the issue is tagged
|
||||
correctly.
|
||||
|
||||
3. By helping to resolve the issue: Typically this is done either in the form of
|
||||
demonstrating that the issue reported is not a problem after all, or more
|
||||
often, by opening a Pull Request that changes some bit of something in
|
||||
Tokio in a concrete and reviewable manner.
|
||||
|
||||
**Anybody can participate in any stage of contribution**. We urge you to
|
||||
participate in the discussion around bugs and participate in reviewing PRs.
|
||||
|
||||
### Asking for General Help
|
||||
|
||||
If you have reviewed existing documentation and still have questions or are
|
||||
having problems, you can open an issue asking for help.
|
||||
|
||||
In exchange for receiving help, we ask that you contribute back a documentation
|
||||
PR that helps others avoid the problems that you encountered.
|
||||
|
||||
### Submitting a Bug Report
|
||||
|
||||
When opening a new issue in the Tokio issue tracker, users will be presented
|
||||
with a [basic template][template] that should be filled in. If you believe that you have
|
||||
uncovered a bug, please fill out this form, following the template to the best
|
||||
of your ability. Do not worry if you cannot answer every detail, just fill in
|
||||
what you can.
|
||||
|
||||
The two most important pieces of information we need in order to properly
|
||||
evaluate the report is a description of the behavior you are seeing and a simple
|
||||
test case we can use to recreate the problem on our own. If we cannot recreate
|
||||
the issue, it becomes impossible for us to fix.
|
||||
|
||||
In order to rule out the possibility of bugs introduced by userland code, test
|
||||
cases should be limited, as much as possible, to using only Tokio APIs.
|
||||
|
||||
See [How to create a Minimal, Complete, and Verifiable example][mcve].
|
||||
|
||||
[mcve]: https://stackoverflow.com/help/mcve
|
||||
[template]: .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Triaging a Bug Report
|
||||
|
||||
Once an issue has been opened, it is not uncommon for there to be discussion
|
||||
around it. Some contributors may have differing opinions about the issue,
|
||||
including whether the behavior being seen is a bug or a feature. This discussion
|
||||
is part of the process and should be kept focused, helpful, and professional.
|
||||
|
||||
Short, clipped responses—that provide neither additional context nor supporting
|
||||
detail—are not helpful or professional. To many, such responses are simply
|
||||
annoying and unfriendly.
|
||||
|
||||
Contributors are encouraged to help one another make forward progress as much as
|
||||
possible, empowering one another to solve issues collaboratively. If you choose
|
||||
to comment on an issue that you feel either is not a problem that needs to be
|
||||
fixed, or if you encounter information in an issue that you feel is incorrect,
|
||||
explain why you feel that way with additional supporting context, and be willing
|
||||
to be convinced that you may be wrong. By doing so, we can often reach the
|
||||
correct outcome much faster.
|
||||
|
||||
### Resolving a Bug Report
|
||||
|
||||
In the majority of cases, issues are resolved by opening a Pull Request. The
|
||||
process for opening and reviewing a Pull Request is similar to that of opening
|
||||
and triaging issues, but carries with it a necessary review and approval
|
||||
workflow that ensures that the proposed changes meet the minimal quality and
|
||||
functional guidelines of the Tokio project.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull Requests are the way concrete changes are made to the code, documentation,
|
||||
and dependencies in the Tokio repository.
|
||||
|
||||
Even tiny pull requests (e.g., one character pull request fixing a typo in API
|
||||
documentation) are greatly appreciated. Before making a large change, it is
|
||||
usually a good idea to first open an issue describing the change to solicit
|
||||
feedback and guidance. This will increase the likelihood of the PR getting
|
||||
merged.
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to Tokio or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that Tokio does not regress in the future.
|
||||
There are two ways to write tests: integration tests and documentation tests
|
||||
(Tokio avoids unit tests as much as possible).
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Integration tests go in the same crate as the code they are testing. Each sub
|
||||
crate should have a `dev-dependency` on `tokio` itself. This makes all Tokio
|
||||
utilities available to use in tests, no matter the crate being tested.
|
||||
|
||||
The best strategy for writing a new integration test is to look at existing
|
||||
integration tests in the crate and follow the style.
|
||||
|
||||
#### Documentation tests
|
||||
|
||||
Ideally, every API has at least one [documentation test] that demonstrates how to
|
||||
use the API. Documentation tests are run with `cargo test --doc`. This ensures
|
||||
that the example is correct and provides additional test coverage.
|
||||
|
||||
The trick to documentation tests is striking a balance between being succinct
|
||||
for a reader to understand and actually testing the API.
|
||||
|
||||
Same as with integration tests, when writing a documentation test, the full
|
||||
`tokio` crate is available. This is especially useful for getting access to the
|
||||
runtime to run the example.
|
||||
|
||||
The documentation tests will be visible from both the crate specific
|
||||
documentation **and** the `tokio` facade documentation via the re-export. The
|
||||
example should be written from the point of view of a user that is using the
|
||||
`tokio` crate. As such, the example should use the API via the facade and not by
|
||||
directly referencing the crate.
|
||||
|
||||
The type level example for `tokio_timer::Timeout` provides a good example of a
|
||||
documentation test:
|
||||
|
||||
```
|
||||
/// // import the `timeout` function, usually this is done
|
||||
/// // with `use tokio::prelude::*`
|
||||
/// use tokio::prelude::FutureExt;
|
||||
/// use futures::Stream;
|
||||
/// use futures::sync::mpsc;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let (tx, rx) = mpsc::unbounded();
|
||||
/// # tx.unbounded_send(()).unwrap();
|
||||
/// # drop(tx);
|
||||
///
|
||||
/// let process = rx.for_each(|item| {
|
||||
/// // do something with `item`
|
||||
/// # drop(item);
|
||||
/// # Ok(())
|
||||
/// });
|
||||
///
|
||||
/// # tokio::runtime::current_thread::block_on_all(
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// process.timeout(Duration::from_millis(10))
|
||||
/// # ).unwrap();
|
||||
/// # }
|
||||
```
|
||||
|
||||
Given that this is a *type* level documentation test and the primary way users
|
||||
of `tokio` will create an instance of `Timeout` is by using
|
||||
`FutureExt::timeout`, this is how the documentation test is structured.
|
||||
|
||||
Lines that start with `/// #` are removed when the documentation is generated.
|
||||
They are only there to get the test to run. The `block_on_all` function is the
|
||||
easiest way to execute a future from a test.
|
||||
|
||||
If this were a documentation test for the `Timeout::new` function, then the
|
||||
example would explicitly use `Timeout::new`. For example:
|
||||
|
||||
```
|
||||
/// use tokio::timer::Timeout;
|
||||
/// use futures::Future;
|
||||
/// use futures::sync::oneshot;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// # fn main() {
|
||||
/// let (tx, rx) = oneshot::channel();
|
||||
/// # tx.send(()).unwrap();
|
||||
///
|
||||
/// # tokio::runtime::current_thread::block_on_all(
|
||||
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
|
||||
/// Timeout::new(rx, Duration::from_millis(10))
|
||||
/// # ).unwrap();
|
||||
/// # }
|
||||
```
|
||||
|
||||
### Commits
|
||||
|
||||
It is a recommended best practice to keep your changes as logically grouped as
|
||||
possible within individual commits. There is no limit to the number of commits
|
||||
any single Pull Request may have, and many contributors find it easier to review
|
||||
changes that are split across multiple commits.
|
||||
|
||||
That said, if you have a number of commits that are "checkpoints" and don't
|
||||
represent a single logical change, please squash those together.
|
||||
|
||||
Note that multiple commits often get squashed when they are landed (see the
|
||||
notes about [commit squashing]).
|
||||
|
||||
#### Commit message guidelines
|
||||
|
||||
A good commit message should describe what changed and why.
|
||||
|
||||
1. The first line should:
|
||||
|
||||
* contain a short description of the change (preferably 50 characters or less,
|
||||
and no more than 72 characters)
|
||||
* be entirely in lowercase with the exception of proper nouns, acronyms, and
|
||||
the words that refer to code, like function/variable names
|
||||
* be prefixed with the name of the sub crate being changed (without the `tokio-`
|
||||
prefix) and start with an imperative verb. If modifying `tokio` proper,
|
||||
omit the crate prefix.
|
||||
|
||||
Examples:
|
||||
|
||||
* timer: introduce `Timeout` and deprecate `Deadline`
|
||||
* export `Encoder`, `Decoder`, `Framed*` from tokio_codec
|
||||
|
||||
2. Keep the second line blank.
|
||||
3. Wrap all other lines at 72 columns (except for long URLs).
|
||||
4. If your patch fixes an open issue, you can add a reference to it at the end
|
||||
of the log. Use the `Fixes: #` prefix and the issue number. For other
|
||||
references use `Refs: #`. `Refs` may include multiple issues, separated by a
|
||||
comma.
|
||||
|
||||
Examples:
|
||||
|
||||
- `Fixes: #1337`
|
||||
- `Refs: #1234`
|
||||
|
||||
Sample complete commit message:
|
||||
|
||||
```txt
|
||||
subcrate: explain the commit in one line
|
||||
|
||||
Body of commit message is a few lines of text, explaining things
|
||||
in more detail, possibly giving some background about the issue
|
||||
being fixed, etc.
|
||||
|
||||
The body of the commit message can be several paragraphs, and
|
||||
please do proper word-wrap and keep columns shorter than about
|
||||
72 characters or so. That way, `git log` will show things
|
||||
nicely even when it is indented.
|
||||
|
||||
Fixes: #1337
|
||||
Refs: #453, #154
|
||||
```
|
||||
|
||||
### Opening the Pull Request
|
||||
|
||||
From within GitHub, opening a new Pull Request will present you with a
|
||||
[template] that should be filled out. Please try to do your best at filling out
|
||||
the details, but feel free to skip parts if you're not sure what to put.
|
||||
|
||||
[template]: .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
### Discuss and update
|
||||
|
||||
You will probably get feedback or requests for changes to your Pull Request.
|
||||
This is a big part of the submission process so don't be discouraged! Some
|
||||
contributors may sign off on the Pull Request right away, others may have
|
||||
more detailed comments or feedback. This is a necessary part of the process
|
||||
in order to evaluate whether the changes are correct and necessary.
|
||||
|
||||
**Any community member can review a PR and you might get conflicting feedback**.
|
||||
Keep an eye out for comments from code owners to provide guidance on conflicting
|
||||
feedback.
|
||||
|
||||
**Once the PR is open, do not rebase the commits**. See [Commit Squashing] for
|
||||
more details.
|
||||
|
||||
### Commit Squashing
|
||||
|
||||
In most cases, **do not squash commits that you add to your Pull Request during
|
||||
the review process**. When the commits in your Pull Request land, they may be
|
||||
squashed into one commit per logical change. Metadata will be added to the
|
||||
commit message (including links to the Pull Request, links to relevant issues,
|
||||
and the names of the reviewers). The commit history of your Pull Request,
|
||||
however, will stay intact on the Pull Request page.
|
||||
|
||||
## Reviewing Pull Requests
|
||||
|
||||
**Any Tokio community member is welcome to review any pull request**.
|
||||
|
||||
All Tokio contributors who choose to review and provide feedback on Pull
|
||||
Requests have a responsibility to both the project and the individual making the
|
||||
contribution. Reviews and feedback must be helpful, insightful, and geared
|
||||
towards improving the contribution as opposed to simply blocking it. If there
|
||||
are reasons why you feel the PR should not land, explain what those are. Do not
|
||||
expect to be able to block a Pull Request from advancing simply because you say
|
||||
"No" without giving an explanation. Be open to having your mind changed. Be open
|
||||
to working with the contributor to make the Pull Request better.
|
||||
|
||||
Reviews that are dismissive or disrespectful of the contributor or any other
|
||||
reviewers are strictly counter to the Code of Conduct.
|
||||
|
||||
When reviewing a Pull Request, the primary goals are for the codebase to improve
|
||||
and for the person submitting the request to succeed. **Even if a Pull Request
|
||||
does not land, the submitters should come away from the experience feeling like
|
||||
their effort was not wasted or unappreciated**. Every Pull Request from a new
|
||||
contributor is an opportunity to grow the community.
|
||||
|
||||
### Review a bit at a time.
|
||||
|
||||
Do not overwhelm new contributors.
|
||||
|
||||
It is tempting to micro-optimize and make everything about relative performance,
|
||||
perfect grammar, or exact style matches. Do not succumb to that temptation.
|
||||
|
||||
Focus first on the most significant aspects of the change:
|
||||
|
||||
1. Does this change make sense for Tokio?
|
||||
2. Does this change make Tokio better, even if only incrementally?
|
||||
3. Are there clear bugs or larger scale issues that need attending to?
|
||||
4. Is the commit message readable and correct? If it contains a breaking change
|
||||
is it clear enough?
|
||||
|
||||
Note that only **incremental** improvement is needed to land a PR. This means
|
||||
that the PR does not need to be perfect, only better than the status quo. Follow
|
||||
up PRs may be opened to continue iterating.
|
||||
|
||||
When changes are necessary, *request* them, do not *demand* them, and **do not
|
||||
assume that the submitter already knows how to add a test or run a benchmark**.
|
||||
|
||||
Specific performance optimization techniques, coding styles and conventions
|
||||
change over time. The first impression you give to a new contributor never does.
|
||||
|
||||
Nits (requests for small changes that are not essential) are fine, but try to
|
||||
avoid stalling the Pull Request. Most nits can typically be fixed by the Tokio
|
||||
Collaborator landing the Pull Request but they can also be an opportunity for
|
||||
the contributor to learn a bit more about the project.
|
||||
|
||||
It is always good to clearly indicate nits when you comment: e.g.
|
||||
`Nit: change foo() to bar(). But this is not blocking.`
|
||||
|
||||
If your comments were addressed but were not folded automatically after new
|
||||
commits or if they proved to be mistaken, please, [hide them][hiding-a-comment]
|
||||
with the appropriate reason to keep the conversation flow concise and relevant.
|
||||
|
||||
### Be aware of the person behind the code
|
||||
|
||||
Be aware that *how* you communicate requests and reviews in your feedback can
|
||||
have a significant impact on the success of the Pull Request. Yes, we may land
|
||||
a particular change that makes Tokio better, but the individual might just not
|
||||
want to have anything to do with Tokio ever again. The goal is not just having
|
||||
good code.
|
||||
|
||||
### Abandoned or Stalled Pull Requests
|
||||
|
||||
If a Pull Request appears to be abandoned or stalled, it is polite to first
|
||||
check with the contributor to see if they intend to continue the work before
|
||||
checking if they would mind if you took it over (especially if it just has nits
|
||||
left). When doing so, it is courteous to give the original contributor credit
|
||||
for the work they started (either by preserving their name and email address in
|
||||
the commit log, or by using an `Author: ` meta-data tag in the commit.
|
||||
|
||||
_Adapted from the [Node.js contributing guide][node]_.
|
||||
|
||||
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
|
||||
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
|
||||
[documentation test]: https://doc.rust-lang.org/rustdoc/documentation-tests.html
|
||||
|
||||
## Releasing
|
||||
|
||||
Since the Tokio project consists of a number of crates, many of which depend on
|
||||
each other, releasing new versions to crates.io can involve some complexities.
|
||||
When releasing a new version of a crate, follow these steps:
|
||||
|
||||
1. **Ensure that the release crate has no path dependencies.** When the HEAD
|
||||
version of a Tokio crate requires unreleased changes in another Tokio crate,
|
||||
the crates.io dependency on the second crate will be replaced with a path
|
||||
dependency. Crates with path dependencies cannot be published, so before
|
||||
publishing the dependent crate, any path dependencies must also be published.
|
||||
This should be done through a form of depth-first tree traversal:
|
||||
|
||||
1. Starting with the first path dependency in the crate to be released,
|
||||
inspect the `Cargo.toml` for the dependency. If the dependency has any
|
||||
path dependencies of its own, repeat this step with the first such
|
||||
dependency.
|
||||
2. Begin the release process for the path dependency.
|
||||
3. Once the path dependency has been published to crates.io, update the
|
||||
dependent crate to depend on the crates.io version.
|
||||
4. When all path dependencies have been published, the dependent crate may
|
||||
be published.
|
||||
|
||||
To verify that a crate is ready to publish, run:
|
||||
|
||||
```bash
|
||||
bin/publish --dry-run <CRATE NAME> <CRATE VERSION>
|
||||
```
|
||||
|
||||
2. **Update Cargo metadata.** After releasing any path dependencies, update the
|
||||
`version` field in `Cargo.toml` to the new version, and the `documentation`
|
||||
field to the docs.rs URL of the new version.
|
||||
3. **Update other documentation links.** Update the `#![doc(html_root_url)]`
|
||||
attribute in the crate's `lib.rs` and the "Documentation" link in the crate's
|
||||
`README.md` to point to the docs.rs URL of the new version.
|
||||
4. **Update the changelog for the crate.** Each crate in the Tokio repository
|
||||
has its own `CHANGELOG.md` in that crate's subdirectory. Any changes to that
|
||||
crate since the last release should be added to the changelog. Change
|
||||
descriptions may be taken from the Git history, but should be edited to
|
||||
ensure a consistent format, based on [Keep A Changelog][keep-a-changelog].
|
||||
Other entries in that crate's changelog may also be used for reference.
|
||||
5. **Perform a final audit for breaking changes.** Compare the HEAD version of
|
||||
crate with the Git tag for the most recent release version. If there are any
|
||||
breaking API changes, determine if those changes can be made without breaking
|
||||
existing APIs. If so, resolve those issues. Otherwise, if it is necessary to
|
||||
make a breaking release, update the version numbers to reflect this.
|
||||
6. **Open a pull request with your changes.** Once that pull request has been
|
||||
approved by a maintainer and the pull request has been merged, continue to
|
||||
the next step.
|
||||
7. **Release the crate.** Run the following command:
|
||||
|
||||
```bash
|
||||
bin/publish <NAME OF CRATE> <VERSION>
|
||||
```
|
||||
|
||||
Your editor and prompt you to edit a message for the tag. Copy the changelog
|
||||
entry for that release version into your editor and close the window.
|
||||
|
||||
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog/blob/master/CHANGELOG.md
|
||||
+23
-13
@@ -1,15 +1,25 @@
|
||||
[workspace]
|
||||
[package]
|
||||
name = "tokio-signal"
|
||||
version = "0.1.1"
|
||||
authors = ["Alex Crichton <[email protected]>"]
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/alexcrichton/tokio-signal"
|
||||
homepage = "https://github.com/alexcrichton/tokio-signal"
|
||||
documentation = "https://alexcrichton.github.io/tokio-signal"
|
||||
description = """
|
||||
An implementation of an asynchronous Unix signal handling backed futures.
|
||||
"""
|
||||
|
||||
members = [
|
||||
"tokio",
|
||||
"tokio-macros",
|
||||
"tokio-test",
|
||||
"tokio-tls",
|
||||
"tokio-util",
|
||||
[dependencies]
|
||||
tokio-core = "0.1"
|
||||
futures = "0.1"
|
||||
|
||||
# Internal
|
||||
"benches",
|
||||
"examples",
|
||||
"tests-build",
|
||||
"tests-integration",
|
||||
]
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
tokio-uds = "0.1"
|
||||
libc = "0.2"
|
||||
mio = "0.6"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.2"
|
||||
kernel32-sys = "0.2"
|
||||
mio = "0.6"
|
||||
|
||||
@@ -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.
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2019 Tokio Contributors
|
||||
Copyright (c) 2016 Alex Crichton
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
@@ -1,166 +1,32 @@
|
||||
# Tokio
|
||||
# tokio-signal
|
||||
|
||||
A runtime for writing reliable, asynchronous, and slim applications with
|
||||
the Rust programming language. It is:
|
||||
An implementation of Unix signal handling for Tokio
|
||||
|
||||
* **Fast**: Tokio's zero-cost abstractions give you bare-metal
|
||||
performance.
|
||||
[](https://travis-ci.org/alexcrichton/tokio-signal)
|
||||
|
||||
* **Reliable**: Tokio leverages Rust's ownership, type system, and
|
||||
concurrency model to reduce bugs and ensure thread safety.
|
||||
[Documentation](https://alexcrichton.github.io/tokio-signal)
|
||||
|
||||
* **Scalable**: Tokio has a minimal footprint, and handles backpressure
|
||||
and cancellation naturally.
|
||||
## Usage
|
||||
|
||||
[![Crates.io][crates-badge]][crates-url]
|
||||
[![MIT licensed][mit-badge]][mit-url]
|
||||
[![Build Status][azure-badge]][azure-url]
|
||||
[![Discord chat][discord-badge]][discord-url]
|
||||
First, add this to your `Cargo.toml`:
|
||||
|
||||
[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
|
||||
[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/) |
|
||||
[API Docs](https://docs.rs/tokio/latest/tokio) |
|
||||
[Roadmap](https://github.com/tokio-rs/tokio/blob/master/ROADMAP.md) |
|
||||
[Chat](https://discord.gg/tokio)
|
||||
|
||||
## Overview
|
||||
|
||||
Tokio is an event-driven, non-blocking I/O platform for writing
|
||||
asynchronous applications with the Rust programming language. At a high
|
||||
level, it provides a few major components:
|
||||
|
||||
* A multithreaded, work-stealing based task [scheduler].
|
||||
* A reactor backed by the operating system's event queue (epoll, kqueue,
|
||||
IOCP, etc...).
|
||||
* Asynchronous [TCP and UDP][net] sockets.
|
||||
|
||||
These components provide the runtime components necessary for building
|
||||
an asynchronous application.
|
||||
|
||||
[net]: https://docs.rs/tokio/latest/tokio/net/index.html
|
||||
[scheduler]: https://docs.rs/tokio/latest/tokio/runtime/index.html
|
||||
|
||||
## Example
|
||||
|
||||
A basic TCP echo server with Tokio:
|
||||
|
||||
```rust,no_run
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Write the data back
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
eprintln!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio-signal = { git = "https://github.com/alexcrichton/tokio-signal" }
|
||||
```
|
||||
|
||||
More examples can be found [here](examples).
|
||||
Next, add this to your crate:
|
||||
|
||||
## Getting Help
|
||||
```rust
|
||||
extern crate tokio_signal;
|
||||
```
|
||||
|
||||
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.
|
||||
# License
|
||||
|
||||
[Guides]: https://tokio.rs/docs/
|
||||
[API documentation]: https://docs.rs/tokio/latest/tokio
|
||||
[chat]: https://discord.gg/tokio
|
||||
[issue]: https://github.com/tokio-rs/tokio/issues/new
|
||||
`tokio-signal` is primarily distributed under the terms of both the MIT
|
||||
license and the Apache License (Version 2.0), with portions covered by various
|
||||
BSD-like licenses.
|
||||
|
||||
## Contributing
|
||||
See LICENSE-APACHE, and LICENSE-MIT for details.
|
||||
|
||||
:balloon: Thanks for your help improving the project! We are so happy to have
|
||||
you! We have a [contributing guide][guide] to help you get involved in the Tokio
|
||||
project.
|
||||
|
||||
[guide]: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
|
||||
|
||||
## Related Projects
|
||||
|
||||
In addition to the crates in this repository, the Tokio project also maintains
|
||||
several other libraries, including:
|
||||
|
||||
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
|
||||
|
||||
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
|
||||
|
||||
* [`warp`]: A super-easy, composable, web server framework for warp speeds.
|
||||
|
||||
* [`tower`]: A library of modular and reusable components for building robust networking clients and servers.
|
||||
|
||||
* [`tracing`] (formerly `tokio-trace`): A framework for application-level
|
||||
tracing and async-aware diagnostics.
|
||||
|
||||
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
|
||||
|
||||
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
|
||||
`tokio`.
|
||||
|
||||
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
|
||||
|
||||
* [`loom`]: A testing tool for concurrent Rust code
|
||||
|
||||
[`warp`]: https://github.com/seanmonstar/warp
|
||||
[`hyper`]: https://github.com/hyperium/hyper
|
||||
[`tonic`]: https://github.com/hyperium/tonic
|
||||
[`tower`]: https://github.com/tower-rs/tower
|
||||
[`loom`]: https://github.com/tokio-rs/loom
|
||||
[`rdbc`]: https://github.com/tokio-rs/rdbc
|
||||
[`tracing`]: https://github.com/tokio-rs/tracing
|
||||
[`mio`]: https://github.com/tokio-rs/mio
|
||||
[`bytes`]: https://github.com/tokio-rs/bytes
|
||||
|
||||
## Supported Rust Versions
|
||||
|
||||
Tokio is built against the latest stable, nightly, and beta Rust releases. The
|
||||
minimum version supported is the stable release from three months before the
|
||||
current stable release version. For example, if the latest stable Rust is 1.29,
|
||||
the minimum version supported is 1.26. The current Tokio version is not
|
||||
guaranteed to build on Rust versions earlier than the minimum supported version.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### 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.
|
||||
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
# Tokio Roadmap
|
||||
|
||||
## A Roadmap to 1.0
|
||||
|
||||
The question of "why not 1.0?" has come up a few times. After all, Tokio 0.1 has
|
||||
been stable for three years. The short answer: because it isn't time. There is
|
||||
nobody who would rather ship a Tokio 1.0 than us. It also isn't something to rush.
|
||||
|
||||
After all, `async / await` only landed in the stable Rust channel weeks ago.
|
||||
There has been no significant production validation yet, except maybe fuchsia
|
||||
and that seems like a fairly specialized use case. This release of Tokio
|
||||
includes significant new code and new strategies with feature flags. Also, there
|
||||
are still big open questions, such as the [proposed changes][pr-1744] to
|
||||
`AsyncRead` and `AsyncWrite`.
|
||||
|
||||
Tokio 1.0 will be released as soon as the APIs are proven to handle real-world
|
||||
production cases.
|
||||
|
||||
### Tokio 1.0 in Q3 2020 with LTS support
|
||||
|
||||
The Tokio 1.0 release will be **no later** than Q3 2020. It will also come with
|
||||
"long-term support" guarantees:
|
||||
|
||||
* A minimum of 5 years of maintenance.
|
||||
* A minimum of 3 years before a hypothetical 2.0 release.
|
||||
|
||||
When Tokio 1.0 is released in Q3 2020, on-going support, security fixes, and
|
||||
critical bug fixes are guaranteed until **at least** Q3 2025. Tokio 2.0 will not
|
||||
be released until **at least** Q3 2023 (though, ideally there will never be a
|
||||
Tokio 2.0 release).
|
||||
|
||||
### How to get there
|
||||
|
||||
While Tokio 0.1 probably should have been a 1.0, Tokio 0.2 will be a **true**
|
||||
0.2 release. There will be breaking change releases every 2 ~ 3 months until 1.0.
|
||||
These changes will be **much** smaller than going from 0.1 -> 0.2. It is
|
||||
expected that the 1.0 release will look a lot like 0.2.
|
||||
|
||||
### What is expected to change
|
||||
|
||||
The biggest change will be the `AsyncRead` and `AsyncWrite` traits. Based on
|
||||
experience gained over the past 3 years, there are a couple of issues to
|
||||
address:
|
||||
|
||||
* Be able to **safely** use uninitialized memory as a read buffer.
|
||||
* Practical read vectored and write vectored APIs.
|
||||
|
||||
There are a few strategies to solve these problems. These strategies need to be
|
||||
investigated and the solution validated. You can see [this comment][pr-1744-comment] for a
|
||||
detailed statement of the problem.
|
||||
|
||||
The other major change, which has been in the works for a while, is updating
|
||||
Mio. Mio 0.6 was first released almost 4 years ago and has not had a breaking
|
||||
change since. Mio 0.7 has been in the works for a while. It includes a full
|
||||
rewrite of the windows support as well as a refined API. More will be written
|
||||
about this shortly.
|
||||
|
||||
Finally, now that the API is starting to stabilize, effort will be put into
|
||||
documentation. Tokio 0.2 is being released before updating the website and many
|
||||
of the old content will no longer be relevant. In the coming weeks, expect to
|
||||
see updates there.
|
||||
|
||||
So, we have our work cut out for us. We hope you enjoy this 0.2 release and are
|
||||
looking forward to your feedback and help.
|
||||
|
||||
[pr-1744]: https://github.com/tokio-rs/tokio/pull/1744
|
||||
[pr-1744-comment]: https://github.com/tokio-rs/tokio/pull/1744#issuecomment-553575438
|
||||
@@ -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
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "benches"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
bencher = "0.1.5"
|
||||
|
||||
[[bench]]
|
||||
name = "spawn"
|
||||
path = "spawn.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "mpsc"
|
||||
path = "mpsc.rs"
|
||||
harness = false
|
||||
-188
@@ -1,188 +0,0 @@
|
||||
use bencher::{black_box, Bencher};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
type Medium = [usize; 64];
|
||||
type Large = [Medium; 64];
|
||||
|
||||
fn create_1_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(1));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100));
|
||||
});
|
||||
}
|
||||
|
||||
fn create_100_000_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
black_box(&mpsc::channel::<Medium>(100_000));
|
||||
});
|
||||
}
|
||||
|
||||
fn send_medium(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = mpsc::channel::<Medium>(1000);
|
||||
|
||||
let _ = tx.try_send([0; 64]);
|
||||
|
||||
rx.try_recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn send_large(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let (mut tx, mut rx) = mpsc::channel::<Large>(1000);
|
||||
|
||||
let _ = tx.try_send([[0; 64]; 64]);
|
||||
|
||||
rx.try_recv().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_bounded_full(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(100);
|
||||
|
||||
for _ in 0..5 {
|
||||
let mut tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn contention_unbounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for _ in 0..5 {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
for i in 0..1000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for _ in 0..1_000 * 5 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_bounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (mut tx, mut rx) = mpsc::channel::<usize>(1_000_000);
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).await.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn uncontented_unbounded(b: &mut Bencher) {
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.core_threads(6)
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
|
||||
|
||||
for i in 0..5000 {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..5_000 {
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
create,
|
||||
create_1_medium,
|
||||
create_100_medium,
|
||||
create_100_000_medium
|
||||
);
|
||||
|
||||
bencher::benchmark_group!(send, send_medium, send_large);
|
||||
|
||||
bencher::benchmark_group!(
|
||||
contention,
|
||||
contention_bounded,
|
||||
contention_bounded_full,
|
||||
contention_unbounded,
|
||||
uncontented_bounded,
|
||||
uncontented_unbounded
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(create, send, contention);
|
||||
@@ -1,70 +0,0 @@
|
||||
//! Benchmark spawning a task onto the basic and threaded Tokio executors.
|
||||
//! This essentially measure the time to enqueue a task in the local and remote
|
||||
//! case.
|
||||
|
||||
use bencher::{black_box, Bencher};
|
||||
|
||||
async fn work() -> usize {
|
||||
let val = 1 + 1;
|
||||
black_box(val)
|
||||
}
|
||||
|
||||
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
bench.iter(|| {
|
||||
let h = tokio::spawn(work());
|
||||
black_box(h);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
|
||||
let mut runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
bench.iter(|| {
|
||||
let h = tokio::spawn(work());
|
||||
black_box(h);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn basic_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
fn threaded_scheduler_remote_spawn(bench: &mut Bencher) {
|
||||
let runtime = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.build()
|
||||
.unwrap();
|
||||
let handle = runtime.handle();
|
||||
bench.iter(|| {
|
||||
let h = handle.spawn(work());
|
||||
black_box(h);
|
||||
});
|
||||
}
|
||||
|
||||
bencher::benchmark_group!(
|
||||
spawn,
|
||||
basic_scheduler_local_spawn,
|
||||
threaded_scheduler_local_spawn,
|
||||
basic_scheduler_remote_spawn,
|
||||
threaded_scheduler_remote_spawn
|
||||
);
|
||||
|
||||
bencher::benchmark_main!(spawn);
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Publish a new release of a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-v, --verbose Use verbose Cargo output
|
||||
-d, --dry-run Perform a dry run (do not publish or tag the release)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
DRY_RUN=""
|
||||
VERBOSE=""
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
verify() {
|
||||
status "Verifying" "if $CRATE v$VERSION can be released"
|
||||
ACTUAL=$(cargo pkgid | sed -n 's/.*#\(.*\)/\1/p')
|
||||
|
||||
if [ "$ACTUAL" != "$VERSION" ]; then
|
||||
err "expected to release version $VERSION, but Cargo.toml contained $ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git tag -l | grep -Fxq "$TAG" ; then
|
||||
err "git tag \`$TAG\` already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATH_DEPS=$(grep -F "path = \"" Cargo.toml | sed -e 's/^/ /')
|
||||
if [ -n "$PATH_DEPS" ]; then
|
||||
err "crate \`$CRATE\` contained path dependencies:\n$PATH_DEPS"
|
||||
echo "path dependencies must be removed prior to release"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
release() {
|
||||
status "Releasing" "$CRATE v$VERSION"
|
||||
cargo package $VERBOSE
|
||||
cargo publish $VERBOSE $DRY_RUN
|
||||
|
||||
status "Tagging" "$TAG"
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
echo "# git tag $TAG && git push --tags"
|
||||
else
|
||||
git tag "$TAG" && git push --tags
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE="--verbose"
|
||||
set +x
|
||||
shift
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
(cd "$CRATE" && verify && release )
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
USAGE="Update links to docs.rs in a tokio crate
|
||||
|
||||
USAGE:
|
||||
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
|
||||
|
||||
OPTIONS:
|
||||
-d, --dry-run Perform a dry run (do not modify any file)
|
||||
-h, --help Show this help text and exit"
|
||||
|
||||
err() {
|
||||
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
|
||||
}
|
||||
|
||||
status() {
|
||||
WIDTH=12
|
||||
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
c1grep() { grep "$@" || test $? = 1; }
|
||||
|
||||
update_versions_in_doc() {
|
||||
# Print what is being/would be done
|
||||
if [ -n "$DRY_RUN" ]; then
|
||||
local MSG="Would change:"
|
||||
else
|
||||
local MSG="Updating:"
|
||||
fi
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed --quiet \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|gp" \
|
||||
| sed -e "s/^/$MSG /"
|
||||
|
||||
# Apply changes if not in dry run
|
||||
if [ -z "$DRY_RUN" ]; then
|
||||
git grep -lr "docs.rs/$CRATE/" \
|
||||
| xargs sed -i \
|
||||
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|g"
|
||||
fi
|
||||
}
|
||||
|
||||
update() {
|
||||
update_versions_in_doc
|
||||
}
|
||||
|
||||
show_outdated() {
|
||||
OUTDATED=$(git grep -rn "docs.rs/$CRATE/" \
|
||||
| c1grep -v "$VERSION" \
|
||||
| sed -e 's/^/ - /')
|
||||
if [[ -n "$OUTDATED" ]]; then
|
||||
echo "Found the following links to docs.rs with an outdated version:"
|
||||
echo "$OUTDATED"
|
||||
echo
|
||||
else
|
||||
echo "Nothing to do."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN="--dry-run"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
err "unknown flag \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
;;
|
||||
*) # crate or version
|
||||
if [ -z "$CRATE" ]; then
|
||||
CRATE="$1"
|
||||
elif [ -z "$VERSION" ]; then
|
||||
VERSION="$1"
|
||||
else
|
||||
err "unknown positional argument \"$1\""
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
err "no version specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$CRATE" ]; then
|
||||
TAG="$CRATE-$VERSION"
|
||||
else
|
||||
err "no crate specified!"
|
||||
HELP=1
|
||||
fi
|
||||
|
||||
if [ -n "$HELP" ]; then
|
||||
echo "$USAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$CRATE" ]; then
|
||||
# Does not cd in order to update everywhere
|
||||
show_outdated && update
|
||||
else
|
||||
err "no such crate \"$CRATE\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,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
|
||||
@@ -1,61 +0,0 @@
|
||||
[package]
|
||||
name = "examples"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio-util = { version = "0.2.0", path = "../tokio-util", features = ["full"] }
|
||||
bytes = "0.5"
|
||||
futures = "0.3.0"
|
||||
http = "0.2"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
httparse = "1.0"
|
||||
time = "0.1"
|
||||
|
||||
[[example]]
|
||||
name = "chat"
|
||||
path = "chat.rs"
|
||||
|
||||
[[example]]
|
||||
name = "connect"
|
||||
path = "connect.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo-udp"
|
||||
path = "echo-udp.rs"
|
||||
|
||||
[[example]]
|
||||
name = "echo"
|
||||
path = "echo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "hello_world.rs"
|
||||
|
||||
[[example]]
|
||||
name = "print_each_packet"
|
||||
path = "print_each_packet.rs"
|
||||
|
||||
[[example]]
|
||||
name = "proxy"
|
||||
path = "proxy.rs"
|
||||
|
||||
[[example]]
|
||||
name = "tinydb"
|
||||
path = "tinydb.rs"
|
||||
|
||||
[[example]]
|
||||
name = "udp-client"
|
||||
path = "udp-client.rs"
|
||||
|
||||
[[example]]
|
||||
name = "udp-codec"
|
||||
path = "udp-codec.rs"
|
||||
|
||||
[[example]]
|
||||
name = "tinyhttp"
|
||||
path = "tinyhttp.rs"
|
||||
@@ -1,20 +0,0 @@
|
||||
## Examples of how to use Tokio
|
||||
|
||||
This directory contains a number of examples showcasing various capabilities of
|
||||
the `tokio` crate.
|
||||
|
||||
All examples can be executed with:
|
||||
|
||||
```
|
||||
cargo run --example $name
|
||||
```
|
||||
|
||||
A good starting point for the examples would be [`hello_world`](hello_world.rs)
|
||||
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
|
||||
additional guides for some of the examples.
|
||||
|
||||
If you've got an example you'd like to see here, please feel free to open an
|
||||
issue. Otherwise if you've got an example you'd like to add, please feel free
|
||||
to make a PR!
|
||||
|
||||
[tokioweb]: https://tokio.rs/docs/overview/
|
||||
@@ -1,258 +0,0 @@
|
||||
//! A chat server that broadcasts a message to all connections.
|
||||
//!
|
||||
//! This example is explicitly more verbose than it has to be. This is to
|
||||
//! illustrate more concepts.
|
||||
//!
|
||||
//! A chat server for telnet clients. After a telnet client connects, the first
|
||||
//! line should contain the client's name. After that, all lines sent by a
|
||||
//! client are broadcasted to all other connected clients.
|
||||
//!
|
||||
//! Because the client is telnet, lines are delimited by "\r\n".
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! cargo run --example chat
|
||||
//!
|
||||
//! And then in another terminal run:
|
||||
//!
|
||||
//! telnet localhost 6142
|
||||
//!
|
||||
//! You can run the `telnet` command in any number of additional windows.
|
||||
//!
|
||||
//! You can run the second command in multiple windows and then chat between the
|
||||
//! two, seeing the messages from the other client as they're received. For all
|
||||
//! connected clients they'll all join the same room and see everyone else's
|
||||
//! messages.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::stream::{Stream, StreamExt};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
|
||||
|
||||
use futures::SinkExt;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Create the shared state. This is how all the peers communicate.
|
||||
//
|
||||
// The server task will hold a handle to this. For every new client, the
|
||||
// `state` handle is cloned and passed into the task that processes the
|
||||
// client connection.
|
||||
let state = Arc::new(Mutex::new(Shared::new()));
|
||||
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:6142".to_string());
|
||||
|
||||
// Bind a TCP listener to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpListener, which is fully async.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
println!("server running on {}", addr);
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound TcpStream.
|
||||
let (stream, addr) = listener.accept().await?;
|
||||
|
||||
// Clone a handle to the `Shared` state for the new connection.
|
||||
let state = Arc::clone(&state);
|
||||
|
||||
// Spawn our handler to be run asynchronously.
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process(state, stream, addr).await {
|
||||
println!("an error occurred; error = {:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorthand for the transmit half of the message channel.
|
||||
type Tx = mpsc::UnboundedSender<String>;
|
||||
|
||||
/// Shorthand for the receive half of the message channel.
|
||||
type Rx = mpsc::UnboundedReceiver<String>;
|
||||
|
||||
/// Data that is shared between all peers in the chat server.
|
||||
///
|
||||
/// This is the set of `Tx` handles for all connected clients. Whenever a
|
||||
/// message is received from a client, it is broadcasted to all peers by
|
||||
/// iterating over the `peers` entries and sending a copy of the message on each
|
||||
/// `Tx`.
|
||||
struct Shared {
|
||||
peers: HashMap<SocketAddr, Tx>,
|
||||
}
|
||||
|
||||
/// The state for each connected client.
|
||||
struct Peer {
|
||||
/// The TCP socket wrapped with the `Lines` codec, defined below.
|
||||
///
|
||||
/// This handles sending and receiving data on the socket. When using
|
||||
/// `Lines`, we can work at the line level instead of having to manage the
|
||||
/// raw byte operations.
|
||||
lines: Framed<TcpStream, LinesCodec>,
|
||||
|
||||
/// Receive half of the message channel.
|
||||
///
|
||||
/// This is used to receive messages from peers. When a message is received
|
||||
/// off of this `Rx`, it will be written to the socket.
|
||||
rx: Rx,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
/// Create a new, empty, instance of `Shared`.
|
||||
fn new() -> Self {
|
||||
Shared {
|
||||
peers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a `LineCodec` encoded message to every peer, except
|
||||
/// for the sender.
|
||||
async fn broadcast(&mut self, sender: SocketAddr, message: &str) {
|
||||
for peer in self.peers.iter_mut() {
|
||||
if *peer.0 != sender {
|
||||
let _ = peer.1.send(message.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Peer {
|
||||
/// Create a new instance of `Peer`.
|
||||
async fn new(
|
||||
state: Arc<Mutex<Shared>>,
|
||||
lines: Framed<TcpStream, LinesCodec>,
|
||||
) -> io::Result<Peer> {
|
||||
// Get the client socket address
|
||||
let addr = lines.get_ref().peer_addr()?;
|
||||
|
||||
// Create a channel for this peer
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
// Add an entry for this `Peer` in the shared state map.
|
||||
state.lock().await.peers.insert(addr, tx);
|
||||
|
||||
Ok(Peer { lines, rx })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Message {
|
||||
/// A message that should be broadcasted to others.
|
||||
Broadcast(String),
|
||||
|
||||
/// A message that should be received by a client
|
||||
Received(String),
|
||||
}
|
||||
|
||||
// Peer implements `Stream` in a way that polls both the `Rx`, and `Framed` types.
|
||||
// A message is produced whenever an event is ready until the `Framed` stream returns `None`.
|
||||
impl Stream for Peer {
|
||||
type Item = Result<Message, LinesCodecError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// First poll the `UnboundedReceiver`.
|
||||
|
||||
if let Poll::Ready(Some(v)) = Pin::new(&mut self.rx).poll_next(cx) {
|
||||
return Poll::Ready(Some(Ok(Message::Received(v))));
|
||||
}
|
||||
|
||||
// Secondly poll the `Framed` stream.
|
||||
let result: Option<_> = futures::ready!(Pin::new(&mut self.lines).poll_next(cx));
|
||||
|
||||
Poll::Ready(match result {
|
||||
// We've received a message we should broadcast to others.
|
||||
Some(Ok(message)) => Some(Ok(Message::Broadcast(message))),
|
||||
|
||||
// An error occurred.
|
||||
Some(Err(e)) => Some(Err(e)),
|
||||
|
||||
// The stream has been exhausted.
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an individual chat client
|
||||
async fn process(
|
||||
state: Arc<Mutex<Shared>>,
|
||||
stream: TcpStream,
|
||||
addr: SocketAddr,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut lines = Framed::new(stream, LinesCodec::new());
|
||||
|
||||
// Send a prompt to the client to enter their username.
|
||||
lines
|
||||
.send(String::from("Please enter your username:"))
|
||||
.await?;
|
||||
|
||||
// Read the first line from the `LineCodec` stream to get the username.
|
||||
let username = match lines.next().await {
|
||||
Some(Ok(line)) => line,
|
||||
// We didn't get a line so we return early here.
|
||||
_ => {
|
||||
println!("Failed to get username from {}. Client disconnected.", addr);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// Register our peer with state which internally sets up some channels.
|
||||
let mut peer = Peer::new(state.clone(), lines).await?;
|
||||
|
||||
// A client has connected, let's let everyone know.
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{} has joined the chat", username);
|
||||
println!("{}", msg);
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
// Process incoming messages until our stream is exhausted by a disconnect.
|
||||
while let Some(result) = peer.next().await {
|
||||
match result {
|
||||
// A message was received from the current user, we should
|
||||
// broadcast this message to the other users.
|
||||
Ok(Message::Broadcast(msg)) => {
|
||||
let mut state = state.lock().await;
|
||||
let msg = format!("{}: {}", username, msg);
|
||||
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
// A message was received from a peer. Send it to the
|
||||
// current user.
|
||||
Ok(Message::Received(msg)) => {
|
||||
peer.lines.send(msg).await?;
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"an error occurred while processing messages for {}; error = {:?}",
|
||||
username, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this section is reached it means that the client was disconnected!
|
||||
// Let's let everyone still connected know about it.
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
state.peers.remove(&addr);
|
||||
|
||||
let msg = format!("{} has left the chat", username);
|
||||
println!("{}", msg);
|
||||
state.broadcast(addr, &msg).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
//! An example of hooking up stdin/stdout to either a TCP or UDP stream.
|
||||
//!
|
||||
//! This example will connect to a socket address specified in the argument list
|
||||
//! and then forward all data read on stdin to the server, printing out all data
|
||||
//! received on stdout. An optional `--udp` argument can be passed to specify
|
||||
//! that the connection should be made over UDP instead of TCP, translating each
|
||||
//! line entered on stdin to a UDP packet to be sent to the remote address.
|
||||
//!
|
||||
//! Note that this is not currently optimized for performance, especially
|
||||
//! around buffer management. Rather it's intended to show an example of
|
||||
//! working with a client.
|
||||
//!
|
||||
//! This example can be quite useful when interacting with the other examples in
|
||||
//! this repository! Many of them recommend running this as a simple "hook up
|
||||
//! stdin/stdout to a server" to get up and running.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use futures::StreamExt;
|
||||
use tokio::io;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Determine if we're going to run in TCP or UDP mode
|
||||
let mut args = env::args().skip(1).collect::<Vec<_>>();
|
||||
let tcp = match args.iter().position(|a| a == "--udp") {
|
||||
Some(i) => {
|
||||
args.remove(i);
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
// Parse what address we're going to connect to
|
||||
let addr = args
|
||||
.first()
|
||||
.ok_or("this program requires at least one argument")?;
|
||||
let addr = addr.parse::<SocketAddr>()?;
|
||||
|
||||
let stdin = FramedRead::new(io::stdin(), BytesCodec::new());
|
||||
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
|
||||
let stdout = FramedWrite::new(io::stdout(), BytesCodec::new());
|
||||
|
||||
if tcp {
|
||||
tcp::connect(&addr, stdin, stdout).await?;
|
||||
} else {
|
||||
udp::connect(&addr, stdin, stdout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod tcp {
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::{error::Error, io, net::SocketAddr};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = TcpStream::connect(addr).await?;
|
||||
let (r, w) = stream.split();
|
||||
let mut sink = FramedWrite::new(w, BytesCodec::new());
|
||||
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
|
||||
// on the event of an Error, log the error and end the stream
|
||||
let mut stream = FramedRead::new(r, BytesCodec::new())
|
||||
.filter_map(|i| match i {
|
||||
//BytesMut into Bytes
|
||||
Ok(i) => future::ready(Some(i.freeze())),
|
||||
Err(e) => {
|
||||
println!("failed to read from socket; error={}", e);
|
||||
future::ready(None)
|
||||
}
|
||||
})
|
||||
.map(Ok);
|
||||
|
||||
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
|
||||
(Err(e), _) | (_, Err(e)) => Err(e.into()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod udp {
|
||||
use bytes::Bytes;
|
||||
use futures::{future, Sink, SinkExt, Stream, StreamExt};
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::udp::{RecvHalf, SendHalf};
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
pub async fn connect(
|
||||
addr: &SocketAddr,
|
||||
stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
// We'll bind our UDP socket to a local IP/port, but for now we
|
||||
// basically let the OS pick both of those.
|
||||
let bind_addr = if addr.ip().is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
};
|
||||
|
||||
let socket = UdpSocket::bind(&bind_addr).await?;
|
||||
socket.connect(addr).await?;
|
||||
let (mut r, mut w) = socket.split();
|
||||
|
||||
future::try_join(send(stdin, &mut w), recv(stdout, &mut r)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send(
|
||||
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
|
||||
writer: &mut SendHalf,
|
||||
) -> Result<(), io::Error> {
|
||||
while let Some(item) = stdin.next().await {
|
||||
let buf = item?;
|
||||
writer.send(&buf[..]).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
|
||||
reader: &mut RecvHalf,
|
||||
) -> Result<(), io::Error> {
|
||||
loop {
|
||||
let mut buf = vec![0; 1024];
|
||||
let n = reader.recv(&mut buf[..]).await?;
|
||||
|
||||
if n > 0 {
|
||||
stdout.send(Bytes::from(buf)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
//! An UDP echo server that just sends back everything that it receives.
|
||||
//!
|
||||
//! If you're on Unix you can test this out by in one terminal executing:
|
||||
//!
|
||||
//! cargo run --example echo-udp
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect -- --udp 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `nc` terminal should be echo'd back to you!
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
use std::{env, io};
|
||||
use tokio;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
struct Server {
|
||||
socket: UdpSocket,
|
||||
buf: Vec<u8>,
|
||||
to_send: Option<(usize, SocketAddr)>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
async fn run(self) -> Result<(), io::Error> {
|
||||
let Server {
|
||||
mut socket,
|
||||
mut buf,
|
||||
mut to_send,
|
||||
} = self;
|
||||
|
||||
loop {
|
||||
// First we check to see if there's a message we need to echo back.
|
||||
// If so then we try to send it back to the original source, waiting
|
||||
// until it's writable and we're able to do so.
|
||||
if let Some((size, peer)) = to_send {
|
||||
let amt = socket.send_to(&buf[..size], &peer).await?;
|
||||
|
||||
println!("Echoed {}/{} bytes to {}", amt, size, peer);
|
||||
}
|
||||
|
||||
// If we're here then `to_send` is `None`, so we take a look for the
|
||||
// next message we're going to echo back.
|
||||
to_send = Some(socket.recv_from(&mut buf).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let socket = UdpSocket::bind(&addr).await?;
|
||||
println!("Listening on: {}", socket.local_addr()?);
|
||||
|
||||
let server = Server {
|
||||
socket,
|
||||
buf: vec![0; 1024],
|
||||
to_send: None,
|
||||
};
|
||||
|
||||
// This starts the server task.
|
||||
server.run().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
//! A "hello world" echo server with Tokio
|
||||
//!
|
||||
//! This server will create a TCP listener, accept connections in a loop, and
|
||||
//! write back everything that's read off of each TCP connection.
|
||||
//!
|
||||
//! Because the Tokio runtime uses a thread pool, each TCP connection is
|
||||
//! processed concurrently with all other TCP connections across multiple
|
||||
//! threads.
|
||||
//!
|
||||
//! To see this server in action, you can run this in one terminal:
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be echo'd back to
|
||||
//! you! If you open up multiple terminals running the `connect` example you
|
||||
//! should be able to see them all make progress simultaneously.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
// program, but otherwise we'll just set up our TCP listener on
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
// And this is where much of the magic of this server happens. We
|
||||
// crucially want all clients to make progress concurrently, rather than
|
||||
// blocking one on completion of another. To achieve this we use the
|
||||
// `tokio::spawn` function to execute the work in the background.
|
||||
//
|
||||
// Essentially here we're executing a new task to run concurrently,
|
||||
// which will allow all of our clients to be processed concurrently.
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
let n = socket
|
||||
.read(&mut buf)
|
||||
.await
|
||||
.expect("failed to read data from socket");
|
||||
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
socket
|
||||
.write_all(&buf[0..n])
|
||||
.await
|
||||
.expect("failed to write data to socket");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//! Hello world server.
|
||||
//!
|
||||
//! A simple client that opens a TCP stream, writes "hello world\n", and closes
|
||||
//! the connection.
|
||||
//!
|
||||
//! You can test this out by running:
|
||||
//!
|
||||
//! ncat -l 6142
|
||||
//!
|
||||
//! And then in another terminal run:
|
||||
//!
|
||||
//! cargo run --example hello_world
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Open a TCP stream to the socket address.
|
||||
//
|
||||
// Note that this is the Tokio TcpStream, which is fully async.
|
||||
let mut stream = TcpStream::connect("127.0.0.1:6142").await?;
|
||||
println!("created stream");
|
||||
|
||||
let result = stream.write(b"hello world\n").await;
|
||||
println!("wrote to stream; success={:?}", result.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_signal;
|
||||
|
||||
use futures::stream::Stream;
|
||||
use tokio_core::reactor::Core;
|
||||
|
||||
fn main() {
|
||||
let mut core = Core::new().unwrap();
|
||||
let ctrlc = tokio_signal::ctrl_c(&core.handle());
|
||||
let stream = core.run(ctrlc).unwrap();
|
||||
|
||||
core.run(stream.for_each(|()| {
|
||||
println!("Ctrl-C received!");
|
||||
Ok(())
|
||||
})).unwrap();
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
//! A "print-each-packet" server with Tokio
|
||||
//!
|
||||
//! This server will create a TCP listener, accept connections in a loop, and
|
||||
//! put down in the stdout everything that's read off of each TCP connection.
|
||||
//!
|
||||
//! Because the Tokio runtime uses a thread pool, each TCP connection is
|
||||
//! processed concurrently with all other TCP connections across multiple
|
||||
//! threads.
|
||||
//!
|
||||
//! To see this server in action, you can run this in one terminal:
|
||||
//!
|
||||
//! cargo run --example print\_each\_packet
|
||||
//!
|
||||
//! and in another terminal you can run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! Each line you type in to the `connect` terminal should be written to terminal!
|
||||
//!
|
||||
//! Minimal js example:
|
||||
//!
|
||||
//! ```js
|
||||
//! var net = require("net");
|
||||
//!
|
||||
//! var listenPort = 8080;
|
||||
//!
|
||||
//! var server = net.createServer(function (socket) {
|
||||
//! socket.on("data", function (bytes) {
|
||||
//! console.log("bytes", bytes);
|
||||
//! });
|
||||
//!
|
||||
//! socket.on("end", function() {
|
||||
//! console.log("Socket received FIN packet and closed connection");
|
||||
//! });
|
||||
//! socket.on("error", function (error) {
|
||||
//! console.log("Socket closed with error", error);
|
||||
//! });
|
||||
//!
|
||||
//! socket.on("close", function (with_error) {
|
||||
//! if (with_error) {
|
||||
//! console.log("Socket closed with result: Err(SomeError)");
|
||||
//! } else {
|
||||
//! console.log("Socket closed with result: Ok(())");
|
||||
//! }
|
||||
//! });
|
||||
//!
|
||||
//! });
|
||||
//!
|
||||
//! server.listen(listenPort);
|
||||
//!
|
||||
//! console.log("Listening on:", listenPort);
|
||||
//! ```
|
||||
//!
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{BytesCodec, Decoder};
|
||||
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Allow passing an address to listen on as the first argument of this
|
||||
// program, but otherwise we'll just set up our TCP listener on
|
||||
// 127.0.0.1:8080 for connections.
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
// Next up we create a TCP listener which will listen for incoming
|
||||
// connections. This TCP listener is bound to the address we determined
|
||||
// above and must be associated with an event loop, so we pass in a handle
|
||||
// to our event loop. After the socket's created we inform that we're ready
|
||||
// to go and start accepting connections.
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
// Asynchronously wait for an inbound socket.
|
||||
let (socket, _) = listener.accept().await?;
|
||||
|
||||
// And this is where much of the magic of this server happens. We
|
||||
// crucially want all clients to make progress concurrently, rather than
|
||||
// blocking one on completion of another. To achieve this we use the
|
||||
// `tokio::spawn` function to execute the work in the background.
|
||||
//
|
||||
// Essentially here we're executing a new task to run concurrently,
|
||||
// which will allow all of our clients to be processed concurrently.
|
||||
tokio::spawn(async move {
|
||||
// We're parsing each socket with the `BytesCodec` included in `tokio::codec`.
|
||||
let mut framed = BytesCodec::new().framed(socket);
|
||||
|
||||
// We loop while there are messages coming from the Stream `framed`.
|
||||
// The stream will return None once the client disconnects.
|
||||
while let Some(message) = framed.next().await {
|
||||
match message {
|
||||
Ok(bytes) => println!("bytes: {:?}", bytes),
|
||||
Err(err) => println!("Socket closed with error: {:?}", err),
|
||||
}
|
||||
}
|
||||
println!("Socket received FIN packet and closed connection");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
//! A proxy that forwards data to another server and forwards that server's
|
||||
//! responses back to clients.
|
||||
//!
|
||||
//! Because the Tokio runtime uses a thread pool, each TCP connection is
|
||||
//! processed concurrently with all other TCP connections across multiple
|
||||
//! threads.
|
||||
//!
|
||||
//! You can showcase this by running this in one terminal:
|
||||
//!
|
||||
//! cargo run --example proxy
|
||||
//!
|
||||
//! This in another terminal
|
||||
//!
|
||||
//! cargo run --example echo
|
||||
//!
|
||||
//! And finally this in another terminal
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8081
|
||||
//!
|
||||
//! This final terminal will connect to our proxy, which will in turn connect to
|
||||
//! the echo server, and you'll be able to see data flowing between them.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use futures::future::try_join;
|
||||
use futures::FutureExt;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let listen_addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8081".to_string());
|
||||
let server_addr = env::args()
|
||||
.nth(2)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
println!("Listening on: {}", listen_addr);
|
||||
println!("Proxying to: {}", server_addr);
|
||||
|
||||
let mut listener = TcpListener::bind(listen_addr).await?;
|
||||
|
||||
while let Ok((inbound, _)) = listener.accept().await {
|
||||
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
|
||||
if let Err(e) = r {
|
||||
println!("Failed to transfer; error={}", e);
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(transfer);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<dyn Error>> {
|
||||
let mut outbound = TcpStream::connect(proxy_addr).await?;
|
||||
|
||||
let (mut ri, mut wi) = inbound.split();
|
||||
let (mut ro, mut wo) = outbound.split();
|
||||
|
||||
let client_to_server = io::copy(&mut ri, &mut wo);
|
||||
let server_to_client = io::copy(&mut ro, &mut wi);
|
||||
|
||||
try_join(client_to_server, server_to_client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
//! A "tiny database" and accompanying protocol
|
||||
//!
|
||||
//! This example shows the usage of shared state amongst all connected clients,
|
||||
//! namely a database of key/value pairs. Each connected client can send a
|
||||
//! series of GET/SET commands to query the current value of a key or set the
|
||||
//! value of a key.
|
||||
//!
|
||||
//! This example has a simple protocol you can use to interact with the server.
|
||||
//! To run, first run this in one terminal window:
|
||||
//!
|
||||
//! cargo run --example tinydb
|
||||
//!
|
||||
//! and next in another windows run:
|
||||
//!
|
||||
//! cargo run --example connect 127.0.0.1:8080
|
||||
//!
|
||||
//! In the `connect` window you can type in commands where when you hit enter
|
||||
//! you'll get a response from the server for that command. An example session
|
||||
//! is:
|
||||
//!
|
||||
//!
|
||||
//! $ cargo run --example connect 127.0.0.1:8080
|
||||
//! GET foo
|
||||
//! foo = bar
|
||||
//! GET FOOBAR
|
||||
//! error: no key FOOBAR
|
||||
//! SET FOOBAR my awesome string
|
||||
//! set FOOBAR = `my awesome string`, previous: None
|
||||
//! SET foo tokio
|
||||
//! set foo = `tokio`, previous: Some("bar")
|
||||
//! GET foo
|
||||
//! foo = tokio
|
||||
//!
|
||||
//! Namely you can issue two forms of commands:
|
||||
//!
|
||||
//! * `GET $key` - this will fetch the value of `$key` from the database and
|
||||
//! return it. The server's database is initially populated with the key `foo`
|
||||
//! set to the value `bar`
|
||||
//! * `SET $key $value` - this will set the value of `$key` to `$value`,
|
||||
//! returning the previous value, if any.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio_util::codec::{Framed, LinesCodec};
|
||||
|
||||
use futures::SinkExt;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// The in-memory database shared amongst all clients.
|
||||
///
|
||||
/// This database will be shared via `Arc`, so to mutate the internal map we're
|
||||
/// going to use a `Mutex` for interior mutability.
|
||||
struct Database {
|
||||
map: Mutex<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// Possible requests our clients can send us
|
||||
enum Request {
|
||||
Get { key: String },
|
||||
Set { key: String, value: String },
|
||||
}
|
||||
|
||||
/// Responses to the `Request` commands above
|
||||
enum Response {
|
||||
Value {
|
||||
key: String,
|
||||
value: String,
|
||||
},
|
||||
Set {
|
||||
key: String,
|
||||
value: String,
|
||||
previous: Option<String>,
|
||||
},
|
||||
Error {
|
||||
msg: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse the address we're going to run this server on
|
||||
// and set up our TCP listener to accept connections.
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
|
||||
let mut listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
// Create the shared state of this server that will be shared amongst all
|
||||
// clients. We populate the initial database and then create the `Database`
|
||||
// structure. Note the usage of `Arc` here which will be used to ensure that
|
||||
// each independently spawned client will have a reference to the in-memory
|
||||
// database.
|
||||
let mut initial_db = HashMap::new();
|
||||
initial_db.insert("foo".to_string(), "bar".to_string());
|
||||
let db = Arc::new(Database {
|
||||
map: Mutex::new(initial_db),
|
||||
});
|
||||
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((socket, _)) => {
|
||||
// After getting a new connection first we see a clone of the database
|
||||
// being created, which is creating a new reference for this connected
|
||||
// client to use.
|
||||
let db = db.clone();
|
||||
|
||||
// Like with other small servers, we'll `spawn` this client to ensure it
|
||||
// runs concurrently with all other clients. The `move` keyword is used
|
||||
// here to move ownership of our db handle into the async closure.
|
||||
tokio::spawn(async move {
|
||||
// Since our protocol is line-based we use `tokio_codecs`'s `LineCodec`
|
||||
// to convert our stream of bytes, `socket`, into a `Stream` of lines
|
||||
// as well as convert our line based responses into a stream of bytes.
|
||||
let mut lines = Framed::new(socket, LinesCodec::new());
|
||||
|
||||
// Here for every line we get back from the `Framed` decoder,
|
||||
// we parse the request, and if it's valid we generate a response
|
||||
// based on the values in the database.
|
||||
while let Some(result) = lines.next().await {
|
||||
match result {
|
||||
Ok(line) => {
|
||||
let response = handle_request(&line, &db);
|
||||
|
||||
let response = response.serialize();
|
||||
|
||||
if let Err(e) = lines.send(response).await {
|
||||
println!("error on sending response; error = {:?}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error on decoding from socket; error = {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The connection will be closed at this point as `lines.next()` has returned `None`.
|
||||
});
|
||||
}
|
||||
Err(e) => println!("error accepting socket; error = {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request(line: &str, db: &Arc<Database>) -> Response {
|
||||
let request = match Request::parse(&line) {
|
||||
Ok(req) => req,
|
||||
Err(e) => return Response::Error { msg: e },
|
||||
};
|
||||
|
||||
let mut db = db.map.lock().unwrap();
|
||||
match request {
|
||||
Request::Get { key } => match db.get(&key) {
|
||||
Some(value) => Response::Value {
|
||||
key,
|
||||
value: value.clone(),
|
||||
},
|
||||
None => Response::Error {
|
||||
msg: format!("no key {}", key),
|
||||
},
|
||||
},
|
||||
Request::Set { key, value } => {
|
||||
let previous = db.insert(key.clone(), value.clone());
|
||||
Response::Set {
|
||||
key,
|
||||
value,
|
||||
previous,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
fn parse(input: &str) -> Result<Request, String> {
|
||||
let mut parts = input.splitn(3, ' ');
|
||||
match parts.next() {
|
||||
Some("GET") => {
|
||||
let key = parts.next().ok_or("GET must be followed by a key")?;
|
||||
if parts.next().is_some() {
|
||||
return Err("GET's key must not be followed by anything".into());
|
||||
}
|
||||
Ok(Request::Get {
|
||||
key: key.to_string(),
|
||||
})
|
||||
}
|
||||
Some("SET") => {
|
||||
let key = match parts.next() {
|
||||
Some(key) => key,
|
||||
None => return Err("SET must be followed by a key".into()),
|
||||
};
|
||||
let value = match parts.next() {
|
||||
Some(value) => value,
|
||||
None => return Err("SET needs a value".into()),
|
||||
};
|
||||
Ok(Request::Set {
|
||||
key: key.to_string(),
|
||||
value: value.to_string(),
|
||||
})
|
||||
}
|
||||
Some(cmd) => Err(format!("unknown command: {}", cmd)),
|
||||
None => Err("empty input".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
fn serialize(&self) -> String {
|
||||
match *self {
|
||||
Response::Value { ref key, ref value } => format!("{} = {}", key, value),
|
||||
Response::Set {
|
||||
ref key,
|
||||
ref value,
|
||||
ref previous,
|
||||
} => format!("set {} = `{}`, previous: {:?}", key, value, previous),
|
||||
Response::Error { ref msg } => format!("error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
//! A "tiny" example of HTTP request/response handling using transports.
|
||||
//!
|
||||
//! This example is intended for *learning purposes* to see how various pieces
|
||||
//! hook up together and how HTTP can get up and running. Note that this example
|
||||
//! is written with the restriction that it *can't* use any "big" library other
|
||||
//! than Tokio, if you'd like a "real world" HTTP library you likely want a
|
||||
//! crate like Hyper.
|
||||
//!
|
||||
//! Code here is based on the `echo-threads` example and implements two paths,
|
||||
//! the `/plaintext` and `/json` routes to respond with some text and json,
|
||||
//! respectively. By default this will run I/O on all the cores your system has
|
||||
//! available, and it doesn't support HTTP request bodies.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use bytes::BytesMut;
|
||||
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;
|
||||
use tokio_util::codec::{Decoder, Encoder, Framed};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse the arguments, bind the TCP socket we'll be listening to, spin up
|
||||
// our worker threads, and start shipping sockets to those worker threads.
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
|
||||
let mut server = TcpListener::bind(&addr).await?;
|
||||
let mut incoming = server.incoming();
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
while let Some(Ok(stream)) = incoming.next().await {
|
||||
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>> {
|
||||
let mut transport = Framed::new(stream, Http);
|
||||
|
||||
while let Some(request) = transport.next().await {
|
||||
match request {
|
||||
Ok(request) => {
|
||||
let response = respond(request).await?;
|
||||
transport.send(response).await?;
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
|
||||
let mut response = Response::builder();
|
||||
let body = match req.uri().path() {
|
||||
"/plaintext" => {
|
||||
response = response.header("Content-Type", "text/plain");
|
||||
"Hello, World!".to_string()
|
||||
}
|
||||
"/json" => {
|
||||
response = response.header("Content-Type", "application/json");
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Message {
|
||||
message: &'static str,
|
||||
}
|
||||
serde_json::to_string(&Message {
|
||||
message: "Hello, World!",
|
||||
})?
|
||||
}
|
||||
_ => {
|
||||
response = response.status(StatusCode::NOT_FOUND);
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
let response = response
|
||||
.body(body)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
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>;
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, item: Response<String>, dst: &mut BytesMut) -> io::Result<()> {
|
||||
use std::fmt::Write;
|
||||
|
||||
write!(
|
||||
BytesWrite(dst),
|
||||
"\
|
||||
HTTP/1.1 {}\r\n\
|
||||
Server: Example\r\n\
|
||||
Content-Length: {}\r\n\
|
||||
Date: {}\r\n\
|
||||
",
|
||||
item.status(),
|
||||
item.body().len(),
|
||||
date::now()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (k, v) in item.headers() {
|
||||
dst.extend_from_slice(k.as_str().as_bytes());
|
||||
dst.extend_from_slice(b": ");
|
||||
dst.extend_from_slice(v.as_bytes());
|
||||
dst.extend_from_slice(b"\r\n");
|
||||
}
|
||||
|
||||
dst.extend_from_slice(b"\r\n");
|
||||
dst.extend_from_slice(item.body().as_bytes());
|
||||
|
||||
return Ok(());
|
||||
|
||||
// Right now `write!` on `Vec<u8>` goes through io::Write and is not
|
||||
// super speedy, so inline a less-crufty implementation here which
|
||||
// doesn't go through io::Error.
|
||||
struct BytesWrite<'a>(&'a mut BytesMut);
|
||||
|
||||
impl fmt::Write for BytesWrite<'_> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.0.extend_from_slice(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
|
||||
fmt::write(self, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of decoding an HTTP request from the bytes we've read so far.
|
||||
/// This leverages the `httparse` crate to do the actual parsing and then we use
|
||||
/// that information to construct an instance of a `http::Request` object,
|
||||
/// trying to avoid allocations where possible.
|
||||
impl Decoder for Http {
|
||||
type Item = Request<()>;
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> io::Result<Option<Request<()>>> {
|
||||
// TODO: we should grow this headers array if parsing fails and asks
|
||||
// for more headers
|
||||
let mut headers = [None; 16];
|
||||
let (method, path, version, amt) = {
|
||||
let mut parsed_headers = [httparse::EMPTY_HEADER; 16];
|
||||
let mut r = httparse::Request::new(&mut parsed_headers);
|
||||
let status = r.parse(src).map_err(|e| {
|
||||
let msg = format!("failed to parse http request: {:?}", e);
|
||||
io::Error::new(io::ErrorKind::Other, msg)
|
||||
})?;
|
||||
|
||||
let amt = match status {
|
||||
httparse::Status::Complete(amt) => amt,
|
||||
httparse::Status::Partial => return Ok(None),
|
||||
};
|
||||
|
||||
let toslice = |a: &[u8]| {
|
||||
let start = a.as_ptr() as usize - src.as_ptr() as usize;
|
||||
assert!(start < src.len());
|
||||
(start, start + a.len())
|
||||
};
|
||||
|
||||
for (i, header) in r.headers.iter().enumerate() {
|
||||
let k = toslice(header.name.as_bytes());
|
||||
let v = toslice(header.value);
|
||||
headers[i] = Some((k, v));
|
||||
}
|
||||
|
||||
(
|
||||
toslice(r.method.unwrap().as_bytes()),
|
||||
toslice(r.path.unwrap().as_bytes()),
|
||||
r.version.unwrap(),
|
||||
amt,
|
||||
)
|
||||
};
|
||||
if version != 1 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"only HTTP/1.1 accepted",
|
||||
));
|
||||
}
|
||||
let data = src.split_to(amt).freeze();
|
||||
let mut ret = Request::builder();
|
||||
ret = ret.method(&data[method.0..method.1]);
|
||||
let s = data.slice(path.0..path.1);
|
||||
let s = unsafe { String::from_utf8_unchecked(Vec::from(s.as_ref())) };
|
||||
ret = ret.uri(s);
|
||||
ret = ret.version(http::Version::HTTP_11);
|
||||
for header in headers.iter() {
|
||||
let (k, v) = match *header {
|
||||
Some((ref k, ref v)) => (k, v),
|
||||
None => break,
|
||||
};
|
||||
let value = HeaderValue::from_bytes(data.slice(v.0..v.1).as_ref())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "header decode error"))?;
|
||||
ret = ret.header(&data[k.0..k.1], value);
|
||||
}
|
||||
|
||||
let req = ret
|
||||
.body(())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok(Some(req))
|
||||
}
|
||||
}
|
||||
|
||||
mod date {
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::{self, Write};
|
||||
use std::str;
|
||||
|
||||
use time::{self, Duration};
|
||||
|
||||
pub struct Now(());
|
||||
|
||||
/// Returns a struct, which when formatted, renders an appropriate `Date`
|
||||
/// header value.
|
||||
pub fn now() -> Now {
|
||||
Now(())
|
||||
}
|
||||
|
||||
// Gee Alex, doesn't this seem like premature optimization. Well you see
|
||||
// there Billy, you're absolutely correct! If your server is *bottlenecked*
|
||||
// on rendering the `Date` header, well then boy do I have news for you, you
|
||||
// don't need this optimization.
|
||||
//
|
||||
// In all seriousness, though, a simple "hello world" benchmark which just
|
||||
// sends back literally "hello world" with standard headers actually is
|
||||
// bottlenecked on rendering a date into a byte buffer. Since it was at the
|
||||
// top of a profile, and this was done for some competitive benchmarks, this
|
||||
// module was written.
|
||||
//
|
||||
// Just to be clear, though, I was not intending on doing this because it
|
||||
// really does seem kinda absurd, but it was done by someone else [1], so I
|
||||
// blame them! :)
|
||||
//
|
||||
// [1]: https://github.com/rapidoid/rapidoid/blob/f1c55c0555007e986b5d069fe1086e6d09933f7b/rapidoid-commons/src/main/java/org/rapidoid/commons/Dates.java#L48-L66
|
||||
|
||||
struct LastRenderedNow {
|
||||
bytes: [u8; 128],
|
||||
amt: usize,
|
||||
next_update: time::Timespec,
|
||||
}
|
||||
|
||||
thread_local!(static LAST: RefCell<LastRenderedNow> = RefCell::new(LastRenderedNow {
|
||||
bytes: [0; 128],
|
||||
amt: 0,
|
||||
next_update: time::Timespec::new(0, 0),
|
||||
}));
|
||||
|
||||
impl fmt::Display for Now {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
LAST.with(|cache| {
|
||||
let mut cache = cache.borrow_mut();
|
||||
let now = time::get_time();
|
||||
if now >= cache.next_update {
|
||||
cache.update(now);
|
||||
}
|
||||
f.write_str(cache.buffer())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl LastRenderedNow {
|
||||
fn buffer(&self) -> &str {
|
||||
str::from_utf8(&self.bytes[..self.amt]).unwrap()
|
||||
}
|
||||
|
||||
fn update(&mut self, now: time::Timespec) {
|
||||
self.amt = 0;
|
||||
write!(LocalBuffer(self), "{}", time::at(now).rfc822()).unwrap();
|
||||
self.next_update = now + Duration::seconds(1);
|
||||
self.next_update.nsec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalBuffer<'a>(&'a mut LastRenderedNow);
|
||||
|
||||
impl fmt::Write for LocalBuffer<'_> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
let start = self.0.amt;
|
||||
let end = start + s.len();
|
||||
self.0.bytes[start..end].copy_from_slice(s.as_bytes());
|
||||
self.0.amt += s.len();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
//! A UDP client that just sends everything it gets via `stdio` in a single datagram, and then
|
||||
//! waits for a reply.
|
||||
//!
|
||||
//! For the reasons of simplicity data from `stdio` is read until `EOF` in a blocking manner.
|
||||
//!
|
||||
//! You can test this out by running an echo server:
|
||||
//!
|
||||
//! ```
|
||||
//! $ cargo run --example echo-udp -- 127.0.0.1:8080
|
||||
//! ```
|
||||
//!
|
||||
//! and running the client in another terminal:
|
||||
//!
|
||||
//! ```
|
||||
//! $ cargo run --example udp-client
|
||||
//! ```
|
||||
//!
|
||||
//! You can optionally provide any custom endpoint address for the client:
|
||||
//!
|
||||
//! ```
|
||||
//! $ cargo run --example udp-client -- 127.0.0.1:8080
|
||||
//! ```
|
||||
//!
|
||||
//! Don't forget to pass `EOF` to the standard input of the client!
|
||||
//!
|
||||
//! Please mind that since the UDP protocol doesn't have any capabilities to detect a broken
|
||||
//! connection the server needs to be run first, otherwise the client will block forever.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::io::{stdin, Read};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
fn get_stdin_data() -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let mut buf = Vec::new();
|
||||
stdin().read_to_end(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let remote_addr: SocketAddr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:8080".into())
|
||||
.parse()?;
|
||||
|
||||
// We use port 0 to let the operating system allocate an available port for us.
|
||||
let local_addr: SocketAddr = if remote_addr.is_ipv4() {
|
||||
"0.0.0.0:0"
|
||||
} else {
|
||||
"[::]:0"
|
||||
}
|
||||
.parse()?;
|
||||
|
||||
let mut socket = UdpSocket::bind(local_addr).await?;
|
||||
const MAX_DATAGRAM_SIZE: usize = 65_507;
|
||||
socket.connect(&remote_addr).await?;
|
||||
let data = get_stdin_data()?;
|
||||
socket.send(&data).await?;
|
||||
let mut data = vec![0u8; MAX_DATAGRAM_SIZE];
|
||||
let len = socket.recv(&mut data).await?;
|
||||
println!(
|
||||
"Received {} bytes:\n{}",
|
||||
len,
|
||||
String::from_utf8_lossy(&data[..len])
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
//! This example leverages `BytesCodec` to create a UDP client and server which
|
||||
//! speak a custom protocol.
|
||||
//!
|
||||
//! Here we're using the codec from `tokio-codec` to convert a UDP socket to a stream of
|
||||
//! client messages. These messages are then processed and returned back as a
|
||||
//! new message with a new destination. Overall, we then use this to construct a
|
||||
//! "ping pong" pair where two sockets are sending messages back and forth.
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::stream::StreamExt;
|
||||
use tokio::{io, time};
|
||||
use tokio_util::codec::BytesCodec;
|
||||
use tokio_util::udp::UdpFramed;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{FutureExt, SinkExt};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let addr = env::args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| "127.0.0.1:0".to_string());
|
||||
|
||||
// Bind both our sockets and then figure out what ports we got.
|
||||
let a = UdpSocket::bind(&addr).await?;
|
||||
let b = UdpSocket::bind(&addr).await?;
|
||||
|
||||
let b_addr = b.local_addr()?;
|
||||
|
||||
let mut a = UdpFramed::new(a, BytesCodec::new());
|
||||
let mut b = UdpFramed::new(b, BytesCodec::new());
|
||||
|
||||
// Start off by sending a ping from a to b, afterwards we just print out
|
||||
// what they send us and continually send pings
|
||||
let a = ping(&mut a, b_addr);
|
||||
|
||||
// The second client we have will receive the pings from `a` and then send
|
||||
// back pongs.
|
||||
let b = pong(&mut b);
|
||||
|
||||
// Run both futures simultaneously of `a` and `b` sending messages back and forth.
|
||||
match futures::future::try_join(a, b).await {
|
||||
Err(e) => println!("an error occurred; error = {:?}", e),
|
||||
_ => println!("done!"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn ping(socket: &mut UdpFramed<BytesCodec>, b_addr: SocketAddr) -> Result<(), io::Error> {
|
||||
socket.send((Bytes::from(&b"PING"[..]), b_addr)).await?;
|
||||
|
||||
for _ in 0..4usize {
|
||||
let (bytes, addr) = socket.next().map(|e| e.unwrap()).await?;
|
||||
|
||||
println!("[a] recv: {}", String::from_utf8_lossy(&bytes));
|
||||
|
||||
socket.send((Bytes::from(&b"PING"[..]), addr)).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn pong(socket: &mut UdpFramed<BytesCodec>) -> Result<(), io::Error> {
|
||||
let timeout = Duration::from_millis(200);
|
||||
|
||||
while let Ok(Some(Ok((bytes, addr)))) = time::timeout(timeout, socket.next()).await {
|
||||
println!("[b] recv: {}", String::from_utf8_lossy(&bytes));
|
||||
|
||||
socket.send((Bytes::from(&b"PONG"[..]), addr)).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
//! Asynchronous signal handling for Tokio
|
||||
//!
|
||||
//! This crate implements asynchronous signal handling for Tokio, an
|
||||
//! asynchronous I/O framework in Rust. The primary type exported from this
|
||||
//! crate, `unix::Signal`, allows listening for arbitrary signals on Unix
|
||||
//! platforms, receiving them in an asynchronous fashion.
|
||||
//!
|
||||
//! Note that signal handling is in general a very tricky topic and should be
|
||||
//! used with great care. This crate attempts to implement 'best practice' for
|
||||
//! signal handling, but it should be evaluated for your own applications' needs
|
||||
//! to see if it's suitable.
|
||||
//!
|
||||
//! The are some fundamental limitations of this crate documented on the
|
||||
//! `Signal` structure as well.
|
||||
//!
|
||||
//! > **Note**: This crate compiles on Windows, but currently contains no
|
||||
//! > bindings. Windows does not have signals like Unix does, but it
|
||||
//! > does have a way to receive ctrl-c notifications at the console.
|
||||
//! > It's planned that this will be bound and exported outside the
|
||||
//! > `unix` module in the future!
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::Future;
|
||||
use futures::stream::Stream;
|
||||
use tokio_core::reactor::Handle;
|
||||
use tokio_core::io::{IoStream, IoFuture};
|
||||
|
||||
pub mod unix;
|
||||
pub mod windows;
|
||||
|
||||
/// Creates a stream which receives "ctrl-c" notifications sent to a process.
|
||||
///
|
||||
/// In general signals are handled very differently across Unix and Windows, but
|
||||
/// this is somewhat cross platform in terms of how it can be handled. A ctrl-c
|
||||
/// event to a console process can be represented as a stream for both Windows
|
||||
/// and Unix.
|
||||
///
|
||||
/// This function receives a `Handle` to an event loop and returns a future
|
||||
/// which when resolves yields a stream receiving all signal events. Note that
|
||||
/// there are a number of caveats listening for signals, and you may wish to
|
||||
/// read up on the documentation in the `unix` or `windows` module to take a
|
||||
/// peek.
|
||||
pub fn ctrl_c(handle: &Handle) -> IoFuture<IoStream<()>> {
|
||||
return ctrl_c_imp(handle);
|
||||
|
||||
#[cfg(unix)]
|
||||
fn ctrl_c_imp(handle: &Handle) -> IoFuture<IoStream<()>> {
|
||||
unix::Signal::new(unix::libc::SIGINT, handle).map(|x| {
|
||||
x.map(|_| ()).boxed()
|
||||
}).boxed()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn ctrl_c_imp(handle: &Handle) -> IoFuture<IoStream<()>> {
|
||||
windows::Event::ctrl_c(handle).map(|x| x.boxed()).boxed()
|
||||
}
|
||||
}
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
//! Unix-specific types for signal handling.
|
||||
//!
|
||||
//! This module is only defined on Unix platforms and contains the primary
|
||||
//! `Signal` type for receiving notifications of signals.
|
||||
|
||||
#![cfg(unix)]
|
||||
|
||||
pub extern crate libc;
|
||||
extern crate mio;
|
||||
extern crate tokio_uds;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::io::{self, Write, Read};
|
||||
use std::mem;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Once, ONCE_INIT, Mutex};
|
||||
|
||||
use futures::stream::{Stream, Fuse};
|
||||
use futures::{self, Future, IntoFuture, Complete, Oneshot, Poll, Async};
|
||||
use self::libc::c_int;
|
||||
use self::tokio_uds::UnixStream;
|
||||
use tokio_core::io::IoFuture;
|
||||
use tokio_core::reactor::{PollEvented, Handle};
|
||||
use tokio_core::channel::{channel, Sender, Receiver};
|
||||
|
||||
static INIT: Once = ONCE_INIT;
|
||||
static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _;
|
||||
|
||||
/// An implementation of `Stream` for receiving a particular type of signal.
|
||||
///
|
||||
/// This structure implements the `Stream` trait and represents notifications
|
||||
/// of the current process receiving a particular signal. The signal being
|
||||
/// listened for is passed to `Signal::new`, and the same signal number is then
|
||||
/// yielded as each element for the stream.
|
||||
///
|
||||
/// In general signal handling on Unix is a pretty tricky topic, and this
|
||||
/// structure is no exception! There are some important limitations to keep in
|
||||
/// mind when using `Signal` streams:
|
||||
///
|
||||
/// * While multiple event loops are supported, the *first* event loop to
|
||||
/// register a signal handler is required to be active to ensure that signals
|
||||
/// for other event loops are delivered. In other words, once an event loop
|
||||
/// registers a signal, it's best to keep it around and running. This is
|
||||
/// normally just a problem for tests, and the "workaround" is to spawn a
|
||||
/// thread in the background at the beginning of the test suite which is
|
||||
/// running an event loop (and listening for a signal).
|
||||
///
|
||||
/// * Signals handling in Unix already necessitates coalescing signals
|
||||
/// together sometimes. This `Signal` stream is also no exception here in
|
||||
/// that it will also coalesce signals. That is, even if the signal handler
|
||||
/// for this process runs multiple times, the `Signal` stream may only return
|
||||
/// one signal notification. Specifically, before `poll` is called, all
|
||||
/// signal notifications are coalesced into one item returned from `poll`.
|
||||
/// Once `poll` has been called, however, a further signal is guaranteed to
|
||||
/// be yielded as an item.
|
||||
///
|
||||
/// * Signal handling in general is relatively inefficient. Although some
|
||||
/// improvements are possible in this crate, it's recommended to not plan on
|
||||
/// having millions of signal channels open.
|
||||
///
|
||||
/// * Currently the "driver task" to process incoming signals never exits.
|
||||
///
|
||||
/// If you've got any questions about this feel free to open an issue on the
|
||||
/// repo, though, as I'd love to chat about this! In other words, I'd love to
|
||||
/// alleviate some of these limitations if possible!
|
||||
pub struct Signal {
|
||||
signum: c_int,
|
||||
reg: PollEvented<MyRegistration>,
|
||||
_finished: Complete<()>,
|
||||
}
|
||||
|
||||
struct GlobalState {
|
||||
write: UnixStream,
|
||||
tx: Mutex<Sender<Message>>,
|
||||
signals: [GlobalSignalState; 32],
|
||||
}
|
||||
|
||||
struct GlobalSignalState {
|
||||
ready: AtomicBool,
|
||||
prev: libc::sigaction,
|
||||
}
|
||||
|
||||
enum Message {
|
||||
NewSignal(c_int, Complete<io::Result<Signal>>),
|
||||
}
|
||||
|
||||
struct DriverTask {
|
||||
handle: Handle,
|
||||
read: UnixStream,
|
||||
rx: Fuse<Receiver<Message>>,
|
||||
signals: [SignalState; 32],
|
||||
}
|
||||
|
||||
struct SignalState {
|
||||
registered: bool,
|
||||
tasks: Vec<(RefCell<Oneshot<()>>, mio::SetReadiness)>,
|
||||
}
|
||||
|
||||
pub use self::libc::{SIGINT, SIGTERM, SIGUSR1, SIGUSR2};
|
||||
pub use self::libc::{SIGHUP, SIGQUIT, SIGPIPE, SIGALRM, SIGTRAP};
|
||||
|
||||
impl Signal {
|
||||
/// Creates a new stream which will receive notifications when the current
|
||||
/// process receives the signal `signum`.
|
||||
///
|
||||
/// This function will create a new stream which may be based on the
|
||||
/// event loop handle provided. This function returns a future which will
|
||||
/// then resolve to the signal stream, if successful.
|
||||
///
|
||||
/// The `Signal` stream is an infinite stream which will receive
|
||||
/// notifications whenever a signal is received. More documentation can be
|
||||
/// found on `Signal` itself, but to reiterate:
|
||||
///
|
||||
/// * Signals may be coalesced beyond what the kernel already does.
|
||||
/// * While multiple event loops are supported, the first event loop to
|
||||
/// register a signal handler must be active to deliver signal
|
||||
/// notifications
|
||||
/// * Once a signal handle is registered with the process the underlying
|
||||
/// libc signal handler is never unregistered.
|
||||
///
|
||||
/// A `Signal` stream can be created for a particular signal number
|
||||
/// multiple times. When a signal is received then all the associated
|
||||
/// channels will receive the signal notification.
|
||||
pub fn new(signum: c_int, handle: &Handle) -> IoFuture<Signal> {
|
||||
let mut init = None;
|
||||
INIT.call_once(|| {
|
||||
init = Some(global_init(handle));
|
||||
});
|
||||
let new_signal = futures::lazy(move || {
|
||||
let (tx, rx) = futures::oneshot();
|
||||
let msg = Message::NewSignal(signum, tx);
|
||||
let res = unsafe {
|
||||
(*GLOBAL_STATE).tx.lock().unwrap().send(msg)
|
||||
};
|
||||
res.expect("failed to request a new signal stream, did the \
|
||||
first event loop go away?");
|
||||
rx.then(|r| r.unwrap())
|
||||
});
|
||||
match init {
|
||||
Some(init) => init.into_future().and_then(|()| new_signal).boxed(),
|
||||
None => new_signal.boxed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Signal {
|
||||
type Item = c_int;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<c_int>, io::Error> {
|
||||
if !self.reg.poll_read().is_ready() {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
self.reg.need_read();
|
||||
self.reg.get_ref()
|
||||
.inner.borrow()
|
||||
.as_ref().unwrap().1
|
||||
.set_readiness(mio::Ready::none())
|
||||
.expect("failed to set readiness");
|
||||
Ok(Async::Ready(Some(self.signum)))
|
||||
}
|
||||
}
|
||||
|
||||
fn global_init(handle: &Handle) -> io::Result<()> {
|
||||
let (tx, rx) = try!(channel(handle));
|
||||
let (read, write) = try!(UnixStream::pair(handle));
|
||||
unsafe {
|
||||
let state = Box::new(GlobalState {
|
||||
write: write,
|
||||
signals: {
|
||||
fn new() -> GlobalSignalState {
|
||||
GlobalSignalState {
|
||||
ready: AtomicBool::new(false),
|
||||
prev: unsafe { mem::zeroed() },
|
||||
}
|
||||
}
|
||||
[
|
||||
new(), new(), new(), new(), new(), new(), new(), new(),
|
||||
new(), new(), new(), new(), new(), new(), new(), new(),
|
||||
new(), new(), new(), new(), new(), new(), new(), new(),
|
||||
new(), new(), new(), new(), new(), new(), new(), new(),
|
||||
]
|
||||
},
|
||||
tx: Mutex::new(tx.clone()),
|
||||
});
|
||||
GLOBAL_STATE = Box::into_raw(state);
|
||||
|
||||
handle.spawn(DriverTask {
|
||||
handle: handle.clone(),
|
||||
rx: rx.fuse(),
|
||||
read: read,
|
||||
signals: {
|
||||
fn new() -> SignalState {
|
||||
SignalState { registered: false, tasks: Vec::new() }
|
||||
}
|
||||
[
|
||||
new(), new(), new(), new(), new(), new(), new(), new(),
|
||||
new(), new(), new(), new(), new(), new(), new(), new(),
|
||||
new(), new(), new(), new(), new(), new(), new(), new(),
|
||||
new(), new(), new(), new(), new(), new(), new(), new(),
|
||||
]
|
||||
},
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for DriverTask {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.check_signal_drops();
|
||||
self.check_messages();
|
||||
self.check_signals();
|
||||
|
||||
// TODO: when to finish this task?
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
impl DriverTask {
|
||||
fn check_signal_drops(&mut self) {
|
||||
for signal in self.signals.iter_mut() {
|
||||
signal.tasks.retain(|task| {
|
||||
!task.0.borrow_mut().poll().is_err()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn check_messages(&mut self) {
|
||||
loop {
|
||||
// Acquire the next message
|
||||
let message = match self.rx.poll() {
|
||||
Ok(Async::Ready(Some(e))) => e,
|
||||
Ok(Async::Ready(None)) |
|
||||
Ok(Async::NotReady) => break,
|
||||
Err(e) => panic!("error on rx: {}", e),
|
||||
};
|
||||
let (sig, complete) = match message {
|
||||
Message::NewSignal(sig, complete) => (sig, complete),
|
||||
};
|
||||
|
||||
// If the signal's too large, then we return an error, otherwise we
|
||||
// use this index to look at the signal slot.
|
||||
//
|
||||
// If the signal wasn't previously registered then we do so now.
|
||||
let signal = match self.signals.get_mut(sig as usize) {
|
||||
Some(signal) => signal,
|
||||
None => {
|
||||
complete.complete(Err(io::Error::new(io::ErrorKind::Other,
|
||||
"signum too large")));
|
||||
continue
|
||||
}
|
||||
};
|
||||
if !signal.registered {
|
||||
unsafe {
|
||||
let mut new: libc::sigaction = mem::zeroed();
|
||||
new.sa_sigaction = handler as usize;
|
||||
new.sa_flags = libc::SA_RESTART | libc::SA_SIGINFO;
|
||||
let mut prev = mem::zeroed();
|
||||
if libc::sigaction(sig, &new, &mut prev) != 0 {
|
||||
complete.complete(Err(io::Error::last_os_error()));
|
||||
continue
|
||||
}
|
||||
signal.registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire the (registration, set_readiness) pair by... assuming
|
||||
// we're on the event loop (true because of the spawn above).
|
||||
let reg = MyRegistration { inner: RefCell::new(None) };
|
||||
let reg = match PollEvented::new(reg, &self.handle) {
|
||||
Ok(reg) => reg,
|
||||
Err(e) => {
|
||||
complete.complete(Err(e));
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
// Create the `Signal` to pass back and then also keep a handle to
|
||||
// the `SetReadiness` for ourselves internally.
|
||||
let (tx, rx) = futures::oneshot();
|
||||
let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone();
|
||||
complete.complete(Ok(Signal {
|
||||
signum: sig,
|
||||
reg: reg,
|
||||
_finished: tx,
|
||||
}));
|
||||
signal.tasks.push((RefCell::new(rx), ready));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_signals(&mut self) {
|
||||
// Drain all data from the pipe
|
||||
let mut buf = [0; 32];
|
||||
let mut any = false;
|
||||
loop {
|
||||
match self.read.read(&mut buf) {
|
||||
Ok(0) => { // EOF == something happened
|
||||
any = true;
|
||||
break
|
||||
}
|
||||
Ok(..) => any = true, // data read, but keep draining
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
|
||||
Err(e) => panic!("bad read: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing happened, no need to check the signals
|
||||
if !any {
|
||||
return
|
||||
}
|
||||
|
||||
for (i, slot) in self.signals.iter().enumerate() {
|
||||
// No need to go farther if we haven't even registered a signal
|
||||
if !slot.registered {
|
||||
continue
|
||||
}
|
||||
|
||||
// See if this signal actually happened since we last checked
|
||||
unsafe {
|
||||
if !(*GLOBAL_STATE).signals[i].ready.swap(false, Ordering::SeqCst) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Wake up all the tasks waiting on this signal
|
||||
for task in slot.tasks.iter() {
|
||||
task.1.set_readiness(mio::Ready::readable())
|
||||
.expect("failed to set readiness");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern fn handler(signum: c_int,
|
||||
info: *mut libc::siginfo_t,
|
||||
ptr: *mut libc::c_void) {
|
||||
type FnSigaction = extern fn(c_int, *mut libc::siginfo_t, *mut libc::c_void);
|
||||
type FnHandler = extern fn(c_int);
|
||||
|
||||
unsafe {
|
||||
let state = match (*GLOBAL_STATE).signals.get(signum as usize) {
|
||||
Some(state) => state,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if !state.ready.swap(true, Ordering::SeqCst) {
|
||||
// Ignore errors here as we're not in a context that can panic,
|
||||
// and otherwise there's not much we can do.
|
||||
drop((&(*GLOBAL_STATE).write).write(&[1]));
|
||||
}
|
||||
|
||||
let fnptr = state.prev.sa_sigaction;
|
||||
if fnptr == 0 || fnptr == libc::SIG_DFL || fnptr == libc::SIG_IGN {
|
||||
return
|
||||
}
|
||||
if state.prev.sa_flags & libc::SA_SIGINFO == 0 {
|
||||
let action = mem::transmute::<usize, FnHandler>(fnptr);
|
||||
action(signum)
|
||||
} else {
|
||||
let action = mem::transmute::<usize, FnSigaction>(fnptr);
|
||||
action(signum, info, ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MyRegistration {
|
||||
inner: RefCell<Option<(mio::Registration, mio::SetReadiness)>>,
|
||||
}
|
||||
|
||||
impl mio::Evented for MyRegistration {
|
||||
fn register(&self,
|
||||
poll: &mio::Poll,
|
||||
token: mio::Token,
|
||||
events: mio::Ready,
|
||||
opts: mio::PollOpt) -> io::Result<()> {
|
||||
let reg = mio::Registration::new(poll, token, events, opts);
|
||||
*self.inner.borrow_mut() = Some(reg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reregister(&self,
|
||||
_poll: &mio::Poll,
|
||||
_token: mio::Token,
|
||||
_events: mio::Ready,
|
||||
_opts: mio::PollOpt) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deregister(&self, _poll: &mio::Poll) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
//! Windows-specific types for signal handling.
|
||||
//!
|
||||
//! This module is only defined on Windows and contains the primary `Event` type
|
||||
//! for receiving notifications of events. These events are listened for via the
|
||||
//! `SetConsoleCtrlHandler` function which receives events of the type
|
||||
//! `CTRL_C_EVENT` and `CTRL_BREAK_EVENT`
|
||||
|
||||
#![cfg(windows)]
|
||||
|
||||
extern crate kernel32;
|
||||
extern crate mio;
|
||||
extern crate winapi;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::io;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Once, ONCE_INIT, Mutex};
|
||||
|
||||
use futures::stream::{Stream, Fuse};
|
||||
use futures::{self, Future, IntoFuture, Complete, Oneshot, Poll, Async};
|
||||
use tokio_core::io::IoFuture;
|
||||
use tokio_core::reactor::{PollEvented, Handle};
|
||||
use tokio_core::channel::{channel, Sender, Receiver};
|
||||
|
||||
static INIT: Once = ONCE_INIT;
|
||||
static mut GLOBAL_STATE: *mut GlobalState = 0 as *mut _;
|
||||
|
||||
/// Stream of events discovered via `SetConsoleCtrlHandler`.
|
||||
///
|
||||
/// This structure can be used to listen for events of the type `CTRL_C_EVENT`
|
||||
/// and `CTRL_BREAK_EVENT`. The `Stream` trait is implemented for this struct
|
||||
/// and will resolve for each notification received by the process. Note that
|
||||
/// there are few limitations with this as well:
|
||||
///
|
||||
/// * A notification to this process notifies *all* `Event` streams for that
|
||||
/// event type.
|
||||
/// * Notifications to an `Event` stream **are coalesced** if they aren't
|
||||
/// processed quickly enough. This means that if two notifications are
|
||||
/// received back-to-back, then the stream may only receive one item about the
|
||||
/// two notifications.
|
||||
pub struct Event {
|
||||
reg: PollEvented<MyRegistration>,
|
||||
_finished: Complete<()>,
|
||||
}
|
||||
|
||||
struct GlobalState {
|
||||
ready: mio::SetReadiness,
|
||||
tx: Mutex<Sender<Message>>,
|
||||
ctrl_c: GlobalEventState,
|
||||
ctrl_break: GlobalEventState,
|
||||
}
|
||||
|
||||
struct GlobalEventState {
|
||||
ready: AtomicBool,
|
||||
}
|
||||
|
||||
enum Message {
|
||||
NewEvent(winapi::DWORD, Complete<io::Result<Event>>),
|
||||
}
|
||||
|
||||
struct DriverTask {
|
||||
handle: Handle,
|
||||
reg: PollEvented<MyRegistration>,
|
||||
rx: Fuse<Receiver<Message>>,
|
||||
ctrl_c: EventState,
|
||||
ctrl_break: EventState,
|
||||
}
|
||||
|
||||
struct EventState {
|
||||
tasks: Vec<(RefCell<Oneshot<()>>, mio::SetReadiness)>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Creates a new stream listening for the `CTRL_C_EVENT` events.
|
||||
///
|
||||
/// This function will register a handler via `SetConsoleCtrlHandler` and
|
||||
/// deliver notifications to the returned stream.
|
||||
pub fn ctrl_c(handle: &Handle) -> IoFuture<Event> {
|
||||
Event::new(winapi::CTRL_C_EVENT, handle)
|
||||
}
|
||||
|
||||
/// Creates a new stream listening for the `CTRL_BREAK_EVENT` events.
|
||||
///
|
||||
/// This function will register a handler via `SetConsoleCtrlHandler` and
|
||||
/// deliver notifications to the returned stream.
|
||||
pub fn ctrl_break(handle: &Handle) -> IoFuture<Event> {
|
||||
Event::new(winapi::CTRL_BREAK_EVENT, handle)
|
||||
}
|
||||
|
||||
fn new(signum: winapi::DWORD, handle: &Handle) -> IoFuture<Event> {
|
||||
let mut init = None;
|
||||
INIT.call_once(|| {
|
||||
init = Some(global_init(handle));
|
||||
});
|
||||
let new_signal = futures::lazy(move || {
|
||||
let (tx, rx) = futures::oneshot();
|
||||
let msg = Message::NewEvent(signum, tx);
|
||||
let res = unsafe {
|
||||
(*GLOBAL_STATE).tx.lock().unwrap().send(msg)
|
||||
};
|
||||
res.expect("failed to request a new signal stream, did the \
|
||||
first event loop go away?");
|
||||
rx.then(|r| r.unwrap())
|
||||
});
|
||||
match init {
|
||||
Some(init) => init.into_future().and_then(|()| new_signal).boxed(),
|
||||
None => new_signal.boxed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Event {
|
||||
type Item = ();
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<()>, io::Error> {
|
||||
if !self.reg.poll_read().is_ready() {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
self.reg.need_read();
|
||||
self.reg.get_ref()
|
||||
.inner.borrow()
|
||||
.as_ref().unwrap().1
|
||||
.set_readiness(mio::Ready::none())
|
||||
.expect("failed to set readiness");
|
||||
Ok(Async::Ready(Some(())))
|
||||
}
|
||||
}
|
||||
|
||||
fn global_init(handle: &Handle) -> io::Result<()> {
|
||||
let (tx, rx) = try!(channel(handle));
|
||||
let reg = MyRegistration { inner: RefCell::new(None) };
|
||||
let reg = try!(PollEvented::new(reg, handle));
|
||||
let ready = reg.get_ref().inner.borrow().as_ref().unwrap().1.clone();
|
||||
unsafe {
|
||||
let state = Box::new(GlobalState {
|
||||
ready: ready,
|
||||
ctrl_c: GlobalEventState { ready: AtomicBool::new(false) },
|
||||
ctrl_break: GlobalEventState { ready: AtomicBool::new(false) },
|
||||
tx: Mutex::new(tx.clone()),
|
||||
});
|
||||
GLOBAL_STATE = Box::into_raw(state);
|
||||
|
||||
let rc = kernel32::SetConsoleCtrlHandler(Some(handler), winapi::TRUE);
|
||||
if rc == 0 {
|
||||
Box::from_raw(GLOBAL_STATE);
|
||||
GLOBAL_STATE = 0 as *mut _;
|
||||
return Err(io::Error::last_os_error())
|
||||
}
|
||||
|
||||
handle.spawn(DriverTask {
|
||||
handle: handle.clone(),
|
||||
rx: rx.fuse(),
|
||||
reg: reg,
|
||||
ctrl_c: EventState { tasks: Vec::new() },
|
||||
ctrl_break: EventState { tasks: Vec::new() },
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for DriverTask {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.check_event_drops();
|
||||
self.check_messages();
|
||||
self.check_events();
|
||||
|
||||
// TODO: when to finish this task?
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
impl DriverTask {
|
||||
fn check_event_drops(&mut self) {
|
||||
self.ctrl_c.tasks.retain(|task| {
|
||||
!task.0.borrow_mut().poll().is_err()
|
||||
});
|
||||
self.ctrl_break.tasks.retain(|task| {
|
||||
!task.0.borrow_mut().poll().is_err()
|
||||
});
|
||||
}
|
||||
|
||||
fn check_messages(&mut self) {
|
||||
loop {
|
||||
// Acquire the next message
|
||||
let message = match self.rx.poll() {
|
||||
Ok(Async::Ready(Some(e))) => e,
|
||||
Ok(Async::Ready(None)) |
|
||||
Ok(Async::NotReady) => break,
|
||||
Err(e) => panic!("error on rx: {}", e),
|
||||
};
|
||||
let (sig, complete) = match message {
|
||||
Message::NewEvent(sig, complete) => (sig, complete),
|
||||
};
|
||||
|
||||
let event = if sig == winapi::CTRL_C_EVENT {
|
||||
&mut self.ctrl_c
|
||||
} else {
|
||||
&mut self.ctrl_break
|
||||
};
|
||||
|
||||
// Acquire the (registration, set_readiness) pair by... assuming
|
||||
// we're on the event loop (true because of the spawn above).
|
||||
let reg = MyRegistration { inner: RefCell::new(None) };
|
||||
let reg = match PollEvented::new(reg, &self.handle) {
|
||||
Ok(reg) => reg,
|
||||
Err(e) => {
|
||||
complete.complete(Err(e));
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
// Create the `Event` to pass back and then also keep a handle to
|
||||
// the `SetReadiness` for ourselves internally.
|
||||
let (tx, rx) = futures::oneshot();
|
||||
let ready = reg.get_ref().inner.borrow_mut().as_mut().unwrap().1.clone();
|
||||
complete.complete(Ok(Event {
|
||||
reg: reg,
|
||||
_finished: tx,
|
||||
}));
|
||||
event.tasks.push((RefCell::new(rx), ready));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_events(&mut self) {
|
||||
if self.reg.poll_read().is_not_ready() {
|
||||
return
|
||||
}
|
||||
self.reg.need_read();
|
||||
self.reg.get_ref().inner.borrow().as_ref().unwrap()
|
||||
.1.set_readiness(mio::Ready::none()).unwrap();
|
||||
|
||||
if unsafe { (*GLOBAL_STATE).ctrl_c.ready.swap(false, Ordering::SeqCst) } {
|
||||
for task in self.ctrl_c.tasks.iter() {
|
||||
task.1.set_readiness(mio::Ready::readable()).unwrap();
|
||||
}
|
||||
}
|
||||
if unsafe { (*GLOBAL_STATE).ctrl_break.ready.swap(false, Ordering::SeqCst) } {
|
||||
for task in self.ctrl_break.tasks.iter() {
|
||||
task.1.set_readiness(mio::Ready::readable()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn handler(ty: winapi::DWORD) -> winapi::BOOL {
|
||||
let event = match ty {
|
||||
winapi::CTRL_C_EVENT => &(*GLOBAL_STATE).ctrl_c,
|
||||
winapi::CTRL_BREAK_EVENT => &(*GLOBAL_STATE).ctrl_break,
|
||||
_ => return winapi::FALSE
|
||||
};
|
||||
if event.ready.swap(true, Ordering::SeqCst) {
|
||||
winapi::FALSE
|
||||
} else {
|
||||
drop((*GLOBAL_STATE).ready.set_readiness(mio::Ready::readable()));
|
||||
// TODO: this will report that we handled a CTRL_BREAK_EVENT when in
|
||||
// fact we may not have any streams actually created for that
|
||||
// event.
|
||||
winapi::TRUE
|
||||
}
|
||||
}
|
||||
|
||||
struct MyRegistration {
|
||||
inner: RefCell<Option<(mio::Registration, mio::SetReadiness)>>,
|
||||
}
|
||||
|
||||
impl mio::Evented for MyRegistration {
|
||||
fn register(&self,
|
||||
poll: &mio::Poll,
|
||||
token: mio::Token,
|
||||
events: mio::Ready,
|
||||
opts: mio::PollOpt) -> io::Result<()> {
|
||||
let reg = mio::Registration::new(poll, token, events, opts);
|
||||
*self.inner.borrow_mut() = Some(reg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reregister(&self,
|
||||
_poll: &mio::Poll,
|
||||
_token: mio::Token,
|
||||
_events: mio::Ready,
|
||||
_opts: mio::PollOpt) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deregister(&self, _poll: &mio::Poll) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "tests-build"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
full = ["tokio/full"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
@@ -1,2 +0,0 @@
|
||||
Tests the various combination of feature flags. This is broken out to a separate
|
||||
crate to work around limitations with cargo features.
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "tokio")]
|
||||
pub use tokio;
|
||||
@@ -1,25 +0,0 @@
|
||||
use tests_build::tokio;
|
||||
|
||||
#[tokio::main]
|
||||
fn main_is_not_async() {}
|
||||
|
||||
#[tokio::main(foo)]
|
||||
async fn main_attr_has_unknown_args() {}
|
||||
|
||||
#[tokio::main(threadpool::bar)]
|
||||
async fn main_attr_has_path_args() {}
|
||||
|
||||
#[tokio::test]
|
||||
fn test_is_not_async() {}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_fn_has_args(_x: u8) {}
|
||||
|
||||
#[tokio::test(foo)]
|
||||
async fn test_attr_has_args() {}
|
||||
|
||||
#[tokio::test]
|
||||
#[test]
|
||||
async fn test_has_second_test_attr() {}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,41 +0,0 @@
|
||||
error: the async keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:4:1
|
||||
|
|
||||
4 | fn main_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
--> $DIR/macros_invalid_input.rs:6:15
|
||||
|
|
||||
6 | #[tokio::main(foo)]
|
||||
| ^^^
|
||||
|
||||
error: Must have specified ident
|
||||
--> $DIR/macros_invalid_input.rs:9:15
|
||||
|
|
||||
9 | #[tokio::main(threadpool::bar)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: the async keyword is missing from the function declaration
|
||||
--> $DIR/macros_invalid_input.rs:13:1
|
||||
|
|
||||
13 | fn test_is_not_async() {}
|
||||
| ^^
|
||||
|
||||
error: the test function cannot accept arguments
|
||||
--> $DIR/macros_invalid_input.rs:16:27
|
||||
|
|
||||
16 | async fn test_fn_has_args(_x: u8) {}
|
||||
| ^^^^^^
|
||||
|
||||
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
|
||||
--> $DIR/macros_invalid_input.rs:18:15
|
||||
|
|
||||
18 | #[tokio::test(foo)]
|
||||
| ^^^
|
||||
|
||||
error: second test attribute is supplied
|
||||
--> $DIR/macros_invalid_input.rs:22:1
|
||||
|
|
||||
22 | #[test]
|
||||
| ^^^^^^^
|
||||
@@ -1,9 +0,0 @@
|
||||
#[test]
|
||||
fn compile_fail() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
t.compile_fail("tests/fail/macros_invalid_input.rs");
|
||||
|
||||
drop(t);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "tests-integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
full = [
|
||||
"macros",
|
||||
"rt-core",
|
||||
"rt-threaded",
|
||||
|
||||
"tokio/full",
|
||||
"tokio-test"
|
||||
]
|
||||
macros = ["tokio/macros"]
|
||||
rt-core = ["tokio/rt-core"]
|
||||
rt-threaded = ["rt-core", "tokio/rt-threaded"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { path = "../tokio" }
|
||||
tokio-test = { path = "../tokio-test", optional = true }
|
||||
doc-comment = "0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { version = "0.3.0", features = ["async-await"] }
|
||||
@@ -1 +0,0 @@
|
||||
Tests that require additional components than just the `tokio` crate.
|
||||
@@ -1,20 +0,0 @@
|
||||
//! A cat-like utility that can be used as a subprocess to test I/O
|
||||
//! stream communication.
|
||||
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
fn main() {
|
||||
let stdin = io::stdin();
|
||||
let mut stdout = io::stdout();
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
line.clear();
|
||||
stdin.read_line(&mut line).unwrap();
|
||||
if line.is_empty() {
|
||||
break;
|
||||
}
|
||||
stdout.write_all(line.as_bytes()).unwrap();
|
||||
}
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#[cfg(feature = "full")]
|
||||
doc_comment::doc_comment!(include_str!("../../README.md"));
|
||||
@@ -1,31 +0,0 @@
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
#[tokio::main]
|
||||
async fn basic_main() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn generic_fun<T: Default>() -> T {
|
||||
T::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "rt-core")]
|
||||
mod spawn {
|
||||
#[tokio::main]
|
||||
async fn spawning() -> usize {
|
||||
let join = tokio::spawn(async { 1 });
|
||||
join.await.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn main_with_spawn() {
|
||||
assert_eq!(1, spawning());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell() {
|
||||
assert_eq!(1, basic_main());
|
||||
assert_eq!(bool::default(), generic_fun::<bool>())
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use futures::executor::block_on;
|
||||
|
||||
async fn my_async_fn() {}
|
||||
|
||||
#[test]
|
||||
fn pin() {
|
||||
block_on(async {
|
||||
let future = my_async_fn();
|
||||
tokio::pin!(future);
|
||||
(&mut future).await
|
||||
});
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use futures::executor::block_on;
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn join_with_select() {
|
||||
block_on(async {
|
||||
let (tx1, mut rx1) = oneshot::channel::<i32>();
|
||||
let (tx2, mut rx2) = oneshot::channel::<i32>();
|
||||
|
||||
thread::spawn(move || {
|
||||
tx1.send(123).unwrap();
|
||||
tx2.send(456).unwrap();
|
||||
});
|
||||
|
||||
let mut a = None;
|
||||
let mut b = None;
|
||||
|
||||
while a.is_none() || b.is_none() {
|
||||
tokio::select! {
|
||||
v1 = (&mut rx1), if a.is_none() => a = Some(v1.unwrap()),
|
||||
v2 = (&mut rx2), if b.is_none() => b = Some(v2.unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
let (a, b) = (a.unwrap(), b.unwrap());
|
||||
|
||||
assert_eq!(a, 123);
|
||||
assert_eq!(b, 456);
|
||||
});
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![cfg(feature = "full")]
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio_test::assert_ok;
|
||||
|
||||
use futures::future::{self, FutureExt};
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
|
||||
fn cat() -> Command {
|
||||
let mut me = env::current_exe().unwrap();
|
||||
me.pop();
|
||||
|
||||
if me.ends_with("deps") {
|
||||
me.pop();
|
||||
}
|
||||
|
||||
me.push("test-cat");
|
||||
|
||||
let mut cmd = Command::new(me);
|
||||
cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
cmd
|
||||
}
|
||||
|
||||
async fn feed_cat(mut cat: Child, n: usize) -> io::Result<ExitStatus> {
|
||||
let mut stdin = cat.stdin.take().unwrap();
|
||||
let stdout = cat.stdout.take().unwrap();
|
||||
|
||||
// Produce n lines on the child's stdout.
|
||||
let write = async {
|
||||
for i in 0..n {
|
||||
let bytes = format!("line {}\n", i).into_bytes();
|
||||
stdin.write_all(&bytes).await.unwrap();
|
||||
}
|
||||
|
||||
drop(stdin);
|
||||
};
|
||||
|
||||
let read = async {
|
||||
let mut reader = BufReader::new(stdout).lines();
|
||||
let mut num_lines = 0;
|
||||
|
||||
// Try to read `n + 1` lines, ensuring the last one is empty
|
||||
// (i.e. EOF is reached after `n` lines.
|
||||
loop {
|
||||
let data = reader
|
||||
.next_line()
|
||||
.await
|
||||
.unwrap_or_else(|_| Some(String::new()))
|
||||
.expect("failed to read line");
|
||||
|
||||
let num_read = data.len();
|
||||
let done = num_lines >= n;
|
||||
|
||||
match (done, num_read) {
|
||||
(false, 0) => panic!("broken pipe"),
|
||||
(true, n) if n != 0 => panic!("extraneous data"),
|
||||
_ => {
|
||||
let expected = format!("line {}", num_lines);
|
||||
assert_eq!(expected, data);
|
||||
}
|
||||
};
|
||||
|
||||
num_lines += 1;
|
||||
if num_lines >= n {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Compose reading and writing concurrently.
|
||||
future::join3(write, read, cat)
|
||||
.map(|(_, _, status)| status)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Check for the following properties when feeding stdin and
|
||||
/// consuming stdout of a cat-like process:
|
||||
///
|
||||
/// - A number of lines that amounts to a number of bytes exceeding a
|
||||
/// typical OS buffer size can be fed to the child without
|
||||
/// deadlock. This tests that we also consume the stdout
|
||||
/// concurrently; otherwise this would deadlock.
|
||||
///
|
||||
/// - We read the same lines from the child that we fed it.
|
||||
///
|
||||
/// - The child does produce EOF on stdout after the last line.
|
||||
#[tokio::test]
|
||||
async fn feed_a_lot() {
|
||||
let child = cat().spawn().unwrap();
|
||||
let status = feed_cat(child, 10000).await.unwrap();
|
||||
assert_eq!(status.code(), Some(0));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn wait_with_output_captures() {
|
||||
let mut child = cat().spawn().unwrap();
|
||||
let mut stdin = child.stdin.take().unwrap();
|
||||
|
||||
let write_bytes = b"1234";
|
||||
|
||||
let future = async {
|
||||
stdin.write_all(write_bytes).await?;
|
||||
drop(stdin);
|
||||
let out = child.wait_with_output();
|
||||
out.await
|
||||
};
|
||||
|
||||
let output = future.await.unwrap();
|
||||
|
||||
assert!(output.status.success());
|
||||
assert_eq!(output.stdout, write_bytes);
|
||||
assert_eq!(output.stderr.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_closes_any_pipes() {
|
||||
// Cat will open a pipe between the parent and child.
|
||||
// If `status_async` doesn't ensure the handles are closed,
|
||||
// we would end up blocking forever (and time out).
|
||||
let child = cat().status();
|
||||
|
||||
assert_ok!(child.await);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#![cfg(unix)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate libc;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_signal;
|
||||
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Once, ONCE_INIT, Mutex, MutexGuard};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::Future;
|
||||
use futures::stream::Stream;
|
||||
use tokio_core::reactor::{Core, Timeout};
|
||||
use tokio_signal::unix::Signal;
|
||||
|
||||
static INIT: Once = ONCE_INIT;
|
||||
static mut LOCK: *mut Mutex<()> = 0 as *mut _;
|
||||
|
||||
fn lock() -> MutexGuard<'static, ()> {
|
||||
unsafe {
|
||||
INIT.call_once(|| {
|
||||
LOCK = Box::into_raw(Box::new(Mutex::new(())));
|
||||
let (tx, rx) = channel();
|
||||
thread::spawn(move || {
|
||||
let mut lp = Core::new().unwrap();
|
||||
let handle = lp.handle();
|
||||
let _signal = lp.run(Signal::new(libc::SIGALRM, &handle)).unwrap();
|
||||
tx.send(()).unwrap();
|
||||
drop(lp.run(futures::empty::<(), ()>()));
|
||||
});
|
||||
rx.recv().unwrap();
|
||||
});
|
||||
(*LOCK).lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let _lock = lock();
|
||||
|
||||
let mut lp = Core::new().unwrap();
|
||||
let handle = lp.handle();
|
||||
let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap();
|
||||
unsafe {
|
||||
assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0);
|
||||
}
|
||||
lp.run(signal.into_future()).ok().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notify_both() {
|
||||
let _lock = lock();
|
||||
|
||||
let mut lp = Core::new().unwrap();
|
||||
let handle = lp.handle();
|
||||
let signal1 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap();
|
||||
let signal2 = lp.run(Signal::new(libc::SIGUSR2, &handle)).unwrap();
|
||||
unsafe {
|
||||
assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR2), 0);
|
||||
}
|
||||
lp.run(signal1.into_future().join(signal2.into_future())).ok().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_then_get_a_signal() {
|
||||
let _lock = lock();
|
||||
|
||||
let mut lp = Core::new().unwrap();
|
||||
let handle = lp.handle();
|
||||
let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap();
|
||||
drop(signal);
|
||||
unsafe {
|
||||
assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0);
|
||||
}
|
||||
let timeout = Timeout::new(Duration::from_millis(1), &lp.handle()).unwrap();
|
||||
lp.run(timeout).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twice() {
|
||||
let _lock = lock();
|
||||
|
||||
let mut lp = Core::new().unwrap();
|
||||
let handle = lp.handle();
|
||||
let signal = lp.run(Signal::new(libc::SIGUSR1, &handle)).unwrap();
|
||||
unsafe {
|
||||
assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0);
|
||||
}
|
||||
let (num, signal) = lp.run(signal.into_future()).ok().unwrap();
|
||||
assert_eq!(num, Some(libc::SIGUSR1));
|
||||
unsafe {
|
||||
assert_eq!(libc::kill(libc::getpid(), libc::SIGUSR1), 0);
|
||||
}
|
||||
lp.run(signal.into_future()).ok().unwrap();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# 0.2.5 (February 27, 2019)
|
||||
|
||||
### Fixed
|
||||
- doc improvements (#2225).
|
||||
|
||||
# 0.2.4 (January 27, 2019)
|
||||
|
||||
### Fixed
|
||||
- generics on `#[tokio::main]` function (#2177).
|
||||
|
||||
### Added
|
||||
- support for `tokio::select!` (#2152).
|
||||
|
||||
# 0.2.3 (January 7, 2019)
|
||||
|
||||
### Fixed
|
||||
- Revert breaking change.
|
||||
|
||||
# 0.2.2 (January 7, 2019)
|
||||
|
||||
### Added
|
||||
- General refactoring and inclusion of additional runtime options (#2022 and #2038)
|
||||
|
||||
# 0.2.1 (December 18, 2019)
|
||||
|
||||
### Fixes
|
||||
- inherit visibility when wrapping async fn (#1954).
|
||||
|
||||
# 0.2.0 (November 26, 2019)
|
||||
|
||||
- Initial release
|
||||
@@ -1,36 +0,0 @@
|
||||
[package]
|
||||
name = "tokio-macros"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag.
|
||||
version = "0.2.5"
|
||||
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"
|
||||
description = """
|
||||
Tokio's proc macros.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.7"
|
||||
quote = "1"
|
||||
syn = { version = "1.0.3", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -1,47 +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.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Yoshua Wuyts
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Tokio Macros
|
||||
|
||||
Procedural macros for use with Tokio
|
||||
|
||||
## 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,359 +0,0 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Runtime {
|
||||
Basic,
|
||||
Threaded,
|
||||
}
|
||||
|
||||
fn parse_knobs(
|
||||
mut input: syn::ItemFn,
|
||||
args: syn::AttributeArgs,
|
||||
is_test: bool,
|
||||
rt_threaded: bool,
|
||||
) -> Result<TokenStream, syn::Error> {
|
||||
let sig = &mut input.sig;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
if sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return Err(syn::Error::new_spanned(sig.fn_token, msg));
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let mut runtime = None;
|
||||
let mut core_threads = None;
|
||||
let mut max_threads = None;
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
|
||||
let ident = namevalue.path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"core_threads" => {
|
||||
if rt_threaded {
|
||||
match &namevalue.lit {
|
||||
syn::Lit::Int(expr) => {
|
||||
let num = expr.base10_parse::<NonZeroUsize>().unwrap();
|
||||
if num.get() > 1 {
|
||||
runtime = Some(Runtime::Threaded);
|
||||
} else {
|
||||
runtime = Some(Runtime::Basic);
|
||||
}
|
||||
|
||||
if let Some(v) = max_threads {
|
||||
if v < num {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads cannot be less than core_threads",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
core_threads = Some(num);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"core_threads argument must be an int",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"core_threads can only be set with rt-threaded feature flag enabled",
|
||||
));
|
||||
}
|
||||
}
|
||||
"max_threads" => match &namevalue.lit {
|
||||
syn::Lit::Int(expr) => {
|
||||
let num = expr.base10_parse::<NonZeroUsize>().unwrap();
|
||||
|
||||
if let Some(v) = core_threads {
|
||||
if num < v {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads cannot be less than core_threads",
|
||||
));
|
||||
}
|
||||
}
|
||||
max_threads = Some(num);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
namevalue,
|
||||
"max_threads argument must be an int",
|
||||
))
|
||||
}
|
||||
},
|
||||
name => {
|
||||
let msg = format!("Unknown attribute pair {} is specified; expected one of: `core_threads`, `max_threads`", name);
|
||||
return Err(syn::Error::new_spanned(namevalue, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => {
|
||||
runtime = Some(runtime.unwrap_or_else(|| Runtime::Threaded))
|
||||
}
|
||||
"basic_scheduler" => runtime = Some(runtime.unwrap_or_else(|| Runtime::Basic)),
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return Err(syn::Error::new_spanned(path, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
other,
|
||||
"Unknown attribute inside the macro",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut rt = quote! { tokio::runtime::Builder::new().basic_scheduler() };
|
||||
if rt_threaded && (runtime == Some(Runtime::Threaded) || (runtime.is_none() && !is_test)) {
|
||||
rt = quote! { #rt.threaded_scheduler() };
|
||||
}
|
||||
if let Some(v) = core_threads.map(|v| v.get()) {
|
||||
rt = quote! { #rt.core_threads(#v) };
|
||||
}
|
||||
if let Some(v) = max_threads.map(|v| v.get()) {
|
||||
rt = quote! { #rt.max_threads(#v) };
|
||||
}
|
||||
|
||||
let header = {
|
||||
if is_test {
|
||||
quote! {
|
||||
#[test]
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
};
|
||||
|
||||
let result = quote! {
|
||||
#header
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
#rt
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
};
|
||||
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, false, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_threaded: bool) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
for attr in &input.attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
if !input.sig.inputs.is_empty() {
|
||||
let msg = "the test function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
parse_knobs(input, args, true, rt_threaded).unwrap_or_else(|e| e.to_compile_error().into())
|
||||
}
|
||||
|
||||
pub(crate) mod old {
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
enum Runtime {
|
||||
Basic,
|
||||
Threaded,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub(crate) fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let sig = &mut input.sig;
|
||||
let name = &sig.ident;
|
||||
let inputs = &sig.inputs;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
if sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if name == "main" && !inputs.is_empty() {
|
||||
let msg = "the main function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
sig.asyncness = None;
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded | Runtime::Auto => quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic => quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
|
||||
let ret = &input.sig.output;
|
||||
let name = &input.sig.ident;
|
||||
let body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let vis = input.vis;
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("test") {
|
||||
let msg = "second test attribute is supplied";
|
||||
return syn::Error::new_spanned(&attr, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
if input.sig.asyncness.is_none() {
|
||||
let msg = "the async keyword is missing from the function declaration";
|
||||
return syn::Error::new_spanned(&input.sig.fn_token, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if !input.sig.inputs.is_empty() {
|
||||
let msg = "the test function cannot accept arguments";
|
||||
return syn::Error::new_spanned(&input.sig.inputs, msg)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut runtime = Runtime::Auto;
|
||||
|
||||
for arg in args {
|
||||
if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = arg {
|
||||
let ident = path.get_ident();
|
||||
if ident.is_none() {
|
||||
let msg = "Must have specified ident";
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
match ident.unwrap().to_string().to_lowercase().as_str() {
|
||||
"threaded_scheduler" => runtime = Runtime::Threaded,
|
||||
"basic_scheduler" => runtime = Runtime::Basic,
|
||||
name => {
|
||||
let msg = format!("Unknown attribute {} is specified; expected `basic_scheduler` or `threaded_scheduler`", name);
|
||||
return syn::Error::new_spanned(path, msg).to_compile_error().into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match runtime {
|
||||
Runtime::Threaded => quote! {
|
||||
#[test]
|
||||
#(#attrs)*
|
||||
#vis fn #name() #ret {
|
||||
tokio::runtime::Runtime::new().unwrap().block_on(async { #body })
|
||||
}
|
||||
},
|
||||
Runtime::Basic | Runtime::Auto => quote! {
|
||||
#[test]
|
||||
#(#attrs)*
|
||||
#vis fn #name() #ret {
|
||||
tokio::runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { #body })
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
result.into()
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-macros/0.2.5")]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
#![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))
|
||||
))]
|
||||
|
||||
//! Macros for use with Tokio
|
||||
|
||||
// This `extern` is required for older `rustc` versions but newer `rustc`
|
||||
// versions warn about the unused `extern crate`.
|
||||
#[allow(unused_extern_crates)]
|
||||
extern crate proc_macro;
|
||||
|
||||
mod entry;
|
||||
mod select;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
/// Marks async function to be executed by selected runtime.
|
||||
///
|
||||
/// ## 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).
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Set number of core threads
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(core_threads = 1)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_threaded(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, true)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime.
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `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
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Select runtime
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main(basic_scheduler)]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
#[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)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by selected runtime.
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Function arguments:
|
||||
///
|
||||
/// Arguments are allowed for any functions aside from `main` which is special
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```rust
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// println!("Hello world");
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||
pub fn main_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::main(args, item, false)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test 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
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test(core_threads = 1)]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Using default
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_threaded(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);
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::old::test(args, item)
|
||||
}
|
||||
|
||||
/// Marks async function to be executed by runtime, suitable to test environment
|
||||
///
|
||||
/// ## Options:
|
||||
///
|
||||
/// - `max_threads=n` - Sets max threads to `n`.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[tokio::test]
|
||||
/// async fn my_test() {
|
||||
/// assert!(true);
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_basic(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
entry::test(args, item, false)
|
||||
}
|
||||
|
||||
/// Implementation detail of the `select!` macro. This macro is **not** intended
|
||||
/// to be used as part of the public API and is permitted to change.
|
||||
#[proc_macro]
|
||||
#[doc(hidden)]
|
||||
pub fn select_priv_declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
select::declare_output_enum(input)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
|
||||
// passed in is: `(_ _ _)` with one `_` per branch
|
||||
let branches = match input.into_iter().next() {
|
||||
Some(TokenTree::Group(group)) => group.stream().into_iter().count(),
|
||||
_ => panic!("unexpected macro input"),
|
||||
};
|
||||
|
||||
let variants = (0..branches)
|
||||
.map(|num| Ident::new(&format!("_{}", num), Span::call_site()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Use a bitfield to track which futures completed
|
||||
let mask = Ident::new(
|
||||
if branches <= 8 {
|
||||
"u8"
|
||||
} else if branches <= 16 {
|
||||
"u16"
|
||||
} else if branches <= 32 {
|
||||
"u32"
|
||||
} else if branches <= 64 {
|
||||
"u64"
|
||||
} else {
|
||||
panic!("up to 64 branches supported");
|
||||
},
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
TokenStream::from(quote! {
|
||||
pub(super) enum Out<#( #variants ),*> {
|
||||
#( #variants(#variants), )*
|
||||
// Include a `Disabled` variant signifying that all select branches
|
||||
// failed to resolve.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub(super) type Mask = #mask;
|
||||
})
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# 0.2.0 (November 25, 2019)
|
||||
|
||||
- Initial release
|
||||
@@ -1,33 +0,0 @@
|
||||
[package]
|
||||
name = "tokio-test"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0"
|
||||
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"
|
||||
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"] }
|
||||
|
||||
bytes = "0.5.0"
|
||||
futures-core = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
futures-util = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -1,13 +0,0 @@
|
||||
# tokio-test
|
||||
|
||||
Tokio and Futures based testing utilities
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](LICENSE).
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
|
||||
terms or conditions.
|
||||
@@ -1,414 +0,0 @@
|
||||
#![cfg(not(loom))]
|
||||
|
||||
//! A mock type implementing [`AsyncRead`] and [`AsyncWrite`].
|
||||
//!
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! Provides a type that implements [`AsyncRead`] + [`AsyncWrite`] that can be configured
|
||||
//! to handle an arbitrary sequence of read and write operations. This is useful
|
||||
//! for writing unit tests for networking services as using an actual network
|
||||
//! type is fairly non deterministic.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Attempting to write data that the mock isn't expected will result in a
|
||||
//! panic.
|
||||
//!
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{self, Delay, Duration, Instant};
|
||||
|
||||
use bytes::Buf;
|
||||
use futures_core::ready;
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{self, Poll, Waker};
|
||||
use std::{cmp, io};
|
||||
|
||||
/// An I/O object that follows a predefined script.
|
||||
///
|
||||
/// This value is created by `Builder` and implements `AsyncRead` + `AsyncWrite`. It
|
||||
/// follows the scenario described by the builder and panics otherwise.
|
||||
#[derive(Debug)]
|
||||
pub struct Mock {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
/// A handle to send additional actions to the related `Mock`.
|
||||
#[derive(Debug)]
|
||||
pub struct Handle {
|
||||
tx: mpsc::UnboundedSender<Action>,
|
||||
}
|
||||
|
||||
/// Builds `Mock` instances.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Builder {
|
||||
// Sequence of actions for the Mock to take
|
||||
actions: VecDeque<Action>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Action {
|
||||
Read(Vec<u8>),
|
||||
Write(Vec<u8>),
|
||||
Wait(Duration),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
actions: VecDeque<Action>,
|
||||
waiting: Option<Instant>,
|
||||
sleep: Option<Delay>,
|
||||
read_wait: Option<Waker>,
|
||||
rx: mpsc::UnboundedReceiver<Action>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Return a new, empty `Builder.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Sequence a `read` operation.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `read` call
|
||||
/// and return `buf`.
|
||||
pub fn read(&mut self, buf: &[u8]) -> &mut Self {
|
||||
self.actions.push_back(Action::Read(buf.into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a `write` operation.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `write`
|
||||
/// call.
|
||||
pub fn write(&mut self, buf: &[u8]) -> &mut Self {
|
||||
self.actions.push_back(Action::Write(buf.into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a wait.
|
||||
///
|
||||
/// The next operation in the mock's script will be to wait without doing so
|
||||
/// for `duration` amount of time.
|
||||
pub fn wait(&mut self, duration: Duration) -> &mut Self {
|
||||
let duration = cmp::max(duration, Duration::from_millis(1));
|
||||
self.actions.push_back(Action::Wait(duration));
|
||||
self
|
||||
}
|
||||
|
||||
/// Build a `Mock` value according to the defined script.
|
||||
pub fn build(&mut self) -> Mock {
|
||||
let (mock, _) = self.build_with_handle();
|
||||
mock
|
||||
}
|
||||
|
||||
/// Build a `Mock` value paired with a handle
|
||||
pub fn build_with_handle(&mut self) -> (Mock, Handle) {
|
||||
let (inner, handle) = Inner::new(self.actions.clone());
|
||||
|
||||
let mock = Mock { inner };
|
||||
|
||||
(mock, handle)
|
||||
}
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Sequence a `read` operation.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `read` call
|
||||
/// and return `buf`.
|
||||
pub fn read(&mut self, buf: &[u8]) -> &mut Self {
|
||||
self.tx.send(Action::Read(buf.into())).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sequence a `write` operation.
|
||||
///
|
||||
/// The next operation in the mock's script will be to expect a `write`
|
||||
/// call.
|
||||
pub fn write(&mut self, buf: &[u8]) -> &mut Self {
|
||||
self.tx.send(Action::Write(buf.into())).unwrap();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new(actions: VecDeque<Action>) -> (Inner, Handle) {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let inner = Inner {
|
||||
actions,
|
||||
sleep: None,
|
||||
read_wait: None,
|
||||
rx,
|
||||
waiting: None,
|
||||
};
|
||||
|
||||
let handle = Handle { tx };
|
||||
|
||||
(inner, handle)
|
||||
}
|
||||
|
||||
fn poll_action(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Action>> {
|
||||
self.rx.poll_recv(cx)
|
||||
}
|
||||
|
||||
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
|
||||
match self.action() {
|
||||
Some(&mut Action::Read(ref mut data)) => {
|
||||
// Figure out how much to copy
|
||||
let n = cmp::min(dst.len(), data.len());
|
||||
|
||||
// Copy the data into the `dst` slice
|
||||
(&mut dst[..n]).copy_from_slice(&data[..n]);
|
||||
|
||||
// Drain the data from the source
|
||||
data.drain(..n);
|
||||
|
||||
// Return the number of bytes read
|
||||
Ok(n)
|
||||
}
|
||||
Some(_) => {
|
||||
// Either waiting or expecting a write
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
None => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, mut src: &[u8]) -> io::Result<usize> {
|
||||
let mut ret = 0;
|
||||
|
||||
if self.actions.is_empty() {
|
||||
return Err(io::ErrorKind::BrokenPipe.into());
|
||||
}
|
||||
|
||||
if let Some(&mut Action::Wait(..)) = self.action() {
|
||||
return Err(io::ErrorKind::WouldBlock.into());
|
||||
}
|
||||
|
||||
for i in 0..self.actions.len() {
|
||||
match self.actions[i] {
|
||||
Action::Write(ref mut expect) => {
|
||||
let n = cmp::min(src.len(), expect.len());
|
||||
|
||||
assert_eq!(&src[..n], &expect[..n]);
|
||||
|
||||
// Drop data that was matched
|
||||
expect.drain(..n);
|
||||
src = &src[n..];
|
||||
|
||||
ret += n;
|
||||
|
||||
if src.is_empty() {
|
||||
return Ok(ret);
|
||||
}
|
||||
}
|
||||
Action::Wait(..) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// TODO: remove write
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn remaining_wait(&mut self) -> Option<Duration> {
|
||||
match self.action() {
|
||||
Some(&mut Action::Wait(dur)) => Some(dur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn action(&mut self) -> Option<&mut Action> {
|
||||
loop {
|
||||
if self.actions.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.actions[0] {
|
||||
Action::Read(ref mut data) => {
|
||||
if !data.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Action::Write(ref mut data) => {
|
||||
if !data.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Action::Wait(ref mut dur) => {
|
||||
if let Some(until) = self.waiting {
|
||||
let now = Instant::now();
|
||||
|
||||
if now < until {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
self.waiting = Some(Instant::now() + *dur);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _action = self.actions.pop_front();
|
||||
}
|
||||
|
||||
self.actions.front_mut()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Inner =====
|
||||
|
||||
impl Mock {
|
||||
fn maybe_wakeup_reader(&mut self) {
|
||||
match self.inner.action() {
|
||||
Some(&mut Action::Read(_)) | None => {
|
||||
if let Some(waker) = self.inner.read_wait.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Mock {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
loop {
|
||||
if let Some(ref mut sleep) = self.inner.sleep {
|
||||
ready!(Pin::new(sleep).poll(cx));
|
||||
}
|
||||
|
||||
// If a sleep is set, it has already fired
|
||||
self.inner.sleep = None;
|
||||
|
||||
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));
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
ret => return Poll::Ready(ret),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Mock {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
loop {
|
||||
if let Some(ref mut sleep) = self.inner.sleep {
|
||||
ready!(Pin::new(sleep).poll(cx));
|
||||
}
|
||||
|
||||
// If a sleep is set, it has already fired
|
||||
self.inner.sleep = None;
|
||||
|
||||
match self.inner.write(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));
|
||||
} else {
|
||||
panic!("unexpected WouldBlock");
|
||||
}
|
||||
}
|
||||
Ok(0) => {
|
||||
// TODO: Is this correct?
|
||||
if !self.inner.actions.is_empty() {
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
// TODO: Extract
|
||||
match ready!(self.inner.poll_action(cx)) {
|
||||
Some(action) => {
|
||||
self.inner.actions.push_back(action);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
panic!("unexpected write");
|
||||
}
|
||||
}
|
||||
}
|
||||
ret => {
|
||||
self.maybe_wakeup_reader();
|
||||
return Poll::Ready(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(()))
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// Returns `true` if called from the context of a futures-rs Task
|
||||
fn is_task_ctx() -> bool {
|
||||
use std::panic;
|
||||
|
||||
// Save the existing panic hook
|
||||
let h = panic::take_hook();
|
||||
|
||||
// Install a new one that does nothing
|
||||
panic::set_hook(Box::new(|_| {}));
|
||||
|
||||
// Attempt to call the fn
|
||||
let r = panic::catch_unwind(|| task::current()).is_ok();
|
||||
|
||||
// Re-install the old one
|
||||
panic::set_hook(h);
|
||||
|
||||
// Return the result
|
||||
r
|
||||
}
|
||||
*/
|
||||
@@ -1,38 +0,0 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-test/0.2.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))
|
||||
))]
|
||||
|
||||
//! Tokio and Futures based testing utilites
|
||||
|
||||
pub mod io;
|
||||
|
||||
mod macros;
|
||||
pub mod task;
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the
|
||||
/// future completes.
|
||||
///
|
||||
/// For more information, see the documentation for
|
||||
/// [`tokio::runtime::current_thread::Runtime::block_on`][runtime-block-on].
|
||||
///
|
||||
/// [runtime-block-on]: https://docs.rs/tokio/0.2.0-alpha.2/tokio/runtime/current_thread/struct.Runtime.html#method.block_on
|
||||
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
|
||||
use tokio::runtime;
|
||||
|
||||
let mut rt = runtime::Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
rt.block_on(future)
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
//! A collection of useful macros for testing futures and tokio based code
|
||||
|
||||
/// Asserts a `Poll` is ready, returning the value.
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready` at
|
||||
/// runtime.
|
||||
///
|
||||
/// # Custom Messages
|
||||
///
|
||||
/// This macro has a second form, where a custom panic message can be provided with or without
|
||||
/// arguments for formatting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use futures_util::future;
|
||||
/// use tokio_test::{assert_ready, task};
|
||||
///
|
||||
/// let mut fut = task::spawn(future::ready(()));
|
||||
/// assert_ready!(fut.poll());
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! assert_ready {
|
||||
($e:expr) => {{
|
||||
use core::task::Poll::*;
|
||||
match $e {
|
||||
Ready(v) => v,
|
||||
Pending => panic!("pending"),
|
||||
}
|
||||
}};
|
||||
($e:expr, $($msg:tt)+) => {{
|
||||
use core::task::Poll::*;
|
||||
match $e {
|
||||
Ready(v) => v,
|
||||
Pending => {
|
||||
panic!("pending; {}", format_args!($($msg)+))
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts a `Poll<Result<...>>` is ready and `Ok`, returning the value.
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready(Ok(..))` at
|
||||
/// runtime.
|
||||
///
|
||||
/// # Custom Messages
|
||||
///
|
||||
/// This macro has a second form, where a custom panic message can be provided with or without
|
||||
/// arguments for formatting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use futures_util::future;
|
||||
/// use tokio_test::{assert_ready_ok, task};
|
||||
///
|
||||
/// let mut fut = task::spawn(future::ok::<_, ()>(()));
|
||||
/// assert_ready_ok!(fut.poll());
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! assert_ready_ok {
|
||||
($e:expr) => {{
|
||||
use tokio_test::{assert_ready, assert_ok};
|
||||
let val = assert_ready!($e);
|
||||
assert_ok!(val)
|
||||
}};
|
||||
($e:expr, $($msg:tt)+) => {{
|
||||
use tokio_test::{assert_ready, assert_ok};
|
||||
let val = assert_ready!($e, $($msg)*);
|
||||
assert_ok!(val, $($msg)*)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts a `Poll<Result<...>>` is ready and `Err`, returning the error.
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready(Err(..))` at
|
||||
/// runtime.
|
||||
///
|
||||
/// # Custom Messages
|
||||
///
|
||||
/// This macro has a second form, where a custom panic message can be provided with or without
|
||||
/// arguments for formatting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use futures_util::future;
|
||||
/// use tokio_test::{assert_ready_err, task};
|
||||
///
|
||||
/// let mut fut = task::spawn(future::err::<(), _>(()));
|
||||
/// assert_ready_err!(fut.poll());
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! assert_ready_err {
|
||||
($e:expr) => {{
|
||||
use tokio_test::{assert_ready, assert_err};
|
||||
let val = assert_ready!($e);
|
||||
assert_err!(val)
|
||||
}};
|
||||
($e:expr, $($msg:tt)+) => {{
|
||||
use tokio_test::{assert_ready, assert_err};
|
||||
let val = assert_ready!($e, $($msg)*);
|
||||
assert_err!(val, $($msg)*)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts a `Poll` is pending.
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Pending` at
|
||||
/// runtime.
|
||||
///
|
||||
/// # Custom Messages
|
||||
///
|
||||
/// This macro has a second form, where a custom panic message can be provided with or without
|
||||
/// arguments for formatting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use futures_util::future;
|
||||
/// use tokio_test::{assert_pending, task};
|
||||
///
|
||||
/// let mut fut = task::spawn(future::pending::<()>());
|
||||
/// assert_pending!(fut.poll());
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! assert_pending {
|
||||
($e:expr) => {{
|
||||
use core::task::Poll::*;
|
||||
match $e {
|
||||
Pending => {}
|
||||
Ready(v) => panic!("ready; value = {:?}", v),
|
||||
}
|
||||
}};
|
||||
($e:expr, $($msg:tt)+) => {{
|
||||
use core::task::Poll::*;
|
||||
match $e {
|
||||
Pending => {}
|
||||
Ready(v) => {
|
||||
panic!("ready; value = {:?}; {}", v, format_args!($($msg)+))
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts if a poll is ready and check for equality on the value
|
||||
///
|
||||
/// This will invoke `panic!` if the provided `Poll` does not evaluate to `Poll::Ready` at
|
||||
/// runtime and the value produced does not partially equal the expected value.
|
||||
///
|
||||
/// # Custom Messages
|
||||
///
|
||||
/// This macro has a second form, where a custom panic message can be provided with or without
|
||||
/// arguments for formatting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use futures_util::future;
|
||||
/// use tokio_test::{assert_ready_eq, task};
|
||||
///
|
||||
/// let mut fut = task::spawn(future::ready(42));
|
||||
/// assert_ready_eq!(fut.poll(), 42);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! assert_ready_eq {
|
||||
($e:expr, $expect:expr) => {
|
||||
let val = $crate::assert_ready!($e);
|
||||
assert_eq!(val, $expect)
|
||||
};
|
||||
|
||||
($e:expr, $expect:expr, $($msg:tt)+) => {
|
||||
let val = $crate::assert_ready!($e, $($msg)*);
|
||||
assert_eq!(val, $expect, $($msg)*)
|
||||
};
|
||||
}
|
||||
|
||||
/// Asserts that the expression evaluates to `Ok` and returns the value.
|
||||
///
|
||||
/// This will invoke the `panic!` macro if the provided expression does not evaluate to `Ok` at
|
||||
/// runtime.
|
||||
///
|
||||
/// # Custom Messages
|
||||
///
|
||||
/// This macro has a second form, where a custom panic message can be provided with or without
|
||||
/// arguments for formatting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_test::assert_ok;
|
||||
///
|
||||
/// let n: u32 = assert_ok!("123".parse());
|
||||
///
|
||||
/// let s = "123";
|
||||
/// let n: u32 = assert_ok!(s.parse(), "testing parsing {:?} as a u32", s);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! assert_ok {
|
||||
($e:expr) => {
|
||||
assert_ok!($e,)
|
||||
};
|
||||
($e:expr,) => {{
|
||||
use std::result::Result::*;
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("assertion failed: Err({:?})", e),
|
||||
}
|
||||
}};
|
||||
($e:expr, $($arg:tt)+) => {{
|
||||
use std::result::Result::*;
|
||||
match $e {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("assertion failed: Err({:?}): {}", e, format_args!($($arg)+)),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts that the expression evaluates to `Err` and returns the error.
|
||||
///
|
||||
/// This will invoke the `panic!` macro if the provided expression does not evaluate to `Err` at
|
||||
/// runtime.
|
||||
///
|
||||
/// # Custom Messages
|
||||
///
|
||||
/// This macro has a second form, where a custom panic message can be provided with or without
|
||||
/// arguments for formatting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio_test::assert_err;
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
///
|
||||
/// let err = assert_err!(u32::from_str("fail"));
|
||||
///
|
||||
/// let msg = "fail";
|
||||
/// let err = assert_err!(u32::from_str(msg), "testing parsing {:?} as u32", msg);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! assert_err {
|
||||
($e:expr) => {
|
||||
assert_err!($e,);
|
||||
};
|
||||
($e:expr,) => {{
|
||||
use std::result::Result::*;
|
||||
match $e {
|
||||
Ok(v) => panic!("assertion failed: Ok({:?})", v),
|
||||
Err(e) => e,
|
||||
}
|
||||
}};
|
||||
($e:expr, $($arg:tt)+) => {{
|
||||
use std::result::Result::*;
|
||||
match $e {
|
||||
Ok(v) => panic!("assertion failed: Ok({:?}): {}", v, format_args!($($arg)+)),
|
||||
Err(e) => e,
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
//! Futures task based helpers
|
||||
|
||||
#![allow(clippy::mutex_atomic)]
|
||||
|
||||
use std::future::Future;
|
||||
use std::mem;
|
||||
use std::ops;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use tokio::stream::Stream;
|
||||
|
||||
/// TOOD: dox
|
||||
pub fn spawn<T>(task: T) -> Spawn<T> {
|
||||
Spawn {
|
||||
task: MockTask::new(),
|
||||
future: Box::pin(task),
|
||||
}
|
||||
}
|
||||
|
||||
/// Future spawned on a mock task
|
||||
#[derive(Debug)]
|
||||
pub struct Spawn<T> {
|
||||
task: MockTask,
|
||||
future: Pin<Box<T>>,
|
||||
}
|
||||
|
||||
/// Mock task
|
||||
///
|
||||
/// A mock task is able to intercept and track wake notifications.
|
||||
#[derive(Debug, Clone)]
|
||||
struct MockTask {
|
||||
waker: Arc<ThreadWaker>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ThreadWaker {
|
||||
state: Mutex<usize>,
|
||||
condvar: Condvar,
|
||||
}
|
||||
|
||||
const IDLE: usize = 0;
|
||||
const WAKE: usize = 1;
|
||||
const SLEEP: usize = 2;
|
||||
|
||||
impl<T> Spawn<T> {
|
||||
/// Consumes `self` returning the inner value
|
||||
pub fn into_inner(mut 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
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the inner future has received a wake notification
|
||||
/// since the last call to `enter`.
|
||||
pub fn is_woken(&self) -> bool {
|
||||
self.task.is_woken()
|
||||
}
|
||||
|
||||
/// Returns the number of references to the task waker
|
||||
///
|
||||
/// The task itself holds a reference. The return value will never be zero.
|
||||
pub fn waker_ref_count(&self) -> usize {
|
||||
self.task.waker_ref_count()
|
||||
}
|
||||
|
||||
/// Enter the task context
|
||||
pub fn enter<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Context<'_>, Pin<&mut T>) -> R,
|
||||
{
|
||||
let fut = self.future.as_mut();
|
||||
self.task.enter(|cx| f(cx, fut))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> ops::Deref for Spawn<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.future
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Unpin> ops::DerefMut for Spawn<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.future
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Future> Spawn<T> {
|
||||
/// Polls a future
|
||||
pub fn poll(&mut self) -> Poll<T::Output> {
|
||||
let fut = self.future.as_mut();
|
||||
self.task.enter(|cx| fut.poll(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Stream> Spawn<T> {
|
||||
/// Polls a stream
|
||||
pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
|
||||
let stream = self.future.as_mut();
|
||||
self.task.enter(|cx| stream.poll_next(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl MockTask {
|
||||
/// Creates new mock task
|
||||
fn new() -> Self {
|
||||
MockTask {
|
||||
waker: Arc::new(ThreadWaker::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a closure from the context of the task.
|
||||
///
|
||||
/// Any wake notifications resulting from the execution of the closure are
|
||||
/// tracked.
|
||||
fn enter<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Context<'_>) -> R,
|
||||
{
|
||||
self.waker.clear();
|
||||
let waker = self.waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
f(&mut cx)
|
||||
}
|
||||
|
||||
/// Returns `true` if the inner future has received a wake notification
|
||||
/// since the last call to `enter`.
|
||||
fn is_woken(&self) -> bool {
|
||||
self.waker.is_woken()
|
||||
}
|
||||
|
||||
/// Returns the number of references to the task waker
|
||||
///
|
||||
/// The task itself holds a reference. The return value will never be zero.
|
||||
fn waker_ref_count(&self) -> usize {
|
||||
Arc::strong_count(&self.waker)
|
||||
}
|
||||
|
||||
fn waker(&self) -> Waker {
|
||||
unsafe {
|
||||
let raw = to_raw(self.waker.clone());
|
||||
Waker::from_raw(raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MockTask {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadWaker {
|
||||
fn new() -> Self {
|
||||
ThreadWaker {
|
||||
state: Mutex::new(IDLE),
|
||||
condvar: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears any previously received wakes, avoiding potential spurrious
|
||||
/// wake notifications. This should only be called immediately before running the
|
||||
/// task.
|
||||
fn clear(&self) {
|
||||
*self.state.lock().unwrap() = IDLE;
|
||||
}
|
||||
|
||||
fn is_woken(&self) -> bool {
|
||||
match *self.state.lock().unwrap() {
|
||||
IDLE => false,
|
||||
WAKE => true,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wake(&self) {
|
||||
// First, try transitioning from IDLE -> NOTIFY, this does not require a lock.
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let prev = *state;
|
||||
|
||||
if prev == WAKE {
|
||||
return;
|
||||
}
|
||||
|
||||
*state = WAKE;
|
||||
|
||||
if prev == IDLE {
|
||||
return;
|
||||
}
|
||||
|
||||
// The other half is sleeping, so we wake it up.
|
||||
assert_eq!(prev, SLEEP);
|
||||
self.condvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
|
||||
|
||||
unsafe fn to_raw(waker: Arc<ThreadWaker>) -> RawWaker {
|
||||
RawWaker::new(Arc::into_raw(waker) as *const (), &VTABLE)
|
||||
}
|
||||
|
||||
unsafe fn from_raw(raw: *const ()) -> Arc<ThreadWaker> {
|
||||
Arc::from_raw(raw as *const ThreadWaker)
|
||||
}
|
||||
|
||||
unsafe fn clone(raw: *const ()) -> RawWaker {
|
||||
let waker = from_raw(raw);
|
||||
|
||||
// Increment the ref count
|
||||
mem::forget(waker.clone());
|
||||
|
||||
to_raw(waker)
|
||||
}
|
||||
|
||||
unsafe fn wake(raw: *const ()) {
|
||||
let waker = from_raw(raw);
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
unsafe fn wake_by_ref(raw: *const ()) {
|
||||
let waker = from_raw(raw);
|
||||
waker.wake();
|
||||
|
||||
// We don't actually own a reference to the unparker
|
||||
mem::forget(waker);
|
||||
}
|
||||
|
||||
unsafe fn drop_waker(raw: *const ()) {
|
||||
let _ = from_raw(raw);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::time::{delay_until, Duration, Instant};
|
||||
use tokio_test::block_on;
|
||||
|
||||
#[test]
|
||||
fn async_block() {
|
||||
assert_eq!(4, block_on(async { 4 }));
|
||||
}
|
||||
|
||||
async fn five() -> u8 {
|
||||
5
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_fn() {
|
||||
assert_eq!(5, block_on(five()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delay() {
|
||||
let deadline = Instant::now() + Duration::from_millis(100);
|
||||
|
||||
block_on(async {
|
||||
delay_until(deadline).await;
|
||||
});
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio_test::io::Builder;
|
||||
|
||||
#[tokio::test]
|
||||
async fn read() {
|
||||
let mut mock = Builder::new().read(b"hello ").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 ");
|
||||
|
||||
let n = mock.read(&mut buf).await.expect("read 2");
|
||||
assert_eq!(&buf[..n], b"world!");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write() {
|
||||
let mut mock = Builder::new().write(b"hello ").write(b"world!").build();
|
||||
|
||||
mock.write_all(b"hello ").await.expect("write 1");
|
||||
mock.write_all(b"world!").await.expect("write 2");
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::task::Poll;
|
||||
use tokio_test::{
|
||||
assert_pending, assert_ready, assert_ready_eq, assert_ready_err, assert_ready_ok,
|
||||
};
|
||||
|
||||
fn ready() -> Poll<()> {
|
||||
Poll::Ready(())
|
||||
}
|
||||
|
||||
fn ready_ok() -> Poll<Result<(), ()>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn ready_err() -> Poll<Result<(), ()>> {
|
||||
Poll::Ready(Err(()))
|
||||
}
|
||||
|
||||
fn pending() -> Poll<()> {
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Test {
|
||||
Data,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_ready() {
|
||||
let poll = ready();
|
||||
assert_ready!(poll);
|
||||
assert_ready!(poll, "some message");
|
||||
assert_ready!(poll, "{:?}", ());
|
||||
assert_ready!(poll, "{:?}", Test::Data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn assert_ready_on_pending() {
|
||||
let poll = pending();
|
||||
assert_ready!(poll);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_pending() {
|
||||
let poll = pending();
|
||||
assert_pending!(poll);
|
||||
assert_pending!(poll, "some message");
|
||||
assert_pending!(poll, "{:?}", ());
|
||||
assert_pending!(poll, "{:?}", Test::Data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn assert_pending_on_ready() {
|
||||
let poll = ready();
|
||||
assert_pending!(poll);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_ready_ok() {
|
||||
let poll = ready_ok();
|
||||
assert_ready_ok!(poll);
|
||||
assert_ready_ok!(poll, "some message");
|
||||
assert_ready_ok!(poll, "{:?}", ());
|
||||
assert_ready_ok!(poll, "{:?}", Test::Data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn assert_ok_on_err() {
|
||||
let poll = ready_err();
|
||||
assert_ready_ok!(poll);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_ready_err() {
|
||||
let poll = ready_err();
|
||||
assert_ready_err!(poll);
|
||||
assert_ready_err!(poll, "some message");
|
||||
assert_ready_err!(poll, "{:?}", ());
|
||||
assert_ready_err!(poll, "{:?}", Test::Data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn assert_err_on_ok() {
|
||||
let poll = ready_ok();
|
||||
assert_ready_err!(poll);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_ready_eq() {
|
||||
let poll = ready();
|
||||
assert_ready_eq!(poll, ());
|
||||
assert_ready_eq!(poll, (), "some message");
|
||||
assert_ready_eq!(poll, (), "{:?}", ());
|
||||
assert_ready_eq!(poll, (), "{:?}", Test::Data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn assert_eq_on_not_eq() {
|
||||
let poll = ready_err();
|
||||
assert_ready_eq!(poll, Ok(()));
|
||||
}
|
||||
@@ -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 +0,0 @@
|
||||
# 0.2.0 (November 26, 2019)
|
||||
|
||||
- Initial release
|
||||
@@ -1,51 +0,0 @@
|
||||
[package]
|
||||
name = "tokio-util"
|
||||
# When releasing to crates.io:
|
||||
# - Remove path dependencies
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.2.x" git tag.
|
||||
version = "0.2.0"
|
||||
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"
|
||||
description = """
|
||||
Additional utilities for working with Tokio.
|
||||
"""
|
||||
categories = ["asynchronous"]
|
||||
|
||||
[features]
|
||||
# No features on by default
|
||||
default = []
|
||||
|
||||
# Shorthand for enabling everything
|
||||
full = ["codec", "udp", "compat"]
|
||||
|
||||
compat = ["futures-io",]
|
||||
codec = ["tokio/stream"]
|
||||
udp = ["tokio/udp"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio" }
|
||||
|
||||
bytes = "0.5.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"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
|
||||
tokio-test = { version = "0.2.0", path = "../tokio-test" }
|
||||
|
||||
futures = "0.3.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
@@ -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.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user