Compare commits

..
2 Commits
Author SHA1 Message Date
Alice Ryhl c8be3e4d18 Start work on compat feature 2020-10-13 23:33:19 +02:00
kalcutter a517dbf605 net: make UnixListener::poll_accept public (#2880)
This makes it consistent with `TcpListener::poll_accept` and other
public poll methods the library provides. This particular method is
useful for writing generic code that accepts connections since async
functions can't easily be used with traits. It is possible to
generically accept connections with `Incoming`, however, this doesn't
return the incoming `SocketAddr`.
2020-09-24 17:36:44 -07:00
953 changed files with 34382 additions and 149088 deletions
+30
View File
@@ -0,0 +1,30 @@
freebsd_instance:
image: freebsd-12-1-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
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-macros --target i686-unknown-freebsd
-3
View File
@@ -1,3 +0,0 @@
# These are supported funding model platforms
github: [tokio-rs]
+6 -1
View File
@@ -9,7 +9,12 @@ assignees: ''
**Version**
List the versions of all `tokio` crates you are using. The easiest way to get
this information is using `cargo tree` subcommand:
this information is using `cargo-tree`.
`cargo install cargo-tree`
(see install here: https://github.com/sfackler/cargo-tree)
Then:
`cargo tree | grep tokio`
-4
View File
@@ -1,4 +0,0 @@
contact_links:
- name: Question
url: https://github.com/tokio-rs/tokio/discussions
about: Questions about Tokio should be posted as a GitHub discussion.
+16
View File
@@ -0,0 +1,16 @@
---
name: Question
about: Please use the discussions tab for questions
title: ''
labels: ''
assignees: ''
---
Please post your question as a discussion here:
https://github.com/tokio-rs/tokio/discussions
You may also be able to find help here:
https://discord.gg/tokio
https://users.rust-lang.org/
-20
View File
@@ -1,20 +0,0 @@
# Buildomat illumos CI
This directory contains CI configurations for the [illumos] operating system.
Tokio's illumos CI jobs are run using [Buildomat], a CI system developed by
Oxide Computer, which supports illumos. See [the Buildomat README] for more
details.
## illumos-Specific CI Failures
If your pull request's CI build fails on illumos, and you aren't able to easily
reproduce the failure on other operating systems, don't worry! The
[tokio-rs/illumos] team is responsible for maintaining Tokio's illumos support,
and can be called on to assist contributors with illumos-specific issues. Please
feel free to tag @tokio-rs/illumos to ask for help resolving build failures on
illumos.
[illumos]: https://www.illumos.org/
[Buildomat]: https://github.com/oxidecomputer/buildomat
[the Buildomat README]: https://github.com/oxidecomputer/buildomat
[tokio-rs/illumos]: https://github.com/orgs/tokio-rs/teams/illumos
-8
View File
@@ -1,8 +0,0 @@
# Repository-level Buildomat configuration.
# See: https://github.com/oxidecomputer/buildomat#per-repository-configuration
# Enable buildomat. This one should be self-explanatory.
enable = true
# Allow CI runs for PRs from users outside the `tokio-rs` organization. Our
# buildomat jobs don't touch any secrets/keys, so this should be fine.
org_only = false
-7
View File
@@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
-42
View File
@@ -1,42 +0,0 @@
R-loom-blocking:
- changed-files:
- any-glob-to-any-file:
- tokio/src/runtime/blocking/*
- tokio/src/runtime/blocking/**/*
R-loom-sync:
- changed-files:
- any-glob-to-any-file:
- tokio/src/sync/*
- tokio/src/sync/**/*
R-loom-time-driver:
- changed-files:
- any-glob-to-any-file:
- tokio/src/runtime/time/*
- tokio/src/runtime/time/**/*
- tokio/src/runtime/time_alt/*
- tokio/src/runtime/time_alt/**/*
R-loom-current-thread:
- changed-files:
- any-glob-to-any-file:
- tokio/src/runtime/scheduler/*
- tokio/src/runtime/scheduler/current_thread/*
- tokio/src/runtime/task/*
- tokio/src/runtime/task/**
R-loom-multi-thread:
- changed-files:
- any-glob-to-any-file:
- tokio/src/runtime/scheduler/*
- tokio/src/runtime/scheduler/multi_thread/*
- tokio/src/runtime/scheduler/multi_thread/**
- tokio/src/runtime/task/*
- tokio/src/runtime/task/**
R-loom-util:
- changed-files:
- any-glob-to-any-file:
- tokio-util/src/*
- tokio-util/src/**/*
+8 -10
View File
@@ -9,16 +9,14 @@ on:
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
permissions:
contents: read
jobs:
cargo-deny:
permissions:
checks: write
contents: read
issues: write
security-audit:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- uses: actions/checkout@v7
- uses: EmbarkStudios/cargo-deny-action@v2
- uses: actions/checkout@v2
- name: Audit Check
uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
+123 -1355
View File
File diff suppressed because it is too large Load Diff
-25
View File
@@ -1,25 +0,0 @@
name: "Pull Request Labeler"
on:
- pull_request_target
# See .github/labeler.yml file
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
permissions:
contents: read
jobs:
triage:
permissions:
contents: read # for actions/labeler to determine modified files
pull-requests: write # for actions/labeler to add labels to PRs
runs-on: ubuntu-latest
if: github.repository_owner == 'tokio-rs'
steps:
- uses: actions/labeler@v7
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
-138
View File
@@ -1,138 +0,0 @@
on:
push:
branches: ["master", "tokio-*.x"]
pull_request:
types: [labeled, opened, synchronize, reopened]
branches: ["master", "tokio-*.x"]
name: Loom
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
RUSTFLAGS: -Dwarnings --cfg loom --cfg tokio_unstable -C debug_assertions
LOOM_MAX_PREEMPTIONS: 2
LOOM_MAX_BRANCHES: 10000
RUST_BACKTRACE: 1
# Change to specific Rust release to pin
rust_stable: stable
permissions:
contents: read
jobs:
loom-blocking:
name: loom tokio::runtime::spawn_blocking
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-blocking') || (github.base_ref == null))
runs-on: ubuntu-latest
strategy:
matrix:
# Run the blocking pool loom tests against both `spawn_blocking`
# queue implementations.
include:
- sharded_blocking_queue: "0"
- sharded_blocking_queue: "1"
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture loom_blocking
working-directory: tokio
env:
TOKIO_UNSTABLE_SHARDED_BLOCKING_QUEUE: ${{ matrix.sharded_blocking_queue }}
loom-sync:
name: loom tokio::sync
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-sync') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture sync::tests
working-directory: tokio
loom-time-driver:
name: loom time driver
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture runtime::time
working-directory: tokio
loom-current-thread:
name: loom current-thread scheduler
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture loom_current_thread
working-directory: tokio
loom-multi-thread:
name: loom multi-thread scheduler
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread') || (github.base_ref == null))
runs-on: ubuntu-latest
strategy:
matrix:
include:
- scope: loom_multi_thread::group_a
- scope: loom_multi_thread::group_b
- scope: loom_multi_thread::group_c
- scope: loom_multi_thread::group_d
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: loom ${{ matrix.scope }}
run: cargo test --lib --release --features full -- --nocapture $SCOPE
working-directory: tokio
env:
SCOPE: ${{ matrix.scope }}
loom-util:
name: loom tokio-util
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-util') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture
working-directory: tokio-util
+19 -10
View File
@@ -8,16 +8,25 @@ on:
paths:
- '**/Cargo.toml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
permissions:
contents: read
jobs:
cargo-deny:
security-audit:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- uses: actions/checkout@v7
- uses: EmbarkStudios/cargo-deny-action@v2
- uses: actions/checkout@v2
- name: Install cargo-audit
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-audit
- name: Generate lockfile
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Audit dependencies
uses: actions-rs/cargo@v1
with:
command: audit
-45
View File
@@ -1,45 +0,0 @@
name: Stress Test
on:
pull_request:
push:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
# Change to specific Rust release to pin
rust_stable: 1.82
permissions:
contents: read
jobs:
stress-test:
name: Stress Test
runs-on: ubuntu-latest
strategy:
matrix:
stress-test:
- simple_echo_tcp
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- name: Install Valgrind
uses: taiki-e/install-action@valgrind
- uses: Swatinem/rust-cache@v2
# Compiles each of the stress test examples.
- name: Compile stress test examples
run: cargo build -p stress-test --release --example ${{ matrix.stress-test }}
# Runs each of the examples using Valgrind. Detects leaks and displays them.
- name: Run valgrind
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/release/examples/${{ matrix.stress-test }}
@@ -1,120 +0,0 @@
name: Uring Kernel Version Test
on:
workflow_call:
inputs:
kernel_version:
description: 'Version of the Linux kernel to build'
required: true
type: string
jobs:
build:
runs-on: ubuntu-latest
env:
KERNEL_VERSION: ${{ inputs.kernel_version }}
steps:
- uses: actions/checkout@v7
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential bison flex libssl-dev libelf-dev \
qemu-system-x86 busybox-static cpio xz-utils wget
- name: Cache Linux source
id: cache-kernel
uses: actions/cache@v6
with:
path: linux-${{ env.KERNEL_VERSION }}
key: kernel-${{ env.KERNEL_VERSION }}
- name: Download & build Linux kernel
if: steps.cache-kernel.outputs.cache-hit != 'true'
run: |
MAJOR=${KERNEL_VERSION%%.*}
wget https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KERNEL_VERSION}.tar.xz
tar xf linux-${KERNEL_VERSION}.tar.xz
cd linux-${KERNEL_VERSION}
make defconfig
make -j$(nproc)
# We are running tests also on a Kernel version that does not support io_uring
# to check if the fallback mechanism works
- name: Generate test binaries with io_uring enabled
run: |
# Build both integration (tokio/tests/) and unit (e.g., tokio/src/fs/file/tests.rs) tests with io_uring enabled
rustup target add x86_64-unknown-linux-musl
RUSTFLAGS="--cfg tokio_unstable" \
cargo test -p tokio --features full,io-uring \
--target x86_64-unknown-linux-musl --test 'fs*' --lib --no-run
- name: Prepare initramfs + tests binaries
run: |
set -e
rm -rf initramfs
mkdir -p initramfs/{bin,bin/tests,sbin,proc,sys,tmp}
# Copy test binaries into initramfs
for bin in target/x86_64-unknown-linux-musl/debug/deps/{fs_*,tokio-*}; do
if [ -f "$bin" ] && [ -x "$bin" ]; then
cp "$bin" initramfs/bin/tests
fi
done
# Add BusyBox & symlinks
cp /usr/bin/busybox initramfs/bin/
for cmd in sh mount uname true sleep; do ln -sf busybox initramfs/bin/$cmd; done
ln -sf ../bin/busybox initramfs/sbin/poweroff
# Generate init script
cat > initramfs/init << 'EOF'
#!/bin/sh
set -e
mkdir -p /dev
# create device nodes, as some tests require them
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/tty c 5 0
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mkdir -p /tmp && mount -t tmpfs -o mode=1777 tmpfs /tmp
for f in /bin/tests/*; do RUST_BACKTRACE=1 "$f" ; done
EOF
chmod +x initramfs/init
# Pack into a CPIO archive
(cd initramfs && find . -print0 \
| cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz)
- name: Run tests in QEMU
shell: bash
run: |
set -euo pipefail
set +e
qemu-system-x86_64 \
-kernel linux-${{ env.KERNEL_VERSION }}/arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 rootfstype=ramfs panic=1" \
-nographic -no-reboot -m 1024 -action panic=exit-failure 2>&1 | tee qemu-output.log
qemu_status=${PIPESTATUS[0]} tee_status=${PIPESTATUS[1]}
set -e
if [ "$qemu_status" -ne 0 ]; then
echo "QEMU exited with status $qemu_status"
exit "$qemu_status"
fi
if [ "$tee_status" -ne 0 ]; then
echo "tee exited with status $tee_status"
exit "$tee_status"
fi
if grep -q "test result: FAILED" qemu-output.log; then
echo "tests reported failures"
exit 1
else
echo "all tests passed"
fi
-4
View File
@@ -1,6 +1,2 @@
target
Cargo.lock
.cargo/config.toml
.cargo/config
+533 -24
View File
@@ -1,6 +1,7 @@
# Contributing to Tokio
Thanks for your help improving Tokio! We are so happy to have you!
: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
@@ -8,11 +9,15 @@ use your help.
**No contribution is too small and all contributions are valued.**
See the [contributing guidelines] to get started.
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.
[contributing guidelines]: docs/contributing/README.md
The [dev channel][dev] is available for any concerns not covered in this guide, please join
us!
## Code of Conduct
[dev]: https://discord.gg/tokio
## Conduct
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
the _minimum_ behavior expected from all contributors. Instances of violations of the
@@ -21,34 +26,538 @@ Code of Conduct can be reported by contacting the project team at
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
## Need Help?
## Contributing in Issues
Reach out to us on the [Discord server] for any concern not covered in this guide.
For any issue, there are fundamentally three ways an individual can contribute:
[Discord server]: https://discord.gg/tokio
1. By opening the issue for discussion: For instance, if you believe that you
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
issue tracker][issue] is the way to report it.
## LTS guarantees
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.
In Tokio ≥1.0.0, each LTS release comes with the guarantee of at least one year of
backported fixes.
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.
The goal of these guarantees is to provide stability to the ecosystem.
[issue]: https://github.com/tokio-rs/tokio/issues
## Minimum Supported Rust Version (MSRV)
**Anybody can participate in any stage of contribution**. We urge you to
participate in the discussion around bugs and participate in reviewing PRs.
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
compiler release.
* The MSRV will only be increased on 1.x releases.
### Asking for General Help
## Versioning Policy
If you have reviewed existing documentation and still have questions or are
having problems, you can [open a discussion] asking for help.
With Tokio ≥1.0.0:
In exchange for receiving help, we ask that you contribute back a documentation
PR that helps others avoid the problems that you encountered.
* Patch (1.\_.x) releases _should only_ contain bug fixes or documentation
changes. Besides this, these releases should not substantially change
runtime behavior.
* Minor (1.x) releases may contain new functionality, MSRV increases (see
above), minor dependency updates, deprecations, and larger internal
implementation changes.
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new
This is as defined by [Semantic Versioning 2.0](https://semver.org/).
### Submitting a Bug Report
When opening a new issue in the Tokio issue tracker, you will be presented
with a basic template that should be filled in. If you believe that you have
uncovered a bug, please fill out this form, following the template to the best
of your ability. Do not worry if you cannot answer every detail, just fill in
what you can.
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
### 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.
### Cargo Commands
Due to the extensive use of features in Tokio, you will often need to add extra
arguments to many common cargo commands. This section lists some commonly needed
commands.
Some commands just need the `--all-features` argument:
```
cargo build --all-features
cargo check --all-features
cargo test --all-features
```
When building documentation normally, the markers that list the features
required for various parts of Tokio are missing. To build the documentation
correctly, use this command:
```
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
```
The `cargo fmt` command does not work on the Tokio codebase. You can use the
command below instead:
```
# Mac or Linux
rustfmt --check --edition 2018 $(find . -name '*.rs' -print)
# Powershell
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
```
The `--check` argument prints the things that need to be fixed. If you remove
it, `rustfmt` will update your files locally instead.
You can run loom tests with
```
cd tokio # tokio crate in workspace
LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \
cargo test --lib --release --features full -- --test-threads=1 --nocapture
```
### Tests
If the change being proposed alters code (as opposed to only documentation for
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-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](#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
## Keeping track of issues and PRs
The Tokio GitHub repository has a lot of issues and PRs, which is not easy to
keep track of. This section explains the meaning of various labels, as well as
our [GitHub project][project]. The section is primarily targeted at maintainers.
**Area.** The area label describes the crates relevant to this issue or PR.
- **A-tokio** This issue concerns the main Tokio crate.
- **A-tokio-util** This issue concerns the `tokio-util` crate.
- **A-tokio-tls** This issue concerns the `tokio-tls` crate. Only used for
older issues, as the crate has been moved to another repository.
- **A-tokio-test** The issue concerns the `tokio-test` crate.
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
be used for the procedural macros, and not `join!` or `select!`.
- **A-ci** This issue concerns our GitHub Actions setup.
**Category.** The category label describes the category.
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
- **C-enhancement** This is a PR that adds a new features.
- **C-maintenance** This is an issue or PR about stuff such as documentation,
GitHub Actions or code quality.
- **C-feature-request** This is a feature request. Implementations of feature
requests use `C-enhancement` instead.
- **C-feature-accepted** If you submit a PR for this feature request, we wont
close it with the reason "we don't want this". Issues with this label should
also have the `C-feature-request` label.
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
world"
- **C-proposal** A proposal of some kind, and a request for comments.
- **C-question** A user question. Large overlap with GitHub discussions.
- **C-request** A non-feature request, e.g. "please add deprecation notices to
`-alpha.*` versions of crates"
**Call for participation.** I don't know why it's called `E-`. Many issues are
missing a difficulty rating, and you should feel free to add one.
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
or `C-feature-accepted`.
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
can do after reading the tutorial on our website.
- **E-medium** This is not `E-easy` or `E-hard`.
- **E-hard** This either involves very tricky code, is something we don't know
how to solve, or is difficult for some other reason.
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
example.
**Module.** A more fine groaned categorization than area.
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
- **M-codec** The `tokio_util::codec` module.
- **M-compat** The `tokio_util::compat` module.
- **M-coop** Things relevant to coop.
- **M-fs** The `tokio::fs` module.
- **M-io** The `tokio::io` module.
- **M-macros** Issues about any kind of macro.
- **M-net** The `tokio::net` module.
- **M-process** The `tokio::process` module.
- **M-runtime** The `tokio::runtime` module.
- **M-signal** The `tokio::signal` module.
- **M-stream** The `tokio::stream` module.
- **M-sync** The `tokio::sync` module.
- **M-task** The `tokio::task` module.
- **M-time** The `tokio::time` module.
- **M-tracing** Tracing support in Tokio.
**Topic.** Some extra information.
- **T-docs** This is about documentation.
- **T-performance** This is about performance.
- **T-v0.1.x** This is about old Tokio.
Any label not listed here is not in active use.
[project]: https://github.com/orgs/tokio-rs/projects/1
## Releasing
Since the Tokio project consists of a number of crates, many of which depend on
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
+1 -26
View File
@@ -1,39 +1,14 @@
[workspace]
resolver = "2"
members = [
"tokio",
"tokio-macros",
"tokio-test",
"tokio-stream",
"tokio-util",
# Internal
"benches",
"examples",
"stress-test",
"tests-build",
"tests-integration",
]
[patch.crates-io]
tokio = { path = "tokio" }
tokio-macros = { path = "tokio-macros" }
tokio-stream = { path = "tokio-stream" }
tokio-test = { path = "tokio-test" }
tokio-util = { path = "tokio-util" }
[workspace.metadata.spellcheck]
config = "spellcheck.toml"
[workspace.lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(fuzzing)',
'cfg(loom)',
'cfg(mio_unsupported_force_poll_poll)',
'cfg(tokio_allow_from_blocking_fd)',
'cfg(tokio_internal_mt_counters)',
'cfg(tokio_no_parking_lot)',
'cfg(tokio_no_tuning_tests)',
'cfg(tokio_unstable)',
'cfg(target_os, values("cygwin"))',
] }
-5
View File
@@ -1,5 +0,0 @@
[build.env]
passthrough = [
"RUSTFLAGS",
"RUST_BACKTRACE",
]
+22 -18
View File
@@ -1,21 +1,25 @@
MIT License
Copyright (c) 2019 Tokio Contributors
Copyright (c) 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:
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 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 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.
+23 -105
View File
@@ -14,21 +14,22 @@ the Rust programming language. It is:
[![Crates.io][crates-badge]][crates-url]
[![MIT licensed][mit-badge]][mit-url]
[![Build Status][actions-badge]][actions-url]
[![Build Status][azure-badge]][azure-url]
[![Discord chat][discord-badge]][discord-url]
[crates-badge]: https://img.shields.io/crates/v/tokio.svg
[crates-url]: https://crates.io/crates/tokio
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: https://github.com/tokio-rs/tokio/blob/master/LICENSE
[actions-badge]: https://github.com/tokio-rs/tokio/workflows/CI/badge.svg
[actions-url]: https://github.com/tokio-rs/tokio/actions?query=workflow%3ACI+branch%3Amaster
[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/tokio/tutorial) |
[API Docs](https://docs.rs/tokio/latest/tokio) |
[Roadmap](https://github.com/tokio-rs/tokio/blob/master/ROADMAP.md) |
[Chat](https://discord.gg/tokio)
## Overview
@@ -39,7 +40,7 @@ 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.).
IOCP, etc...).
* Asynchronous [TCP and UDP][net] sockets.
These components provide the runtime components necessary for building
@@ -50,23 +51,15 @@ an asynchronous application.
## Example
A basic TCP echo server with Tokio.
Make sure you enable the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.53.1", features = ["full"] }
```
Then, on your main.rs:
A basic TCP echo server with Tokio:
```rust,no_run
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
@@ -78,7 +71,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
loop {
let n = match socket.read(&mut buf).await {
// socket closed
Ok(0) => return,
Ok(n) if n == 0 => return,
Ok(n) => n,
Err(e) => {
eprintln!("failed to read from socket; err = {:?}", e);
@@ -103,7 +96,7 @@ More examples can be found [here][examples]. For a larger "real world" example,
[examples]: https://github.com/tokio-rs/tokio/tree/master/examples
[mini-redis]: https://github.com/tokio-rs/mini-redis/
To see a list of the available feature flags that can be enabled, check our
To see a list of the available features flags that can be enabled, check our
[docs][feature-flag-docs].
## Getting Help
@@ -125,15 +118,13 @@ question. You can also ask your question on [the discussions page][discussions].
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/docs/contributing/README.md
[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:
* [`axum`]: A web application framework that focuses on ergonomics and modularity.
* [`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.
@@ -142,106 +133,33 @@ several other libraries, including:
* [`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.
* [`tracing`] (formerly `tokio-trace`): A framework for application-level
tracing and async-aware diagnostics.
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers `tokio`.
* [`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.
* [`loom`]: A testing tool for concurrent Rust code
[`axum`]: https://github.com/tokio-rs/axum
[`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
## Changelog
The Tokio repository contains multiple crates. Each crate has its own changelog.
* `tokio` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio/CHANGELOG.md)
* `tokio-util` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-util/CHANGELOG.md)
* `tokio-stream` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-stream/CHANGELOG.md)
* `tokio-macros` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-macros/CHANGELOG.md)
* `tokio-test` - [view changelog](https://github.com/tokio-rs/tokio/blob/master/tokio-test/CHANGELOG.md)
## Supported Rust Versions
<!--
When updating this, also update:
- .github/workflows/ci.yml
- CONTRIBUTING.md
- README.md
- tokio/README.md
- tokio/Cargo.toml
- tokio-util/Cargo.toml
- tokio-test/Cargo.toml
- tokio-stream/Cargo.toml
-->
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
least** 6 months. When increasing the MSRV, the new Rust version must have been
released at least six months ago. The current MSRV is 1.71.
Note that the MSRV is not increased automatically, and only as part of a minor
release. The MSRV history for past minor releases can be found below:
* 1.48 to now - Rust 1.71
* 1.39 to 1.47 - Rust 1.70
* 1.30 to 1.38 - Rust 1.63
* 1.27 to 1.29 - Rust 1.56
* 1.17 to 1.26 - Rust 1.49
* 1.15 to 1.16 - Rust 1.46
* 1.0 to 1.14 - Rust 1.45
Note that although we try to avoid the situation where a dependency transitively
increases the MSRV of Tokio, we do not guarantee that this does not happen.
However, every minor release will have some set of versions of dependencies that
works with the MSRV of that minor release.
## Release schedule
Tokio doesn't follow a fixed release schedule, but we typically make one minor
release each month. We make patch releases for bugfixes as necessary.
## Bug patching policy
For the purposes of making patch releases with bugfixes, we have designated
certain minor releases as LTS (long term support) releases. Whenever a bug
warrants a patch release with a fix for the bug, it will be backported and
released as a new patch release for each LTS minor version. Our current LTS
releases are:
* `1.47.x` - LTS release until September 2026. (MSRV 1.70)
* `1.51.x` - LTS release until March 2027. (MSRV 1.71)
Each LTS release will continue to receive backported fixes for at least a year.
If you wish to use a fixed minor release in your project, we recommend that you
use an LTS release.
To use a fixed minor version, you can specify the version with a tilde. For
example, to specify that you wish to use the newest `1.47.x` patch release, you
can use the following dependency specification:
```text
tokio = { version = "~1.47", features = [...] }
```
### Previous LTS releases
* `1.8.x` - LTS release until February 2022.
* `1.14.x` - LTS release until June 2022.
* `1.18.x` - LTS release until June 2023.
* `1.20.x` - LTS release until September 2023.
* `1.25.x` - LTS release until March 2024.
* `1.32.x` - LTS release until September 2024.
* `1.36.x` - LTS release until March 2025.
* `1.38.x` - LTS release until July 2025.
* `1.43.x` - LTS release until March 2026.
Tokio is built against the latest stable release. The minimum supported version is 1.39.
The current Tokio version is not guaranteed to build on Rust versions earlier than the
minimum supported version.
## License
@@ -252,5 +170,5 @@ This project is licensed under the [MIT 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
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
terms or conditions.
+67
View File
@@ -0,0 +1,67 @@
# Tokio Roadmap
## A Roadmap to 1.0
The question of "why not 1.0?" has come up a few times. After all, Tokio 0.1 has
been stable for three years. The short answer: because it isn't time. There is
nobody who would rather ship a Tokio 1.0 than us. It also isn't something to rush.
After all, `async / await` only landed in the stable Rust channel weeks ago.
There has been no significant production validation yet, except maybe fuchsia
and that seems like a fairly specialized use case. This release of Tokio
includes significant new code and new strategies with feature flags. Also, there
are still big open questions, such as the [proposed changes][pr-1744] to
`AsyncRead` and `AsyncWrite`.
Tokio 1.0 will be released as soon as the APIs are proven to handle real-world
production cases.
### Tokio 1.0 in Q3 2020 with LTS support
The Tokio 1.0 release will be **no later** than Q3 2020. It will also come with
"long-term support" guarantees:
* A minimum of 5 years of maintenance.
* A minimum of 3 years before a hypothetical 2.0 release.
When Tokio 1.0 is released in Q3 2020, on-going support, security fixes, and
critical bug fixes are guaranteed until **at least** Q3 2025. Tokio 2.0 will not
be released until **at least** Q3 2023 (though, ideally there will never be a
Tokio 2.0 release).
### How to get there
While Tokio 0.1 probably should have been a 1.0, Tokio 0.2 will be a **true**
0.2 release. There will be breaking change releases every 2 ~ 3 months until 1.0.
These changes will be **much** smaller than going from 0.1 -> 0.2. It is
expected that the 1.0 release will look a lot like 0.2.
### What is expected to change
The biggest change will be the `AsyncRead` and `AsyncWrite` traits. Based on
experience gained over the past 3 years, there are a couple of issues to
address:
* Be able to **safely** use uninitialized memory as a read buffer.
* Practical read vectored and write vectored APIs.
There are a few strategies to solve these problems. These strategies need to be
investigated and the solution validated. You can see [this comment][pr-1744-comment] for a
detailed statement of the problem.
The other major change, which has been in the works for a while, is updating
Mio. Mio 0.6 was first released almost 4 years ago and has not had a breaking
change since. Mio 0.7 has been in the works for a while. It includes a full
rewrite of the windows support as well as a refined API. More will be written
about this shortly.
Finally, now that the API is starting to stabilize, effort will be put into
documentation. Tokio 0.2 is being released before updating the website and many
of the old content will no longer be relevant. In the coming weeks, expect to
see updates there.
So, we have our work cut out for us. We hope you enjoy this 0.2 release and are
looking forward to your feedback and help.
[pr-1744]: https://github.com/tokio-rs/tokio/pull/1744
[pr-1744-comment]: https://github.com/tokio-rs/tokio/pull/1744#issuecomment-553575438
+4 -4
View File
@@ -1,13 +1,13 @@
## Report a security issue
The Tokio project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [[email protected]](mailto:[email protected]). Security issues should not be reported via the public GitHub Issue tracker.
The Tokio project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [[email protected]](mailto:[email protected]). Security issues should not be reported via the public Github Issue tracker.
## Vulnerability coordination
Remediation of security vulnerabilities is prioritized by the project team. The project team coordinates remediation with third-party project stakeholders via [GitHub Security Advisories](https://help.github.com/en/github/managing-security-vulnerabilities/about-github-security-advisories). Third-party stakeholders may include the reporter of the issue, affected direct or indirect users of Tokio, and maintainers of upstream dependencies if applicable.
Remediation of security vulnerabilities is prioritized by the project team. The project team coordinates remediation with third-party project stakeholders via [Github Security Advisories](https://help.github.com/en/github/managing-security-vulnerabilities/about-github-security-advisories). Third-party stakeholders may include the reporter of the issue, affected direct or indirect users of Tokio, and maintainers of upstream dependencies if applicable.
Downstream project maintainers and Tokio users can request participation in coordination of applicable security issues by sending your contact email address, GitHub username(s) and any other salient information to [[email protected]](mailto:[email protected]). Participation in security issue coordination processes is at the discretion of the Tokio team.
Downstream project maintainers and Tokio users can request participation in coordination of applicable security issues by sending your contact email address, Github username(s) and any other salient information to [[email protected]](mailto:[email protected]). Participation in security issue coordination processes is at the discretion of the Tokio team.
## Security advisories
The project team is committed to transparency in the security issue disclosure process. The Tokio team announces security issues via [project GitHub Release notes](https://github.com/tokio-rs/tokio/releases) and the [RustSec advisory database](https://github.com/RustSec/advisory-db) (i.e. `cargo-audit`).
The project team is committed to transparency in the security issue disclosure process. The Tokio team announces security issues via [project Github Release notes](https://github.com/tokio-rs/tokio/releases) and the [RustSec advisory database](https://github.com/RustSec/advisory-db) (i.e. `cargo-audit`).
+7 -82
View File
@@ -2,24 +2,11 @@
name = "benches"
version = "0.0.0"
publish = false
edition = "2021"
license = "MIT"
[features]
test-util = ["tokio/test-util"]
edition = "2018"
[dependencies]
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
criterion = "0.5.1"
rand = "0.9"
rand_chacha = "0.9"
[dev-dependencies]
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
[target.'cfg(unix)'.dependencies]
libc = "0.2.42"
tokio = { version = "0.2.0", path = "../tokio", features = ["full"] }
bencher = "0.1.5"
[[bench]]
name = "spawn"
@@ -27,39 +14,15 @@ path = "spawn.rs"
harness = false
[[bench]]
name = "sync_broadcast"
path = "sync_broadcast.rs"
name = "mpsc"
path = "mpsc.rs"
harness = false
[[bench]]
name = "sync_mpsc"
path = "sync_mpsc.rs"
name = "scheduler"
path = "scheduler.rs"
harness = false
[[bench]]
name = "sync_mpsc_oneshot"
path = "sync_mpsc_oneshot.rs"
harness = false
[[bench]]
name = "sync_watch"
path = "sync_watch.rs"
harness = false
[[bench]]
name = "rt_current_thread"
path = "rt_current_thread.rs"
harness = false
[[bench]]
name = "rt_multi_threaded"
path = "rt_multi_threaded.rs"
harness = false
[[bench]]
name = "sync_notify"
path = "sync_notify.rs"
harness = false
[[bench]]
name = "sync_rwlock"
@@ -70,41 +33,3 @@ harness = false
name = "sync_semaphore"
path = "sync_semaphore.rs"
harness = false
[[bench]]
name = "signal"
path = "signal.rs"
harness = false
[[bench]]
name = "fs"
path = "fs.rs"
harness = false
[[bench]]
name = "copy"
path = "copy.rs"
harness = false
[[bench]]
name = "time_now"
path = "time_now.rs"
harness = false
[[bench]]
name = "time_timeout"
path = "time_timeout.rs"
harness = false
[[bench]]
name = "spawn_blocking"
path = "spawn_blocking.rs"
harness = false
[[bench]]
name = "remote_spawn"
path = "remote_spawn.rs"
harness = false
[lints]
workspace = true
-250
View File
@@ -1,250 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use tokio::io::{copy, repeat, AsyncRead, AsyncReadExt, AsyncWrite};
use tokio::time::{interval, Interval, MissedTickBehavior};
use std::task::Poll;
use std::time::Duration;
const KILO: usize = 1024;
// Tunable parameters if you want to change this benchmark. If reader and writer
// are matched in kilobytes per second, then this only exposes buffering to the
// benchmark.
const RNG_SEED: u64 = 0;
// How much data to copy in a single benchmark run
const SOURCE_SIZE: u64 = 256 * KILO as u64;
// Read side provides CHUNK_SIZE every READ_SERVICE_PERIOD. If it's not called
// frequently, it'll burst to catch up (representing OS buffers draining)
const CHUNK_SIZE: usize = 2 * KILO;
const READ_SERVICE_PERIOD: Duration = Duration::from_millis(1);
// Write side buffers up to WRITE_BUFFER, and flushes to disk every
// WRITE_SERVICE_PERIOD.
const WRITE_BUFFER: usize = 40 * KILO;
const WRITE_SERVICE_PERIOD: Duration = Duration::from_millis(20);
// How likely you are to have to wait for previously written data to be flushed
// because another writer claimed the buffer space
const PROBABILITY_FLUSH_WAIT: f64 = 0.1;
/// A slow writer that aims to simulate HDD behavior under heavy load.
///
/// There is a limited buffer, which is fully drained on the next write after
/// a time limit is reached. Flush waits for the time limit to be reached
/// and then drains the buffer.
///
/// At random, the HDD will stall writers while it flushes out all buffers. If
/// this happens to you, you will be unable to write until the next time the
/// buffer is drained.
struct SlowHddWriter {
service_intervals: Interval,
blocking_rng: ChaCha20Rng,
buffer_size: usize,
buffer_used: usize,
}
impl SlowHddWriter {
fn new(service_interval: Duration, buffer_size: usize) -> Self {
let blocking_rng = ChaCha20Rng::seed_from_u64(RNG_SEED);
let mut service_intervals = interval(service_interval);
service_intervals.set_missed_tick_behavior(MissedTickBehavior::Delay);
Self {
service_intervals,
blocking_rng,
buffer_size,
buffer_used: 0,
}
}
fn service_write(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
// If we hit a service interval, the buffer can be cleared
let res = self.service_intervals.poll_tick(cx).map(|_| Ok(()));
if res.is_ready() {
self.buffer_used = 0;
}
res
}
fn write_bytes(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
writable: usize,
) -> std::task::Poll<Result<usize, std::io::Error>> {
let service_res = self.as_mut().service_write(cx);
if service_res.is_pending() && self.blocking_rng.random_bool(PROBABILITY_FLUSH_WAIT) {
return Poll::Pending;
}
let available = self.buffer_size - self.buffer_used;
if available == 0 {
assert!(service_res.is_pending());
Poll::Pending
} else {
let written = available.min(writable);
self.buffer_used += written;
Poll::Ready(Ok(written))
}
}
}
impl Unpin for SlowHddWriter {}
impl AsyncWrite for SlowHddWriter {
fn poll_write(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
self.write_bytes(cx, buf.len())
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
self.service_write(cx)
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
self.service_write(cx)
}
fn poll_write_vectored(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> std::task::Poll<Result<usize, std::io::Error>> {
let writable = bufs.iter().fold(0, |acc, buf| acc + buf.len());
self.write_bytes(cx, writable)
}
fn is_write_vectored(&self) -> bool {
true
}
}
/// A reader that limits the maximum chunk it'll give you back
///
/// Simulates something reading from a slow link - you get one chunk per call,
/// and you are offered chunks on a schedule
struct ChunkReader {
data: Vec<u8>,
service_intervals: Interval,
}
impl ChunkReader {
fn new(chunk_size: usize, service_interval: Duration) -> Self {
let mut service_intervals = interval(service_interval);
service_intervals.set_missed_tick_behavior(MissedTickBehavior::Burst);
let data: Vec<u8> = std::iter::repeat_n(0, chunk_size).collect();
Self {
data,
service_intervals,
}
}
}
impl AsyncRead for ChunkReader {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
if self.service_intervals.poll_tick(cx).is_pending() {
return Poll::Pending;
}
buf.put_slice(&self.data[..buf.remaining().min(self.data.len())]);
Poll::Ready(Ok(()))
}
}
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap()
}
fn copy_mem_to_mem(c: &mut Criterion) {
let rt = rt();
c.bench_function("copy_mem_to_mem", |b| {
b.iter(|| {
let task = || async {
let mut source = repeat(0).take(SOURCE_SIZE);
let mut dest = Vec::new();
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
});
}
fn copy_mem_to_slow_hdd(c: &mut Criterion) {
let rt = rt();
c.bench_function("copy_mem_to_slow_hdd", |b| {
b.iter(|| {
let task = || async {
let mut source = repeat(0).take(SOURCE_SIZE);
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
});
}
fn copy_chunk_to_mem(c: &mut Criterion) {
let rt = rt();
c.bench_function("copy_chunk_to_mem", |b| {
b.iter(|| {
let task = || async {
let mut source =
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
let mut dest = Vec::new();
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
});
}
fn copy_chunk_to_slow_hdd(c: &mut Criterion) {
let rt = rt();
c.bench_function("copy_chunk_to_slow_hdd", |b| {
b.iter(|| {
let task = || async {
let mut source =
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
});
}
criterion_group!(
copy_bench,
copy_mem_to_mem,
copy_mem_to_slow_hdd,
copy_chunk_to_mem,
copy_chunk_to_slow_hdd,
);
criterion_main!(copy_bench);
-112
View File
@@ -1,112 +0,0 @@
#![cfg(unix)]
use tokio_stream::StreamExt;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use tokio_util::codec::{BytesCodec, FramedRead /*FramedWrite*/};
use criterion::{criterion_group, criterion_main, Criterion};
use std::fs::File as StdFile;
use std::io::Read as StdRead;
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.build()
.unwrap()
}
const BLOCK_COUNT: usize = 1_000;
const BUFFER_SIZE: usize = 4096;
const DEV_ZERO: &str = "/dev/zero";
fn async_read_codec(c: &mut Criterion) {
let rt = rt();
c.bench_function("async_read_codec", |b| {
b.iter(|| {
let task = || async {
let file = File::open(DEV_ZERO).await.unwrap();
let mut input_stream =
FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
for _i in 0..BLOCK_COUNT {
let _bytes = input_stream.next().await.unwrap();
}
};
rt.block_on(task());
})
});
}
fn async_read_buf(c: &mut Criterion) {
let rt = rt();
c.bench_function("async_read_buf", |b| {
b.iter(|| {
let task = || async {
let mut file = File::open(DEV_ZERO).await.unwrap();
let mut buffer = [0u8; BUFFER_SIZE];
for _i in 0..BLOCK_COUNT {
let count = file.read(&mut buffer).await.unwrap();
if count == 0 {
break;
}
}
};
rt.block_on(task());
});
});
}
fn async_read_std_file(c: &mut Criterion) {
let rt = rt();
c.bench_function("async_read_std_file", |b| {
b.iter(|| {
let task = || async {
let mut file =
tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
for _i in 0..BLOCK_COUNT {
let mut buffer = [0u8; BUFFER_SIZE];
let mut file_ref = file.as_mut();
tokio::task::block_in_place(move || {
file_ref.read_exact(&mut buffer).unwrap();
});
}
};
rt.block_on(task());
});
});
}
fn sync_read(c: &mut Criterion) {
c.bench_function("sync_read", |b| {
b.iter(|| {
let mut file = StdFile::open(DEV_ZERO).unwrap();
let mut buffer = [0u8; BUFFER_SIZE];
for _i in 0..BLOCK_COUNT {
file.read_exact(&mut buffer).unwrap();
}
})
});
}
criterion_group!(
file,
async_read_std_file,
async_read_buf,
async_read_codec,
sync_read
);
criterion_main!(file);
+188
View File
@@ -0,0 +1,188 @@
use bencher::{black_box, Bencher};
use tokio::sync::mpsc;
type Medium = [usize; 64];
type Large = [Medium; 64];
fn create_1_medium(b: &mut Bencher) {
b.iter(|| {
black_box(&mpsc::channel::<Medium>(1));
});
}
fn create_100_medium(b: &mut Bencher) {
b.iter(|| {
black_box(&mpsc::channel::<Medium>(100));
});
}
fn create_100_000_medium(b: &mut Bencher) {
b.iter(|| {
black_box(&mpsc::channel::<Medium>(100_000));
});
}
fn send_medium(b: &mut Bencher) {
b.iter(|| {
let (mut tx, mut rx) = mpsc::channel::<Medium>(1000);
let _ = tx.try_send([0; 64]);
rx.try_recv().unwrap();
});
}
fn send_large(b: &mut Bencher) {
b.iter(|| {
let (mut tx, mut rx) = mpsc::channel::<Large>(1000);
let _ = tx.try_send([[0; 64]; 64]);
rx.try_recv().unwrap();
});
}
fn contention_bounded(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for _ in 0..5 {
let mut tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
})
});
}
fn contention_bounded_full(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(100);
for _ in 0..5 {
let mut tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
})
});
}
fn contention_unbounded(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).unwrap();
}
});
}
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
})
});
}
fn uncontented_bounded(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
b.iter(|| {
rt.block_on(async move {
let (mut tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for i in 0..5000 {
tx.send(i).await.unwrap();
}
for _ in 0..5_000 {
let _ = rx.recv().await;
}
})
});
}
fn uncontented_unbounded(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
for i in 0..5000 {
tx.send(i).unwrap();
}
for _ in 0..5_000 {
let _ = rx.recv().await;
}
})
});
}
bencher::benchmark_group!(
create,
create_1_medium,
create_100_medium,
create_100_000_medium
);
bencher::benchmark_group!(send, send_medium, send_large);
bencher::benchmark_group!(
contention,
contention_bounded,
contention_bounded_full,
contention_unbounded,
uncontented_bounded,
uncontented_unbounded
);
bencher::benchmark_main!(create, send, contention);
-101
View File
@@ -1,101 +0,0 @@
//! Benchmark remote task spawning (push_remote_task) at different concurrency
//! levels on the multi-threaded scheduler.
//!
//! This measures contention on the scheduler's inject queue mutex when multiple
//! external (non-worker) threads spawn tasks into the tokio runtime simultaneously.
//! Every rt.spawn() from an external thread unconditionally goes through
//! push_remote_task, making this a direct measurement of inject queue contention.
//!
//! For each parallelism level N (1, 2, 4, 8, 16, 32, 64, capped at available parallelism):
//! - Spawns N std::threads (external to the runtime)
//! - Each thread spawns TOTAL_TASKS / N tasks into the runtime via rt.spawn()
//! - All threads are synchronized with a barrier to maximize contention
//! - Tasks are trivial no-ops to isolate the push overhead
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use std::sync::Barrier;
use tokio::runtime::{self, Runtime};
/// Total number of tasks spawned across all threads per iteration.
/// Must be divisible by the largest parallelism level (64).
const TOTAL_TASKS: usize = 12_800;
const _: () = assert!(
TOTAL_TASKS.is_multiple_of(64),
"TOTAL_TASKS must be divisible by 64"
);
fn remote_spawn_contention(c: &mut Criterion) {
let parallelism_levels = parallelism_levels();
let mut group = c.benchmark_group("remote_spawn");
for num_threads in &parallelism_levels {
let num_threads = *num_threads;
group.bench_with_input(
BenchmarkId::new("threads", num_threads),
&num_threads,
|b, &num_threads| {
let rt = rt();
let tasks_per_thread = TOTAL_TASKS / num_threads;
let barrier = Barrier::new(num_threads);
b.iter_custom(|iters| {
let mut total_duration = std::time::Duration::ZERO;
for _ in 0..iters {
let start = std::time::Instant::now();
let all_handles = std::thread::scope(|s| {
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let barrier = &barrier;
let rt = &rt;
s.spawn(move || {
let mut join_handles = Vec::with_capacity(tasks_per_thread);
barrier.wait();
for _ in 0..tasks_per_thread {
join_handles.push(rt.spawn(async {}));
}
join_handles
})
})
.collect();
handles
.into_iter()
.flat_map(|h| h.join().unwrap())
.collect::<Vec<_>>()
});
total_duration += start.elapsed();
rt.block_on(async {
for h in all_handles {
h.await.unwrap();
}
});
}
total_duration
});
},
);
}
group.finish();
}
fn parallelism_levels() -> Vec<usize> {
let max_parallelism = std::thread::available_parallelism().map_or(1, |p| p.get());
[1, 2, 4, 8, 16, 32, 64]
.into_iter()
.filter(|&n| n <= max_parallelism)
.collect()
}
fn rt() -> Runtime {
runtime::Builder::new_multi_thread().build().unwrap()
}
criterion_group!(remote_spawn_benches, remote_spawn_contention);
criterion_main!(remote_spawn_benches);
-89
View File
@@ -1,89 +0,0 @@
//! Benchmark implementation details of the threaded scheduler. These benches are
//! intended to be used as a form of regression testing and not as a general
//! purpose benchmark demonstrating real-world performance.
use tokio::runtime::{self, Runtime};
use criterion::{criterion_group, criterion_main, Criterion};
const NUM_SPAWN: usize = 1_000;
fn rt_curr_spawn_many_local(c: &mut Criterion) {
let rt = rt();
let mut handles = Vec::with_capacity(NUM_SPAWN);
c.bench_function("spawn_many_local", |b| {
b.iter(|| {
rt.block_on(async {
for _ in 0..NUM_SPAWN {
handles.push(tokio::spawn(async move {}));
}
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
}
fn rt_curr_spawn_many_remote_idle(c: &mut Criterion) {
let rt = rt();
let rt_handle = rt.handle();
let mut handles = Vec::with_capacity(NUM_SPAWN);
c.bench_function("spawn_many_remote_idle", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
}
fn rt_curr_spawn_many_remote_busy(c: &mut Criterion) {
let rt = rt();
let rt_handle = rt.handle();
let mut handles = Vec::with_capacity(NUM_SPAWN);
rt.spawn(async {
fn iter() {
tokio::spawn(async { iter() });
}
iter()
});
c.bench_function("spawn_many_remote_busy", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
}
fn rt() -> Runtime {
runtime::Builder::new_current_thread().build().unwrap()
}
criterion_group!(
rt_curr_scheduler,
rt_curr_spawn_many_local,
rt_curr_spawn_many_remote_idle,
rt_curr_spawn_many_remote_busy
);
criterion_main!(rt_curr_scheduler);
-276
View File
@@ -1,276 +0,0 @@
//! Benchmark implementation details of the threaded scheduler. These benches are
//! intended to be used as a form of regression testing and not as a general
//! purpose benchmark demonstrating real-world performance.
use tokio::runtime::{self, Runtime};
use tokio::sync::oneshot;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::{mpsc, Arc};
use std::time::{Duration, Instant};
use criterion::{criterion_group, criterion_main, Criterion};
const NUM_WORKERS: usize = 4;
const NUM_SPAWN: usize = 10_000;
const STALL_DUR: Duration = Duration::from_micros(10);
fn rt_multi_spawn_many_local(c: &mut Criterion) {
let rt = rt();
let (tx, rx) = mpsc::sync_channel(1000);
let rem = Arc::new(AtomicUsize::new(0));
c.bench_function("spawn_many_local", |b| {
b.iter(|| {
rem.store(NUM_SPAWN, Relaxed);
rt.block_on(async {
for _ in 0..NUM_SPAWN {
let tx = tx.clone();
let rem = rem.clone();
tokio::spawn(async move {
if 1 == rem.fetch_sub(1, Relaxed) {
tx.send(()).unwrap();
}
});
}
rx.recv().unwrap();
});
})
});
}
fn rt_multi_spawn_many_remote_idle(c: &mut Criterion) {
let rt = rt();
let mut handles = Vec::with_capacity(NUM_SPAWN);
c.bench_function("spawn_many_remote_idle", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
}
// The runtime is busy with tasks that consume CPU time and yield. Yielding is a
// lower notification priority than spawning / regular notification.
fn rt_multi_spawn_many_remote_busy1(c: &mut Criterion) {
let rt = rt();
let rt_handle = rt.handle();
let mut handles = Vec::with_capacity(NUM_SPAWN);
let flag = Arc::new(AtomicBool::new(true));
// Spawn some tasks to keep the runtimes busy
for _ in 0..(2 * NUM_WORKERS) {
let flag = flag.clone();
rt.spawn(async move {
while flag.load(Relaxed) {
tokio::task::yield_now().await;
stall();
}
});
}
c.bench_function("spawn_many_remote_busy1", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
flag.store(false, Relaxed);
}
// The runtime is busy with tasks that consume CPU time and spawn new high-CPU
// tasks. Spawning goes via a higher notification priority than yielding.
fn rt_multi_spawn_many_remote_busy2(c: &mut Criterion) {
const NUM_SPAWN: usize = 1_000;
let rt = rt();
let rt_handle = rt.handle();
let mut handles = Vec::with_capacity(NUM_SPAWN);
let flag = Arc::new(AtomicBool::new(true));
// Spawn some tasks to keep the runtimes busy
for _ in 0..(NUM_WORKERS) {
let flag = flag.clone();
fn iter(flag: Arc<AtomicBool>) {
tokio::spawn(async {
if flag.load(Relaxed) {
stall();
iter(flag);
}
});
}
rt.spawn(async {
iter(flag);
});
}
c.bench_function("spawn_many_remote_busy2", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
flag.store(false, Relaxed);
}
fn rt_multi_yield_many(c: &mut Criterion) {
const NUM_YIELD: usize = 1_000;
const TASKS: usize = 200;
c.bench_function("yield_many", |b| {
let rt = rt();
let (tx, rx) = mpsc::sync_channel(TASKS);
b.iter(move || {
for _ in 0..TASKS {
let tx = tx.clone();
rt.spawn(async move {
for _ in 0..NUM_YIELD {
tokio::task::yield_now().await;
}
tx.send(()).unwrap();
});
}
for _ in 0..TASKS {
rx.recv().unwrap();
}
})
});
}
fn rt_multi_ping_pong(c: &mut Criterion) {
const NUM_PINGS: usize = 1_000;
let rt = rt();
let (done_tx, done_rx) = mpsc::sync_channel(1000);
let rem = Arc::new(AtomicUsize::new(0));
c.bench_function("ping_pong", |b| {
b.iter(|| {
let done_tx = done_tx.clone();
let rem = rem.clone();
rem.store(NUM_PINGS, Relaxed);
rt.block_on(async {
tokio::spawn(async move {
for _ in 0..NUM_PINGS {
let rem = rem.clone();
let done_tx = done_tx.clone();
tokio::spawn(async move {
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
tokio::spawn(async move {
rx1.await.unwrap();
tx2.send(()).unwrap();
});
tx1.send(()).unwrap();
rx2.await.unwrap();
if 1 == rem.fetch_sub(1, Relaxed) {
done_tx.send(()).unwrap();
}
});
}
});
done_rx.recv().unwrap();
});
})
});
}
fn rt_multi_chained_spawn(c: &mut Criterion) {
const ITER: usize = 1_000;
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
if n == 0 {
done_tx.send(()).unwrap();
} else {
tokio::spawn(async move {
iter(done_tx, n - 1);
});
}
}
c.bench_function("chained_spawn", |b| {
let rt = rt();
let (done_tx, done_rx) = mpsc::sync_channel(1000);
b.iter(move || {
let done_tx = done_tx.clone();
rt.block_on(async {
tokio::spawn(async move {
iter(done_tx, ITER);
});
done_rx.recv().unwrap();
});
})
});
}
fn rt() -> Runtime {
runtime::Builder::new_multi_thread()
.worker_threads(NUM_WORKERS)
.enable_all()
.build()
.unwrap()
}
fn stall() {
let now = Instant::now();
while now.elapsed() < STALL_DUR {
std::thread::yield_now();
}
}
criterion_group!(
rt_multi_scheduler,
rt_multi_spawn_many_local,
rt_multi_spawn_many_remote_idle,
rt_multi_spawn_many_remote_busy1,
rt_multi_spawn_many_remote_busy2,
rt_multi_ping_pong,
rt_multi_yield_many,
rt_multi_chained_spawn,
);
criterion_main!(rt_multi_scheduler);
+152
View File
@@ -0,0 +1,152 @@
//! Benchmark implementation details of the theaded scheduler. These benches are
//! intended to be used as a form of regression testing and not as a general
//! purpose benchmark demonstrating real-world performance.
use tokio::runtime::{self, Runtime};
use tokio::sync::oneshot;
use bencher::{benchmark_group, benchmark_main, Bencher};
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::{mpsc, Arc};
fn spawn_many(b: &mut Bencher) {
const NUM_SPAWN: usize = 10_000;
let mut rt = rt();
let (tx, rx) = mpsc::sync_channel(1000);
let rem = Arc::new(AtomicUsize::new(0));
b.iter(|| {
rem.store(NUM_SPAWN, Relaxed);
rt.block_on(async {
for _ in 0..NUM_SPAWN {
let tx = tx.clone();
let rem = rem.clone();
tokio::spawn(async move {
if 1 == rem.fetch_sub(1, Relaxed) {
tx.send(()).unwrap();
}
});
}
let _ = rx.recv().unwrap();
});
});
}
fn yield_many(b: &mut Bencher) {
const NUM_YIELD: usize = 1_000;
const TASKS: usize = 200;
let rt = rt();
let (tx, rx) = mpsc::sync_channel(TASKS);
b.iter(move || {
for _ in 0..TASKS {
let tx = tx.clone();
rt.spawn(async move {
for _ in 0..NUM_YIELD {
tokio::task::yield_now().await;
}
tx.send(()).unwrap();
});
}
for _ in 0..TASKS {
let _ = rx.recv().unwrap();
}
});
}
fn ping_pong(b: &mut Bencher) {
const NUM_PINGS: usize = 1_000;
let mut rt = rt();
let (done_tx, done_rx) = mpsc::sync_channel(1000);
let rem = Arc::new(AtomicUsize::new(0));
b.iter(|| {
let done_tx = done_tx.clone();
let rem = rem.clone();
rem.store(NUM_PINGS, Relaxed);
rt.block_on(async {
tokio::spawn(async move {
for _ in 0..NUM_PINGS {
let rem = rem.clone();
let done_tx = done_tx.clone();
tokio::spawn(async move {
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
tokio::spawn(async move {
rx1.await.unwrap();
tx2.send(()).unwrap();
});
tx1.send(()).unwrap();
rx2.await.unwrap();
if 1 == rem.fetch_sub(1, Relaxed) {
done_tx.send(()).unwrap();
}
});
}
});
done_rx.recv().unwrap();
});
});
}
fn chained_spawn(b: &mut Bencher) {
const ITER: usize = 1_000;
let mut rt = rt();
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
if n == 0 {
done_tx.send(()).unwrap();
} else {
tokio::spawn(async move {
iter(done_tx, n - 1);
});
}
}
let (done_tx, done_rx) = mpsc::sync_channel(1000);
b.iter(move || {
let done_tx = done_tx.clone();
rt.block_on(async {
tokio::spawn(async move {
iter(done_tx, ITER);
});
done_rx.recv().unwrap();
});
});
}
fn rt() -> Runtime {
runtime::Builder::new()
.threaded_scheduler()
.core_threads(4)
.enable_all()
.build()
.unwrap()
}
benchmark_group!(scheduler, spawn_many, ping_pong, yield_many, chained_spawn,);
benchmark_main!(scheduler);
-97
View File
@@ -1,97 +0,0 @@
//! Benchmark the delay in propagating OS signals to any listeners.
#![cfg(unix)]
use criterion::{criterion_group, criterion_main, Criterion};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::runtime;
use tokio::signal::unix::{signal, SignalKind};
use tokio::sync::mpsc;
struct Spinner {
count: usize,
}
impl Future for Spinner {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.count > 3 {
Poll::Ready(())
} else {
self.count += 1;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
impl Spinner {
fn new() -> Self {
Self { count: 0 }
}
}
pub fn send_signal(signal: libc::c_int) {
use libc::{getpid, kill};
unsafe {
assert_eq!(kill(getpid(), signal), 0);
}
}
fn many_signals(c: &mut Criterion) {
let num_signals = 10;
let (tx, mut rx) = mpsc::channel(num_signals);
// Intentionally single threaded to measure delays in propagating wakes
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let spawn_signal = |kind| {
let tx = tx.clone();
rt.spawn(async move {
let mut signal = signal(kind).expect("failed to create signal");
while signal.recv().await.is_some() {
if tx.send(()).await.is_err() {
break;
}
}
});
};
for _ in 0..num_signals {
// Pick some random signals which don't terminate the test harness
spawn_signal(SignalKind::child());
spawn_signal(SignalKind::io());
}
drop(tx);
// Turn the runtime for a while to ensure that all the spawned
// tasks have been polled at least once
rt.block_on(Spinner::new());
c.bench_function("many_signals", |b| {
b.iter(|| {
rt.block_on(async {
send_signal(libc::SIGCHLD);
for _ in 0..num_signals {
rx.recv().await.expect("channel closed");
}
send_signal(libc::SIGIO);
for _ in 0..num_signals {
rx.recv().await.expect("channel closed");
}
});
})
});
}
criterion_group!(signal_group, many_signals);
criterion_main!(signal_group);
+43 -69
View File
@@ -2,95 +2,69 @@
//! This essentially measure the time to enqueue a task in the local and remote
//! case.
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use bencher::{black_box, Bencher};
async fn work() -> usize {
let val = 1 + 1;
tokio::task::yield_now().await;
black_box(val)
}
fn single_rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
let mut runtime = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap()
.unwrap();
runtime.block_on(async {
bench.iter(|| {
let h = tokio::spawn(work());
black_box(h);
})
});
}
fn multi_rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
let mut runtime = tokio::runtime::Builder::new()
.threaded_scheduler()
.build()
.unwrap()
}
fn basic_scheduler_spawn(c: &mut Criterion) {
let runtime = single_rt();
c.bench_function("basic_scheduler_spawn", |b| {
b.iter(|| {
runtime.block_on(async {
let h = tokio::spawn(work());
assert_eq!(h.await.unwrap(), 2);
});
.unwrap();
runtime.block_on(async {
bench.iter(|| {
let h = tokio::spawn(work());
black_box(h);
})
});
}
fn basic_scheduler_spawn10(c: &mut Criterion) {
let runtime = single_rt();
c.bench_function("basic_scheduler_spawn10", |b| {
b.iter(|| {
runtime.block_on(async {
let mut handles = Vec::with_capacity(10);
for _ in 0..10 {
handles.push(tokio::spawn(work()));
}
for handle in handles {
assert_eq!(handle.await.unwrap(), 2);
}
});
})
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_spawn(c: &mut Criterion) {
let runtime = multi_rt();
c.bench_function("threaded_scheduler_spawn", |b| {
b.iter(|| {
runtime.block_on(async {
let h = tokio::spawn(work());
assert_eq!(h.await.unwrap(), 2);
});
})
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);
});
}
fn threaded_scheduler_spawn10(c: &mut Criterion) {
let runtime = multi_rt();
c.bench_function("threaded_scheduler_spawn10", |b| {
b.iter(|| {
runtime.block_on(async {
let mut handles = Vec::with_capacity(10);
for _ in 0..10 {
handles.push(tokio::spawn(work()));
}
for handle in handles {
assert_eq!(handle.await.unwrap(), 2);
}
});
})
});
}
criterion_group!(
bencher::benchmark_group!(
spawn,
basic_scheduler_spawn,
basic_scheduler_spawn10,
threaded_scheduler_spawn,
threaded_scheduler_spawn10,
basic_scheduler_local_spawn,
threaded_scheduler_local_spawn,
basic_scheduler_remote_spawn,
threaded_scheduler_remote_spawn
);
criterion_main!(spawn);
bencher::benchmark_main!(spawn);
-71
View File
@@ -1,71 +0,0 @@
//! Benchmark spawn_blocking at different concurrency levels on the multi-threaded scheduler.
//!
//! For each parallelism level N (1, 2, 4, 8, 16, 32, 64, capped at available parallelism):
//! - Spawns N regular async tasks
//! - Each task spawns M batches of B spawn_blocking tasks (no-ops)
//! - Each batch is awaited to completion before starting the next
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use tokio::runtime::{self, Runtime};
use tokio::task::JoinSet;
/// Number of batches per task
const NUM_BATCHES: usize = 100;
/// Number of spawn_blocking calls per batch
const BATCH_SIZE: usize = 16;
fn spawn_blocking_concurrency(c: &mut Criterion) {
let max_parallelism = std::thread::available_parallelism().map_or(1, |p| p.get());
let parallelism_levels: Vec<usize> = [1, 2, 4, 8, 16, 32, 64]
.into_iter()
.filter(|&n| n <= max_parallelism)
.collect();
let mut group = c.benchmark_group("spawn_blocking");
for num_tasks in parallelism_levels {
group.bench_with_input(
BenchmarkId::new("concurrency", num_tasks),
&num_tasks,
|b, &num_tasks| {
let rt = rt();
b.iter(|| {
rt.block_on(async {
let mut tasks = JoinSet::new();
for _ in 0..num_tasks {
tasks.spawn(async {
for _ in 0..NUM_BATCHES {
let mut batch = JoinSet::new();
for _ in 0..BATCH_SIZE {
batch.spawn_blocking(|| black_box(0));
}
batch.join_all().await;
}
});
}
tasks.join_all().await;
});
});
},
);
}
group.finish();
}
fn rt() -> Runtime {
runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
}
criterion_group!(spawn_blocking_benches, spawn_blocking_concurrency);
criterion_main!(spawn_blocking_benches);
-116
View File
@@ -1,116 +0,0 @@
use rand::{Rng, RngCore, SeedableRng};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::{broadcast, Notify};
use criterion::measurement::WallTime;
use criterion::{
black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion, Throughput,
};
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap()
}
fn do_work(rng: &mut impl RngCore) -> u32 {
use std::fmt::Write;
let mut message = String::new();
for i in 1..=10 {
let _ = write!(&mut message, " {i}={}", rng.random::<f64>());
}
message
.as_bytes()
.iter()
.map(|&c| c as u32)
.fold(0, u32::wrapping_add)
}
fn contention_impl<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
let (tx, _rx) = broadcast::channel::<usize>(1000);
let wg = Arc::new((AtomicUsize::new(0), Notify::new()));
for n in 0..N_TASKS {
let wg = wg.clone();
let mut rx = tx.subscribe();
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
rt.spawn(async move {
while (rx.recv().await).is_ok() {
let r = do_work(&mut rng);
let _ = black_box(r);
if wg.0.fetch_sub(1, Ordering::Relaxed) == 1 {
wg.1.notify_one();
}
}
});
}
const N_ITERS: usize = 100;
g.bench_function(N_TASKS.to_string(), |b| {
b.iter(|| {
rt.block_on({
let wg = wg.clone();
let tx = tx.clone();
async move {
for i in 0..N_ITERS {
assert_eq!(wg.0.fetch_add(N_TASKS, Ordering::Relaxed), 0);
tx.send(i).unwrap();
while wg.0.load(Ordering::Relaxed) > 0 {
wg.1.notified().await;
}
}
}
})
})
});
}
fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("contention");
contention_impl::<10>(&mut group);
contention_impl::<100>(&mut group);
contention_impl::<500>(&mut group);
contention_impl::<1000>(&mut group);
group.finish();
}
fn bench_try_recv(c: &mut Criterion) {
let mut group = c.benchmark_group("try_recv");
const MESSAGES: usize = 256;
for receiver_count in [1usize, 4, 16] {
group.throughput(Throughput::Elements((MESSAGES * receiver_count) as u64));
group.bench_function(receiver_count.to_string(), |b| {
let (tx, first_rx) = broadcast::channel::<usize>(MESSAGES);
let mut receivers = Vec::with_capacity(receiver_count);
receivers.push(first_rx);
for _ in 1..receiver_count {
receivers.push(tx.subscribe());
}
b.iter(|| {
for message in 0..MESSAGES {
tx.send(black_box(message)).unwrap();
}
for rx in &mut receivers {
for _ in 0..MESSAGES {
black_box(rx.try_recv().unwrap());
}
}
});
});
}
group.finish();
}
criterion_group!(contention, bench_contention, bench_try_recv);
criterion_main!(contention);
-331
View File
@@ -1,331 +0,0 @@
use tokio::sync::mpsc;
use criterion::measurement::WallTime;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
#[derive(Debug, Copy, Clone)]
struct Medium(#[allow(dead_code)] [usize; 64]);
impl Default for Medium {
fn default() -> Self {
Medium([0; 64])
}
}
#[derive(Debug, Copy, Clone)]
struct Large(#[allow(dead_code)] [Medium; 64]);
impl Default for Large {
fn default() -> Self {
Large([Medium::default(); 64])
}
}
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap()
}
fn create_medium<const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>) {
g.bench_function(SIZE.to_string(), |b| {
b.iter(|| {
black_box(&mpsc::channel::<Medium>(SIZE));
})
});
}
fn send_data<T: Default, const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>, prefix: &str) {
let rt = rt();
g.bench_function(format!("{prefix}_{SIZE}"), |b| {
b.iter(|| {
let (tx, mut rx) = mpsc::channel::<T>(SIZE);
let _ = rt.block_on(tx.send(T::default()));
rt.block_on(rx.recv()).unwrap();
})
});
}
fn contention_bounded(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("bounded", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for _ in 0..5 {
let 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_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("bounded_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn contention_bounded_full(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("bounded_full", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(100);
for _ in 0..5 {
let 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_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("bounded_full_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(100);
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn contention_unbounded(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("unbounded", |b| {
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 contention_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("unbounded_recv_many", |b| {
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();
}
});
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn uncontented_bounded(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("bounded", |b| {
b.iter(|| {
rt.block_on(async move {
let (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_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("bounded_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for i in 0..5000 {
tx.send(i).await.unwrap();
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn uncontented_unbounded(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("unbounded", |b| {
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;
}
})
})
});
}
fn uncontented_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("unbounded_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
for i in 0..5000 {
tx.send(i).unwrap();
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn bench_create_medium(c: &mut Criterion) {
let mut group = c.benchmark_group("create_medium");
create_medium::<1>(&mut group);
create_medium::<100>(&mut group);
create_medium::<100_000>(&mut group);
group.finish();
}
fn bench_send(c: &mut Criterion) {
let mut group = c.benchmark_group("send");
send_data::<Medium, 1000>(&mut group, "medium");
send_data::<Large, 1000>(&mut group, "large");
group.finish();
}
fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("contention");
contention_bounded(&mut group);
contention_bounded_recv_many(&mut group);
contention_bounded_full(&mut group);
contention_bounded_full_recv_many(&mut group);
contention_unbounded(&mut group);
contention_unbounded_recv_many(&mut group);
group.finish();
}
fn bench_uncontented(c: &mut Criterion) {
let mut group = c.benchmark_group("uncontented");
uncontented_bounded(&mut group);
uncontented_bounded_recv_many(&mut group);
uncontented_unbounded(&mut group);
uncontented_unbounded_recv_many(&mut group);
group.finish();
}
criterion_group!(create, bench_create_medium);
criterion_group!(send, bench_send);
criterion_group!(contention, bench_contention);
criterion_group!(uncontented, bench_uncontented);
criterion_main!(create, send, contention, uncontented);
-56
View File
@@ -1,56 +0,0 @@
use tokio::{
runtime::Runtime,
sync::{mpsc, oneshot},
};
use criterion::{criterion_group, criterion_main, Criterion};
fn request_reply_current_thread(c: &mut Criterion) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
request_reply(c, rt);
}
fn request_reply_multi_threaded(c: &mut Criterion) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.build()
.unwrap();
request_reply(c, rt);
}
fn request_reply(b: &mut Criterion, rt: Runtime) {
let tx = rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<oneshot::Sender<()>>(10);
tokio::spawn(async move {
while let Some(reply) = rx.recv().await {
reply.send(()).unwrap();
}
});
tx
});
b.bench_function("request_reply", |b| {
b.iter(|| {
let task_tx = tx.clone();
rt.block_on(async move {
for _ in 0..1_000 {
let (o_tx, o_rx) = oneshot::channel();
task_tx.send(o_tx).await.unwrap();
let _ = o_rx.await;
}
})
})
});
}
criterion_group!(
sync_mpsc_oneshot_group,
request_reply_current_thread,
request_reply_multi_threaded,
);
criterion_main!(sync_mpsc_oneshot_group);
-104
View File
@@ -1,104 +0,0 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::Notify;
use criterion::measurement::WallTime;
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap()
}
fn notify_waiters<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
let notify = Arc::new(Notify::new());
let counter = Arc::new(AtomicUsize::new(0));
for _ in 0..N_WAITERS {
rt.spawn({
let notify = notify.clone();
let counter = counter.clone();
async move {
loop {
notify.notified().await;
counter.fetch_add(1, Ordering::Relaxed);
}
}
});
}
const N_ITERS: usize = 500;
g.bench_function(N_WAITERS.to_string(), |b| {
b.iter(|| {
counter.store(0, Ordering::Relaxed);
loop {
notify.notify_waiters();
if counter.load(Ordering::Relaxed) >= N_ITERS {
break;
}
}
})
});
}
fn notify_one<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
let notify = Arc::new(Notify::new());
let counter = Arc::new(AtomicUsize::new(0));
for _ in 0..N_WAITERS {
rt.spawn({
let notify = notify.clone();
let counter = counter.clone();
async move {
loop {
notify.notified().await;
counter.fetch_add(1, Ordering::Relaxed);
}
}
});
}
const N_ITERS: usize = 500;
g.bench_function(N_WAITERS.to_string(), |b| {
b.iter(|| {
counter.store(0, Ordering::Relaxed);
loop {
notify.notify_one();
if counter.load(Ordering::Relaxed) >= N_ITERS {
break;
}
}
})
});
}
fn bench_notify_one(c: &mut Criterion) {
let mut group = c.benchmark_group("notify_one");
notify_one::<10>(&mut group);
notify_one::<50>(&mut group);
notify_one::<100>(&mut group);
notify_one::<200>(&mut group);
notify_one::<500>(&mut group);
group.finish();
}
fn bench_notify_waiters(c: &mut Criterion) {
let mut group = c.benchmark_group("notify_waiters");
notify_waiters::<10>(&mut group);
notify_waiters::<50>(&mut group);
notify_waiters::<100>(&mut group);
notify_waiters::<200>(&mut group);
notify_waiters::<500>(&mut group);
group.finish();
}
criterion_group!(
notify_waiters_simple,
bench_notify_one,
bench_notify_waiters
);
criterion_main!(notify_waiters_simple);
+87 -103
View File
@@ -1,163 +1,147 @@
use bencher::{black_box, Bencher};
use std::sync::Arc;
use tokio::{sync::RwLock, task};
use criterion::measurement::WallTime;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn read_uncontended(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
fn read_uncontended(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
let lock = Arc::new(RwLock::new(()));
g.bench_function("read", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
for _ in 0..6 {
let read = lock.read().await;
let _read = black_box(read);
}
})
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
for _ in 0..6 {
let read = lock.read().await;
black_box(read);
}
})
});
}
fn read_concurrent_uncontended_multi(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
fn read_concurrent_uncontended_multi(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
async fn task(lock: Arc<RwLock<()>>) {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
let lock = Arc::new(RwLock::new(()));
g.bench_function("read_concurrent_multi", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone()))
};
j.unwrap();
})
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone()))
};
j.unwrap();
})
});
}
fn read_concurrent_uncontended(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_current_thread()
fn read_concurrent_uncontended(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap();
async fn task(lock: Arc<RwLock<()>>) {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
let lock = Arc::new(RwLock::new(()));
g.bench_function("read_concurrent", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
tokio::join! {
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone())
};
})
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
tokio::join! {
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone())
};
})
});
}
fn read_concurrent_contended_multi(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
fn read_concurrent_contended_multi(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
async fn task(lock: Arc<RwLock<()>>) {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
let lock = Arc::new(RwLock::new(()));
g.bench_function("read_concurrent_multi", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let write = lock.write().await;
let j = tokio::try_join! {
async move { drop(write); Ok(()) },
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
};
j.unwrap();
})
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let write = lock.write().await;
let j = tokio::try_join! {
async move { drop(write); Ok(()) },
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
};
j.unwrap();
})
});
}
fn read_concurrent_contended(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_current_thread()
fn read_concurrent_contended(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap();
async fn task(lock: Arc<RwLock<()>>) {
let read = lock.read().await;
let _read = black_box(read);
black_box(read);
}
let lock = Arc::new(RwLock::new(()));
g.bench_function("read_concurrent", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let write = lock.write().await;
tokio::join! {
async move { drop(write) },
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
};
})
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let write = lock.write().await;
tokio::join! {
async move { drop(write) },
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
};
})
});
}
fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("contention");
read_concurrent_contended(&mut group);
read_concurrent_contended_multi(&mut group);
group.finish();
}
bencher::benchmark_group!(
sync_rwlock,
read_uncontended,
read_concurrent_uncontended,
read_concurrent_uncontended_multi,
read_concurrent_contended,
read_concurrent_contended_multi
);
fn bench_uncontented(c: &mut Criterion) {
let mut group = c.benchmark_group("uncontented");
read_uncontended(&mut group);
read_concurrent_uncontended(&mut group);
read_concurrent_uncontended_multi(&mut group);
group.finish();
}
criterion_group!(contention, bench_contention);
criterion_group!(uncontented, bench_uncontented);
criterion_main!(contention, uncontented);
bencher::benchmark_main!(sync_rwlock);
+90 -107
View File
@@ -1,36 +1,22 @@
use bencher::Bencher;
use std::sync::Arc;
use tokio::runtime::Runtime;
use tokio::{sync::Semaphore, task};
use criterion::measurement::WallTime;
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn single_rt() -> Runtime {
tokio::runtime::Builder::new_current_thread()
fn uncontended(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap()
}
fn multi_rt() -> Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap()
}
fn uncontended(g: &mut BenchmarkGroup<WallTime>) {
let rt = multi_rt();
.unwrap();
let s = Arc::new(Semaphore::new(10));
g.bench_function("multi", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
for _ in 0..6 {
let permit = s.acquire().await;
drop(permit);
}
})
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
for _ in 0..6 {
let permit = s.acquire().await;
drop(permit);
}
})
});
}
@@ -40,108 +26,105 @@ async fn task(s: Arc<Semaphore>) {
drop(permit);
}
fn uncontended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
let rt = multi_rt();
fn uncontended_concurrent_multi(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
let s = Arc::new(Semaphore::new(10));
g.bench_function("concurrent_multi", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone()))
};
j.unwrap();
})
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone()))
};
j.unwrap();
})
});
}
fn uncontended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
let rt = single_rt();
fn uncontended_concurrent_single(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap();
let s = Arc::new(Semaphore::new(10));
g.bench_function("concurrent_single", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
tokio::join! {
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone())
};
})
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
tokio::join! {
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone())
};
})
});
}
fn contended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
let rt = multi_rt();
fn contended_concurrent_multi(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.core_threads(6)
.threaded_scheduler()
.build()
.unwrap();
let s = Arc::new(Semaphore::new(5));
g.bench_function("concurrent_multi", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone()))
};
j.unwrap();
})
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone()))
};
j.unwrap();
})
});
}
fn contended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
let rt = single_rt();
fn contended_concurrent_single(b: &mut Bencher) {
let mut rt = tokio::runtime::Builder::new()
.basic_scheduler()
.build()
.unwrap();
let s = Arc::new(Semaphore::new(5));
g.bench_function("concurrent_single", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
tokio::join! {
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone())
};
})
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
tokio::join! {
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone())
};
})
});
}
fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("contention");
contended_concurrent_multi(&mut group);
contended_concurrent_single(&mut group);
group.finish();
}
bencher::benchmark_group!(
sync_semaphore,
uncontended,
uncontended_concurrent_multi,
uncontended_concurrent_single,
contended_concurrent_multi,
contended_concurrent_single
);
fn bench_uncontented(c: &mut Criterion) {
let mut group = c.benchmark_group("uncontented");
uncontended(&mut group);
uncontended_concurrent_multi(&mut group);
uncontended_concurrent_single(&mut group);
group.finish();
}
criterion_group!(contention, bench_contention);
criterion_group!(uncontented, bench_uncontented);
criterion_main!(contention, uncontented);
bencher::benchmark_main!(sync_semaphore);
-85
View File
@@ -1,85 +0,0 @@
use rand::prelude::*;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tokio::sync::{watch, Notify};
use criterion::measurement::WallTime;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap()
}
fn do_work(rng: &mut impl RngCore) -> u32 {
use std::fmt::Write;
let mut message = String::new();
for i in 1..=10 {
let _ = write!(&mut message, " {i}={}", rng.random::<f64>());
}
message
.as_bytes()
.iter()
.map(|&c| c as u32)
.fold(0, u32::wrapping_add)
}
fn contention_resubscribe<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
let (snd, _) = watch::channel(0i32);
let snd = Arc::new(snd);
let wg = Arc::new((AtomicU64::new(0), Notify::new()));
for n in 0..N_TASKS {
let mut rcv = snd.subscribe();
let wg = wg.clone();
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
rt.spawn(async move {
while rcv.changed().await.is_ok() {
let _ = *rcv.borrow(); // contend on rwlock
let r = do_work(&mut rng);
let _ = black_box(r);
if wg.0.fetch_sub(1, Ordering::Release) == 1 {
wg.1.notify_one();
}
}
});
}
const N_ITERS: usize = 100;
g.bench_function(N_TASKS.to_string(), |b| {
b.iter(|| {
rt.block_on({
let snd = snd.clone();
let wg = wg.clone();
async move {
tokio::spawn(async move {
for _ in 0..N_ITERS {
assert_eq!(wg.0.fetch_add(N_TASKS as u64, Ordering::Relaxed), 0);
let _ = snd.send(black_box(42));
while wg.0.load(Ordering::Acquire) > 0 {
wg.1.notified().await;
}
}
})
.await
.unwrap();
}
});
})
});
}
fn bench_contention_resubscribe(c: &mut Criterion) {
let mut group = c.benchmark_group("contention_resubscribe");
contention_resubscribe::<10>(&mut group);
contention_resubscribe::<100>(&mut group);
contention_resubscribe::<500>(&mut group);
contention_resubscribe::<1000>(&mut group);
group.finish();
}
criterion_group!(contention, bench_contention_resubscribe);
criterion_main!(contention);
-24
View File
@@ -1,24 +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 criterion::{black_box, criterion_group, criterion_main, Criterion};
fn time_now_current_thread(c: &mut Criterion) {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap();
c.bench_function("time_now_current_thread", |b| {
b.iter(|| {
rt.block_on(async {
black_box(tokio::time::Instant::now());
})
})
});
}
criterion_group!(time_now, time_now_current_thread);
criterion_main!(time_now);
-109
View File
@@ -1,109 +0,0 @@
use std::time::{Duration, Instant};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tokio::{
runtime::Runtime,
time::{sleep, timeout},
};
// a very quick async task, but might timeout
async fn quick_job() -> usize {
1
}
fn build_run_time(workers: usize) -> Runtime {
if workers == 1 {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
} else {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(workers)
.build()
.unwrap()
}
}
fn single_thread_scheduler_timeout(c: &mut Criterion) {
do_timeout_test(c, 1, "single_thread_timeout");
}
fn multi_thread_scheduler_timeout(c: &mut Criterion) {
do_timeout_test(c, 8, "multi_thread_timeout-8");
}
fn do_timeout_test(c: &mut Criterion, workers: usize, name: &str) {
let runtime = build_run_time(workers);
c.bench_function(name, |b| {
b.iter_custom(|iters| {
let start = Instant::now();
runtime.block_on(async {
black_box(spawn_timeout_job(iters as usize, workers)).await;
});
start.elapsed()
})
});
}
async fn spawn_timeout_job(iters: usize, procs: usize) {
let mut handles = Vec::with_capacity(procs);
for _ in 0..procs {
handles.push(tokio::spawn(async move {
for _ in 0..iters / procs {
let h = timeout(Duration::from_secs(1), quick_job());
assert_eq!(black_box(h.await.unwrap()), 1);
}
}));
}
for handle in handles {
handle.await.unwrap();
}
}
fn single_thread_scheduler_sleep(c: &mut Criterion) {
do_sleep_test(c, 1, "single_thread_sleep");
}
fn multi_thread_scheduler_sleep(c: &mut Criterion) {
do_sleep_test(c, 8, "multi_thread_sleep-8");
}
fn do_sleep_test(c: &mut Criterion, workers: usize, name: &str) {
let runtime = build_run_time(workers);
c.bench_function(name, |b| {
b.iter_custom(|iters| {
let start = Instant::now();
runtime.block_on(async {
black_box(spawn_sleep_job(iters as usize, workers)).await;
});
start.elapsed()
})
});
}
async fn spawn_sleep_job(iters: usize, procs: usize) {
let mut handles = Vec::with_capacity(procs);
for _ in 0..procs {
handles.push(tokio::spawn(async move {
for _ in 0..iters / procs {
let _h = black_box(sleep(Duration::from_secs(1)));
}
}));
}
for handle in handles {
handle.await.unwrap();
}
}
criterion_group!(
timeout_benchmark,
single_thread_scheduler_timeout,
multi_thread_scheduler_timeout,
single_thread_scheduler_sleep,
multi_thread_scheduler_sleep
);
criterion_main!(timeout_benchmark);
Executable
+121
View File
@@ -0,0 +1,121 @@
#!/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
Executable
+118
View File
@@ -0,0 +1,118 @@
#!/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
-21
View File
@@ -1,21 +0,0 @@
# https://embarkstudios.github.io/cargo-deny/cli/init.html
[graph]
all-features = true
[licenses]
allow = [
"MIT",
"Apache-2.0",
]
exceptions = [
{ allow = ["Unicode-3.0"], crate = "unicode-ident" },
]
[bans]
multiple-versions = "allow"
wildcards = "deny"
[sources]
unknown-registry = "deny"
unknown-git = "deny"
-48
View File
@@ -1,48 +0,0 @@
# Contributing
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.
## Quick start
If you are unsure where to begin, use the following guides:
- Want to report or triage a bug? Start with [Contributing in Issues](contributing-in-issues.md).
- Looking for something to work on? Check the open [`issues`](https://github.com/tokio-rs/tokio/issues)
(some might be in progress or still under discussion, so leave a comment before starting the work).
- Planning to submit a PR? Read [Pull Requests](pull-requests.md) for the full workflow and required checks.
- Want to understand what the labels on issues mean? See [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md).
- Interested in code review? See [Reviewing Pull Requests](reviewing-pull-requests.md).
## Table of Contents
- [Contributing in Issues](contributing-in-issues.md)
- [Asking for General Help](contributing-in-issues.md#asking-for-general-help)
- [Submitting a Bug Report](contributing-in-issues.md#submitting-a-bug-report)
- [Triaging a Bug Report](contributing-in-issues.md#triaging-a-bug-report)
- [Resolving a Bug Report](contributing-in-issues.md#resolving-a-bug-report)
- [Pull Requests](pull-requests.md)
- [Step-by-Step Contribution Workflow](pull-requests.md#step-by-step-contribution-workflow)
- [Cargo Commands](pull-requests.md#cargo-commands)
- [Performing spellcheck on Tokio codebase](pull-requests.md#performing-spellcheck-on-tokio-codebase)
- [Tests](pull-requests.md#tests)
- [Integration tests](pull-requests.md#integration-tests)
- [Fuzz tests](pull-requests.md#fuzz-tests)
- [Documentation tests](pull-requests.md#documentation-tests)
- [Benchmarks](pull-requests.md#benchmarks)
- [Commits](pull-requests.md#commits)
- [Commit message guidelines](pull-requests.md#commit-message-guidelines)
- [Opening the Pull Request](pull-requests.md#opening-the-pull-request)
- [Discuss and update](pull-requests.md#discuss-and-update)
- [Commit Squashing](pull-requests.md#commit-squashing)
- [Reviewing Pull Requests](reviewing-pull-requests.md)
- [Review a bit at a time](reviewing-pull-requests.md#review-a-bit-at-a-time)
- [Be aware of the person behind the code](reviewing-pull-requests.md#be-aware-of-the-person-behind-the-code)
- [Abandoned or Stalled Pull Requests](reviewing-pull-requests.md#abandoned-or-stalled-pull-requests)
- [How to specify crates dependencies versions](how-to-specify-crates-dependencies-versions.md)
- [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md)
- [Area](keeping-track-of-issues-and-prs.md#area)
- [Category](keeping-track-of-issues-and-prs.md#category)
- [Calls for participation](keeping-track-of-issues-and-prs.md#calls-for-participation)
- [Module](keeping-track-of-issues-and-prs.md#module)
- [Topic](keeping-track-of-issues-and-prs.md#topic)
@@ -1,79 +0,0 @@
## 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 discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
issue tracker][issue] is the way to report it.
2. By helping to triage the issue: This can be done by providing
supporting details (a test case that demonstrates a bug), providing
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.
[issue]: https://github.com/tokio-rs/tokio/issues
**Anybody can participate in any stage of contribution**. We urge you to
participate in the discussion around bugs and participate in reviewing PRs.
### Asking for General Help
If you have reviewed existing documentation and still have questions or are
having problems, you can [open a discussion] asking for help.
In exchange for receiving help, we ask that you contribute back a documentation
PR that helps others avoid the problems that you encountered.
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new/choose
### Submitting a Bug Report
When opening a new issue in the Tokio issue tracker, you will be presented
with a basic template that should be filled in. If you believe that you have
uncovered a bug, please fill out this form, following the template to the best
of your ability. Do not worry if you cannot answer every detail, just fill in
what you can.
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
### 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.
@@ -1,32 +0,0 @@
# How to specify crates dependencies versions
Each crate (e.g., `tokio-util`, `tokio-stream`, etc.) should specify dependencies
according to the following rules:
1. The listed version should be the oldest version that the crate works with
(e.g., if `tokio-util` works with `tokio` version `1.44` but not `1.43`, then
`tokio-util` should specify version `1.44` for its `tokio` dependency).
We don't require users to use the latest version unnecessarily.
2. When a crate starts using a newer feature in a dependency, the version
should be bumped to the version that introduced it.
3. If a crate depends on an unreleased feature in a dependency, it may use
`path =` dependency to specify this. Since path dependencies must be removed
during the release of the crate, this ensures that it can't be released until
the dependency has a new version.
Consider the following example from `tokio-stream`:
```toml
[dependencies]
futures-core = { version = "0.3.0" }
pin-project-lite = "0.2.11"
tokio = { version = "1.38.0", path = "../tokio", features = ["sync"] }
```
In this case, local development of `tokio-stream` uses the local version
of `tokio` via the `path` dependency. This means that it's currently not
possible to release `tokio-stream`, and `tokio` should be released first.
Once a new version of `tokio` is released (in this example the `1.38.0`),
the path dependency should be removed.
As mentioned before, this version should only be bumped when adding a new
feature in the crate that relies on a newer version.
@@ -1,91 +0,0 @@
## Keeping track of issues and PRs
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
section explains the meaning of various labels, as well as our [GitHub
project][project]. The section is primarily targeted at maintainers. Most
contributors aren't able to set these labels.
### Area
The area label describes the crates relevant to this issue or PR.
- **A-ci** This issue concerns our GitHub Actions setup.
- **A-tokio** This issue concerns the main Tokio crate.
- **A-readme** This issue is related to documentation such as README.md.
- **A-benches** This issue concerns the benchmarks.
- **A-examples** This issue concerns the examples.
- **A-tokio-test** The issue concerns the `tokio-test` crate.
- **A-tokio-util** This issue concerns the `tokio-util` crate.
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
be used for the procedural macros, and not `join!` or `select!`.
- **A-tokio-stream** This issue concerns the `tokio-stream` crate.
### Category
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
- **C-enhancement** This is a PR that adds a new features.
- **C-maintenance** This is an issue or PR about stuff such as documentation,
GitHub Actions or code quality.
- **C-feature-request** This is a feature request. Implementations of feature
requests use `C-enhancement` instead.
- **C-feature-accepted** If you submit a PR for this feature request, we won't
close it with the reason "we don't want this". Issues with this label should
also have the `C-feature-request` label.
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
world"
- **C-proposal** A proposal of some kind, and a request for comments.
- **C-question** A user question. Large overlap with GitHub discussions.
- **C-request** A non-feature request, e.g. "please add deprecation notices to
`-alpha.*` versions of crates"
### Calls for participation
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
or `C-feature-accepted`.
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
can do after reading the tutorial on our website.
- **E-medium** This is not `E-easy` or `E-hard`.
- **E-hard** This either involves very tricky code, is something we don't know
how to solve, or is challenging for some other reason.
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
example.
The "E-" prefix is the same as used in the Rust compiler repository. Some
issues are missing a difficulty rating, but feel free to ask on our Discord
server if you want to know how challenging an issue likely is.
### Module
The module label provides a more fine grained categorization than **Area**.
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
- **M-codec** The `tokio_util::codec` module.
- **M-compat** The `tokio_util::compat` module.
- **M-coop** Things relevant to coop.
- **M-fs** The `tokio::fs` module.
- **M-io** The `tokio::io` module.
- **M-macros** Issues about any kind of macro.
- **M-metrics** Things relevant to `tokio::runtime::metrics`.
- **M-net** The `tokio::net` module.
- **M-process** The `tokio::process` module.
- **M-runtime** The `tokio::runtime` module.
- **M-signal** The `tokio::signal` module.
- **M-sync** The `tokio::sync` module.
- **M-task** The `tokio::task` module.
- **M-time** The `tokio::time` module.
- **M-tracing** Tracing support in Tokio.
- **M-taskdump** Things relevant to taskdump.
### Topic
Some extra information.
- **T-docs** This is about documentation.
- **T-io-uring** This is about io-uring (Linux).
- **T-performance** This is about performance.
- **T-v0.1.x** This is about old Tokio.
- **T-wasm** This is about Web Assembly.
Any label not listed here is not in active use.
[project]: https://github.com/orgs/tokio-rs/projects/1
-404
View File
@@ -1,404 +0,0 @@
## 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.
> [!NOTE]
> 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.
### Step-by-Step Contribution Workflow
Once you've found an issue you'd like to work on, follow these steps before opening a Pull Request. This workflow provides a chronological overview of the contribution process and points to the relevant documentation where additional detail is available.
#### 1. Fork and Clone the Repository
Fork the repository to your GitHub account and clone your fork locally.
If you've previously cloned the repository, ensure your local copy is up to date before creating a new branch.
```bash
git checkout master
git pull upstream master
```
---
#### 2. Create a Feature Branch
Never commit directly to your local `master` branch. Instead, create a descriptive feature branch for each change you work on.
```bash
git checkout -b my-feature
```
Using a dedicated branch keeps your default branch clean and makes it easier to update your Pull Request during code review.
---
#### 3. Implement Your Changes
Make the code or documentation changes needed. Check your work with `git diff` to confirm the changes look correct before moving on.
If your changes introduce new functionality or modify existing behavior, consider whether additional tests or documentation should also be added.
---
#### 4. Verify Your Changes
Before opening a Pull Request, run the project's verification steps. Refer to the sections below for details on when each command should be used.
---
#### 5. Commit and Push Your Changes
Commit your changes following the [project's commit message guidelines](#commit-message-guidelines).
```bash
git add .
git commit -m "module: describe your change"
git push origin my-feature
```
---
#### 6. Open a Pull Request
Open a Pull Request from your feature branch to the main repository. See [Opening the Pull Request](#opening-the-pull-request) for what to include and how the review process works.
### Cargo Commands
Due to the extensive use of features in Tokio, you will often need to add extra
arguments to many common cargo commands. This section lists some commonly needed
commands.
Some commands just need the `--all-features` argument:
```
cargo build --all-features
cargo check --all-features
cargo test --all-features
```
Ideally, you should use the same version of clippy as the one used in CI
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
might have new lints:
[ci.yml]: ../../.github/workflows/ci.yml
<!--
When updating this, also update:
- .github/workflows/ci.yml
- README.md
- tokio/README.md
- tokio/Cargo.toml
- tokio-util/Cargo.toml
- tokio-test/Cargo.toml
- tokio-stream/Cargo.toml
-->
```
cargo +1.88 clippy --all --tests --all-features
```
When building documentation, a simple `cargo doc` is not sufficient. To produce
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
docs, please use:
```
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
```
This turns on indicators to display the Cargo features required for
conditionally compiled APIs in Tokio, and it enables documentation of unstable
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
*and* rustc.
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
as docs.rs itself does.
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
```
cargo install --locked cargo-docs-rs
cargo +nightly docs-rs [--open]
```
The `cargo fmt` command does not work on the Tokio codebase. You can use the
command below instead:
```
# Mac or Linux
rustfmt --check --edition 2021 $(git ls-files '*.rs')
# Powershell
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
```
The `--check` argument prints the things that need to be fixed. If you remove
it, `rustfmt` will update your files locally instead.
You can run loom tests with
```
cd tokio # tokio crate in workspace
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
cargo test --lib --release --features full -- --test-threads=1 --nocapture
```
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
run loom tests that test unstable features.
You can run miri tests with
```
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance" \
cargo +nightly miri test --features full --lib --tests
```
### Performing spellcheck on Tokio codebase
You can perform a spell-check on the Tokio codebase. For details of how to use the spellcheck tool, feel free to visit
https://github.com/drahnr/cargo-spellcheck
```
# First install the spell-check plugin
cargo install --locked cargo-spellcheck
# Then run the cargo spell check command
cargo spellcheck check
```
If the command rejects a word, you should backtick the rejected word if it's code related. If not, the
rejected word should be put into `spellcheck.dic` file.
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
the total number of words included in the file
### 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][integration-tests]
and [documentation tests][documentation-tests].
(Tokio avoids [unit tests][unit-tests] as much as possible).
Tokio uses [conditional compilation attributes][conditional-compilation]
throughout the codebase, to modify rustc's behavior. Code marked with such
attributes can be enabled using `RUSTFLAGS` and `RUSTDOCFLAGS` environment
variables. One of the most prevalent flags passed in these variables is
the `--cfg` option. To run tests in a particular file, check first what
options #![cfg] declaration defines for that file.
For instance, to run a test marked with the 'tokio_unstable' cfg option,
you must pass this flag to the compiler when running the test.
```
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
```
#### 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.
#### Fuzz tests
Some of our crates include a set of fuzz tests, this will be marked by a
directory `fuzz`. It is a good idea to run fuzz tests after each change.
To get started with fuzz testing you'll need to install
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
`cargo install --locked cargo-fuzz`
To list the available fuzzing harnesses you can run;
```bash
$ cd tokio
$ cargo fuzz list
fuzz_linked_list
```
Running a fuzz test is as simple as;
`cargo fuzz run fuzz_linked_list`
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
harness will run forever and will only exit if you `ctrl-c` or it finds
a bug.
#### 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::time::timeout` provides a good example of a
documentation test:
```
/// Create a new `Timeout` set to expire in 10 milliseconds.
///
/// ```rust
/// use tokio::time::timeout;
/// use tokio::sync::oneshot;
///
/// use std::time::Duration;
///
/// # async fn dox() {
/// let (tx, rx) = oneshot::channel();
/// # tx.send(()).unwrap();
///
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
/// if let Err(_) = timeout(Duration::from_millis(10), rx).await {
/// println!("did not receive value within 10 ms");
/// }
/// # }
/// ```
```
Lines that start with `/// #` are removed when the documentation is generated.
### Benchmarks
You can run benchmarks locally for the changes you've made to the tokio codebase.
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
against the changes you have made, for example, you can run;
```bash
cd benches
# Run all benchmarks.
cargo bench
# Run all tests in the `benches/fs.rs` file
cargo bench --bench fs
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
cargo bench async_read_buf
# After running benches, you can check the statistics under `tokio/target/criterion/`
```
You can also refer to [Criterion] docs for additional options and details.
[Criterion]: https://docs.rs/criterion/latest/criterion/
### 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-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
* start with an imperative verb
* not have a period at the end
* be prefixed with the name of the module being changed; usually this is the
same as the M-* label on the PR
Examples:
* time: introduce `Timeout` and deprecate `Deadline`
* codec: export `Encoder`, `Decoder`, `Framed*`
* ci: fix the FreeBSD ci configuration
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
module: 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](#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.
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
@@ -1,80 +0,0 @@
## 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
+15 -67
View File
@@ -2,61 +2,40 @@
name = "examples"
version = "0.0.0"
publish = false
edition = "2021"
license = "MIT"
edition = "2018"
# If you copy one of the examples into a new project, you should be using
# [dependencies] instead, and delete the **path**.
# [dependencies] instead.
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing"] }
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
tokio = { version = "0.2.0", path = "../tokio", features = ["full", "tracing"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3.1", default-features = false, features = ["fmt", "ansi", "env-filter", "tracing-log"] }
bytes = "1.0.0"
futures = { version = "0.3.0", features = ["thread-pool"]}
http = "1"
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
tokio-util = { version = "0.3.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"
httpdate = "1.0"
once_cell = "1.5.2"
[target.'cfg(target_os = "linux")'.dev-dependencies]
libc = "0.2"
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing", "taskdump"] }
[target.'cfg(windows)'.dev-dependencies.windows-sys]
version = "0.61"
time = "0.1"
[[example]]
name = "chat"
path = "chat.rs"
[[example]]
name = "connect-tcp"
path = "connect-tcp.rs"
[[example]]
name = "connect-udp"
path = "connect-udp.rs"
[[example]]
name = "echo-tcp"
path = "echo-tcp.rs"
[[example]]
name = "graceful-shutdown"
path = "graceful-shutdown.rs"
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"
@@ -84,34 +63,3 @@ path = "udp-codec.rs"
[[example]]
name = "tinyhttp"
path = "tinyhttp.rs"
[[example]]
name = "custom-executor"
path = "custom-executor.rs"
[[example]]
name = "custom-executor-tokio-context"
path = "custom-executor-tokio-context.rs"
[[example]]
name = "named-pipe"
path = "named-pipe.rs"
[[example]]
name = "named-pipe-ready"
path = "named-pipe-ready.rs"
[[example]]
name = "named-pipe-multi-client"
path = "named-pipe-multi-client.rs"
[[example]]
name = "dump"
path = "dump.rs"
[[example]]
name = "prewarm-fd-table"
path = "prewarm-fd-table.rs"
[lints]
workspace = true
+1 -1
View File
@@ -10,7 +10,7 @@ cargo run --example $name
```
A good starting point for the examples would be [`hello_world`](hello_world.rs)
and [`echo-tcp`](echo-tcp.rs). Additionally [the tokio website][tokioweb] contains
and [`echo`](echo.rs). Additionally [the tokio website][tokioweb] contains
additional guides for some of the examples.
For a larger "real world" example, see the [`mini-redis`][redis] repository.
+80 -58
View File
@@ -27,9 +27,9 @@
#![warn(rust_2018_idioms)]
use tokio::net::{TcpListener, TcpStream};
use tokio::stream::{Stream, StreamExt};
use tokio::sync::{mpsc, Mutex};
use tokio_stream::StreamExt;
use tokio_util::codec::{Framed, LinesCodec};
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
use futures::SinkExt;
use std::collections::HashMap;
@@ -37,9 +37,9 @@ use std::env;
use std::error::Error;
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Arc;
const DEFAULT_ADDR: &str = "127.0.0.1:6142";
use std::task::{Context, Poll};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
@@ -72,14 +72,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
let addr = env::args()
.nth(1)
.unwrap_or_else(|| DEFAULT_ADDR.to_string());
.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 listener = TcpListener::bind(&addr).await?;
let mut listener = TcpListener::bind(&addr).await?;
tracing::info!("server running on {addr}");
tracing::info!("server running on {}", addr);
loop {
// Asynchronously wait for an inbound TcpStream.
@@ -90,9 +90,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
// Spawn our handler to be run asynchronously.
tokio::spawn(async move {
tracing::debug!("accepted connection from {addr}");
tracing::debug!("accepted connection");
if let Err(e) = process(state, stream, addr).await {
tracing::warn!("Connection from {addr} failed: {e:?}");
tracing::info!("an error occurred; error = {:?}", e);
}
});
}
@@ -140,24 +140,12 @@ impl Shared {
/// Send a `LineCodec` encoded message to every peer, except
/// for the sender.
///
/// This function also cleans up disconnected peers automatically.
async fn broadcast(&mut self, sender: SocketAddr, message: &str) {
let mut failed_peers = Vec::new();
let message = message.to_string(); // Clone once for all sends
for (addr, tx) in self.peers.iter() {
if *addr != sender && tx.send(message.clone()).is_err() {
// Receiver has been dropped, mark for removal
failed_peers.push(*addr);
for peer in self.peers.iter_mut() {
if *peer.0 != sender {
let _ = peer.1.send(message.into());
}
}
// Clean up disconnected peers
for addr in failed_peers {
self.peers.remove(&addr);
tracing::debug!("Removed disconnected peer: {addr}");
}
}
}
@@ -180,6 +168,43 @@ impl Peer {
}
}
#[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>>,
@@ -192,10 +217,13 @@ async fn process(
lines.send("Please enter your username:").await?;
// Read the first line from the `LineCodec` stream to get the username.
let Some(Ok(username)) = lines.next().await else {
let username = match lines.next().await {
Some(Ok(line)) => line,
// We didn't get a line so we return early here.
tracing::error!("Failed to get username from {addr}. Client disconnected.");
return Ok(());
_ => {
tracing::error!("Failed to get username from {}. Client disconnected.", addr);
return Ok(());
}
};
// Register our peer with state which internally sets up some channels.
@@ -204,40 +232,34 @@ async fn process(
// A client has connected, let's let everyone know.
{
let mut state = state.lock().await;
let msg = format!("{username} has joined the chat");
tracing::info!("{msg}");
let msg = format!("{} has joined the chat", username);
tracing::info!("{}", msg);
state.broadcast(addr, &msg).await;
}
// Process incoming messages until our stream is exhausted by a disconnect.
loop {
tokio::select! {
// A message was received from a peer. Send it to the current user.
Some(msg) = peer.rx.recv() => {
if let Err(e) = peer.lines.send(&msg).await {
tracing::error!("Failed to send message to {username}: {e:?}");
break;
}
}
result = peer.lines.next() => match result {
// A message was received from the current user, we should
// broadcast this message to the other users.
Some(Ok(msg)) => {
let mut state = state.lock().await;
let msg = format!("{username}: {msg}");
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;
}
// An error occurred.
Some(Err(e)) => {
tracing::error!(
"an error occurred while processing messages for {username}; error = {e:?}"
);
break;
}
// The stream has been exhausted.
None => break,
},
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) => {
tracing::error!(
"an error occurred while processing messages for {}; error = {:?}",
username,
e
);
}
}
}
@@ -247,8 +269,8 @@ async fn process(
let mut state = state.lock().await;
state.peers.remove(&addr);
let msg = format!("{username} has left the chat");
tracing::info!("{msg}");
let msg = format!("{} has left the chat", username);
tracing::info!("{}", msg);
state.broadcast(addr, &msg).await;
}
-73
View File
@@ -1,73 +0,0 @@
//! An example of hooking up stdin/stdout to a TCP 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. Each line entered on stdin will be translated to a TCP
//! packet which is then sent to the remote address.
//!
//! Note that this is not currently optimized for performance, especially
//! around buffer management. Rather it's intended to show an example of
//! working with a client.
//!
//! This example can be quite useful when interacting with the other examples in
//! this repository! Many of them recommend running this as a simple "hook up
//! stdin/stdout to a server" to get up and running.
#![warn(rust_2018_idioms)]
use tokio::io::{stdin, stdout};
use tokio::net::TcpStream;
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
use bytes::Bytes;
use futures::{future, Sink, SinkExt, Stream, StreamExt};
use std::env;
use std::error::Error;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Parse what address we're going to connect to
let args = env::args().skip(1).collect::<Vec<_>>();
let addr = args
.first()
.ok_or("this program requires at least one argument")?;
let addr = addr.parse::<SocketAddr>()?;
let stdin = FramedRead::new(stdin(), BytesCodec::new());
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
connect(&addr, stdin, stdout).await?;
Ok(())
}
pub async fn connect(
addr: &SocketAddr,
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
mut stdout: impl Sink<Bytes, Error = std::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) => {
eprintln!("failed to read from socket; error={e}");
future::ready(None)
}
})
.map(Ok);
tokio::select! {
r = sink.send_all(&mut stdin) => r?,
r = stdout.send_all(&mut stream) => r?,
}
Ok(())
}
-94
View File
@@ -1,94 +0,0 @@
//! An example of hooking up stdin/stdout to a 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. Each line entered on stdin will be translated to a UDP
//! packet which is then sent to the remote address.
//!
//! Note that this is not currently optimized for performance, especially
//! around buffer management. Rather it's intended to show an example of
//! working with a client.
//!
//! This example can be quite useful when interacting with the other examples in
//! this repository! Many of them recommend running this as a simple "hook up
//! stdin/stdout to a server" to get up and running.
#![warn(rust_2018_idioms)]
use tokio::io::{stdin, stdout};
use tokio::net::UdpSocket;
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
use bytes::Bytes;
use futures::{Sink, SinkExt, Stream, StreamExt};
use std::env;
use std::error::Error;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Parse what address we're going to connect to
let args = env::args().skip(1).collect::<Vec<_>>();
let addr = args
.first()
.ok_or("this program requires at least one argument")?;
let addr = addr.parse::<SocketAddr>()?;
let stdin = FramedRead::new(stdin(), BytesCodec::new());
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
let stdout = FramedWrite::new(stdout(), BytesCodec::new());
connect(&addr, stdin, stdout).await?;
Ok(())
}
pub async fn connect(
addr: &SocketAddr,
stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
stdout: impl Sink<Bytes, Error = std::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?;
tokio::select! {
r = send(stdin, &socket) => r?,
r = recv(stdout, &socket) => r?,
}
Ok(())
}
async fn send(
mut stdin: impl Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
writer: &UdpSocket,
) -> Result<(), std::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 = std::io::Error> + Unpin,
reader: &UdpSocket,
) -> Result<(), std::io::Error> {
loop {
let mut buf = vec![0; 1024];
let n = reader.recv(&mut buf[..]).await?;
if n > 0 {
stdout.send(Bytes::copy_from_slice(&buf[..n])).await?;
}
}
}
+149
View File
@@ -0,0 +1,149 @@
//! An example of hooking up stdin/stdout to either a TCP or UDP stream.
//!
//! This example will connect to a socket address specified in the argument list
//! and then forward all data read on stdin to the server, printing out all data
//! received on stdout. An optional `--udp` argument can be passed to specify
//! that the connection should be made over UDP instead of TCP, translating each
//! line entered on stdin to a UDP packet to be sent to the remote address.
//!
//! Note that this is not currently optimized for performance, especially
//! around buffer management. Rather it's intended to show an example of
//! working with a client.
//!
//! This example can be quite useful when interacting with the other examples in
//! this repository! Many of them recommend running this as a simple "hook up
//! stdin/stdout to a server" to get up and running.
#![warn(rust_2018_idioms)]
use futures::StreamExt;
use tokio::io;
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
use std::env;
use std::error::Error;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Determine if we're going to run in TCP or UDP mode
let mut args = env::args().skip(1).collect::<Vec<_>>();
let tcp = match args.iter().position(|a| a == "--udp") {
Some(i) => {
args.remove(i);
false
}
None => true,
};
// Parse what address we're going to connect to
let addr = args
.first()
.ok_or("this program requires at least one argument")?;
let addr = addr.parse::<SocketAddr>()?;
let stdin = FramedRead::new(io::stdin(), BytesCodec::new());
let stdin = stdin.map(|i| i.map(|bytes| bytes.freeze()));
let stdout = FramedWrite::new(io::stdout(), BytesCodec::new());
if tcp {
tcp::connect(&addr, stdin, stdout).await?;
} else {
udp::connect(&addr, stdin, stdout).await?;
}
Ok(())
}
mod tcp {
use bytes::Bytes;
use futures::{future, Sink, SinkExt, Stream, StreamExt};
use std::{error::Error, io, net::SocketAddr};
use tokio::net::TcpStream;
use tokio_util::codec::{BytesCodec, FramedRead, FramedWrite};
pub async fn connect(
addr: &SocketAddr,
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
) -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect(addr).await?;
let (r, w) = stream.split();
let mut sink = FramedWrite::new(w, BytesCodec::new());
// filter map Result<BytesMut, Error> stream into just a Bytes stream to match stdout Sink
// on the event of an Error, log the error and end the stream
let mut stream = FramedRead::new(r, BytesCodec::new())
.filter_map(|i| match i {
//BytesMut into Bytes
Ok(i) => future::ready(Some(i.freeze())),
Err(e) => {
println!("failed to read from socket; error={}", e);
future::ready(None)
}
})
.map(Ok);
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
(Err(e), _) | (_, Err(e)) => Err(e.into()),
_ => Ok(()),
}
}
}
mod udp {
use bytes::Bytes;
use futures::{future, Sink, SinkExt, Stream, StreamExt};
use std::error::Error;
use std::io;
use std::net::SocketAddr;
use tokio::net::udp::{RecvHalf, SendHalf};
use tokio::net::UdpSocket;
pub async fn connect(
addr: &SocketAddr,
stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
) -> Result<(), Box<dyn Error>> {
// We'll bind our UDP socket to a local IP/port, but for now we
// basically let the OS pick both of those.
let bind_addr = if addr.ip().is_ipv4() {
"0.0.0.0:0"
} else {
"[::]:0"
};
let socket = UdpSocket::bind(&bind_addr).await?;
socket.connect(addr).await?;
let (mut r, mut w) = socket.split();
future::try_join(send(stdin, &mut w), recv(stdout, &mut r)).await?;
Ok(())
}
async fn send(
mut stdin: impl Stream<Item = Result<Bytes, io::Error>> + Unpin,
writer: &mut SendHalf,
) -> Result<(), io::Error> {
while let Some(item) = stdin.next().await {
let buf = item?;
writer.send(&buf[..]).await?;
}
Ok(())
}
async fn recv(
mut stdout: impl Sink<Bytes, Error = io::Error> + Unpin,
reader: &mut RecvHalf,
) -> Result<(), io::Error> {
loop {
let mut buf = vec![0; 1024];
let n = reader.recv(&mut buf[..]).await?;
if n > 0 {
stdout.send(Bytes::from(buf)).await?;
}
}
}
}
-32
View File
@@ -1,32 +0,0 @@
// This example shows how to use the tokio runtime with any other executor
//
//It takes advantage from RuntimeExt which provides the extension to customize your
//runtime.
use tokio::net::TcpListener;
use tokio::runtime::Builder;
use tokio::sync::oneshot;
use tokio_util::context::RuntimeExt;
fn main() {
let (tx, rx) = oneshot::channel();
let rt1 = Builder::new_multi_thread()
.worker_threads(1)
// no timer!
.build()
.unwrap();
let rt2 = Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.unwrap();
// Without the `HandleExt.wrap()` there would be a panic because there is
// no timer running, since it would be referencing runtime r1.
rt1.block_on(rt2.wrap(async move {
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
println!("addr: {:?}", listener.local_addr());
tx.send(()).unwrap();
}));
futures::executor::block_on(rx).unwrap();
}
-56
View File
@@ -1,56 +0,0 @@
// This example shows how to use the tokio runtime with any other executor
//
// The main components are a spawn fn that will wrap futures in a special future
// that will always enter the tokio context on poll. This only spawns one extra thread
// to manage and run the tokio drivers in the background.
use tokio::net::TcpListener;
use tokio::sync::oneshot;
fn main() {
let (tx, rx) = oneshot::channel();
my_custom_runtime::spawn(async move {
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
println!("addr: {:?}", listener.local_addr());
tx.send(()).unwrap();
});
futures::executor::block_on(rx).unwrap();
}
mod my_custom_runtime {
use once_cell::sync::Lazy;
use std::future::Future;
use tokio_util::context::TokioContext;
pub fn spawn(f: impl Future<Output = ()> + Send + 'static) {
EXECUTOR.spawn(f);
}
struct ThreadPool {
inner: futures::executor::ThreadPool,
rt: tokio::runtime::Runtime,
}
static EXECUTOR: Lazy<ThreadPool> = Lazy::new(|| {
// Spawn tokio runtime on a single background thread
// enabling IO and timers.
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let inner = futures::executor::ThreadPool::builder().create().unwrap();
ThreadPool { inner, rt }
});
impl ThreadPool {
fn spawn(&self, f: impl Future<Output = ()> + Send + 'static) {
let handle = self.rt.handle().clone();
self.inner.spawn_ok(TokioContext::new(f, handle));
}
}
}
-99
View File
@@ -1,99 +0,0 @@
//! This example demonstrates tokio's experimental task dumping functionality.
//! This application deadlocks. Input CTRL+C to display traces of each task, or
//! input CTRL+C twice within 1 second to quit.
#[cfg(all(
tokio_unstable,
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "s390x"
)
))]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::sync::Arc;
use tokio::sync::Barrier;
#[inline(never)]
async fn a(barrier: Arc<Barrier>) {
b(barrier).await
}
#[inline(never)]
async fn b(barrier: Arc<Barrier>) {
c(barrier).await
}
#[inline(never)]
async fn c(barrier: Arc<Barrier>) {
barrier.wait().await;
}
// Prints a task dump upon receipt of CTRL+C, or returns if CTRL+C is
// inputted twice within a second.
async fn dump_or_quit() {
use tokio::time::{timeout, Duration, Instant};
let handle = tokio::runtime::Handle::current();
let mut last_signal: Option<Instant> = None;
// wait for CTRL+C
while let Ok(_) = tokio::signal::ctrl_c().await {
// exit if a CTRL+C is inputted twice within 1 second
if let Some(time_since_last_signal) = last_signal.map(|i| i.elapsed()) {
if time_since_last_signal < Duration::from_secs(1) {
return;
}
}
last_signal = Some(Instant::now());
// capture a dump, and print each trace
println!("{:-<80}", "");
if let Ok(dump) = timeout(Duration::from_secs(2), handle.dump()).await {
for task in dump.tasks().iter() {
let id = task.id();
let trace = task.trace();
println!("TASK {id}:");
println!("{trace}\n");
}
} else {
println!("Task dumping timed out. Use a native debugger (like gdb) to debug the deadlock.");
}
println!("{:-<80}", "");
println!("Input CTRL+C twice within 1 second to exit.");
}
}
println!("This program has a deadlock.");
println!("Input CTRL+C to print a task dump.");
println!("Input CTRL+C twice within 1 second to exit.");
// oops! this barrier waits for one more task than will ever come.
let barrier = Arc::new(Barrier::new(3));
let task_1 = tokio::spawn(a(barrier.clone()));
let task_2 = tokio::spawn(a(barrier));
tokio::select!(
_ = dump_or_quit() => {},
_ = task_1 => {},
_ = task_2 => {},
);
Ok(())
}
#[cfg(not(all(
tokio_unstable,
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "s390x"
)
)))]
fn main() {
println!("task dumps are not available")
}
+4 -26
View File
@@ -6,31 +6,9 @@
//!
//! and in another terminal you can run:
//!
//! cargo run --example connect-udp 127.0.0.1:8080
//! cargo run --example connect -- --udp 127.0.0.1:8080
//!
//! Each line you type in to the `connect-udp` terminal should be echo'd back to you!
//!
//! # Binding to all interfaces
//!
//! By default this example binds to `127.0.0.1` so it is only reachable
//! from the local machine.
//!
//! To listen on all interfaces instead:
//!
//! ```sh
//! cargo run --example echo-udp -- 0.0.0.0:8080
//! ```
//!
//! Binding to `0.0.0.0` exposes the server on all network interfaces.
//! Only do this in trusted network environments.
//!
//! On multi-homed systems, a UDP socket bound to a wildcard address
//! (`0.0.0.0` or `::`) cannot always send replies from the same local IP
//! that received the packet. Replies may therefore originate from a
//! different address than the client targeted. See [this Cloudflare blog
//! post][udp-blog] for more details.
//!
//! [udp-blog]: https://blog.cloudflare.com/everything-you-ever-wanted-to-know-about-udp-sockets-but-were-afraid-to-ask-part-1
//! Each line you type in to the `nc` terminal should be echo'd back to you!
#![warn(rust_2018_idioms)]
@@ -48,7 +26,7 @@ struct Server {
impl Server {
async fn run(self) -> Result<(), io::Error> {
let Server {
socket,
mut socket,
mut buf,
mut to_send,
} = self;
@@ -60,7 +38,7 @@ impl Server {
if let Some((size, peer)) = to_send {
let amt = socket.send_to(&buf[..size], &peer).await?;
println!("Echoed {amt}/{size} bytes to {peer}");
println!("Echoed {}/{} bytes to {}", amt, size, peer);
}
// If we're here then `to_send` is `None`, so we take a look for the
+21 -28
View File
@@ -9,14 +9,14 @@
//!
//! To see this server in action, you can run this in one terminal:
//!
//! cargo run --example echo-tcp
//! cargo run --example echo
//!
//! and in another terminal you can run:
//!
//! cargo run --example connect-tcp 127.0.0.1:8080
//! cargo run --example connect 127.0.0.1:8080
//!
//! Each line you type in to the `connect-tcp` terminal should be echo'd back to
//! you! If you open up multiple terminals running the `connect-tcp` example you
//! 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)]
@@ -27,9 +27,6 @@ use tokio::net::TcpListener;
use std::env;
use std::error::Error;
const DEFAULT_ADDR: &str = "127.0.0.1:8080";
const BUFFER_SIZE: usize = 4096;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Allow passing an address to listen on as the first argument of this
@@ -37,17 +34,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
// 127.0.0.1:8080 for connections.
let addr = env::args()
.nth(1)
.unwrap_or_else(|| DEFAULT_ADDR.to_string());
.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 listener = TcpListener::bind(&addr).await?;
println!("Listening on: {addr}");
let mut listener = TcpListener::bind(&addr).await?;
println!("Listening on: {}", addr);
loop {
// Asynchronously wait for an inbound socket.
let (mut socket, addr) = listener.accept().await?;
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
@@ -58,27 +55,23 @@ async fn main() -> Result<(), Box<dyn Error>> {
// which will allow all of our clients to be processed concurrently.
tokio::spawn(async move {
let mut buf = vec![0; BUFFER_SIZE];
let mut buf = [0; 1024];
// In a loop, read data from the socket and write the data back.
loop {
match socket.read(&mut buf).await {
Ok(0) => {
// Connection closed by peer
return;
}
Ok(n) => {
// Write the data back. If writing fails, log the error and exit.
if let Err(e) = socket.write_all(&buf[0..n]).await {
eprintln!("Failed to write to socket {}: {}", addr, e);
return;
}
}
Err(e) => {
eprintln!("Failed to read from socket {}: {}", addr, e);
return;
}
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");
}
});
}
-120
View File
@@ -1,120 +0,0 @@
//! Graceful shutdown example.
//!
//! This example follows the same approach described in the
//! [Graceful Shutdown tutorial](https://tokio.rs/tokio/topics/shutdown):
//!
//! - A [`CancellationToken`] tells tasks to stop accepting new work.
//! - A [`TaskTracker`] waits for in-flight work to complete.
//!
//! It runs a TCP echo server on `127.0.0.1:6142`. When Ctrl+C is
//! pressed, the server stops accepting connections and waits for all
//! active connections to finish before exiting.
//!
//! Start the server:
//!
//! cargo run --example graceful-shutdown
//!
//! Then connect with:
//!
//! nc 127.0.0.1 6142
//!
//! Press Ctrl+C on the server to trigger a graceful shutdown.
#![warn(rust_2018_idioms)]
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::{TcpListener, TcpStream};
use tokio::time::{self, Duration};
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
use std::error::Error;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind("127.0.0.1:6142").await?;
println!("listening on 127.0.0.1:6142");
let token = CancellationToken::new();
let tracker = TaskTracker::new();
loop {
tokio::select! {
result = listener.accept() => {
let (socket, addr) = match result {
Ok(conn) => conn,
Err(e) => {
// Transient errors (e.g. fd exhaustion) are recoverable,
// so we log and continue. A production server might add a
// backoff or break on fatal errors to avoid a busy loop.
eprintln!("failed to accept: {e}");
continue;
}
};
println!("accepted connection from {addr}");
let token = token.clone();
tracker.spawn(handle_connection(socket, addr, token));
}
_ = tokio::signal::ctrl_c() => {
println!("\nshutdown signal received, waiting for connections to finish");
break;
}
}
}
// Signal all tasks to stop and wait for them to complete.
token.cancel();
tracker.close();
tracker.wait().await;
println!("shutdown complete");
Ok(())
}
async fn handle_connection(mut socket: TcpStream, addr: SocketAddr, token: CancellationToken) {
tokio::select! {
_ = echo(&mut socket) => {}
_ = token.cancelled() => {
notify_shutdown(&mut socket).await;
}
}
println!("connection from {addr} closed");
}
/// Reads lines from the client and writes them back.
///
/// Called for every accepted connection. Runs until the client disconnects
/// or a read/write error occurs.
async fn echo(socket: &mut TcpStream) {
let (reader, mut writer) = socket.split();
let mut reader = BufReader::new(reader);
let mut line = String::new();
loop {
match reader.read_line(&mut line).await {
Ok(0) | Err(_) => return,
Ok(_) => {
if writer.write_all(line.as_bytes()).await.is_err() {
return;
}
line.clear();
}
}
}
}
/// Sends a shutdown notice to the client before closing the connection.
///
/// Called when the cancellation token fires. Uses a timeout so that a
/// slow or unresponsive client cannot hold up the server shutdown.
async fn notify_shutdown(socket: &mut TcpStream) {
let _ = time::timeout(
Duration::from_secs(1),
socket.write_all(b"server shutting down\n"),
)
.await;
}
+4 -2
View File
@@ -1,7 +1,9 @@
//! Hello world server.
//!
//! A simple client that opens a TCP stream, writes "hello world\n", and closes
//! the connection.
//!
//! To start a server that this client can talk to on port 6142, you can use this command:
//! You can test this out by running:
//!
//! ncat -l 6142
//!
@@ -24,7 +26,7 @@ pub async fn main() -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect("127.0.0.1:6142").await?;
println!("created stream");
let result = stream.write_all(b"hello world\n").await;
let result = stream.write(b"hello world\n").await;
println!("wrote to stream; success={:?}", result.is_ok());
Ok(())
-98
View File
@@ -1,98 +0,0 @@
use std::io;
#[cfg(windows)]
async fn windows_main() -> io::Result<()> {
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
use tokio::time;
use windows_sys::Win32::Foundation::ERROR_PIPE_BUSY;
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-multi-client";
const N: usize = 10;
// The first server needs to be constructed early so that clients can
// be correctly connected. Otherwise a waiting client will error.
//
// Here we also make use of `first_pipe_instance`, which will ensure
// that there are no other servers up and running already.
let mut server = ServerOptions::new()
.first_pipe_instance(true)
.create(PIPE_NAME)?;
let server = tokio::spawn(async move {
// Artificial workload.
time::sleep(Duration::from_secs(1)).await;
for _ in 0..N {
// Wait for client to connect.
server.connect().await?;
let mut inner = server;
// Construct the next server to be connected before sending the one
// we already have of onto a task. This ensures that the server
// isn't closed (after it's done in the task) before a new one is
// available. Otherwise the client might error with
// `io::ErrorKind::NotFound`.
server = ServerOptions::new().create(PIPE_NAME)?;
let _ = tokio::spawn(async move {
let mut buf = vec![0u8; 4];
inner.read_exact(&mut buf).await?;
inner.write_all(b"pong").await?;
Ok::<_, io::Error>(())
});
}
Ok::<_, io::Error>(())
});
let mut clients = Vec::new();
for _ in 0..N {
clients.push(tokio::spawn(async move {
// This showcases a generic connect loop.
//
// We immediately try to create a client, if it's not found or
// the pipe is busy we use the specialized wait function on the
// client builder.
let mut client = loop {
match ClientOptions::new().open(PIPE_NAME) {
Ok(client) => break client,
Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY as i32) => (),
Err(e) => return Err(e),
}
time::sleep(Duration::from_millis(5)).await;
};
let mut buf = [0u8; 4];
client.write_all(b"ping").await?;
client.read_exact(&mut buf).await?;
Ok::<_, io::Error>(buf)
}));
}
for client in clients {
let result = client.await?;
assert_eq!(&result?[..], b"pong");
}
server.await??;
Ok(())
}
#[tokio::main]
async fn main() -> io::Result<()> {
#[cfg(windows)]
{
windows_main().await?;
}
#[cfg(not(windows))]
{
println!("Named pipes are only supported on Windows!");
}
Ok(())
}
-161
View File
@@ -1,161 +0,0 @@
use std::io;
#[cfg(windows)]
async fn windows_main() -> io::Result<()> {
use tokio::io::Interest;
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client";
let server = ServerOptions::new().create(PIPE_NAME)?;
let server = tokio::spawn(async move {
// Note: we wait for a client to connect.
server.connect().await?;
let buf = {
let mut read_buf = [0u8; 5];
let mut read_buf_cursor = 0;
loop {
server.readable().await?;
let buf = &mut read_buf[read_buf_cursor..];
match server.try_read(buf) {
Ok(n) => {
read_buf_cursor += n;
if read_buf_cursor == read_buf.len() {
break;
}
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(e);
}
}
}
read_buf
};
{
let write_buf = b"pong\n";
let mut write_buf_cursor = 0;
loop {
let buf = &write_buf[write_buf_cursor..];
if buf.is_empty() {
break;
}
server.writable().await?;
match server.try_write(buf) {
Ok(n) => {
write_buf_cursor += n;
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(e);
}
}
}
}
Ok::<_, io::Error>(buf)
});
let client = tokio::spawn(async move {
// There's no need to use a connect loop here, since we know that the
// server is already up - `open` was called before spawning any of the
// tasks.
let client = ClientOptions::new().open(PIPE_NAME)?;
let mut read_buf = [0u8; 5];
let mut read_buf_cursor = 0;
let write_buf = b"ping\n";
let mut write_buf_cursor = 0;
loop {
let mut interest = Interest::READABLE;
if write_buf_cursor < write_buf.len() {
interest |= Interest::WRITABLE;
}
let ready = client.ready(interest).await?;
if ready.is_readable() {
let buf = &mut read_buf[read_buf_cursor..];
match client.try_read(buf) {
Ok(n) => {
read_buf_cursor += n;
if read_buf_cursor == read_buf.len() {
break;
}
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(e);
}
}
}
if ready.is_writable() {
let buf = &write_buf[write_buf_cursor..];
if buf.is_empty() {
continue;
}
match client.try_write(buf) {
Ok(n) => {
write_buf_cursor += n;
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(e);
}
}
}
}
let buf = String::from_utf8_lossy(&read_buf).into_owned();
Ok::<_, io::Error>(buf)
});
let (server, client) = tokio::try_join!(server, client)?;
assert_eq!(server?, *b"ping\n");
assert_eq!(client?, "pong\n");
Ok(())
}
#[tokio::main]
async fn main() -> io::Result<()> {
#[cfg(windows)]
{
windows_main().await?;
}
#[cfg(not(windows))]
{
println!("Named pipes are only supported on Windows!");
}
Ok(())
}
-60
View File
@@ -1,60 +0,0 @@
use std::io;
#[cfg(windows)]
async fn windows_main() -> io::Result<()> {
use tokio::io::AsyncWriteExt;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client";
let server = ServerOptions::new().create(PIPE_NAME)?;
let server = tokio::spawn(async move {
// Note: we wait for a client to connect.
server.connect().await?;
let mut server = BufReader::new(server);
let mut buf = String::new();
server.read_line(&mut buf).await?;
server.write_all(b"pong\n").await?;
Ok::<_, io::Error>(buf)
});
let client = tokio::spawn(async move {
// There's no need to use a connect loop here, since we know that the
// server is already up - `open` was called before spawning any of the
// tasks.
let client = ClientOptions::new().open(PIPE_NAME)?;
let mut client = BufReader::new(client);
let mut buf = String::new();
client.write_all(b"ping\n").await?;
client.read_line(&mut buf).await?;
Ok::<_, io::Error>(buf)
});
let (server, client) = tokio::try_join!(server, client)?;
assert_eq!(server?, "ping\n");
assert_eq!(client?, "pong\n");
Ok(())
}
#[tokio::main]
async fn main() -> io::Result<()> {
#[cfg(windows)]
{
windows_main().await?;
}
#[cfg(not(windows))]
{
println!("Named pipes are only supported on Windows!");
}
Ok(())
}
-86
View File
@@ -1,86 +0,0 @@
//! Demonstrates pre-warming the Linux file descriptor table to avoid latency
//! spikes caused by file descriptor table growth in multi-threaded processes.
//!
//! On Linux, the kernel's FD table is grown lazily and protected by RCU
//! synchronization. In multi-threaded processes, when a syscall like `socket()`
//! triggers a table resize, the calling thread blocks until all RCU readers
//! quiesce. This can cause stalls of tens of milliseconds on tokio worker threads,
//! blocking the entire event loop (not just one task).
//!
//! The workaround is to force the kernel to expand the FD table once per process
//! (before any runtime starts), by duplicating an FD to a high slot and then
//! closing it. The kernel never shrinks the FD table during a process's lifetime,
//! so the capacity persists.
//!
//! This is most relevant for services that open many connections concurrently
//! (e.g. HTTP servers, connection pools). The pre-warm target should be at least
//! your expected peak FD count, and must not exceed `RLIMIT_NOFILE`.
//!
//! See: <https://github.com/tokio-rs/tokio/issues/7970>
//!
//! Usage:
//!
//! cargo run --example prewarm-fd-table
#![warn(rust_2018_idioms)]
/// Pre-warms the FD table using `fcntl(F_DUPFD_CLOEXEC)` to duplicate an FD
/// into a high slot, expanding the table in a single syscall. `F_DUPFD_CLOEXEC`
/// allocates the lowest available FD >= `target`, so it never clobbers an
/// existing FD.
#[cfg(target_os = "linux")]
fn prewarm_fd_table(target: i32) -> std::io::Result<()> {
use std::os::unix::io::{FromRawFd, OwnedFd};
let dev_null = std::fs::File::open("/dev/null")?;
let raw = unsafe {
libc::fcntl(
std::os::unix::io::AsRawFd::as_raw_fd(&dev_null),
libc::F_DUPFD_CLOEXEC,
target,
)
};
if raw < 0 {
return Err(std::io::Error::last_os_error());
}
// Close both FDs. The table capacity persists.
let _owned = unsafe { OwnedFd::from_raw_fd(raw) };
drop(dev_null);
Ok(())
}
/// Fully safe alternative using only stdlib. Requires O(n) syscalls instead of
/// one, but avoids `unsafe` entirely.
#[cfg(target_os = "linux")]
#[allow(dead_code)]
fn prewarm_fd_table_safe(target: i32) -> std::io::Result<()> {
let f = std::fs::File::open("/dev/null")?;
let _fds: Vec<_> = (0..target)
.map(|_| f.try_clone())
.collect::<Result<_, _>>()?;
Ok(())
}
fn main() {
#[cfg(target_os = "linux")]
{
const FD_TARGET: i32 = 10_000;
println!("Pre-warming FD table to {FD_TARGET} entries...");
if let Err(e) = prewarm_fd_table(FD_TARGET) {
eprintln!("Warning: failed to pre-warm FD table: {e}");
} else {
println!("FD table pre-warmed successfully.");
}
}
// Build the runtime *after* pre-warming.
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(async {});
}
+7 -7
View File
@@ -13,9 +13,9 @@
//!
//! and in another terminal you can run:
//!
//! cargo run --example connect-tcp 127.0.0.1:8080
//! cargo run --example connect 127.0.0.1:8080
//!
//! Each line you type in to the `connect-tcp` terminal should be written to terminal!
//! Each line you type in to the `connect` terminal should be written to terminal!
//!
//! Minimal js example:
//!
@@ -55,7 +55,7 @@
#![warn(rust_2018_idioms)]
use tokio::net::TcpListener;
use tokio_stream::StreamExt;
use tokio::stream::StreamExt;
use tokio_util::codec::{BytesCodec, Decoder};
use std::env;
@@ -74,8 +74,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// above and must be associated with an event loop, so we pass in a handle
// to our event loop. After the socket's created we inform that we're ready
// to go and start accepting connections.
let listener = TcpListener::bind(&addr).await?;
println!("Listening on: {addr}");
let mut listener = TcpListener::bind(&addr).await?;
println!("Listening on: {}", addr);
loop {
// Asynchronously wait for an inbound socket.
@@ -96,8 +96,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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:?}"),
Ok(bytes) => println!("bytes: {:?}", bytes),
Err(err) => println!("Socket closed with error: {:?}", err),
}
}
println!("Socket received FIN packet and closed connection");
+36 -20
View File
@@ -11,20 +11,23 @@
//!
//! This in another terminal
//!
//! cargo run --example echo-tcp
//! cargo run --example echo
//!
//! And finally this in another terminal
//!
//! cargo run --example connect-tcp 127.0.0.1:8081
//! 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::copy_bidirectional;
use tokio::io;
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};
use futures::future::try_join;
use futures::FutureExt;
use std::env;
use std::error::Error;
@@ -37,28 +40,41 @@ async fn main() -> Result<(), Box<dyn Error>> {
.nth(2)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
println!("Listening on: {listen_addr}");
println!("Proxying to: {server_addr}");
println!("Listening on: {}", listen_addr);
println!("Proxying to: {}", server_addr);
let listener = TcpListener::bind(listen_addr).await?;
let mut listener = TcpListener::bind(listen_addr).await?;
while let Ok((mut inbound, _)) = listener.accept().await {
let server_addr = server_addr.clone();
tokio::spawn(async move {
let mut outbound = match TcpStream::connect(server_addr).await {
Ok(outbound) => outbound,
Err(e) => {
println!("Failed to connect; error={e}");
return;
}
};
if let Err(e) = copy_bidirectional(&mut inbound, &mut outbound).await {
println!("Failed to transfer; error={e}");
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 = async {
io::copy(&mut ri, &mut wo).await?;
wo.shutdown().await
};
let server_to_client = async {
io::copy(&mut ro, &mut wi).await?;
wi.shutdown().await
};
try_join(client_to_server, server_to_client).await?;
Ok(())
}
+15 -15
View File
@@ -12,14 +12,14 @@
//!
//! and next in another windows run:
//!
//! cargo run --example connect-tcp 127.0.0.1:8080
//! cargo run --example connect 127.0.0.1:8080
//!
//! In the `connect-tcp` window you can type in commands where when you hit enter
//! 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-tcp 127.0.0.1:8080
//! $ cargo run --example connect 127.0.0.1:8080
//! GET foo
//! foo = bar
//! GET FOOBAR
@@ -42,7 +42,7 @@
#![warn(rust_2018_idioms)]
use tokio::net::TcpListener;
use tokio_stream::StreamExt;
use tokio::stream::StreamExt;
use tokio_util::codec::{Framed, LinesCodec};
use futures::SinkExt;
@@ -89,8 +89,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
.nth(1)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
let listener = TcpListener::bind(&addr).await?;
println!("Listening on: {addr}");
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`
@@ -131,11 +131,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
let response = response.serialize();
if let Err(e) = lines.send(response.as_str()).await {
println!("error on sending response; error = {e:?}");
println!("error on sending response; error = {:?}", e);
}
}
Err(e) => {
println!("error on decoding from socket; error = {e:?}");
println!("error on decoding from socket; error = {:?}", e);
}
}
}
@@ -143,13 +143,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
// The connection will be closed at this point as `lines.next()` has returned `None`.
});
}
Err(e) => println!("error accepting socket; error = {e:?}"),
Err(e) => println!("error accepting socket; error = {:?}", e),
}
}
}
fn handle_request(line: &str, db: &Arc<Database>) -> Response {
let request = match Request::parse(line) {
let request = match Request::parse(&line) {
Ok(req) => req,
Err(e) => return Response::Error { msg: e },
};
@@ -162,7 +162,7 @@ fn handle_request(line: &str, db: &Arc<Database>) -> Response {
value: value.clone(),
},
None => Response::Error {
msg: format!("no key {key}"),
msg: format!("no key {}", key),
},
},
Request::Set { key, value } => {
@@ -203,7 +203,7 @@ impl Request {
value: value.to_string(),
})
}
Some(cmd) => Err(format!("unknown command: {cmd}")),
Some(cmd) => Err(format!("unknown command: {}", cmd)),
None => Err("empty input".into()),
}
}
@@ -212,13 +212,13 @@ impl Request {
impl Response {
fn serialize(&self) -> String {
match *self {
Response::Value { ref key, ref value } => format!("{key} = {value}"),
Response::Value { ref key, ref value } => format!("{} = {}", key, value),
Response::Set {
ref key,
ref value,
ref previous,
} => format!("set {key} = `{value}`, previous: {previous:?}"),
Response::Error { ref msg } => format!("error: {msg}"),
} => format!("set {} = `{}`, previous: {:?}", key, value, previous),
Response::Error { ref msg } => format!("error: {}", msg),
}
}
}
+35 -31
View File
@@ -20,7 +20,7 @@ use http::{header::HeaderValue, Request, Response, StatusCode};
extern crate serde_derive;
use std::{env, error::Error, fmt, io};
use tokio::net::{TcpListener, TcpStream};
use tokio_stream::StreamExt;
use tokio::stream::StreamExt;
use tokio_util::codec::{Decoder, Encoder, Framed};
#[tokio::main]
@@ -30,17 +30,19 @@ async fn main() -> Result<(), Box<dyn Error>> {
let addr = env::args()
.nth(1)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
let server = TcpListener::bind(&addr).await?;
println!("Listening on: {addr}");
let mut server = TcpListener::bind(&addr).await?;
let mut incoming = server.incoming();
println!("Listening on: {}", addr);
loop {
let (stream, _) = server.accept().await?;
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}");
println!("failed to process connection; error = {}", e);
}
});
}
Ok(())
}
async fn process(stream: TcpStream) -> Result<(), Box<dyn Error>> {
@@ -82,7 +84,9 @@ async fn respond(req: Request<()>) -> Result<Response<String>, Box<dyn Error>> {
String::new()
}
};
let response = response.body(body).map_err(io::Error::other)?;
let response = response
.body(body)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
Ok(response)
}
@@ -157,8 +161,8 @@ impl Decoder for Http {
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::other(msg)
let msg = format!("failed to parse http request: {:?}", e);
io::Error::new(io::ErrorKind::Other, msg)
})?;
let amt = match status {
@@ -178,21 +182,22 @@ impl Decoder for Http {
headers[i] = Some((k, v));
}
let method = http::Method::try_from(r.method.unwrap()).map_err(io::Error::other)?;
(
method,
toslice(r.method.unwrap().as_bytes()),
toslice(r.path.unwrap().as_bytes()),
r.version.unwrap(),
amt,
)
};
if version != 1 {
return Err(io::Error::other("only HTTP/1.1 accepted"));
return Err(io::Error::new(
io::ErrorKind::Other,
"only HTTP/1.1 accepted",
));
}
let data = src.split_to(amt).freeze();
let mut ret = Request::builder();
ret = ret.method(method);
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);
@@ -203,11 +208,13 @@ impl Decoder for Http {
None => break,
};
let value = HeaderValue::from_bytes(data.slice(v.0..v.1).as_ref())
.map_err(|_| io::Error::other("header decode error"))?;
.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(io::Error::other)?;
let req = ret
.body(())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(Some(req))
}
}
@@ -216,9 +223,8 @@ mod date {
use std::cell::RefCell;
use std::fmt::{self, Write};
use std::str;
use std::time::SystemTime;
use httpdate::HttpDate;
use time::{self, Duration};
pub struct Now(());
@@ -248,25 +254,22 @@ mod date {
struct LastRenderedNow {
bytes: [u8; 128],
amt: usize,
unix_date: u64,
next_update: time::Timespec,
}
thread_local!(static LAST: RefCell<LastRenderedNow> = const { RefCell::new(LastRenderedNow {
thread_local!(static LAST: RefCell<LastRenderedNow> = RefCell::new(LastRenderedNow {
bytes: [0; 128],
amt: 0,
unix_date: 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 = SystemTime::now();
let now_unix = now
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |since_epoch| since_epoch.as_secs());
if cache.unix_date != now_unix {
cache.update(now, now_unix);
let now = time::get_time();
if now >= cache.next_update {
cache.update(now);
}
f.write_str(cache.buffer())
})
@@ -278,10 +281,11 @@ mod date {
str::from_utf8(&self.bytes[..self.amt]).unwrap()
}
fn update(&mut self, now: SystemTime, now_unix: u64) {
fn update(&mut self, now: time::Timespec) {
self.amt = 0;
self.unix_date = now_unix;
write!(LocalBuffer(self), "{}", HttpDate::from(now)).unwrap();
write!(LocalBuffer(self), "{}", time::at(now).rfc822()).unwrap();
self.next_update = now + Duration::seconds(1);
self.next_update.nsec = 0;
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
}
.parse()?;
let socket = UdpSocket::bind(local_addr).await?;
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()?;
+3 -3
View File
@@ -9,8 +9,8 @@
#![warn(rust_2018_idioms)]
use tokio::net::UdpSocket;
use tokio::stream::StreamExt;
use tokio::{io, time};
use tokio_stream::StreamExt;
use tokio_util::codec::BytesCodec;
use tokio_util::udp::UdpFramed;
@@ -45,8 +45,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
let b = pong(&mut b);
// Run both futures simultaneously of `a` and `b` sending messages back and forth.
match tokio::try_join!(a, b) {
Err(e) => println!("an error occurred; error = {e:?}"),
match futures::future::try_join(a, b).await {
Err(e) => println!("an error occurred; error = {:?}", e),
_ => println!("done!"),
}
-19
View File
@@ -1,19 +0,0 @@
[build]
command = """
rustup install nightly --profile minimal && cargo doc --no-deps --all-features
"""
publish = "target/doc"
[build.environment]
RUSTDOCFLAGS="""
--cfg docsrs \
--cfg tokio_unstable \
"""
RUSTFLAGS="""
--cfg docsrs \
--cfg tokio_unstable
"""
[[redirects]]
from = "/"
to = "/tokio"
-332
View File
@@ -1,332 +0,0 @@
331
&
+
<
=
>
\
~
0o777
0s
0xA
0xD
100ms
100ns
10ms
10μs
~12
120s
12.5%
±1m
±1ms
1ms
1s
25%
250ms
2x
~4
443
450ms
50ms
8MB
ABI
accessors
adaptor
adaptors
Adaptors
AIO
ambient
amongst
api
APIs
async
atomics
awaitable
backend
backpressure
backtrace
backtraces
backtracing
binded
bitfield
bitfields
Blockingly
boolean
broadcasted
cancelled
cancelling
Cancelling
CLI
cloneable
codebase
codec
codecs
combinator
combinators
condvar
config
Config
connectionless
coroutines
cpu
cpus
cqe
CQE
cqe's
customizable
Customizable
Cygwin
datagram
Datagram
datagrams
deallocate
deallocated
Deallocates
debuginfo
decrement
decrementing
demangled
dequeued
dereferenced
derefs
deregister
deregistered
deregistering
deregisters
Deregisters
deregistration
descriptor's
destructor
destructors
destructure
Destructures
Dev
dns
DNS
DoS
dwOpenMode
Emscripten
Emscripten's
endian
enqueue
enqueued
EntryInner
enum
eof
Erlang's
errored
EWMA
expirations
fcntl
fd
fd's
FIFOs
filename
filesystem
filesystems
fn
fns
FreeBSD
frontend
fs
functionalities
getters
GID
goroutines
Growable
gzip
H2
hashmaps
HashMaps
hashsets
HdrHistogram
ICMP
ie
iff
Illumos
impl
implementers
implementor
implementors
incrementing
inlining
interoperate
invariants
Invariants
io
IOCP
iOS
IOs
io_uring
IP
IPv4
IPv6
iteratively
Kotlin's
latencies
Lauck
libc
lifecycle
lifo
LLVM
lookups
macOS
MacOS
Marsaglia's
metadata
mio
Mio
mio's
miri
misconfigured
mock's
monomorphization
mpmc
mpsc
multi
Multi
multicast
Multithreaded
mut
mutex
Mutex
Nagle
namespace
NetBSD
nonblocking
nondecreasing
noop
ntasks
NTO
NUMA
ok
oneshot
opcode
OpenBSD
ORed
os
parker
parsers
peekable
PGID
PID
plaintext
poller
POSIX
proxied
pthreads
qos
RAII
RCU
reallocations
recv's
Redox
refactors
refcount
refcounting
repo
repr
representable
reqwest
reregistering
resize
resized
RMW
RNG
runtime
runtime's
runtimes
rwlock
rx
scalability
scheduler's
semver
setpgid
sharded
signalled
signalling
SmallCrush
Solaris
spawner
spawners
Splitter
spmc
spsc
SQE
src
stabilised
startup
stateful
stderr
stdin
stdout
stealable
stealer
stealers
struct
structs
subfield
suboptimal
subprocess
superset
symlink
symlinks
sys
syscall
syscalls
tcp
TCP
TestU01
threadpool
timestamp
timestamps
TLS
TOCTOU
TODO
tokio
Tokio
tokio's
Tokio's
tuple
Tuple
tvOS
tx
udp
UDP
UID
unhandled
unix
unlink
unpark
Unpark
unparked
unparking
unparks
Unparks
unreceived
unsafety
unsets
Unsets
unsynchronized
untrusted
uring
usecases
Valgrind
Varghese
vec
versa
versioned
versioning
visionOS
vtable
waker
wakers
Wakers
wakeup
wakeups
WASI
Wasm
watchOS
workstealing
ZST
-13
View File
@@ -1,13 +0,0 @@
dev_comments = false
skip_readme = false
[Hunspell]
lang = "en_US"
search_dirs = ["."]
extra_dictionaries = ["spellcheck.dic"]
skip_os_lookups = true
use_builtin = true
[Hunspell.quirks]
allow_concatenation = true
-18
View File
@@ -1,18 +0,0 @@
[package]
name = "stress-test"
version = "0.1.0"
authors = ["Tokio Contributors <[email protected]>"]
edition = "2021"
license = "MIT"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.0.0", path = "../tokio/", features = ["full"] }
[dev-dependencies]
rand = "0.9"
[lints]
workspace = true
-58
View File
@@ -1,58 +0,0 @@
//! Simple TCP echo server to check memory leaks using Valgrind.
use std::{thread::sleep, time::Duration};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpSocket},
runtime::Builder,
sync::oneshot,
};
const TCP_ENDPOINT: &str = "127.0.0.1:8080";
const NUM_MSGS: usize = 100;
const MSG_SIZE: usize = 1024;
fn main() {
let rt = Builder::new_multi_thread().enable_io().build().unwrap();
let rt2 = Builder::new_multi_thread().enable_io().build().unwrap();
rt.spawn(async {
let listener = TcpListener::bind(TCP_ENDPOINT).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
let (mut rd, mut wr) = socket.split();
while tokio::io::copy(&mut rd, &mut wr).await.is_ok() {}
});
// wait a bit so that the listener binds.
sleep(Duration::from_millis(100));
// create a channel to let the main thread know that all the messages were sent and received.
let (tx, mut rx) = oneshot::channel();
rt2.spawn(async {
let addr = TCP_ENDPOINT.parse().unwrap();
let socket = TcpSocket::new_v4().unwrap();
let mut stream = socket.connect(addr).await.unwrap();
let mut buff = [0; MSG_SIZE];
for _ in 0..NUM_MSGS {
let one_mega_random_bytes: Vec<u8> =
(0..MSG_SIZE).map(|_| rand::random::<u8>()).collect();
stream
.write_all(one_mega_random_bytes.as_slice())
.await
.unwrap();
let _ = stream.read(&mut buff).await.unwrap();
}
tx.send(()).unwrap();
});
loop {
// check that we're done.
match rx.try_recv() {
Err(oneshot::error::TryRecvError::Empty) => (),
Err(oneshot::error::TryRecvError::Closed) => panic!("channel got closed..."),
Ok(()) => break,
}
}
}
-9
View File
@@ -1,9 +0,0 @@
This is used for the `no-atomic-u64-test` ci check that verifies that Tokio
works even if the `AtomicU64` type is missing.
When increasing the nightly compiler version, you may need to regenerate this
target using the following command:
```
rustc +nightly -Z unstable-options --print target-spec-json --target i686-unknown-linux-gnu | grep -v 'is-builtin' | sed 's/"max-atomic-width": 64/"max-atomic-width": 32/' > target-specs/i686-unknown-linux-gnu.json
```
-48
View File
@@ -1,48 +0,0 @@
{
"arch": "x86",
"cpu": "pentium4",
"crt-objects-fallback": "false",
"crt-static-respected": true,
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"default-uwtable": true,
"dynamic-linking": true,
"env": "gnu",
"has-rpath": true,
"has-thread-local": true,
"linker-flavor": "gnu-cc",
"llvm-target": "i686-unknown-linux-gnu",
"max-atomic-width": 32,
"metadata": {
"description": "32-bit Linux (kernel 3.2, glibc 2.17+)",
"host_tools": true,
"std": true,
"tier": 1
},
"os": "linux",
"position-independent-executables": true,
"pre-link-args": {
"gnu-cc": [
"-m32"
],
"gnu-lld-cc": [
"-m32"
]
},
"relro-level": "full",
"rustc-abi": "x86-sse2",
"stack-probes": {
"kind": "inline"
},
"supported-sanitizers": [
"address"
],
"supported-split-debuginfo": [
"packed",
"unpacked",
"off"
],
"target-family": [
"unix"
],
"target-pointer-width": 32
}
+2 -7
View File
@@ -2,19 +2,14 @@
name = "tests-build"
version = "0.1.0"
authors = ["Tokio Contributors <[email protected]>"]
edition = "2021"
license = "MIT"
edition = "2018"
publish = false
[features]
full = ["tokio/full"]
rt = ["tokio/rt", "tokio/macros"]
[dependencies]
tokio = { version = "1.0.0", path = "../tokio", optional = true }
tokio = { path = "../tokio", optional = true }
[dev-dependencies]
trybuild = "1.0"
[lints]
workspace = true
-8
View File
@@ -1,10 +1,2 @@
Tests the various combination of feature flags. This is broken out to a separate
crate to work around limitations with cargo features.
To run all of the tests in this directory, run the following commands:
```
cargo test --features full
cargo test --features rt
```
If any of the tests fail, you can pass `TRYBUILD=overwrite` to the `cargo test`
command that failed to have it regenerate the test output.
@@ -1,6 +0,0 @@
use tests_build::tokio;
#[tokio::main]
async fn my_fn() {}
fn main() {}
@@ -1,7 +0,0 @@
error: The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled.
--> $DIR/macros_core_no_default.rs:3:1
|
3 | #[tokio::main]
| ^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -1,8 +0,0 @@
#![deny(dead_code)]
use tests_build::tokio;
#[tokio::main]
async fn f() {}
fn main() {}
@@ -1,11 +0,0 @@
error: function `f` is never used
--> $DIR/macros_dead_code.rs:6:10
|
6 | async fn f() {}
| ^
|
note: the lint level is defined here
--> $DIR/macros_dead_code.rs:1:9
|
1 | #![deny(dead_code)]
| ^^^^^^^^^
+3 -52
View File
@@ -1,5 +1,3 @@
#![deny(duplicate_macro_attributes)]
use tests_build::tokio;
#[tokio::main]
@@ -14,61 +12,14 @@ 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(foo = 123)]
async fn test_unexpected_attr() {}
#[tokio::test(flavor = 123)]
async fn test_flavor_not_string() {}
#[tokio::test(flavor = "foo")]
async fn test_unknown_flavor() {}
#[tokio::test(flavor = "multi_thread", start_paused = false)]
async fn test_multi_thread_with_start_paused() {}
#[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
async fn test_worker_threads_not_int() {}
#[tokio::test(flavor = "current_thread", worker_threads = 4)]
async fn test_worker_threads_and_current_thread() {}
#[tokio::test(crate = 456)]
async fn test_crate_not_path_int() {}
#[tokio::test(crate = "456")]
async fn test_crate_not_path_invalid() {}
#[tokio::test(flavor = "multi_thread", unhandled_panic = "shutdown_runtime")]
async fn test_multi_thread_with_unhandled_panic() {}
#[tokio::test]
#[test]
async fn test_has_second_test_attr() {}
#[tokio::test]
#[::core::prelude::v1::test]
async fn test_has_second_test_attr_v1() {}
#[tokio::test]
#[core::prelude::rust_2015::test]
async fn test_has_second_test_attr_rust_2015() {}
#[tokio::test]
#[::std::prelude::rust_2018::test]
async fn test_has_second_test_attr_rust_2018() {}
#[tokio::test]
#[std::prelude::rust_2021::test]
async fn test_has_second_test_attr_rust_2021() {}
#[tokio::test]
#[tokio::test]
async fn test_has_generated_second_test_attr() {}
#[tokio::test(name = 123)]
async fn test_name_not_string() {}
fn main() {}
@@ -1,127 +1,41 @@
error: the `async` keyword is missing from the function declaration
--> tests/fail/macros_invalid_input.rs:6:1
error: the async keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:4:1
|
6 | fn main_is_not_async() {}
4 | fn main_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
--> tests/fail/macros_invalid_input.rs:8:15
error: Unknown attribute foo is specified; expected `basic_scheduler` or `threaded_scheduler`
--> $DIR/macros_invalid_input.rs:6:15
|
8 | #[tokio::main(foo)]
6 | #[tokio::main(foo)]
| ^^^
error: Must have specified ident
--> tests/fail/macros_invalid_input.rs:11:15
|
11 | #[tokio::main(threadpool::bar)]
| ^^^^^^^^^^^^^^^
--> $DIR/macros_invalid_input.rs:9:15
|
9 | #[tokio::main(threadpool::bar)]
| ^^^^^^^^^^^^^^^
error: the `async` keyword is missing from the function declaration
--> tests/fail/macros_invalid_input.rs:15:1
error: the async keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:13:1
|
15 | fn test_is_not_async() {}
13 | fn test_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
--> tests/fail/macros_invalid_input.rs:17:15
error: the test function cannot accept arguments
--> $DIR/macros_invalid_input.rs:16:27
|
17 | #[tokio::test(foo)]
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: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.
--> tests/fail/macros_invalid_input.rs:20:15
error: second test attribute is supplied
--> $DIR/macros_invalid_input.rs:22:1
|
20 | #[tokio::test(foo = 123)]
| ^^^^^^^^^
error: Failed to parse value of `flavor` as string.
--> tests/fail/macros_invalid_input.rs:23:24
|
23 | #[tokio::test(flavor = 123)]
| ^^^
error: No such runtime flavor `foo`. The runtime flavors are `current_thread`, `local`, and `multi_thread`.
--> tests/fail/macros_invalid_input.rs:26:24
|
26 | #[tokio::test(flavor = "foo")]
| ^^^^^
error: The `start_paused` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
--> tests/fail/macros_invalid_input.rs:29:55
|
29 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
| ^^^^^
error: Failed to parse value of `worker_threads` as integer.
--> tests/fail/macros_invalid_input.rs:32:57
|
32 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
| ^^^^^
error: The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[tokio::test(flavor = "multi_thread")]`
--> tests/fail/macros_invalid_input.rs:35:59
|
35 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
| ^
error: Failed to parse value of `crate` as path.
--> tests/fail/macros_invalid_input.rs:38:23
|
38 | #[tokio::test(crate = 456)]
| ^^^
error: Failed to parse value of `crate` as path: "456"
--> tests/fail/macros_invalid_input.rs:41:23
|
41 | #[tokio::test(crate = "456")]
| ^^^^^
error: The `unhandled_panic` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
--> tests/fail/macros_invalid_input.rs:44:58
|
44 | #[tokio::test(flavor = "multi_thread", unhandled_panic = "shutdown_runtime")]
| ^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> tests/fail/macros_invalid_input.rs:48:1
|
48 | #[test]
22 | #[test]
| ^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> tests/fail/macros_invalid_input.rs:52:1
|
52 | #[::core::prelude::v1::test]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> tests/fail/macros_invalid_input.rs:56:1
|
56 | #[core::prelude::rust_2015::test]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> tests/fail/macros_invalid_input.rs:60:1
|
60 | #[::std::prelude::rust_2018::test]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> tests/fail/macros_invalid_input.rs:64:1
|
64 | #[std::prelude::rust_2021::test]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: second test attribute is supplied, consider removing or changing the order of your test attributes
--> tests/fail/macros_invalid_input.rs:67:1
|
67 | #[tokio::test]
| ^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `tokio::test` (in Nightly builds, run with -Z macro-backtrace for more info)
error: Failed to parse value of `name` as string.
--> tests/fail/macros_invalid_input.rs:71:22
|
71 | #[tokio::test(name = 123)]
| ^^^
-42
View File
@@ -1,42 +0,0 @@
use tests_build::tokio;
#[tokio::main]
async fn main() {
// do not leak `RotatorSelect`
let _ = tokio::join!(async {
fn foo(_: impl RotatorSelect) {}
});
// do not leak `std::task::Poll::Pending`
let _ = tokio::join!(async { Pending });
// do not leak `std::task::Poll::Ready`
let _ = tokio::join!(async { Ready(0) });
// do not leak `std::future::Future`
let _ = tokio::join!(async {
struct MyFuture;
impl Future for MyFuture {
type Output = ();
fn poll(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
todo!()
}
}
});
// do not leak `std::pin::Pin`
let _ = tokio::join!(async {
let mut x = 5;
let _ = Pin::new(&mut x);
});
// do not leak `std::future::poll_fn`
let _ = tokio::join!(async {
let _ = poll_fn(|_cx| todo!());
});
}
-60
View File
@@ -1,60 +0,0 @@
error[E0405]: cannot find trait `RotatorSelect` in this scope
--> tests/fail/macros_join.rs:7:24
|
7 | fn foo(_: impl RotatorSelect) {}
| ^^^^^^^^^^^^^ not found in this scope
error[E0425]: cannot find value `Pending` in this scope
--> tests/fail/macros_join.rs:11:34
|
11 | let _ = tokio::join!(async { Pending });
| ^^^^^^^ not found in this scope
|
help: consider importing this unit variant
|
1 + use std::task::Poll::Pending;
|
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
--> tests/fail/macros_join.rs:14:34
|
14 | let _ = tokio::join!(async { Ready(0) });
| ^^^^^ not found in this scope
|
help: consider importing this tuple variant
|
1 + use std::task::Poll::Ready;
|
error[E0405]: cannot find trait `Future` in this scope
--> tests/fail/macros_join.rs:20:14
|
20 | impl Future for MyFuture {
| ^^^^^^ not found in this scope
|
help: consider importing this trait
|
1 + use std::future::Future;
|
error[E0433]: cannot find type `Pin` in this scope
--> tests/fail/macros_join.rs:35:17
|
35 | let _ = Pin::new(&mut x);
| ^^^ use of undeclared type `Pin`
|
help: consider importing this struct
|
1 + use std::pin::Pin;
|
error[E0425]: cannot find function `poll_fn` in this scope
--> tests/fail/macros_join.rs:40:17
|
40 | let _ = poll_fn(|_cx| todo!());
| ^^^^^^^ not found in this scope
|
help: consider importing this function
|
1 + use std::future::poll_fn;
|
-42
View File
@@ -1,42 +0,0 @@
use tests_build::tokio;
#[tokio::main]
async fn main() {
// do not leak `RotatorSelect`
let _ = tokio::try_join!(async {
fn foo(_: impl RotatorSelect) {}
});
// do not leak `std::task::Poll::Pending`
let _ = tokio::try_join!(async { Pending });
// do not leak `std::task::Poll::Ready`
let _ = tokio::try_join!(async { Ready(0) });
// do not leak `std::future::Future`
let _ = tokio::try_join!(async {
struct MyFuture;
impl Future for MyFuture {
type Output = ();
fn poll(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
todo!()
}
}
});
// do not leak `std::pin::Pin`
let _ = tokio::try_join!(async {
let mut x = 5;
let _ = Pin::new(&mut x);
});
// do not leak `std::future::poll_fn`
let _ = tokio::try_join!(async {
let _ = poll_fn(|_cx| todo!());
});
}
@@ -1,60 +0,0 @@
error[E0405]: cannot find trait `RotatorSelect` in this scope
--> tests/fail/macros_try_join.rs:7:24
|
7 | fn foo(_: impl RotatorSelect) {}
| ^^^^^^^^^^^^^ not found in this scope
error[E0425]: cannot find value `Pending` in this scope
--> tests/fail/macros_try_join.rs:11:38
|
11 | let _ = tokio::try_join!(async { Pending });
| ^^^^^^^ not found in this scope
|
help: consider importing this unit variant
|
1 + use std::task::Poll::Pending;
|
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
--> tests/fail/macros_try_join.rs:14:38
|
14 | let _ = tokio::try_join!(async { Ready(0) });
| ^^^^^ not found in this scope
|
help: consider importing this tuple variant
|
1 + use std::task::Poll::Ready;
|
error[E0405]: cannot find trait `Future` in this scope
--> tests/fail/macros_try_join.rs:20:14
|
20 | impl Future for MyFuture {
| ^^^^^^ not found in this scope
|
help: consider importing this trait
|
1 + use std::future::Future;
|
error[E0433]: cannot find type `Pin` in this scope
--> tests/fail/macros_try_join.rs:35:17
|
35 | let _ = Pin::new(&mut x);
| ^^^ use of undeclared type `Pin`
|
help: consider importing this struct
|
1 + use std::pin::Pin;
|
error[E0425]: cannot find function `poll_fn` in this scope
--> tests/fail/macros_try_join.rs:40:17
|
40 | let _ = poll_fn(|_cx| todo!());
| ^^^^^^^ not found in this scope
|
help: consider importing this function
|
1 + use std::future::poll_fn;
|
@@ -1,69 +0,0 @@
use tests_build::tokio;
#[tokio::main]
async fn missing_semicolon_or_return_type() {
Ok(())
}
#[tokio::main]
async fn missing_return_type() {
return Ok(());
}
#[tokio::main]
async fn extra_semicolon() -> Result<(), ()> {
/* TODO(taiki-e): help message still wrong
help: try using a variant of the expected enum
|
23 | Ok(Ok(());)
|
23 | Err(Ok(());)
|
*/
Ok(());
}
/// This test is a characterization test for the `?` operator.
///
/// See <https://github.com/tokio-rs/tokio/issues/6930#issuecomment-2572502517> for more details.
///
/// It should fail with a single error message about the return type of the function, but instead
/// if fails with an extra error message due to the `?` operator being used within the async block
/// rather than the original function.
///
/// ```text
/// 28 | None?;
/// | ^ cannot use the `?` operator in an async block that returns `()`
/// ```
#[tokio::main]
async fn question_mark_operator_with_invalid_option() -> Option<()> {
None?;
}
/// This test is a characterization test for the `?` operator.
///
/// See <https://github.com/tokio-rs/tokio/issues/6930#issuecomment-2572502517> for more details.
///
/// It should fail with a single error message about the return type of the function, but instead
/// if fails with an extra error message due to the `?` operator being used within the async block
/// rather than the original function.
///
/// ```text
/// 33 | Ok(())?;
/// | ^ cannot use the `?` operator in an async block that returns `()`
/// ```
#[tokio::main]
async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
Ok(())?;
}
// https://github.com/tokio-rs/tokio/issues/4635
#[allow(redundant_semicolons)]
#[rustfmt::skip]
#[tokio::main]
async fn issue_4635() {
return 1;
;
}
fn main() {}
@@ -1,176 +0,0 @@
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:3:1: 3:15}` to be a future that resolves to `()`, but it resolves to `Result<(), _>`
--> tests/fail/macros_type_mismatch.rs:3:1
|
3 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
= note: expected unit type `()`
found enum `Result<(), _>`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:3:1: 3:15}` to `&dyn Future<Output = ()>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:5:5
|
5 | Ok(())
| ^^^^^^ expected `()`, found `Result<(), _>`
|
= note: expected unit type `()`
found enum `Result<(), _>`
help: a return type might be missing here
|
4 | async fn missing_semicolon_or_return_type() -> _ {
| ++++
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
5 | Ok(()).expect("REASON")
| +++++++++++++++++
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:8:1: 8:15}` to be a future that resolves to `()`, but it resolves to `Result<(), _>`
--> tests/fail/macros_type_mismatch.rs:8:1
|
8 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
= note: expected unit type `()`
found enum `Result<(), _>`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:8:1: 8:15}` to `&dyn Future<Output = ()>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:10:5
|
10 | return Ok(());
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
= note: expected unit type `()`
found enum `Result<(), _>`
help: a return type might be missing here
|
9 | async fn missing_return_type() -> _ {
| ++++
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
10 | return Ok(());.expect("REASON")
| +++++++++++++++++
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:13:1: 13:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
--> tests/fail/macros_type_mismatch.rs:13:1
|
13 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:13:1: 13:15}` to `&dyn Future<Output = Result<(), ()>>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:23:5
|
14 | async fn extra_semicolon() -> Result<(), ()> {
| -------------- expected `Result<(), ()>` because of return type
...
23 | Ok(());
| ^^^^^^^ expected `Result<(), ()>`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
help: try adding an expression at the end of the block
|
23 ~ Ok(());;
24 + Ok(())
|
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> tests/fail/macros_type_mismatch.rs:40:9
|
38 | #[tokio::main]
| -------------- this function should return `Result` or `Option` to accept `?`
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
40 | None?;
| ^ cannot use the `?` operator in an async block that returns `()`
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to be a future that resolves to `Option<()>`, but it resolves to `()`
--> tests/fail/macros_type_mismatch.rs:38:1
|
38 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `Option<()>`, found `()`
|
= note: expected enum `Option<()>`
found unit type `()`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to `&dyn Future<Output = Option<()>>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:40:5
|
39 | async fn question_mark_operator_with_invalid_option() -> Option<()> {
| ---------- expected `Option<()>` because of return type
40 | None?;
| ^^^^^^ expected `Option<()>`, found `()`
|
= note: expected enum `Option<()>`
found unit type `()`
help: try adding an expression at the end of the block
|
40 ~ None?;;
41 + None
|
40 ~ None?;;
41 + Some(())
|
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> tests/fail/macros_type_mismatch.rs:57:11
|
55 | #[tokio::main]
| -------------- this function should return `Result` or `Option` to accept `?`
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
57 | Ok(())?;
| ^ cannot use the `?` operator in an async block that returns `()`
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
--> tests/fail/macros_type_mismatch.rs:55:1
|
55 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to `&dyn Future<Output = Result<(), ()>>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:57:5
|
56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> {
| -------------- expected `Result<(), ()>` because of return type
57 | Ok(())?;
| ^^^^^^^^ expected `Result<(), ()>`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
help: try adding an expression at the end of the block
|
57 ~ Ok(())?;;
58 + Ok(())
|
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:63:1: 63:15}` to be a future that resolves to `()`, but it resolves to `{integer}`
--> tests/fail/macros_type_mismatch.rs:63:1
|
63 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `()`, found integer
|
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:63:1: 63:15}` to `&dyn Future<Output = ()>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:66:5
|
64 | async fn issue_4635() {
| - help: try adding a return type: `-> i32`
65 | return 1;
66 | ;
| ^ expected `()`, found integer
+1 -32
View File
@@ -1,40 +1,9 @@
#[test]
#[cfg_attr(miri, ignore)]
fn compile_fail_full() {
fn compile_fail() {
let t = trybuild::TestCases::new();
#[cfg(feature = "full")]
t.pass("tests/pass/forward_args_and_output.rs");
#[cfg(feature = "full")]
t.pass("tests/pass/macros_main_return.rs");
#[cfg(feature = "full")]
t.pass("tests/pass/macros_main_loop.rs");
#[cfg(feature = "full")]
t.pass("tests/pass/impl_trait.rs");
#[cfg(feature = "full")]
t.pass("tests/pass/use_builder_outer.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_invalid_input.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_dead_code.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_join.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_try_join.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_type_mismatch.rs");
#[cfg(all(feature = "rt", not(feature = "full")))]
t.compile_fail("tests/fail/macros_core_no_default.rs");
drop(t);
}
-7
View File
@@ -1,7 +0,0 @@
#[cfg(feature = "full")]
#[tokio::test]
async fn test_with_semicolon_without_return_type() {
#![deny(clippy::semicolon_if_nothing_returned)]
dbg!(0);
}
@@ -1,13 +0,0 @@
use tests_build::tokio;
fn main() {}
// arguments and output type is forwarded so other macros can access them
#[tokio::test]
async fn test_fn_has_args(_x: u8) {}
#[tokio::test]
async fn test_has_output() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

Some files were not shown because too many files have changed in this diff Show More