Compare commits

...
Author SHA1 Message Date
Alice Ryhl efdba5fcf0 chore: prepare Tokio v1.52.4 (#8289) 2026-07-16 15:23:48 +02:00
Alice Ryhl b0ba02e755 Merge 'tokio-1.51.4' into 'tokio-1.52.x' (#8288) 2026-07-16 12:18:21 +00:00
Alice Ryhl 7bcd2d343d taskdump: remove crate disambiguators from output (#8288) 2026-07-16 12:17:13 +00:00
Alice Ryhl f84b209126 chore: prepare Tokio v1.51.4 (#8286) 2026-07-16 13:59:24 +02:00
Amey Pawar eacb98e189 runtime: don't skip the driver when before_park schedules work (#8222) 2026-07-16 13:16:47 +02:00
Alice Ryhl d87569164f chore: prepare Tokio v1.52.3 (#8130) 2026-05-08 14:52:32 +02:00
Alice Ryhl e1aebb031c Merge 'tokio-1.51.3' into 'tokio-1.52.x' (#8129) 2026-05-08 09:30:59 +00:00
Alice Ryhl fd63094ee0 chore: prepare Tokio v1.51.3 (#8127) 2026-05-08 10:45:32 +02:00
Alice Ryhl 8c600d0fd2 Merge 'tokio-1.47.5' into 'tokio-1.51.x' (#8123) 2026-05-07 13:59:06 +02:00
Alice Ryhl 11bfc1345b chore: prepare Tokio v1.47.5 (#8122) 2026-05-07 13:55:35 +02:00
Alice Ryhl f085b6211b sync: notify receivers in mpsc OwnedPermit::release() method (#8075) 2026-05-07 09:32:14 +02:00
Alice Ryhl 30d25ccb8b sync: require that an RwLock has max_readers != 0 (#8076) 2026-05-07 09:31:12 +02:00
Alice Ryhl 9fccf5339d sync: return Empty from try_recv() when mpsc is closed with outstanding permits (#8074) 2026-05-07 09:30:50 +02:00
Alice Ryhl ebf61b45b5 sync: fix underflow in mpsc channel len() (#8062) 2026-05-07 09:29:33 +02:00
Alice Ryhl 4abe9d732e chore: prepare Tokio v1.52.2 (#8115) 2026-05-04 14:39:25 +02:00
Alice Ryhl f82bcf3f45 Merge 'tokio-1.51.2' into 'tokio-1.52.x' (#8114) 2026-05-04 12:16:52 +02:00
Alice Ryhl 7db9bc41f1 test: revert "remove churn() task from lifo_stealable" (#8114)
This reverts commit 6c03e03898.
2026-05-04 12:15:37 +02:00
Alice Ryhl 64834ec701 chore: prepare Tokio v1.51.2 (#8113) 2026-05-04 12:11:07 +02:00
Alice Ryhl 967f5715a7 runtime: revert "steal tasks from the LIFO slot" (#8100)
This reverts commit eeb55c733b.
2026-05-04 10:08:30 +02:00
Alice Ryhl 9271e3ed05 Merge tokio-1.51.x (for #8101) into tokio-1.52.x (#8106) 2026-05-02 15:13:06 +02:00
Alice Ryhl cd1823f43e Revert "Pin stable to 1.94 for tokio-1.51.x" (#8106)
As we are merging tokio-1.51.x into tokio-1.52.x and then master, we
should not include this change.

This reverts commit bde3f20b0f.
2026-05-02 15:12:29 +02:00
Alice Ryhl a97cf12ed9 Merge tokio-1.47.x (commit 670a907c55) into tokio-1.51.x (#8105) 2026-05-02 13:52:07 +02:00
Alice Ryhl bde3f20b0f Pin stable to 1.94 for tokio-1.51.x (#8105) 2026-05-02 13:51:31 +02:00
Alice RyhlandMattia Pitossi 670a907c55 ci: fix CI on tokio-1.47.x (#8101)
Co-authored-by: Mattia Pitossi <[email protected]>
2026-05-02 13:33:40 +02:00
Mawer d3565a2923 fs: rename Windows symlink dir test (#8098) 2026-04-30 10:47:45 +02:00
Alice Ryhl 8f81e0814b time: avoid stack overflow in runtime constructor (#8093) 2026-04-29 05:26:57 +00:00
stormshield-fabs 47cc31590f tokio-stream: implement FusedStream for Fuse (#8090) 2026-04-28 17:33:54 +02:00
Alex Gaynor dc0f728162 blocking: introduce a regression test for #8056 (#8068) 2026-04-28 12:47:49 +02:00
Ralf Jung 89713ad1ec miri tests: fstat is supported now (#8088) 2026-04-27 15:23:40 +03:00
Eliza Weisman 6c03e03898 test: remove churn() task from lifo_stealable (#8070)
Currently, the `rt_threaded::lifo_stealable` test I added in #7431
spawns an additional task which sleeps on a 4ms timer in a loop. This
ensures that no worker remains permanently parked. This was added
because it was necessary to stop the LIFO slot deadlock from occurring
prior to changes in the logic for determining whether to notify another
worker, which is what @Darksonn  was referring to in [this comment][1].
Removing the `churn()` test makes the test actually validate that
another worker is notified to steal the LIFO task, and that the changes
from #7431 will *always* prevent a LIFO slot deadlock, regardless of the
behavior of other tasks on the runtime. See also [this comment][2] for
further discussion.

[1]: https://github.com/tokio-rs/tokio/pull/7431#discussion_r2184724657
[2]: https://github.com/tokio-rs/tokio/pull/8069#issuecomment-4274244723
2026-04-20 21:03:42 +02:00
Alice Ryhl 16bc4e2e28 time: add #[track_caller] and panic docs to timeout_at() (#8077) 2026-04-20 20:22:36 +02:00
BarryandMattia Pitossi 1afb391350 net: document pipe try_read*/try_write* readiness behavior (#8032)
Add a Notes section to all five try_* methods on pipe::Sender and
pipe::Receiver explaining that the runtime's I/O driver only delivers
readiness events after control is yielded back to it, so calling
try_read/try_write before any .await returns WouldBlock even when the
operation could otherwise succeed.

This is the same readiness model used by every other Tokio I/O type;
the pipe docs simply did not previously call it out. Refs #7625.

---------

Co-authored-by: Mattia Pitossi <[email protected]>
2026-04-19 19:52:41 +02:00
Mattia Pitossi b010b5ddaf test taskdump docs (#8064) 2026-04-17 23:32:22 +03:00
Eliza Weisman 905c146aed chore: prepare to release v1.52.1 (#8059)
# 1.52.1 (April 16th, 2026)

## Fixed

- runtime: revert [#7757] to fix [a regression][#8056] that causes
  `spawn_blocking` to hang ([#8057])

[#7757]: https://github.com/tokio-rs/tokio/pull/7757
[#8056]: https://github.com/tokio-rs/tokio/pull/8056
[#8057]: https://github.com/tokio-rs/tokio/pull/8057
2026-04-16 21:28:05 +00:00
Eliza Weisman 56aaa43e91 rt: revert #7757 to fix regression in spawn_blocking (#8057)
This reverts commit 1604bc3351.

Unfortunately, this commit introduced a regression that causes programs
using `spawn_blocking` to hang (see #8056). To fix the regression, we
need to undo this change and publish a v1.52.1 release as soon as
possible.

In the future, we may wish to bring back a sharded queue for
`spawn_blocking` tasks, either based on the implementation added in
#7757 or a new one. However, since this is a substantial change to the
runtime internals, I think such a change should probably be done as an
unstable, opt-in `tokio::runtime::Builder` setting initially, so that we
don't regress existing users. I had hoped we could do this now, but
unfortunately, the sharded queue implementation from #7757 is kind of
tightly coupled with the rest of the `spawn_blocking` machinery and
cannot be easily swapped out --- and the hang still occurs with
`NUM_SHARDS` set to 1, so there isn't an easy way to turn it on and off.
Therefore, in the interest of getting a fix out ASAP, this is just a
simple revert.

Fixes #8056
2026-04-16 20:57:22 +00:00
Eliza Weisman 57ff47ab58 ci: update trybuild to expect output from rustc 1.95.0 (#8058) 2026-04-16 20:32:53 +00:00
dependabot[bot] 812de3e134 ci: bump taiki-e/cache-cargo-install-action from 1 to 3 (#8053)
Bumps [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action) from 1 to 3.
- [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases)
- [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/v1...v3)

---
updated-dependencies:
- dependency-name: taiki-e/cache-cargo-install-action
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 09:30:55 +02:00
Martin Grigorov ba82e73c7b ci: use Dependabot to keep github actions up to date (#8052) 2026-04-16 08:51:22 +02:00
Mattia Pitossi 2e85f9ddf8 ci: replace cirrus-ci with freebsd-vm (#8041) 2026-04-16 08:40:04 +02:00
Alice Ryhl a7e1cd8ff8 ci: update GitHub Actions workflows to use latest tool versions (#8047) 2026-04-15 16:57:43 +00:00
Eliza Weisman 5f7be0ac42 chore: perpare 1.52.0 (#8045)
# 1.52.0 (April 14th, 2026)

## Added

- io: `AioSource::register_borrowed` for I/O safety support ([#7992])
- net: add `try_io` function to `unix::pipe` sender and receiver types
  ([#8030])

## Added (unstable)

- runtime: `Builder::enable_eager_driver_handoff` setting enable eager
  hand off of the I/O and time drivers before polling tasks ([#8010])
- taskdump: add `trace_with()` for customized task dumps ([#8025])
- taskdump: allow `impl FnMut()` in `trace_with` instead of just `fn()`
  ([#8040])
- fs: support `io_uring` in `AsyncRead` for `File` ([#7907])

## Changed

- runtime: improve `spawn_blocking` scalability with sharded queue
  ([#7757])
- runtime: use `compare_exchange_weak()` in worker queue ([#8028])

## Fixed

- runtime: overflow second half of tasks when local queue is filled
  instead of first half ([#8029])

## Documented

- docs: fix typo in `oneshot::Sender::send` docs ([#8026])
- docs: hide #[tokio::main] attribute in the docs of `sync::watch`
  ([#8035])
- net: add docs on `ConnectionRefused` errors with UDP sockets ([#7870])

[#7757]: https://github.com/tokio-rs/tokio/pull/7757
[#7870]: https://github.com/tokio-rs/tokio/pull/7870
[#7907]: https://github.com/tokio-rs/tokio/pull/7907
[#7992]: https://github.com/tokio-rs/tokio/pull/7992
[#8010]: https://github.com/tokio-rs/tokio/pull/8010
[#8025]: https://github.com/tokio-rs/tokio/pull/8025
[#8026]: https://github.com/tokio-rs/tokio/pull/8026
[#8028]: https://github.com/tokio-rs/tokio/pull/8028
[#8029]: https://github.com/tokio-rs/tokio/pull/8029
[#8030]: https://github.com/tokio-rs/tokio/pull/8030
[#8035]: https://github.com/tokio-rs/tokio/pull/8035
[#8040]: https://github.com/tokio-rs/tokio/pull/8040
2026-04-14 11:56:01 -07:00
Alice Ryhl 36d12d2686 taskdump: allow impl FnMut() in taskdumps instead of just fn() (#8040) 2026-04-14 19:18:56 +02:00
Carter Green f943312865 fs: support io-uring in AsyncRead for File (#7907) 2026-04-13 20:36:23 +02:00
Timo 5db10f538b net: add 'try_io' function to 'unix::pipe' sender and receiver types (#8030) 2026-04-12 15:06:06 +02:00
Russell Cohen bbdba7101d taskdump: add trace_with for customized task dumps (#8025)
provided trace function.
2026-04-12 11:13:24 +02:00
xtqqczze 7cfce54386 ci: update FreeBSD image to 14.4 (#8038) 2026-04-11 22:20:55 +02:00
Alice Ryhl 81370e6202 net: add docs on ConnectionRefused errors with udp sockets (#7870) 2026-04-11 20:06:08 +00:00
Alice Ryhl 203af02126 runtime: overflow second half of tasks when local queue is filled instead of first half (#8029) 2026-04-11 12:14:26 +02:00
Joel Dice de230926dc net: temporarily disable tcp_stream try_read_buf test on WASI (#8036)
This test is flaky on WASI until
https://github.com/bytecodealliance/wasmtime/issues/13040 has been addressed.

See https://github.com/tokio-rs/tokio/issues/8034 for additional details.
2026-04-11 12:06:46 +02:00
Abhinav 432ec3f2b2 sync: hide #[tokio::main] attribute in the docs of sync::watch (#8035) 2026-04-10 13:15:37 -07:00
Alex Gaynor 1604bc3351 rt: improve spawn_blocking scalability with sharded queue (#7757) 2026-04-10 16:06:19 +02:00
Alice Ryhl 4c7d8b2a14 runtime: use compare_exchange_weak() in worker queue (#8028) 2026-04-10 09:05:18 +02:00
Alan Somers d7db722bb4 io: AioSource now employs IO Safety (#7992) 2026-04-08 22:59:32 +02:00
Eliza Weisman fccc28f1d0 runtime: optional eager I/O driver/timer handoff when polling tasks (#8010) 2026-04-08 22:46:27 +02:00
winningMove 927df0e9d9 sync: fix typo in oneshot send doc (#8026) 2026-04-08 19:31:22 +02:00
Alice Ryhl 98df02d7a4 chore: prepare Tokio v1.51.1 (#8023) 2026-04-08 12:37:44 +02:00
dentiny 3ea11e2a5f sync: fix semaphore reopens after forget (#8021) 2026-04-08 07:59:54 +00:00
Mattia Pitossi c79121391d rt: do not leak fd when cancelling io_uring open operation (#7983) 2026-04-08 07:37:19 +02:00
Marvin Vogt ad8c59add6 net: surface errors from SO_ERROR on recv for UDP sockets on Linux (#8001) 2026-04-04 21:46:10 +02:00
Alice Ryhl 654d38b132 metrics: fix worker_local_schedule_count test (#8008) 2026-04-04 09:20:01 +02:00
Mattia Pitossi 857ba80933 docs: improve contributing docs on how to specify crates dependency versions (#8009) 2026-04-04 09:03:49 +02:00
Alice Ryhl 95b9342da7 chore: remove path deps for tokio-macros 2.7.0 (#8007) 2026-04-03 11:10:55 +02:00
Alice Ryhl 0af06b7bab chore: prepare Tokio v1.51.0 (#8005) 2026-04-03 10:42:05 +02:00
Alice Ryhl 01a7f1dfab chore: prepare tokio-macros v2.7.0 (#8004) 2026-04-03 09:37:58 +02:00
Eliza Weisman eeb55c733b runtime: steal tasks from the LIFO slot (#7431) 2026-04-03 09:11:58 +02:00
Daksh 1fc450aefb runtime: stabilize LocalRuntime (#7557) 2026-04-02 13:13:24 +00:00
Alice Ryhl 324218f9bb Merge tag 'tokio-1.47.4' (#8003) 2026-04-02 14:16:40 +02:00
Alice Ryhl aa65d0d0b8 chore: prepare Tokio v1.47.4 (#8002) 2026-04-02 14:12:06 +02:00
LeoniePhiline bf18ed452d sync: fix panic in Chan::recv_many when called with non-empty vector on closed channel (#7991)
`Chan::recv_many` intends to assert that no slots
have been consumed when exiting with `Ready` via
the `rx_closed` code path.

Instead of asserting no items were added to the
buffer, it asserted buffer emptiness, incorrectly
making assumptions about the provided buffer.

When `recv_many` was called on an empty channel
with idle semaphore after the receiver was closed,
the method would panic.

The branch coverage had been previously missing.

This changeset corrects the assertion
and adds tests covering the code path.

Fixes #7990.
2026-04-02 13:16:03 +02:00
Joel Dice 43134f1e57 wasm: add wasm32-wasip2 networking support (#7933)
Motivation

This adds networking support for the `wasm32-wasip2` target platform, which
includes more extensive support for sockets than `wasm32-wasip1`.

Solution

The bulk of the changes are in https://github.com/tokio-rs/mio/pull/1931.  This
patch mainly tweaks a few `cfg` directives to indicate `wasm32-wasip2`'s
additional capabilities.

Note that this is a draft PR until until
https://github.com/tokio-rs/mio/pull/1931 and
https://github.com/rust-lang/socket2/pull/639 have been include in stable
releases of their respective projects.

Also note that I've added a `wasm32-wasip2` target to CI and triaged each test
which was previously disabled for WASI into one of three categories:

- Disabled on both WASIp1 and p2 due to not-yet-supported features such as multithreading
- Disabled on p1 but enabled on p2
- Disabled on p1 and _temporarily_ disabled on p2 due to `wasi-libc` bugfixes which have been merged but not yet included in a Rust release.  I'll open an issue to re-enable them when the fixes land in Rust.

Future Work

In the future, we could consider adding support for `tokio::net::lookup_host`.
WASIp2 natively supports asynchronous DNS lookups and is single threaded,
whereas Tokio currently assumes DNS lookups are blocking and require
multithreading to emulate async lookups.  A WASIp2-specific implementation could
do the lookup directly without multithreading.

WASIp2 also supports single-threaded, asynchronous file I/O, timers, etc.  We
could either support those directly or wait for WASIp3's multithreading support,
in which case most of `tokio::fs` (as well as `tokio::net::lookup_host`, etc.)
_should_ work unchanged via `wasi-libc` and worker threads.

Currently, building for WASIp2 requires RUSTFLAGS="--cfg tokio_unstable"`.  Once
we have a solid maintenance plan, we can remove that requirement.
2026-04-02 11:53:17 +02:00
Adam Martinez b4c3246d33 macros: improve overall macro hygiene (#7997)
Multiple macro expansions were previously assumming the existence of
some standard library symbols to both be in-scope and referring to the
right symbol, and not only having the same identifier.

This has now been refactored into using reexports from the `support`
module under the `macros` module. Some other macro invocations were also
using absolute standard library paths instead of calling into the afore
mentioned module through the special `$crate` macro. This has been
changed, thus also reexporting some other items in `support`.
2026-04-02 11:46:57 +02:00
Mattia Pitossi 7947fa4bd7 rt: add runtime name (#7924) 2026-04-01 09:46:27 +00:00
Hegui Dai 9f132172db sync: fix notify_waiters priority in Notify (#7996)
Previously, if a `notify_waiters()` was followed by a `notify_one()`,
an unpolled `Notified` future created before the `notify_waiters()`
call would consume the `NOTIFIED` permit created by the
`notify_one()` call.

This commit fixes this by verifying the `notify_waiters_calls` count
before optimistically attempting to acquire the `NOTIFIED` permit. If
the count indicates a `notify_waiters()` call has already happened, the
future transitions directly to `State::Done` and leaves the permit
intact for other waiters.

Fixes: #7965
2026-03-31 13:52:02 +02:00
Sim-hu 6752f50154 examples: add graceful shutdown example (#7962) 2026-03-30 11:05:53 +02:00
xtqqczze 6c72168a93 ci: remove Rust 1.94.0 workarounds (#7987) 2026-03-30 10:44:47 +02:00
Pino Toscano ee4de81806 net: use null get_peer_cred on Hurd (#7989) 2026-03-27 09:38:54 +01:00
Jess Izen a8435c0fc8 examples: add FD table pre-warming example (#7978) 2026-03-26 08:17:27 -07:00
Charlie Tonneslan 467d614267 bench: use is_multiple_of instead of manual modulo check (#7984)
Addresses clippy::manual_is_multiple_of warning on latest stable.
2026-03-22 14:00:12 +01:00
Martin Grigorov 66c786540e chore: Do not show "Available on non-loom only." doc label (#7977)
* chore: Do not show "Available on non-loom only." doc label

Closes #7976

Uses the unstable #[doc(cfg()]. https://github.com/rust-lang/rust/issues/43781

* Use build.rs to detect nightly builds

* Use `docsrs` instead of `nightly`. Drop build.rs

* Use doc(auto_cfg(hide(config_name)))

* Use same nightly on CirrusCI (FreeBSD) as on Github Actions CI
2026-03-18 15:14:02 +02:00
figsoda dd1196d369 ci: patch workspace members to disambiguate --package (#7967) 2026-03-18 07:27:46 +01:00
figsoda 2db0070eb8 stream: impl FromStream for std::collections::* (#7966) 2026-03-16 16:00:49 +01:00
Russell Cohen 26de3187e7 runtime: add tokio::runtime::worker_index() (#7921) 2026-03-14 22:44:41 -07:00
Chris Denton d3c7f56c10 ci: workaround for OpenOptionsExt in 1.94.0 (#7968) 2026-03-13 10:53:13 +00:00
Alex Gaynor ee04abe797 bench: add remote_spawn benchmark for inject queue contention (#7944) 2026-03-11 10:13:39 +01:00
figsoda aa1e7e2b9d stream: impl FromStream for BTreeSet (#7954) 2026-03-09 10:20:21 +00:00
ADD-SP 961757f548 ci: freeze rustc on 1.93.1 (#7961) 2026-03-09 07:43:48 +01:00
Carl Lerche e67cd87f71 chore: link to tokioconf.com (#7953) 2026-03-06 12:00:42 -08:00
winlogon a502d1b5c8 loom: remove StaticAtomicU64 (#7902) 2026-03-04 16:16:50 +01:00
131 changed files with 4457 additions and 603 deletions
-71
View File
@@ -1,71 +0,0 @@
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
freebsd_instance:
image_family: freebsd-14-3
env:
RUST_STABLE: stable
RUST_NIGHTLY: nightly-2025-01-25
RUSTFLAGS: -D warnings
# This excludes unstable features like io_uring, which require '--cfg tokio_unstable'.
TOKIO_STABLE_FEATURES: full,test-util
# 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 64-bit
setup_script:
- pkg install -y bash
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
- . $HOME/.cargo/env
- |
echo "~~~~ rustc --version ~~~~"
rustc --version
test_script:
- . $HOME/.cargo/env
- cargo test --workspace --features $TOKIO_STABLE_FEATURES
# Free the disk space before the next build,
# otherwise cirrus-ci complains about "No space left on device".
- cargo clean
# Enable all unstable features except `taskdump`, which is Linux-only.
- |
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable" \
RUSTDOCFLAGS="$RUSTDOCFLAGS --cfg tokio_unstable" \
cargo test \
--features $TOKIO_STABLE_FEATURES,io-uring,tracing
task:
name: FreeBSD docs
env:
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
setup_script:
- pkg install -y bash
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_NIGHTLY
- . $HOME/.cargo/env
- |
echo "~~~~ rustc --version ~~~~"
rustc --version
test_script:
- . $HOME/.cargo/env
# We use `--features $TOKIO_STABLE_FEATURES,io-uring,tracing` instead of
# `--all-features` to exclude `taskdump`, which is Linux-only.
- cargo doc --lib --no-deps --features $TOKIO_STABLE_FEATURES,io-uring,tracing --document-private-items
task:
name: FreeBSD 32-bit
setup_script:
- pkg install -y bash
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
- . $HOME/.cargo/env
- rustup target add i686-unknown-freebsd
- |
echo "~~~~ rustc --version ~~~~"
rustc --version
test_script:
- . $HOME/.cargo/env
- cargo test --workspace --features $TOKIO_STABLE_FEATURES --target i686-unknown-freebsd
+7
View File
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
+29 -17
View File
@@ -1,28 +1,40 @@
R-loom-blocking:
- tokio/src/runtime/blocking/*
- tokio/src/runtime/blocking/**/*
- changed-files:
- any-glob-to-any-file:
- tokio/src/runtime/blocking/*
- tokio/src/runtime/blocking/**/*
R-loom-sync:
- tokio/src/sync/*
- tokio/src/sync/**/*
- changed-files:
- any-glob-to-any-file:
- tokio/src/sync/*
- tokio/src/sync/**/*
R-loom-time-driver:
- tokio/src/runtime/time/*
- tokio/src/runtime/time/**/*
- changed-files:
- any-glob-to-any-file:
- tokio/src/runtime/time/*
- tokio/src/runtime/time/**/*
R-loom-current-thread:
- tokio/src/runtime/scheduler/*
- tokio/src/runtime/scheduler/current_thread/*
- tokio/src/runtime/task/*
- tokio/src/runtime/task/**
- 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:
- tokio/src/runtime/scheduler/*
- tokio/src/runtime/scheduler/multi_thread/*
- tokio/src/runtime/scheduler/multi_thread/**
- tokio/src/runtime/task/*
- tokio/src/runtime/task/**
- 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:
- tokio-util/src/*
- tokio-util/src/**/*
- changed-files:
- any-glob-to-any-file:
- tokio-util/src/*
- tokio-util/src/**/*
+1 -1
View File
@@ -20,5 +20,5 @@ jobs:
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: EmbarkStudios/cargo-deny-action@v2
+144 -44
View File
@@ -18,7 +18,7 @@ env:
rust_stable: stable
rust_nightly: nightly-2025-10-12
# Pin a specific miri version
rust_miri_nightly: nightly-2025-11-13
rust_miri_nightly: nightly-2026-04-27
rust_clippy: '1.88'
# When updating this, also update:
# - README.md
@@ -64,7 +64,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -96,7 +96,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -114,7 +114,7 @@ jobs:
cargo nextest run --workspace --features $TOKIO_STABLE_FEATURES
# Removing workspace patches to run tests without path dependencies
# (if not specified differently in the crate)
sed -i.bak '/^\[patch\.crates-io\]$/d; /^tokio = { path = "tokio"\s*}$/d' Cargo.toml && rm -f Cargo.toml.bak
perl -0 -i -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
cargo nextest run \
--workspace \
--exclude tokio \
@@ -135,7 +135,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -168,7 +168,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -208,7 +208,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -226,7 +226,7 @@ jobs:
cargo check --workspace --tests --features $TOKIO_STABLE_FEATURES
# Removing the tokio workspace patch to run tests without path dependencies
# (if not specified differently in the crate)
sed -i '/^\[patch\.crates-io\]$/d; /^tokio = { path = "tokio"\s*}$/d' Cargo.toml
perl -0 -i -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
cargo check --workspace --exclude tokio --tests --features $TOKIO_STABLE_FEATURES
valgrind:
@@ -234,7 +234,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -275,7 +275,7 @@ jobs:
- { os: ubuntu-latest, extra_features: io-uring }
- { os: macos-latest, extra_features: "" }
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -309,7 +309,7 @@ jobs:
include:
- os: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -343,7 +343,7 @@ jobs:
include:
- os: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -372,7 +372,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -395,7 +395,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -418,7 +418,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -437,7 +437,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install llvm
# Required to resolve symbols in sanitizer output
run: sudo apt-get install -y llvm
@@ -459,7 +459,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Check `tokio` semver
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
@@ -488,7 +488,7 @@ jobs:
- powerpc64-unknown-linux-gnu
- arm-linux-androideabi
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -513,7 +513,7 @@ jobs:
# - name: armv7-sony-vita-newlibeabihf
# exclude_features: "process,signal,rt-process-signal,full,taskdump"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@nightly
with:
@@ -547,7 +547,7 @@ jobs:
- target: aarch64-pc-windows-msvc
os: windows-11-arm
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
@@ -597,7 +597,7 @@ jobs:
- target: aarch64-pc-windows-msvc
os: windows-11-arm
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
@@ -638,7 +638,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -670,7 +670,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -708,7 +708,7 @@ jobs:
rustflags: "--cfg tokio_unstable -Dwarnings"
exclude_features: ""
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -727,7 +727,7 @@ jobs:
name: minrust
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_min }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -756,7 +756,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -789,7 +789,7 @@ jobs:
name: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -809,7 +809,7 @@ jobs:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_clippy }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -822,14 +822,17 @@ jobs:
cargo clippy --workspace --tests --no-deps --features $TOKIO_STABLE_FEATURES
# Removing the tokio workspace patch to check without path dependencies
# (if not specified differently in the crate)
sed -i '/^\[patch\.crates-io\]$/d; /^tokio = { path = "tokio"\s*}$/d' Cargo.toml
perl -0 -i.bak -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
cargo clippy --workspace --exclude tokio --tests --no-deps --features $TOKIO_STABLE_FEATURES
- name: "clippy --workspace --all-features --unstable"
run: |
# Forcing the cargo lock regeneration to apply the tokio patch
rm Cargo.lock
cargo clippy --workspace --tests --no-deps --all-features --config 'patch.crates-io.tokio.path="tokio"'
mv Cargo.toml Cargo.toml.nopatch
mv Cargo.toml.bak Cargo.toml
cargo clippy --workspace --tests --no-deps --all-features
# check without path dependencies
mv Cargo.toml.nopatch Cargo.toml
cargo clippy --workspace --exclude tokio --tests --no-deps --all-features
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
@@ -846,7 +849,7 @@ jobs:
extra_features: "tracing,io-uring,taskdump"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -863,7 +866,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -879,7 +882,7 @@ jobs:
name: Check README
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Verify that both READMEs are identical
run: diff README.md tokio/README.md
@@ -898,7 +901,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -950,7 +953,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -994,7 +997,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1011,7 +1014,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
with:
@@ -1035,7 +1038,7 @@ jobs:
- name: macros sync time rt
features: "macros sync time rt"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1058,7 +1061,7 @@ jobs:
- wasm32-wasip1
- wasm32-wasip1-threads
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1114,6 +1117,32 @@ jobs:
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -W shared-memory=y -S threads=y --"
RUSTFLAGS: --cfg tokio_unstable -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864
wasm32-wasip2:
name: test tokio for wasm32-wasip2
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_stable }}
targets: wasm32-wasip2
- name: Install cargo-nextest, wasmtime
uses: taiki-e/install-action@v2
with:
tool: cargo-nextest,wasmtime-cli
- uses: Swatinem/rust-cache@v2
- name: test tokio --target wasm32-wasip2
run: cargo nextest run --target wasm32-wasip2 --features net,macros,rt,io-util
working-directory: tokio
env:
RUSTFLAGS: --cfg tokio_unstable
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -Sinherit-network
check-external-types:
name: check-external-types (${{ matrix.os }})
needs: basics
@@ -1128,7 +1157,7 @@ jobs:
# includes all unstable features.
extra_features: "tracing,io-uring,taskdump"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ matrix.rust }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1137,7 +1166,7 @@ jobs:
toolchain: nightly-2025-08-06
- uses: Swatinem/rust-cache@v2
- name: Install cargo-check-external-types
uses: taiki-e/cache-cargo-install-action@v1
uses: taiki-e/cache-cargo-install-action@v3
with:
tool: [email protected]
- name: check-external-types
@@ -1152,7 +1181,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1172,7 +1201,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1181,7 +1210,7 @@ jobs:
uses: taiki-e/install-action@v2
with:
tool: cargo-spellcheck
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Make sure dictionary words are sorted and unique
run: |
FILE="spellcheck.dic"
@@ -1263,3 +1292,74 @@ jobs:
uses: ./.github/workflows/uring-kernel-version-test.yml
with:
kernel_version: ${{ matrix.kernel_version }}
freebsd-x86_64:
name: FreeBSD x86_64
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Test in FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: '14.4'
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
prepare: |
pkg install -y curl
curl https://sh.rustup.rs -sSf --output rustup.sh
sh rustup.sh -y --profile minimal --default-toolchain ${{ env.rust_stable }}
run: |
. $HOME/.cargo/env
cargo test --workspace --features $TOKIO_STABLE_FEATURES
# Enable all unstable features except `io_uring` and `taskdump`,
# which are Linux-only features.
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable" \
cargo test \
--features $TOKIO_STABLE_FEATURES,tracing
freebsd-docs:
name: FreeBSD docs
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Test in FreeBSD
uses: vmactions/freebsd-vm@v1
env:
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
with:
release: '14.4'
envs: "TOKIO_STABLE_FEATURES RUSTDOCFLAGS RUSTFLAGS"
prepare: |
pkg install -y curl
curl https://sh.rustup.rs -sSf --output rustup.sh
sh rustup.sh -y --profile minimal --default-toolchain ${{ env.rust_nightly }}
run: |
. $HOME/.cargo/env
# We use `--features $TOKIO_STABLE_FEATURES,io-uring,tracing` instead of
# `--all-features` to exclude `taskdump` and `io_uring`, which are Linux-only
# features.
cargo doc --lib --no-deps --features $TOKIO_STABLE_FEATURES,tracing \
--document-private-items
freebsd-i686:
name: FreeBSD i686
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Test in FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: '14.4'
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
prepare: |
pkg install -y curl
curl https://sh.rustup.rs -sSf --output rustup.sh
sh rustup.sh -y --profile minimal --default-toolchain ${{ env.rust_stable }}
run: |
. $HOME/.cargo/env
rustup target add i686-unknown-freebsd
cargo test --workspace --features $TOKIO_STABLE_FEATURES \
--target i686-unknown-freebsd
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'tokio-rs'
steps:
- uses: actions/labeler@v3
- uses: actions/labeler@v6
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
+6 -6
View File
@@ -29,7 +29,7 @@ jobs:
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-blocking') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -45,7 +45,7 @@ jobs:
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@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -61,7 +61,7 @@ jobs:
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@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -77,7 +77,7 @@ jobs:
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@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -100,7 +100,7 @@ jobs:
- scope: loom_multi_thread::group_c
- scope: loom_multi_thread::group_d
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -118,7 +118,7 @@ jobs:
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@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
+1 -1
View File
@@ -19,5 +19,5 @@ jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: EmbarkStudios/cargo-deny-action@v2
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
stress-test:
- simple_echo_tcp
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -14,7 +14,7 @@ jobs:
env:
KERNEL_VERSION: ${{ inputs.kernel_version }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install system dependencies
run: |
@@ -25,7 +25,7 @@ jobs:
- name: Cache Linux source
id: cache-kernel
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: linux-${{ env.KERNEL_VERSION }}
key: kernel-${{ env.KERNEL_VERSION }}
+4
View File
@@ -17,6 +17,10 @@ members = [
[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"
+9 -4
View File
@@ -1,3 +1,7 @@
*[TokioConf 2026 program and tickets are now available!](https://tokioconf.com)*
---
# Tokio
A runtime for writing reliable, asynchronous, and slim applications with
@@ -56,7 +60,7 @@ Make sure you enable the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.50.0", features = ["full"] }
tokio = { version = "1.52.4", features = ["full"] }
```
Then, on your main.rs:
@@ -217,18 +221,18 @@ 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.43.x` - LTS release until March 2026. (MSRV 1.70)
* `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.43.x` patch release, you
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.43", features = [...] }
tokio = { version = "~1.47", features = [...] }
```
### Previous LTS releases
@@ -241,6 +245,7 @@ tokio = { version = "~1.43", features = [...] }
* `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.
## License
+5
View File
@@ -101,5 +101,10 @@ name = "spawn_blocking"
path = "spawn_blocking.rs"
harness = false
[[bench]]
name = "remote_spawn"
path = "remote_spawn.rs"
harness = false
[lints]
workspace = true
+103
View File
@@ -0,0 +1,103 @@
//! 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(|p| p.get())
.unwrap_or(1);
[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);
@@ -1,7 +1,7 @@
# How to specify crates dependencies versions
Each crate (e.g., `tokio-util`, `tokio-stream`, etc.) should specify dependencies
according to these rules:
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
@@ -10,7 +10,7 @@ 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
`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.
@@ -20,12 +20,13 @@ Consider the following example from `tokio-stream`:
[dependencies]
futures-core = { version = "0.3.0" }
pin-project-lite = "0.2.11"
tokio = { path = "../tokio", features = ["sync"] }
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`. Once a new version of `tokio` is
released, the path dependency will be removed from `tokio-stream`.
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.
+11
View File
@@ -24,6 +24,9 @@ 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"] }
@@ -46,6 +49,10 @@ path = "connect-udp.rs"
name = "echo-tcp"
path = "echo-tcp.rs"
[[example]]
name = "graceful-shutdown"
path = "graceful-shutdown.rs"
[[example]]
name = "echo-udp"
path = "echo-udp.rs"
@@ -102,5 +109,9 @@ path = "named-pipe-multi-client.rs"
name = "dump"
path = "dump.rs"
[[example]]
name = "prewarm-fd-table"
path = "prewarm-fd-table.rs"
[lints]
workspace = true
+120
View File
@@ -0,0 +1,120 @@
//! 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;
}
+86
View File
@@ -0,0 +1,86 @@
//! 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 {});
}
+5 -1
View File
@@ -1,4 +1,4 @@
312
316
&
+
<
@@ -138,6 +138,7 @@ hashmaps
HashMaps
hashsets
HdrHistogram
ICMP
ie
Illumos
impl
@@ -209,6 +210,7 @@ POSIX
proxied
qos
RAII
RCU
reallocations
recv's
refactors
@@ -240,6 +242,7 @@ spawner
Splitter
spmc
spsc
SQE
src
stabilised
startup
@@ -309,5 +312,6 @@ wakers
Wakers
wakeup
wakeups
WASI
workstealing
ZST
@@ -68,4 +68,7 @@ async fn test_has_second_test_attr_rust_2021() {}
#[tokio::test]
async fn test_has_generated_second_test_attr() {}
#[tokio::test(name = 123)]
async fn test_name_not_string() {}
fn main() {}
@@ -4,7 +4,7 @@ error: the `async` keyword is missing from the function declaration
6 | fn main_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
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
|
8 | #[tokio::main(foo)]
@@ -22,13 +22,13 @@ error: the `async` keyword is missing from the function declaration
15 | fn test_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.
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
|
17 | #[tokio::test(foo)]
| ^^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`
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
|
20 | #[tokio::test(foo = 123)]
@@ -119,3 +119,9 @@ error: second test attribute is supplied, consider removing or changing the orde
| ^^^^^^^^^^^^^^
|
= 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)]
| ^^^
+1 -1
View File
@@ -37,7 +37,7 @@ help: consider importing this trait
1 + use std::future::Future;
|
error[E0433]: failed to resolve: use of undeclared type `Pin`
error[E0433]: cannot find type `Pin` in this scope
--> tests/fail/macros_join.rs:35:17
|
35 | let _ = Pin::new(&mut x);
@@ -37,7 +37,7 @@ help: consider importing this trait
1 + use std::future::Future;
|
error[E0433]: failed to resolve: use of undeclared type `Pin`
error[E0433]: cannot find type `Pin` in this scope
--> tests/fail/macros_try_join.rs:35:17
|
35 | let _ = Pin::new(&mut x);
+8
View File
@@ -1,3 +1,11 @@
# 2.7.0 (April 3rd, 2026)
- macros: stabilize `LocalRuntime` ([#7557])
- macros: add runtime name ([#7924])
[#7557]: https://github.com/tokio-rs/tokio/pull/7557
[#7924]: https://github.com/tokio-rs/tokio/pull/7924
# 2.6.1 (Mar 2nd, 2026)
- macros: improve error message for return type mismatch in #[tokio::main] ([#7856])
+1 -1
View File
@@ -4,7 +4,7 @@ name = "tokio-macros"
# - Remove path dependencies (if any)
# - Update CHANGELOG.md.
# - Create "tokio-macros-x.y.z" git tag.
version = "2.6.1"
version = "2.7.0"
edition = "2021"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
+25 -25
View File
@@ -53,6 +53,7 @@ impl UnhandledPanic {
}
struct FinalConfig {
name: Option<String>,
flavor: RuntimeFlavor,
worker_threads: Option<usize>,
start_paused: Option<bool>,
@@ -62,6 +63,7 @@ struct FinalConfig {
/// Config used in case of the attribute not being able to build a valid config
const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
name: None,
flavor: RuntimeFlavor::CurrentThread,
worker_threads: None,
start_paused: None,
@@ -70,6 +72,7 @@ const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
};
struct Configuration {
name: Option<String>,
rt_multi_thread_available: bool,
default_flavor: RuntimeFlavor,
flavor: Option<RuntimeFlavor>,
@@ -83,6 +86,7 @@ struct Configuration {
impl Configuration {
fn new(is_test: bool, rt_multi_thread: bool) -> Self {
Configuration {
name: None,
rt_multi_thread_available: rt_multi_thread,
default_flavor: match is_test {
true => RuntimeFlavor::CurrentThread,
@@ -97,6 +101,16 @@ impl Configuration {
}
}
fn set_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> {
if self.name.is_some() {
return Err(syn::Error::new(span, "`name` set multiple times."));
}
let runtime_name = parse_string(name, span, "name")?;
self.name = Some(runtime_name);
Ok(())
}
fn set_flavor(&mut self, runtime: syn::Lit, span: Span) -> Result<(), syn::Error> {
if self.flavor.is_some() {
return Err(syn::Error::new(span, "`flavor` set multiple times."));
@@ -227,6 +241,7 @@ impl Configuration {
};
Ok(FinalConfig {
name: self.name.clone(),
crate_name: self.crate_name.clone(),
flavor,
worker_threads,
@@ -372,9 +387,12 @@ fn build_config(
config
.set_unhandled_panic(lit.clone(), syn::spanned::Spanned::span(lit))?;
}
"name" => {
config.set_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
}
name => {
let msg = format!(
"Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
"Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.",
);
return Err(syn::Error::new_spanned(namevalue, msg));
}
@@ -397,11 +415,12 @@ fn build_config(
"Set the runtime flavor with #[{macro_name}(flavor = \"current_thread\")]."
)
}
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
"flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic"
| "name" => {
format!("The `{name}` attribute requires an argument.")
}
name => {
format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.")
format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`, `name`.")
}
};
return Err(syn::Error::new_spanned(path, msg));
@@ -457,12 +476,7 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
},
};
let mut checks = vec![];
let mut errors = vec![];
let build = if let RuntimeFlavor::Local = config.flavor {
checks.push(quote! { tokio_unstable });
errors.push("The local runtime flavor is only available when `tokio_unstable` is set.");
quote_spanned! {last_stmt_start_span=> build_local(Default::default())}
} else {
quote_spanned! {last_stmt_start_span=> build()}
@@ -478,6 +492,9 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
let unhandled_panic = v.into_tokens(&crate_path);
rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) };
}
if let Some(v) = config.name {
rt = quote_spanned! {last_stmt_start_span=> #rt.name(#v) };
}
let generated_attrs = if is_test {
quote! {
@@ -487,23 +504,10 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
quote! {}
};
let do_checks: TokenStream = checks
.iter()
.zip(&errors)
.map(|(check, error)| {
quote! {
#[cfg(not(#check))]
compile_error!(#error);
}
})
.collect();
let body_ident = quote! { body };
// This explicit `return` is intentional. See tokio-rs/tokio#4636
let last_block = quote_spanned! {last_stmt_end_span=>
#do_checks
#[cfg(all(#(#checks),*))]
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return, clippy::unwrap_in_result)]
{
#use_builder
@@ -515,10 +519,6 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
.block_on(#body_ident);
}
#[cfg(not(all(#(#checks),*)))]
{
panic!("fell through checks")
}
};
let body = input.body();
+49 -13
View File
@@ -73,16 +73,11 @@ use proc_macro::TokenStream;
///
/// ## Local
///
/// [Unstable API][unstable] only.
///
/// To use the [local runtime], the macro can be configured using
///
/// ```rust
/// # #[cfg(tokio_unstable)]
/// #[tokio::main(flavor = "local")]
/// # async fn main() {}
/// # #[cfg(not(tokio_unstable))]
/// # fn main() {}
/// ```
///
/// # Function arguments
@@ -91,6 +86,30 @@ use proc_macro::TokenStream;
///
/// # Usage
///
/// ## Set the name of the runtime
///
/// ```rust
/// #[tokio::main(name = "my-runtime")]
/// async fn main() {
/// println!("Hello world");
/// }
/// ```
///
/// Equivalent code not using `#[tokio::main]`
///
/// ```rust
/// fn main() {
/// tokio::runtime::Builder::new_multi_thread()
/// .enable_all()
/// .name("my-runtime")
/// .build()
/// .unwrap()
/// .block_on(async {
/// println!("Hello world");
/// })
/// }
/// ```
///
/// ## Using the multi-threaded runtime
///
/// ```rust
@@ -141,25 +160,19 @@ use proc_macro::TokenStream;
///
/// ## Using the local runtime
///
/// Available in the [unstable API][unstable] only.
///
/// The [local runtime] is similar to the current-thread runtime but
/// supports [`task::spawn_local`](../tokio/task/fn.spawn_local.html).
///
/// ```rust
/// # #[cfg(tokio_unstable)]
/// #[tokio::main(flavor = "local")]
/// async fn main() {
/// println!("Hello world");
/// }
/// # #[cfg(not(tokio_unstable))]
/// # fn main() {}
/// ```
///
/// Equivalent code not using `#[tokio::main]`
///
/// ```rust
/// # #[cfg(tokio_unstable)]
/// fn main() {
/// tokio::runtime::Builder::new_current_thread()
/// .enable_all()
@@ -169,8 +182,6 @@ use proc_macro::TokenStream;
/// println!("Hello world");
/// })
/// }
/// # #[cfg(not(tokio_unstable))]
/// # fn main() {}
/// ```
///
///
@@ -408,6 +419,31 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
///
/// ## Usage
///
/// ### Set the name of the runtime
///
/// ```no_run
/// #[tokio::test(name = "my-test-runtime")]
/// async fn my_test() {
/// assert!(true);
/// }
/// ```
///
/// Equivalent code not using `#[tokio::test]`
///
/// ```no_run
/// #[test]
/// fn my_test() {
/// tokio::runtime::Builder::new_current_thread()
/// .enable_all()
/// .name("my-test-runtime")
/// .build()
/// .unwrap()
/// .block_on(async {
/// assert!(true);
/// })
/// }
/// ```
///
/// ### Using the multi-thread runtime
///
/// ```no_run
+135
View File
@@ -6,6 +6,8 @@ use core::mem;
use core::pin::Pin;
use core::task::{ready, Context, Poll};
use pin_project_lite::pin_project;
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
use std::hash::Hash;
// Do not export this struct until `FromStream` can be unsealed.
pin_project! {
@@ -135,6 +137,139 @@ impl<T> sealed::FromStreamPriv<T> for Vec<T> {
}
}
impl<T> FromStream<T> for VecDeque<T> {}
impl<T> sealed::FromStreamPriv<T> for VecDeque<T> {
type InternalCollection = VecDeque<T>;
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> VecDeque<T> {
VecDeque::with_capacity(lower)
}
fn extend(_: sealed::Internal, collection: &mut VecDeque<T>, item: T) -> bool {
collection.push_back(item);
true
}
fn finalize(_: sealed::Internal, collection: &mut VecDeque<T>) -> VecDeque<T> {
mem::take(collection)
}
}
impl<T> FromStream<T> for LinkedList<T> {}
impl<T> sealed::FromStreamPriv<T> for LinkedList<T> {
type InternalCollection = LinkedList<T>;
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> LinkedList<T> {
LinkedList::new()
}
fn extend(_: sealed::Internal, collection: &mut LinkedList<T>, item: T) -> bool {
collection.push_back(item);
true
}
fn finalize(_: sealed::Internal, collection: &mut LinkedList<T>) -> LinkedList<T> {
mem::take(collection)
}
}
impl<T: Ord> FromStream<T> for BTreeSet<T> {}
impl<T: Ord> sealed::FromStreamPriv<T> for BTreeSet<T> {
type InternalCollection = BTreeSet<T>;
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> BTreeSet<T> {
BTreeSet::new()
}
fn extend(_: sealed::Internal, collection: &mut BTreeSet<T>, item: T) -> bool {
collection.insert(item);
true
}
fn finalize(_: sealed::Internal, collection: &mut BTreeSet<T>) -> BTreeSet<T> {
mem::take(collection)
}
}
impl<K: Ord, V> FromStream<(K, V)> for BTreeMap<K, V> {}
impl<K: Ord, V> sealed::FromStreamPriv<(K, V)> for BTreeMap<K, V> {
type InternalCollection = BTreeMap<K, V>;
fn initialize(_: sealed::Internal, _lower: usize, _upper: Option<usize>) -> BTreeMap<K, V> {
BTreeMap::new()
}
fn extend(_: sealed::Internal, collection: &mut BTreeMap<K, V>, (key, value): (K, V)) -> bool {
collection.insert(key, value);
true
}
fn finalize(_: sealed::Internal, collection: &mut BTreeMap<K, V>) -> BTreeMap<K, V> {
mem::take(collection)
}
}
impl<T: Eq + Hash> FromStream<T> for HashSet<T> {}
impl<T: Eq + Hash> sealed::FromStreamPriv<T> for HashSet<T> {
type InternalCollection = HashSet<T>;
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> HashSet<T> {
HashSet::with_capacity(lower)
}
fn extend(_: sealed::Internal, collection: &mut HashSet<T>, item: T) -> bool {
collection.insert(item);
true
}
fn finalize(_: sealed::Internal, collection: &mut HashSet<T>) -> HashSet<T> {
mem::take(collection)
}
}
impl<K: Eq + Hash, V> FromStream<(K, V)> for HashMap<K, V> {}
impl<K: Eq + Hash, V> sealed::FromStreamPriv<(K, V)> for HashMap<K, V> {
type InternalCollection = HashMap<K, V>;
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> HashMap<K, V> {
HashMap::with_capacity(lower)
}
fn extend(_: sealed::Internal, collection: &mut HashMap<K, V>, (key, value): (K, V)) -> bool {
collection.insert(key, value);
true
}
fn finalize(_: sealed::Internal, collection: &mut HashMap<K, V>) -> HashMap<K, V> {
mem::take(collection)
}
}
impl<T: Ord> FromStream<T> for BinaryHeap<T> {}
impl<T: Ord> sealed::FromStreamPriv<T> for BinaryHeap<T> {
type InternalCollection = BinaryHeap<T>;
fn initialize(_: sealed::Internal, lower: usize, _upper: Option<usize>) -> BinaryHeap<T> {
BinaryHeap::with_capacity(lower)
}
fn extend(_: sealed::Internal, collection: &mut BinaryHeap<T>, item: T) -> bool {
collection.push(item);
true
}
fn finalize(_: sealed::Internal, collection: &mut BinaryHeap<T>) -> BinaryHeap<T> {
mem::take(collection)
}
}
impl<T> FromStream<T> for Box<[T]> {}
impl<T> sealed::FromStreamPriv<T> for Box<[T]> {
+10
View File
@@ -1,5 +1,6 @@
use crate::Stream;
use futures_core::FusedStream;
use pin_project_lite::pin_project;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
@@ -51,3 +52,12 @@ where
}
}
}
impl<T> FusedStream for Fuse<T>
where
T: Stream,
{
fn is_terminated(&self) -> bool {
self.stream.is_none()
}
}
+151
View File
@@ -1,3 +1,5 @@
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
use tokio_stream::{self as stream, StreamExt};
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok, task};
@@ -61,6 +63,155 @@ async fn collect_vec_items() {
assert_eq!(vec![1, 2], coll);
}
#[tokio::test]
async fn collect_vecdeque_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<VecDeque<i32>>());
assert_pending!(fut.poll());
let xs = [1, 2, 42, 3];
for x in xs {
tx.send(x).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
}
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!(coll, VecDeque::from(xs));
assert_eq!(coll.into_iter().collect::<Vec<_>>(), xs);
}
#[tokio::test]
async fn collect_linkedlist_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<LinkedList<i32>>());
assert_pending!(fut.poll());
let xs = [1, 2, 42, 3];
for x in xs {
tx.send(x).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
}
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!(coll, LinkedList::from(xs));
assert_eq!(coll.into_iter().collect::<Vec<_>>(), xs);
}
#[tokio::test]
async fn collect_btreeset_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<BTreeSet<i32>>());
assert_pending!(fut.poll());
tx.send(2).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send(1).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!(BTreeSet::from([1, 2]), coll);
}
#[tokio::test]
async fn collect_btreemap_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<BTreeMap<i32, i32>>());
assert_pending!(fut.poll());
tx.send((3, 4)).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send((1, 2)).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!(BTreeMap::from([(1, 2), (3, 4)]), coll);
}
#[tokio::test]
async fn collect_hashset_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<HashSet<i32>>());
assert_pending!(fut.poll());
tx.send(1).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send(2).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!(HashSet::from([1, 2]), coll);
}
#[tokio::test]
async fn collect_hashmap_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<HashMap<i32, i32>>());
assert_pending!(fut.poll());
tx.send((1, 2)).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send((3, 4)).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!(HashMap::from([(1, 2), (3, 4)]), coll);
}
#[tokio::test]
async fn collect_binaryheap_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
let mut fut = task::spawn(rx.collect::<BinaryHeap<i32>>());
assert_pending!(fut.poll());
tx.send(2).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
tx.send(1).unwrap();
assert!(fut.is_woken());
assert_pending!(fut.poll());
drop(tx);
assert!(fut.is_woken());
let coll = assert_ready!(fut.poll());
assert_eq!(vec![1, 2], coll.into_sorted_vec());
}
#[tokio::test]
async fn collect_string_items() {
let (tx, rx) = mpsc::unbounded_channel_stream();
+7
View File
@@ -1,3 +1,4 @@
use futures_core::FusedStream;
use tokio_stream::{Stream, StreamExt};
use std::pin::Pin;
@@ -37,16 +38,22 @@ async fn basic_usage() {
// however, once it is fused
let mut stream = stream.fuse();
assert!(!stream.is_terminated());
assert_eq!(stream.size_hint(), (0, None));
assert_eq!(stream.next().await, Some(4));
assert!(!stream.is_terminated());
assert_eq!(stream.size_hint(), (0, None));
assert_eq!(stream.next().await, None);
assert!(stream.is_terminated());
// it will always return `None` after the first time.
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(stream.next().await, None);
assert_eq!(stream.size_hint(), (0, Some(0)));
assert!(stream.is_terminated());
}
#[tokio::test]
+175 -1
View File
@@ -1,3 +1,155 @@
# 1.52.4 (July 16th, 2026)
### Fixed
- runtime: don't skip the driver when `before_park` schedules work ([#8222])
### Fixed (unstable)
- taskdump: remove crate disambiguators from output ([#8264])
[#8264]: https://github.com/tokio-rs/tokio/pull/8264
# 1.52.3 (May 8th, 2026)
### Fixed
* sync: fix underflow in mpsc channel `len()` ([#8062])
* sync: notify receivers in mpsc `OwnedPermit::release()` method ([#8075])
* sync: require that an `RwLock` has `max_readers != 0` ([#8076])
* sync: return `Empty` from `try_recv()` when mpsc is closed with outstanding permits ([#8074])
# 1.52.2 (May 4th, 2026)
This release reverts the LIFO slot stealing change introduced in 1.51.0
([#7431]), due to [its performance impact][#8065]. ([#8100])
# 1.52.1 (April 16th, 2026)
## Fixed
- runtime: revert [#7757] to fix [a regression][#8056] that causes `spawn_blocking` to hang ([#8057])
[#7757]: https://github.com/tokio-rs/tokio/pull/7757
[#8056]: https://github.com/tokio-rs/tokio/pull/8056
[#8057]: https://github.com/tokio-rs/tokio/pull/8057
# 1.52.0 (April 14th, 2026)
## Added
- io: `AioSource::register_borrowed` for I/O safety support ([#7992])
- net: add `try_io` function to `unix::pipe` sender and receiver types ([#8030])
## Added (unstable)
- runtime: `Builder::enable_eager_driver_handoff` setting enable eager hand off of the I/O and time drivers before polling tasks ([#8010])
- taskdump: add `trace_with()` for customized task dumps ([#8025])
- taskdump: allow `impl FnMut()` in `trace_with` instead of just `fn()` ([#8040])
- fs: support `io_uring` in `AsyncRead` for `File` ([#7907])
## Changed
- runtime: improve `spawn_blocking` scalability with sharded queue ([#7757])
- runtime: use `compare_exchange_weak()` in worker queue ([#8028])
## Fixed
- runtime: overflow second half of tasks when local queue is filled instead of first half ([#8029])
## Documented
- docs: fix typo in `oneshot::Sender::send` docs ([#8026])
- docs: hide #[tokio::main] attribute in the docs of `sync::watch` ([#8035])
- net: add docs on `ConnectionRefused` errors with UDP sockets ([#7870])
[#7757]: https://github.com/tokio-rs/tokio/pull/7757
[#7870]: https://github.com/tokio-rs/tokio/pull/7870
[#7907]: https://github.com/tokio-rs/tokio/pull/7907
[#7992]: https://github.com/tokio-rs/tokio/pull/7992
[#8010]: https://github.com/tokio-rs/tokio/pull/8010
[#8025]: https://github.com/tokio-rs/tokio/pull/8025
[#8026]: https://github.com/tokio-rs/tokio/pull/8026
[#8028]: https://github.com/tokio-rs/tokio/pull/8028
[#8029]: https://github.com/tokio-rs/tokio/pull/8029
[#8030]: https://github.com/tokio-rs/tokio/pull/8030
[#8035]: https://github.com/tokio-rs/tokio/pull/8035
[#8040]: https://github.com/tokio-rs/tokio/pull/8040
# 1.51.4 (July 16th, 2026)
### Fixed
- runtime: don't skip the driver when `before_park` schedules work ([#8222])
[#8222]: https://github.com/tokio-rs/tokio/pull/8222
# 1.51.3 (May 8th, 2026)
### Fixed
* sync: fix underflow in mpsc channel `len()` ([#8062])
* sync: notify receivers in mpsc `OwnedPermit::release()` method ([#8075])
* sync: require that an `RwLock` has `max_readers != 0` ([#8076])
* sync: return `Empty` from `try_recv()` when mpsc is closed with outstanding permits ([#8074])
# 1.51.2 (May 4th, 2026)
This release reverts the LIFO slot stealing change introduced in 1.51.0
([#7431]), due to [its performance impact][#8065]. ([#8100])
[#8065]: https://github.com/tokio-rs/tokio/pull/8065
[#8100]: https://github.com/tokio-rs/tokio/pull/8100
# 1.51.1 (April 8th, 2026)
### Fixed
- sync: fix semaphore reopens after forget ([#8021])
- net: surface errors from `SO_ERROR` on `recv` for UDP sockets on Linux ([#8001])
### Fixed (unstable)
- metrics: fix `worker_local_schedule_count` test ([#8008])
- rt: do not leak fd when cancelling io\_uring open operation ([#7983])
[#7983]: https://github.com/tokio-rs/tokio/pull/7983
[#8001]: https://github.com/tokio-rs/tokio/pull/8001
[#8008]: https://github.com/tokio-rs/tokio/pull/8008
[#8021]: https://github.com/tokio-rs/tokio/pull/8021
# 1.51.0 (April 3rd, 2026)
### Added
- net: implement `get_peer_cred` on Hurd ([#7989])
- runtime: add `tokio::runtime::worker_index()` ([#7921])
- runtime: add runtime name ([#7924])
- runtime: stabilize `LocalRuntime` ([#7557])
- wasm: add wasm32-wasip2 networking support ([#7933])
### Changed
- runtime: steal tasks from the LIFO slot ([#7431])
### Fixed
- docs: do not show "Available on non-loom only." doc label ([#7977])
- macros: improve overall macro hygiene ([#7997])
- sync: fix `notify_waiters` priority in `Notify` ([#7996])
- sync: fix panic in `Chan::recv_many` when called with non-empty vector on closed channel ([#7991])
[#7431]: https://github.com/tokio-rs/tokio/pull/7431
[#7557]: https://github.com/tokio-rs/tokio/pull/7557
[#7921]: https://github.com/tokio-rs/tokio/pull/7921
[#7924]: https://github.com/tokio-rs/tokio/pull/7924
[#7933]: https://github.com/tokio-rs/tokio/pull/7933
[#7977]: https://github.com/tokio-rs/tokio/pull/7977
[#7989]: https://github.com/tokio-rs/tokio/pull/7989
[#7991]: https://github.com/tokio-rs/tokio/pull/7991
[#7996]: https://github.com/tokio-rs/tokio/pull/7996
[#7997]: https://github.com/tokio-rs/tokio/pull/7997
# 1.50.0 (Mar 3rd, 2026)
### Added
@@ -273,7 +425,29 @@ The MSRV is increased to 1.71.
[#7672]: https://github.com/tokio-rs/tokio/pull/7672
[#7675]: https://github.com/tokio-rs/tokio/pull/7675
# 1.47.3 (Januar 3rd, 2026)
# 1.47.5 (May 7th, 2026)
### Fixed
* sync: fix underflow in mpsc channel `len()` ([#8062])
* sync: notify receivers in mpsc `OwnedPermit::release()` method ([#8075])
* sync: require that an `RwLock` has `max_readers != 0` ([#8076])
* sync: return `Empty` from `try_recv()` when mpsc is closed with outstanding permits ([#8074])
[#8062]: https://github.com/tokio-rs/tokio/pull/8062
[#8074]: https://github.com/tokio-rs/tokio/pull/8074
[#8075]: https://github.com/tokio-rs/tokio/pull/8075
[#8076]: https://github.com/tokio-rs/tokio/pull/8076
# 1.47.4 (April 2nd, 2026)
### Fixed
* sync: fix panic in `Chan::recv_many` when called with non-empty vector on closed channel ([#7991])
[#7991]: https://github.com/tokio-rs/tokio/pull/7991
# 1.47.3 (January 3rd, 2026)
### Fixed
+14 -8
View File
@@ -6,7 +6,7 @@ name = "tokio"
# - README.md
# - Update CHANGELOG.md.
# - Create "v1.x.y" git tag.
version = "1.50.0"
version = "1.52.4"
edition = "2021"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
@@ -90,17 +90,17 @@ io-uring = ["dep:io-uring", "libc", "mio/os-poll", "mio/os-ext", "dep:slab"]
taskdump = ["dep:backtrace"]
[dependencies]
tokio-macros = { version = "~2.6.0", optional = true }
tokio-macros = { version = "~2.7.0", optional = true }
pin-project-lite = "0.2.11"
# Everything else is optional...
bytes = { version = "1.2.1", optional = true }
mio = { version = "1.0.1", optional = true, default-features = false }
mio = { version = "1.2.0", optional = true, default-features = false }
parking_lot = { version = "0.12.0", optional = true }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
socket2 = { version = "0.6.0", optional = true, features = ["all"] }
[target.'cfg(any(not(target_family = "wasm"), all(target_os = "wasi", not(target_env = "p1"))))'.dependencies]
socket2 = { version = "0.6.3", optional = true, features = ["all"] }
# Currently unstable. The API exposed by these features may be broken at any time.
# Requires `--cfg tokio_unstable` to enable.
@@ -112,17 +112,23 @@ tracing = { version = "0.1.29", default-features = false, features = ["std"], op
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dependencies]
io-uring = { version = "0.7.11", default-features = false, optional = true }
libc = { version = "0.2.168", optional = true }
mio = { version = "1.0.1", default-features = false, features = ["os-poll", "os-ext"], optional = true }
mio = { version = "1.2.0", default-features = false, features = ["os-poll", "os-ext"], optional = true }
slab = { version = "0.4.9", optional = true }
backtrace = { version = "0.3.58", optional = true }
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
backtrace = "0.3.58"
[target.'cfg(unix)'.dependencies]
libc = { version = "0.2.168", optional = true }
signal-hook-registry = { version = "1.1.1", optional = true }
[target.'cfg(target_os = "wasi")'.dependencies]
libc = { version = "0.2.168", optional = true }
[target.'cfg(unix)'.dev-dependencies]
libc = { version = "0.2.168" }
nix = { version = "0.29.0", default-features = false, features = ["aio", "fs", "socket"] }
nix = { version = "0.31.0", default-features = false, features = ["aio", "fs", "socket"] }
[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.61"
@@ -157,7 +163,7 @@ rand = "0.9"
wasm-bindgen-test = "0.3.0"
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
mio-aio = { version = "1", features = ["tokio"] }
mio-aio = { version = "2", features = ["tokio"] }
[target.'cfg(loom)'.dev-dependencies]
loom = { version = "0.7", features = ["futures", "checkpoint"] }
+9 -4
View File
@@ -1,3 +1,7 @@
*[TokioConf 2026 program and tickets are now available!](https://tokioconf.com)*
---
# Tokio
A runtime for writing reliable, asynchronous, and slim applications with
@@ -56,7 +60,7 @@ Make sure you enable the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.50.0", features = ["full"] }
tokio = { version = "1.52.4", features = ["full"] }
```
Then, on your main.rs:
@@ -217,18 +221,18 @@ 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.43.x` - LTS release until March 2026. (MSRV 1.70)
* `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.43.x` patch release, you
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.43", features = [...] }
tokio = { version = "~1.47", features = [...] }
```
### Previous LTS releases
@@ -241,6 +245,7 @@ tokio = { version = "~1.43", features = [...] }
* `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.
## License
+125 -7
View File
@@ -30,6 +30,11 @@ use crate::blocking::{spawn_blocking, spawn_mandatory_blocking};
#[cfg(not(test))]
use std::fs::File as StdFile;
cfg_io_uring! {
#[cfg(not(test))]
use crate::spawn;
}
/// A reference to an open file on the filesystem.
///
/// This is a specialized version of [`std::fs::File`] for usage from the
@@ -613,13 +618,7 @@ impl AsyncRead for File {
let std = me.std.clone();
let max_buf_size = cmp::min(dst.remaining(), me.max_buf_size);
inner.state = State::Busy(spawn_blocking(move || {
// SAFETY: the `Read` implementation of `std` does not
// read from the buffer it is borrowing and correctly
// reports the length of the data written into the buffer.
let res = unsafe { buf.read_from(&mut &*std, max_buf_size) };
(Operation::Read(res), buf)
}));
inner.state = State::Busy(Inner::poll_read_inner(std, buf, max_buf_size)?);
}
State::Busy(ref mut rx) => {
let (op, mut buf) = ready!(Pin::new(rx).poll(cx))?;
@@ -952,6 +951,125 @@ cfg_windows! {
}
impl Inner {
fn poll_read_inner(
std: Arc<StdFile>,
buf: Buf,
max_buf_size: usize,
) -> io::Result<JoinHandle<(Operation, Buf)>> {
// Unit tests use `MockFile` and the mock `spawn_blocking` infrastructure,
// which can't drive real io_uring operations. The io_uring read path
// is tested through integration tests in `tests/fs_uring_file_read.rs`.
#[cfg(all(
not(test),
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux",
))]
{
if let Ok(handle) = crate::runtime::Handle::try_current() {
let driver_handle = handle.inner.driver().io();
if driver_handle.is_uring_ready(io_uring::opcode::Read::CODE) {
// Fast path: uring already initialized and Read supported.
let fd: crate::io::uring::utils::ArcFd = std;
return Ok(spawn(Self::uring_read(fd, buf, max_buf_size)));
}
if !driver_handle.is_uring_probed() {
// Not yet probed: lazy init inside an async task so
// `File::from_std()` can still benefit from io-uring.
return Ok(spawn(Self::lazy_init_read(std, buf, max_buf_size)));
}
// Probed but unsupported: fall through to spawn_blocking.
}
}
// Fallback: spawn_blocking
let join = Self::spawn_blocking_read(buf, std, max_buf_size);
Ok(join)
}
/// Perform an io-uring read with interrupt retry.
#[cfg(all(
not(test),
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux",
))]
async fn uring_read(
mut fd: crate::io::uring::utils::ArcFd,
mut buf: Buf,
max_buf_size: usize,
) -> (Operation, Buf) {
use crate::runtime::driver::op::Op;
loop {
let (res, r_fd, r_buf) =
// u64::MAX to use and advance the file position
Op::read_at(fd, buf, max_buf_size, u64::MAX).await;
match res {
Err(e) if e.kind() == io::ErrorKind::Interrupted => {
buf = r_buf;
fd = r_fd;
continue;
}
Err(e) => break (Operation::Read(Err(e)), r_buf),
Ok(n) => break (Operation::Read(Ok(n as usize)), r_buf),
}
}
}
/// Attempt lazy io-uring initialization, then read via uring or fall back
/// to a blocking read. Covers the `File::from_std()` path where
/// `check_and_init()` hasn't been called yet.
#[cfg(all(
not(test),
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux",
))]
async fn lazy_init_read(std: Arc<StdFile>, buf: Buf, max_buf_size: usize) -> (Operation, Buf) {
let handle = crate::runtime::Handle::current();
let driver_handle = handle.inner.driver().io();
if driver_handle
.check_and_init(io_uring::opcode::Read::CODE)
.await
.unwrap_or(false)
{
let fd: crate::io::uring::utils::ArcFd = std;
Self::uring_read(fd, buf, max_buf_size).await
} else {
match Self::spawn_blocking_read(buf, std, max_buf_size).await {
Ok(result) => result,
Err(e) => (
Operation::Read(Err(io::Error::new(io::ErrorKind::Other, e))),
Buf::with_capacity(0),
),
}
}
}
fn spawn_blocking_read(
buf: Buf,
std: Arc<StdFile>,
max_buf_size: usize,
) -> JoinHandle<(Operation, Buf)> {
spawn_blocking(move || {
let mut buf = buf;
// SAFETY: the `Read` implementation of `std` does not
// read from the buffer it is borrowing and correctly
// reports the length of the data written into the buffer.
let res = unsafe { buf.read_from(&mut &*std, max_buf_size) };
(Operation::Read(res), buf)
})
}
async fn complete_inflight(&mut self) {
use std::future::poll_fn;
+1 -1
View File
@@ -116,7 +116,7 @@ async fn op_read(
read_len: u32,
) -> io::Result<(OwnedFd, Vec<u8>, bool)> {
loop {
let (res, r_fd, r_buf) = Op::read(fd, buf, read_len, *offset).await;
let (res, r_fd, r_buf) = Op::read_at(fd, buf, read_len as usize, *offset).await;
match res {
Err(e) if e.kind() == ErrorKind::Interrupted => {
+33
View File
@@ -277,6 +277,39 @@ impl Buf {
}
}
cfg_io_uring! {
impl Buf {
/// Prepare the internal buffer for an io-uring read operation.
///
/// Returns a pointer to the spare capacity and the length available
/// for the kernel to write into.
pub(crate) fn prepare_uring_read(&mut self, max_buf_size: usize) -> (*mut u8, u32) {
assert!(self.is_empty());
self.buf.reserve(max_buf_size);
let spare = self.buf.spare_capacity_mut();
let len = std::cmp::min(spare.len(), max_buf_size);
let ptr = spare.as_mut_ptr().cast::<u8>();
(ptr, len as u32)
}
/// Complete an io-uring read operation.
///
/// # Safety
///
/// The caller must ensure that the kernel wrote exactly `n` bytes
/// into the buffer that was returned by `prepare_uring_read`.
pub(crate) unsafe fn complete_uring_read(&mut self, n: usize) {
assert_eq!(self.pos, 0);
// SAFETY: `prepare_uring_read` handed out a pointer to
// `self.buf.spare_capacity_mut()` after asserting it's empty.
// The caller guarantees the kernel initialised exactly `n` bytes
// starting at that pointer, so bytes `0..n` are now initialised and
// it is sound to set the Vec length to `n`.
unsafe { self.buf.set_len(n) };
}
}
}
cfg_fs! {
impl Buf {
pub(crate) fn discard_read(&mut self) -> i64 {
+27 -6
View File
@@ -9,17 +9,36 @@ use mio::Token;
use std::fmt;
use std::io;
use std::ops::{Deref, DerefMut};
use std::os::unix::io::AsRawFd;
use std::os::unix::prelude::RawFd;
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::io::{AsRawFd, RawFd};
use std::task::{ready, Context, Poll};
/// Like [`mio::event::Source`], but for POSIX AIO only.
///
/// Tokio's consumer must pass an implementor of this trait to create a
/// [`Aio`] object.
/// [`Aio`] object. Implementors must implement at least one of [`AioSource::register`] and
/// [`AioSource::register_borrowed`].
pub trait AioSource {
/// Registers this AIO event source with Tokio's reactor.
fn register(&mut self, kq: RawFd, token: usize);
///
/// # Safety
///
/// It's memory-safe, but not I/O safe. If the file referenced by `kq` gets dropped, then this
/// source may end up notifying the wrong file.
#[deprecated(since = "1.52.0", note = "use register_borrowed instead")]
fn register(&mut self, _kq: RawFd, _token: usize) {
// This default implementation exists so new AioSource implementors that implement the
// register_borrowed method can compile without the need to implement register.
unimplemented!("Use AioSource::register_borrowed instead")
}
/// Registers this AIO event source with Tokio's reactor.
fn register_borrowed(&mut self, kq: BorrowedFd<'_>, token: usize) {
// This default implementation serves to provide backwards compatibility with AioSource
// implementors written before 1.52.0 that only implemented the unsafe `register` method.
#[allow(deprecated)]
self.register(kq.as_raw_fd(), token)
}
/// Deregisters this AIO event source with Tokio's reactor.
fn deregister(&mut self);
@@ -37,7 +56,8 @@ impl<T: AioSource> Source for MioSource<T> {
interests: mio::Interest,
) -> io::Result<()> {
assert!(interests.is_aio() || interests.is_lio());
self.0.register(registry.as_raw_fd(), usize::from(token));
self.0
.register_borrowed(registry.as_fd(), usize::from(token));
Ok(())
}
@@ -53,7 +73,8 @@ impl<T: AioSource> Source for MioSource<T> {
interests: mio::Interest,
) -> io::Result<()> {
assert!(interests.is_aio() || interests.is_lio());
self.0.register(registry.as_raw_fd(), usize::from(token));
self.0
.register_borrowed(registry.as_fd(), usize::from(token));
Ok(())
}
}
+83 -30
View File
@@ -1,27 +1,74 @@
use crate::io::blocking::Buf;
use crate::io::uring::utils::{ArcFd, UringFd};
use crate::runtime::driver::op::{CancelData, Cancellable, Completable, CqeResult, Op};
use io_uring::{opcode, types};
use std::fmt;
use std::io::{self, Error};
use std::os::fd::{AsRawFd, OwnedFd};
use std::os::fd::OwnedFd;
#[derive(Debug)]
pub(crate) struct Read {
fd: OwnedFd,
buf: Vec<u8>,
/// Trait for buffers that can be used with io-uring read operations.
pub(crate) trait ReadBuffer: Send + 'static {
/// Prepare the buffer for a read operation.
/// Returns a pointer and length for the io-uring SQE.
fn uring_read_prepare(&mut self, max_len: usize) -> (*mut u8, u32);
/// Complete a read of `n` bytes.
///
/// # Safety
///
/// The caller must ensure the kernel wrote exactly `n` bytes
/// into the buffer at the pointer returned by `uring_read_prepare`.
unsafe fn uring_read_complete(&mut self, n: u32);
}
impl Completable for Read {
type Output = (io::Result<u32>, OwnedFd, Vec<u8>);
impl ReadBuffer for Vec<u8> {
fn uring_read_prepare(&mut self, max_len: usize) -> (*mut u8, u32) {
assert!(self.spare_capacity_mut().len() >= max_len);
let ptr = self.spare_capacity_mut().as_mut_ptr().cast();
(ptr, max_len as u32)
}
unsafe fn uring_read_complete(&mut self, n: u32) {
// SAFETY: the kernel wrote `n` bytes into spare capacity starting
// at the old self.len(), so self.len() + n bytes are now initialized.
unsafe { self.set_len(self.len() + n as usize) };
}
}
impl ReadBuffer for Buf {
fn uring_read_prepare(&mut self, max_len: usize) -> (*mut u8, u32) {
self.prepare_uring_read(max_len)
}
unsafe fn uring_read_complete(&mut self, n: u32) {
// SAFETY: caller guarantees kernel wrote exactly n bytes.
unsafe { self.complete_uring_read(n as usize) };
}
}
pub(crate) struct Read<B, F = ArcFd> {
fd: F,
buf: B,
}
impl<B: fmt::Debug, F> fmt::Debug for Read<B, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Read")
.field("buf", &self.buf)
.finish_non_exhaustive()
}
}
impl<B: ReadBuffer, F: UringFd> Completable for Read<B, F> {
type Output = (io::Result<u32>, F, B);
fn complete(self, cqe: CqeResult) -> Self::Output {
let mut buf = self.buf;
if let Ok(len) = cqe.result {
let new_len = buf.len() + len as usize;
// SAFETY: Kernel read len bytes
unsafe { buf.set_len(new_len) };
// SAFETY: kernel wrote exactly `len` bytes into the prepared buffer.
unsafe { buf.uring_read_complete(len) };
}
(cqe.result, self.fd, buf)
}
@@ -30,32 +77,38 @@ impl Completable for Read {
}
}
impl Cancellable for Read {
impl Cancellable for Read<Vec<u8>, OwnedFd> {
fn cancel(self) -> CancelData {
CancelData::Read(self)
CancelData::ReadVec(self)
}
}
impl Op<Read> {
// Submit a request to read a FD at given length and offset into a
// dynamic buffer with uninitialized memory. The read happens on uninitialized
// buffer and no overwriting happens.
impl Cancellable for Read<Buf, ArcFd> {
fn cancel(self) -> CancelData {
CancelData::ReadBuf(self)
}
}
// SAFETY: The `len` of the amount to be read and the buffer that is passed
// should have capacity > len.
//
// If `len` read is higher than vector capacity then setting its length by
// the caller in terms of size_read can be unsound.
pub(crate) fn read(fd: OwnedFd, mut buf: Vec<u8>, len: u32, offset: u64) -> Self {
// don't overwrite on already written part
assert!(buf.spare_capacity_mut().len() >= len as usize);
let buf_mut_ptr = buf.spare_capacity_mut().as_mut_ptr().cast();
impl<B, F> Op<Read<B, F>>
where
B: ReadBuffer + fmt::Debug,
F: UringFd,
Read<B, F>: Cancellable,
{
/// Submit a read operation via io-uring.
///
/// `max_len` is the maximum number of bytes to read.
/// `offset` is the file offset; use `u64::MAX` for the current cursor.
pub(crate) fn read_at(fd: F, mut buf: B, max_len: usize, offset: u64) -> Self {
let (ptr, len) = buf.uring_read_prepare(max_len);
let read_op = opcode::Read::new(types::Fd(fd.as_raw_fd()), buf_mut_ptr, len)
let sqe = opcode::Read::new(types::Fd(UringFd::as_raw_fd(&fd)), ptr, len)
.offset(offset)
.build();
// SAFETY: Parameters are valid for the entire duration of the operation
unsafe { Op::new(read_op, Read { fd, buf }) }
// SAFETY: `fd` and `buf`, which owns the heap buffer, are moved into `Read`,
// which is held by the `Op` for the entire duration of the io-uring operation.
// The buffer pointer remains valid because Vec heap data doesn't move.
unsafe { Op::new(sqe, Read { fd, buf }) }
}
}
+25
View File
@@ -1,6 +1,31 @@
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::os::unix::ffi::OsStrExt;
use std::sync::Arc;
use std::{ffi::CString, io, path::Path};
pub(crate) type ArcFd = Arc<dyn AsRawFd + Send + Sync + 'static>;
/// Raw file descriptor trait for io-uring operations.
///
/// `Arc<dyn AsRawFd>` does not satisfy `AsRawFd` because the blanket impl
/// for `Arc<T>` requires `T: Sized`. This trait bridges that gap so both
/// `OwnedFd` and `ArcFd` can be used generically with `Op::read_at`.
pub(crate) trait UringFd: Send + Sync + 'static {
fn as_raw_fd(&self) -> RawFd;
}
impl UringFd for OwnedFd {
fn as_raw_fd(&self) -> RawFd {
AsRawFd::as_raw_fd(self)
}
}
impl UringFd for ArcFd {
fn as_raw_fd(&self) -> RawFd {
(**self).as_raw_fd()
}
}
pub(crate) fn cstr(p: &Path) -> io::Result<CString> {
Ok(CString::new(p.as_os_str().as_bytes())?)
}
+3
View File
@@ -15,6 +15,9 @@
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
))]
// loom is an internal implementation detail.
// Do not show "Available on non-loom only" label
#![cfg_attr(docsrs, doc(auto_cfg(hide(loom))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, allow(unused_attributes))]
#![cfg_attr(loom, allow(dead_code, unreachable_pub))]
-7
View File
@@ -58,13 +58,6 @@ pub(crate) mod sync {
}
pub(crate) use loom::sync::*;
pub(crate) mod atomic {
pub(crate) use loom::sync::atomic::*;
// TODO: implement a loom version
pub(crate) type StaticAtomicU64 = std::sync::atomic::AtomicU64;
}
}
pub(crate) mod rand {
+1 -1
View File
@@ -16,4 +16,4 @@ cfg_not_has_atomic_u64! {
mod imp;
}
pub(crate) use imp::{AtomicU64, StaticAtomicU64};
pub(crate) use imp::AtomicU64;
+1 -1
View File
@@ -71,7 +71,7 @@ pub(crate) mod sync {
pub(crate) mod atomic {
pub(crate) use crate::loom::std::atomic_u16::AtomicU16;
pub(crate) use crate::loom::std::atomic_u32::AtomicU32;
pub(crate) use crate::loom::std::atomic_u64::{AtomicU64, StaticAtomicU64};
pub(crate) use crate::loom::std::atomic_u64::AtomicU64;
pub(crate) use crate::loom::std::atomic_usize::AtomicUsize;
pub(crate) use std::sync::atomic::{fence, AtomicBool, AtomicPtr, AtomicU8, Ordering};
+35
View File
@@ -58,6 +58,18 @@ macro_rules! cfg_unix {
}
}
/// Enables Unix-specific code, including WASI.
/// Use this macro instead of `cfg(any(unix, target_os = "wasi"))` to generate docs properly.
macro_rules! cfg_unix_or_wasi {
($($item:item)*) => {
$(
#[cfg(any(all(doc, docsrs), unix, target_os = "wasi"))]
#[cfg_attr(docsrs, doc(cfg(any(unix, target_os = "wasi"))))]
$item
)*
}
}
/// Enables unstable Windows-specific code.
/// Use this macro instead of `cfg(windows)` to generate docs properly.
macro_rules! cfg_unstable_windows {
@@ -508,6 +520,20 @@ macro_rules! cfg_taskdump {
target_arch = "x86_64"
)
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
tokio_unstable,
feature = "taskdump",
feature = "rt",
target_os = "linux",
any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64"
)
)))
)]
$item
)*
};
@@ -674,6 +700,15 @@ macro_rules! cfg_not_wasi {
}
}
macro_rules! cfg_not_wasip1 {
($($item:item)*) => {
$(
#[cfg(not(all(target_os = "wasi", target_env = "p1")))]
$item
)*
}
}
macro_rules! cfg_is_wasm_not_wasi {
($($item:item)*) => {
$(
+2 -2
View File
@@ -659,7 +659,7 @@ doc! {macro_rules! select {
$crate::macros::support::poll_fn(|cx| {
// Return `Pending` when the task budget is depleted since budget-aware futures
// are going to yield anyway and other futures will not cooperate.
::std::task::ready!($crate::macros::support::poll_budget_available(cx));
$crate::macros::support::ready!($crate::macros::support::poll_budget_available(cx));
// Track if any branch returns pending. If no branch completes
// **or** returns pending, this implies that all branches are
@@ -699,7 +699,7 @@ doc! {macro_rules! select {
// Safety: future is stored on the stack above
// and never moved.
let mut fut = unsafe { Pin::new_unchecked(fut) };
let mut fut = unsafe { $crate::macros::support::Pin::new_unchecked(fut) };
// Try polling it
let out = match $crate::macros::support::Future::poll(fut, cx) {
+2 -1
View File
@@ -29,4 +29,5 @@ cfg_macros! {
pub use std::future::{Future, IntoFuture};
pub use std::pin::Pin;
pub use std::task::{Context, Poll};
pub use std::result::Result;
pub use std::task::{ready, Context, Poll};
+4 -4
View File
@@ -12,13 +12,13 @@ cfg_trace! {
macro_rules! trace_poll_op {
($name:expr, $poll:expr $(,)*) => {
match $poll {
std::task::Poll::Ready(t) => {
$crate::macros::support::Poll::Ready(t) => {
trace_op!($name, true);
std::task::Poll::Ready(t)
$crate::macros::support::Poll::Ready(t)
}
std::task::Poll::Pending => {
$crate::macros::support::Poll::Pending => {
trace_op!($name, false);
return std::task::Poll::Pending;
return $crate::macros::support::Poll::Pending;
}
}
};
+2 -2
View File
@@ -229,7 +229,7 @@ doc! {macro_rules! try_join {
if $crate::macros::support::Future::poll(fut.as_mut(), cx).is_pending() {
is_pending = true;
} else if fut.as_mut().output_mut().expect("expected completed future").is_err() {
return $crate::macros::support::Poll::Ready(Err(fut.take_output().expect("expected completed future").err().unwrap()))
return $crate::macros::support::Poll::Ready($crate::macros::support::Result::Err(fut.take_output().expect("expected completed future").err().unwrap()))
}
} else {
// Future skipped, one less future to skip in the next iteration
@@ -241,7 +241,7 @@ doc! {macro_rules! try_join {
if is_pending {
$crate::macros::support::Poll::Pending
} else {
$crate::macros::support::Poll::Ready(Ok(($({
$crate::macros::support::Poll::Ready($crate::macros::support::Result::Ok(($({
// Extract the future for this branch from the tuple.
let ( $($skip,)* fut, .. ) = &mut futures;
+2 -2
View File
@@ -29,7 +29,7 @@
//! [`AsyncFd`]: crate::io::unix::AsyncFd
mod addr;
cfg_not_wasi! {
cfg_not_wasip1! {
#[cfg(feature = "net")]
pub(crate) use addr::to_socket_addrs;
}
@@ -42,7 +42,7 @@ cfg_net! {
pub mod tcp;
pub use tcp::listener::TcpListener;
pub use tcp::stream::TcpStream;
cfg_not_wasi! {
cfg_not_wasip1! {
pub use tcp::socket::TcpSocket;
mod udp;
+5 -5
View File
@@ -2,7 +2,7 @@ use crate::io::{Interest, PollEvented};
use crate::net::tcp::TcpStream;
use crate::util::check_socket_for_blocking;
cfg_not_wasi! {
cfg_not_wasip1! {
use crate::net::{to_socket_addrs, ToSocketAddrs};
}
@@ -60,7 +60,7 @@ cfg_net! {
}
impl TcpListener {
cfg_not_wasi! {
cfg_not_wasip1! {
/// Creates a new `TcpListener`, which will be bound to the specified address.
///
/// The returned listener is ready for accepting connections.
@@ -292,7 +292,7 @@ impl TcpListener {
#[cfg(target_os = "wasi")]
{
use std::os::wasi::io::{FromRawFd, IntoRawFd};
use std::os::fd::{FromRawFd, IntoRawFd};
self.io
.into_inner()
.map(|io| io.into_raw_fd())
@@ -300,7 +300,7 @@ impl TcpListener {
}
}
cfg_not_wasi! {
cfg_not_wasip1! {
pub(crate) fn new(listener: mio::net::TcpListener) -> io::Result<TcpListener> {
let io = PollEvented::new(listener)?;
Ok(TcpListener { io })
@@ -427,7 +427,7 @@ cfg_unstable! {
#[cfg(target_os = "wasi")]
mod sys {
use super::TcpListener;
use std::os::wasi::prelude::*;
use std::os::fd::{RawFd, BorrowedFd, AsRawFd, AsFd};
impl AsRawFd for TcpListener {
fn as_raw_fd(&self) -> RawFd {
+1 -1
View File
@@ -2,7 +2,7 @@
pub(crate) mod listener;
cfg_not_wasi! {
cfg_not_wasip1! {
pub(crate) mod socket;
}
+31 -21
View File
@@ -4,8 +4,8 @@ use std::fmt;
use std::io;
use std::net::SocketAddr;
#[cfg(unix)]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
#[cfg(not(windows))]
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
use std::time::Duration;
cfg_windows! {
@@ -170,7 +170,8 @@ impl TcpSocket {
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
target_os = "openbsd",
target_os = "wasi",
))]
let ty = ty.nonblocking();
let inner = socket2::Socket::new(domain, ty, Some(socket2::Protocol::TCP))?;
@@ -182,7 +183,8 @@ impl TcpSocket {
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
target_os = "openbsd",
target_os = "wasi",
)))]
inner.set_nonblocking(true)?;
Ok(TcpSocket { inner })
@@ -597,7 +599,8 @@ impl TcpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
)))]
#[cfg_attr(
docsrs,
@@ -606,7 +609,8 @@ impl TcpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
))))
)]
pub fn tos_v4(&self) -> io::Result<u32> {
@@ -625,7 +629,8 @@ impl TcpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
)))]
#[cfg_attr(
docsrs,
@@ -634,7 +639,8 @@ impl TcpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
))))
)]
pub fn tos(&self) -> io::Result<u32> {
@@ -657,7 +663,8 @@ impl TcpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
)))]
#[cfg_attr(
docsrs,
@@ -666,7 +673,8 @@ impl TcpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
))))
)]
pub fn set_tos_v4(&self, tos: u32) -> io::Result<()> {
@@ -685,7 +693,8 @@ impl TcpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
)))]
#[cfg_attr(
docsrs,
@@ -694,7 +703,8 @@ impl TcpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
))))
)]
pub fn set_tos(&self, tos: u32) -> io::Result<()> {
@@ -826,7 +836,7 @@ impl TcpSocket {
/// ```
pub async fn connect(self, addr: SocketAddr) -> io::Result<TcpStream> {
if let Err(err) = self.inner.connect(&addr.into()) {
#[cfg(unix)]
#[cfg(not(windows))]
if err.raw_os_error() != Some(libc::EINPROGRESS) {
return Err(err);
}
@@ -835,9 +845,9 @@ impl TcpSocket {
return Err(err);
}
}
#[cfg(unix)]
#[cfg(not(windows))]
let mio = {
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::fd::{FromRawFd, IntoRawFd};
let raw_fd = self.inner.into_raw_fd();
unsafe { mio::net::TcpStream::from_raw_fd(raw_fd) }
@@ -891,9 +901,9 @@ impl TcpSocket {
/// ```
pub fn listen(self, backlog: u32) -> io::Result<TcpListener> {
self.inner.listen(backlog as i32)?;
#[cfg(unix)]
#[cfg(not(windows))]
let mio = {
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::fd::{FromRawFd, IntoRawFd};
let raw_fd = self.inner.into_raw_fd();
unsafe { mio::net::TcpListener::from_raw_fd(raw_fd) }
@@ -945,9 +955,9 @@ impl TcpSocket {
/// }
/// ```
pub fn from_std_stream(std_stream: std::net::TcpStream) -> TcpSocket {
#[cfg(unix)]
#[cfg(not(windows))]
{
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::fd::{FromRawFd, IntoRawFd};
let raw_fd = std_stream.into_raw_fd();
unsafe { TcpSocket::from_raw_fd(raw_fd) }
@@ -981,8 +991,8 @@ impl fmt::Debug for TcpSocket {
// These trait implementations can't be build on Windows, so we completely
// ignore them, even when building documentation.
#[cfg(unix)]
cfg_unix! {
#[cfg(any(unix, target_os = "wasi"))]
cfg_unix_or_wasi! {
impl AsRawFd for TcpSocket {
fn as_raw_fd(&self) -> RawFd {
self.inner.as_raw_fd()
+7 -4
View File
@@ -1,7 +1,10 @@
cfg_not_wasi! {
use std::time::Duration;
}
cfg_not_wasip1! {
use crate::net::{to_socket_addrs, ToSocketAddrs};
use std::future::poll_fn;
use std::time::Duration;
}
use crate::io::{AsyncRead, AsyncWrite, Interest, PollEvented, ReadBuf, Ready};
@@ -73,7 +76,7 @@ cfg_net! {
}
impl TcpStream {
cfg_not_wasi! {
cfg_not_wasip1! {
/// Opens a TCP connection to a remote host.
///
/// `addr` is an address of the remote host. Anything which implements the
@@ -272,7 +275,7 @@ impl TcpStream {
#[cfg(target_os = "wasi")]
{
use std::os::wasi::io::{FromRawFd, IntoRawFd};
use std::os::fd::{FromRawFd, IntoRawFd};
self.io
.into_inner()
.map(|io| io.into_raw_fd())
@@ -1564,7 +1567,7 @@ cfg_windows! {
#[cfg(all(tokio_unstable, target_os = "wasi"))]
mod sys {
use super::TcpStream;
use std::os::wasi::prelude::*;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
impl AsRawFd for TcpStream {
fn as_raw_fd(&self) -> RawFd {
+47 -22
View File
@@ -254,9 +254,9 @@ impl UdpSocket {
/// [`std::net::UdpSocket`]: std::net::UdpSocket
/// [`set_nonblocking`]: fn@std::net::UdpSocket::set_nonblocking
pub fn into_std(self) -> io::Result<std::net::UdpSocket> {
#[cfg(unix)]
#[cfg(not(windows))]
{
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::fd::{FromRawFd, IntoRawFd};
self.io
.into_inner()
.map(IntoRawFd::into_raw_fd)
@@ -531,7 +531,12 @@ impl UdpSocket {
/// The [`connect`] method will connect this socket to a remote address.
/// This method will fail if the socket is not connected.
///
/// This method may fail with a [`ConnectionRefused`] error if the remote
/// address has replied with ICMP Unreachable to a previously sent packet.
/// However, this behavior depends on the OS.
///
/// [`connect`]: method@Self::connect
/// [`ConnectionRefused`]: std::io::ErrorKind::ConnectionRefused
///
/// # Return
///
@@ -785,7 +790,7 @@ impl UdpSocket {
pub async fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
self.io
.registration()
.async_io(Interest::READABLE, || self.io.recv(buf))
.async_io(Interest::READABLE | Interest::ERROR, || self.io.recv(buf))
.await
}
@@ -986,7 +991,9 @@ impl UdpSocket {
/// }
/// ```
pub async fn recv_buf<B: BufMut>(&self, buf: &mut B) -> io::Result<usize> {
self.io.registration().async_io(Interest::READABLE, || {
self.io
.registration()
.async_io(Interest::READABLE | Interest::ERROR, || {
let dst = buf.chunk_mut();
let dst =
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
@@ -1000,7 +1007,8 @@ impl UdpSocket {
}
Ok(n)
}).await
})
.await
}
/// Tries to receive a single datagram message on the socket. On success,
@@ -1117,7 +1125,9 @@ impl UdpSocket {
/// }
/// ```
pub async fn recv_buf_from<B: BufMut>(&self, buf: &mut B) -> io::Result<(usize, SocketAddr)> {
self.io.registration().async_io(Interest::READABLE, || {
self.io
.registration()
.async_io(Interest::READABLE | Interest::ERROR, || {
let dst = buf.chunk_mut();
let dst =
unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]) };
@@ -1130,8 +1140,9 @@ impl UdpSocket {
buf.advance_mut(n);
}
Ok((n,addr))
}).await
Ok((n, addr))
})
.await
}
}
@@ -1315,7 +1326,9 @@ impl UdpSocket {
pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
self.io
.registration()
.async_io(Interest::READABLE, || self.io.recv_from(buf))
.async_io(Interest::READABLE | Interest::ERROR, || {
self.io.recv_from(buf)
})
.await
}
@@ -1556,7 +1569,7 @@ impl UdpSocket {
pub async fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
self.io
.registration()
.async_io(Interest::READABLE, || self.io.peek(buf))
.async_io(Interest::READABLE | Interest::ERROR, || self.io.peek(buf))
.await
}
@@ -1698,7 +1711,9 @@ impl UdpSocket {
pub async fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
self.io
.registration()
.async_io(Interest::READABLE, || self.io.peek_from(buf))
.async_io(Interest::READABLE | Interest::ERROR, || {
self.io.peek_from(buf)
})
.await
}
@@ -1815,7 +1830,9 @@ impl UdpSocket {
pub async fn peek_sender(&self) -> io::Result<SocketAddr> {
self.io
.registration()
.async_io(Interest::READABLE, || self.peek_sender_inner())
.async_io(Interest::READABLE | Interest::ERROR, || {
self.peek_sender_inner()
})
.await
}
@@ -2084,7 +2101,8 @@ impl UdpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
)))]
#[cfg_attr(
docsrs,
@@ -2093,7 +2111,8 @@ impl UdpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
))))
)]
pub fn tos_v4(&self) -> io::Result<u32> {
@@ -2112,7 +2131,8 @@ impl UdpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
)))]
#[cfg_attr(
docsrs,
@@ -2121,7 +2141,8 @@ impl UdpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
))))
)]
pub fn tos(&self) -> io::Result<u32> {
@@ -2144,7 +2165,8 @@ impl UdpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
)))]
#[cfg_attr(
docsrs,
@@ -2153,7 +2175,8 @@ impl UdpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
))))
)]
pub fn set_tos_v4(&self, tos: u32) -> io::Result<()> {
@@ -2172,7 +2195,8 @@ impl UdpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
)))]
#[cfg_attr(
docsrs,
@@ -2181,7 +2205,8 @@ impl UdpSocket {
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku"
target_os = "haiku",
target_os = "wasi",
))))
)]
pub fn set_tos(&self, tos: u32) -> io::Result<()> {
@@ -2297,10 +2322,10 @@ impl fmt::Debug for UdpSocket {
}
}
#[cfg(unix)]
#[cfg(not(windows))]
mod sys {
use super::UdpSocket;
use std::os::unix::prelude::*;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd};
impl AsRawFd for UdpSocket {
fn as_raw_fd(&self) -> RawFd {
+114
View File
@@ -617,6 +617,14 @@ impl Sender {
/// number of bytes written. If the pipe is not ready to write data,
/// `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Notes
///
/// To avoid unnecessary syscalls, this will only attempt the write
/// operation if the OS has informed Tokio that this pipe has become
/// writable. Because of this, `try_write()` may fail with a
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
/// this pipe has become writable.
///
/// # Examples
///
/// ```no_run
@@ -650,6 +658,8 @@ impl Sender {
/// Ok(())
/// }
/// ```
///
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
pub fn try_write(&self, buf: &[u8]) -> io::Result<usize> {
self.io
.registration()
@@ -681,6 +691,14 @@ impl Sender {
/// number of bytes written. If the pipe is not ready to write data,
/// `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Notes
///
/// To avoid unnecessary syscalls, this will only attempt the write
/// operation if the OS has informed Tokio that this pipe has become
/// writable. Because of this, `try_write_vectored()` may fail with a
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
/// this pipe has become writable.
///
/// # Examples
///
/// ```no_run
@@ -716,12 +734,46 @@ impl Sender {
/// Ok(())
/// }
/// ```
///
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
pub fn try_write_vectored(&self, buf: &[io::IoSlice<'_>]) -> io::Result<usize> {
self.io
.registration()
.try_io(Interest::WRITABLE, || (&*self.io).write_vectored(buf))
}
/// Tries to write from the socket using a user-provided IO operation.
///
/// If the socket is ready, the provided closure is called. The closure
/// should attempt to perform IO operation on the socket by manually
/// calling the appropriate syscall. If the operation fails because the
/// socket is not actually ready, then the closure should return a
/// `WouldBlock` error and the readiness flag is cleared. The return value
/// of the closure is then returned by `try_io`.
///
/// If the socket is not ready, then the closure is not called
/// and a `WouldBlock` error is returned.
///
/// The closure should only return a `WouldBlock` error if it has performed
/// an IO operation on the socket that failed due to the socket not being
/// ready. Returning a `WouldBlock` error in any other situation will
/// incorrectly clear the readiness flag, which can cause the socket to
/// behave incorrectly.
///
/// The closure should not perform the IO operation using any of the methods
/// defined on the Tokio `pipe::Sender` type, as this will mess with the
/// readiness flag and can cause the socket to behave incorrectly.
///
/// Usually, [`writable()`] or [`ready()`] is used with this function.
///
/// [`writable()`]: Self::writable()
/// [`ready()`]: Self::ready()
pub fn try_io<R>(&self, f: impl FnOnce() -> io::Result<R>) -> io::Result<R> {
self.io
.registration()
.try_io(Interest::WRITABLE, || self.io.try_io(f))
}
/// Converts the pipe into an [`OwnedFd`] in blocking mode.
///
/// This function will deregister this pipe end from the event loop, set
@@ -1120,6 +1172,14 @@ impl Receiver {
/// If the pipe is not ready to read data,
/// `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Notes
///
/// To avoid unnecessary syscalls, this will only attempt the read
/// operation if the OS has informed Tokio that this pipe has become
/// readable. Because of this, `try_read()` may fail with a
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
/// this pipe has become readable.
///
/// # Examples
///
/// ```no_run
@@ -1157,6 +1217,8 @@ impl Receiver {
/// Ok(())
/// }
/// ```
///
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
pub fn try_read(&self, buf: &mut [u8]) -> io::Result<usize> {
self.io
.registration()
@@ -1188,6 +1250,14 @@ impl Receiver {
/// closed and will no longer write data. If the pipe is not ready to read
/// data `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Notes
///
/// To avoid unnecessary syscalls, this will only attempt the read
/// operation if the OS has informed Tokio that this pipe has become
/// readable. Because of this, `try_read_vectored()` may fail with a
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
/// this pipe has become readable.
///
/// # Examples
///
/// ```no_run
@@ -1231,12 +1301,46 @@ impl Receiver {
/// Ok(())
/// }
/// ```
///
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
pub fn try_read_vectored(&self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
self.io
.registration()
.try_io(Interest::READABLE, || (&*self.io).read_vectored(bufs))
}
/// Tries to read to the socket using a user-provided IO operation.
///
/// If the socket is ready, the provided closure is called. The closure
/// should attempt to perform IO operation on the socket by manually
/// calling the appropriate syscall. If the operation fails because the
/// socket is not actually ready, then the closure should return a
/// `WouldBlock` error and the readiness flag is cleared. The return value
/// of the closure is then returned by `try_io`.
///
/// If the socket is not ready, then the closure is not called
/// and a `WouldBlock` error is returned.
///
/// The closure should only return a `WouldBlock` error if it has performed
/// an IO operation on the socket that failed due to the socket not being
/// ready. Returning a `WouldBlock` error in any other situation will
/// incorrectly clear the readiness flag, which can cause the socket to
/// behave incorrectly.
///
/// The closure should not perform the IO operation using any of the methods
/// defined on the Tokio `pipe::Receiver` type, as this will mess with the
/// readiness flag and can cause the socket to behave incorrectly.
///
/// Usually, [`readable()`] or [`ready()`] is used with this function.
///
/// [`readable()`]: Self::readable()
/// [`ready()`]: Self::ready()
pub fn try_io<R>(&self, f: impl FnOnce() -> io::Result<R>) -> io::Result<R> {
self.io
.registration()
.try_io(Interest::READABLE, || self.io.try_io(f))
}
cfg_io_util! {
/// Tries to read data from the pipe into the provided buffer, advancing the
/// buffer's internal cursor, returning how many bytes were read.
@@ -1258,6 +1362,14 @@ impl Receiver {
/// closed and will no longer write data. If the pipe is not ready to read
/// data `Err(io::ErrorKind::WouldBlock)` is returned.
///
/// # Notes
///
/// To avoid unnecessary syscalls, this will only attempt the read
/// operation if the OS has informed Tokio that this pipe has become
/// readable. Because of this, `try_read_buf()` may fail with a
/// [`WouldBlock`] error if Tokio has not yet heard from the OS that
/// this pipe has become readable.
///
/// # Examples
///
/// ```no_run
@@ -1294,6 +1406,8 @@ impl Receiver {
/// Ok(())
/// }
/// ```
///
/// [`WouldBlock`]: std::io::ErrorKind::WouldBlock
pub fn try_read_buf<B: BufMut>(&self, buf: &mut B) -> io::Result<usize> {
self.io.registration().try_io(Interest::READABLE, || {
use std::io::Read;
+2 -2
View File
@@ -62,7 +62,7 @@ pub(crate) use self::impl_solaris::get_peer_cred;
#[cfg(target_os = "aix")]
pub(crate) use self::impl_aix::get_peer_cred;
#[cfg(any(target_os = "espidf", target_os = "vita"))]
#[cfg(any(target_os = "espidf", target_os = "vita", target_os = "hurd"))]
pub(crate) use self::impl_noproc::get_peer_cred;
#[cfg(any(
@@ -317,7 +317,7 @@ pub(crate) mod impl_aix {
}
}
#[cfg(any(target_os = "espidf", target_os = "vita"))]
#[cfg(any(target_os = "espidf", target_os = "vita", target_os = "hurd"))]
pub(crate) mod impl_noproc {
use crate::net::unix::UnixStream;
use std::io;
+103 -7
View File
@@ -5,7 +5,9 @@ use crate::runtime::{
blocking, driver, Callback, HistogramBuilder, Runtime, TaskCallback, TimerFlavor,
};
#[cfg(tokio_unstable)]
use crate::runtime::{metrics::HistogramConfiguration, LocalOptions, LocalRuntime, TaskMeta};
use crate::runtime::{metrics::HistogramConfiguration, TaskMeta};
use crate::runtime::{LocalOptions, LocalRuntime};
use crate::util::rand::{RngSeed, RngSeedGenerator};
use crate::runtime::blocking::BlockingPool;
@@ -54,6 +56,9 @@ pub struct Builder {
/// Runtime type
kind: Kind,
/// Name of the runtime.
name: Option<String>,
/// Whether or not to enable the I/O driver
enable_io: bool,
nevents: usize,
@@ -137,6 +142,10 @@ pub struct Builder {
pub(super) unhandled_panic: UnhandledPanic,
timer_flavor: TimerFlavor,
/// Whether or not to enable eager hand-off for the I/O and time drivers (in
/// `tokio_unstable`).
enable_eager_driver_handoff: bool,
}
cfg_unstable! {
@@ -238,7 +247,7 @@ impl Builder {
/// Configuration methods can be chained on the return value.
///
/// To spawn non-`Send` tasks on the resulting runtime, combine it with a
/// [`LocalSet`], or call [`build_local`] to create a [`LocalRuntime`] (unstable).
/// [`LocalSet`], or call [`build_local`] to create a [`LocalRuntime`].
///
/// [`LocalSet`]: crate::task::LocalSet
/// [`LocalRuntime`]: crate::runtime::LocalRuntime
@@ -271,6 +280,9 @@ impl Builder {
Builder {
kind,
// Default runtime name
name: None,
// I/O defaults to "off"
enable_io: false,
nevents: 1024,
@@ -326,6 +338,9 @@ impl Builder {
disable_lifo_slot: false,
timer_flavor: TimerFlavor::Traditional,
// Eager driver handoff is disabled by default.
enable_eager_driver_handoff: false,
}
}
@@ -406,6 +421,40 @@ impl Builder {
self
}
/// Enable eager hand-off of the I/O and time drivers for multi-threaded
/// runtimes, which is disabled by default.
///
/// When this option is enabled, a worker thread which has parked on the I/O
/// or time driver will notify another worker thread once it is preparing to
/// begin polling a task from the run queue, so that the notified worker can
/// begin polling the I/O or time driver. This can reduce the latency with
/// which I/O and timer notifications are processed, especially when some
/// tasks have polls that take a long time to complete. In addition, it can
/// reduce the risk of a deadlock which may occur when a task blocks the
/// worker thread which is holding the I/O or time driver until some other
/// task, which is waiting for a notification from *that* driver, unblocks
/// it.
///
/// This option is disabled by default, as enabling it may potentially
/// increase contention due to extra synchronization in cross-driver
/// wakeups.
///
/// This option only applies to multi-threaded runtimes. Attempting to use
/// this option with any other runtime type will have no effect.
///
/// **Note**: This is an [unstable API][unstable]. Eager driver hand-off is
/// an experimental feature whose behavior may be removed or changed in 1.x
/// releases. See [the documentation on unstable features][unstable] for
/// details.
///
/// [unstable]: crate#unstable-features
#[cfg(all(tokio_unstable, feature = "rt-multi-thread"))]
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt-multi-thread"))))]
pub fn enable_eager_driver_handoff(&mut self) -> &mut Self {
self.enable_eager_driver_handoff = true;
self
}
/// Sets the number of worker threads the `Runtime` will use.
///
/// This can be any number above 0 though it is advised to keep this value
@@ -538,6 +587,34 @@ impl Builder {
self
}
/// Sets the name of the runtime.
///
/// # Examples
///
/// ```
/// # #[cfg(not(target_family = "wasm"))]
/// # {
/// # use tokio::runtime;
///
/// # pub fn main() {
/// let rt = runtime::Builder::new_multi_thread()
/// .name("my-runtime")
/// .build();
/// # }
/// # }
/// ```
/// # Panics
///
/// This function will panic if an empty value is passed as an argument.
///
#[track_caller]
pub fn name(&mut self, val: impl Into<String>) -> &mut Self {
let val = val.into();
assert!(!val.trim().is_empty(), "runtime name shouldn't be empty");
self.name = Some(val);
self
}
/// Sets a function used to generate the name of threads spawned by the `Runtime`'s thread pool.
///
/// The default name fn is `|| "tokio-rt-worker".into()`.
@@ -1014,8 +1091,6 @@ impl Builder {
/// });
/// ```
#[allow(unused_variables, unreachable_patterns)]
#[cfg(tokio_unstable)]
#[cfg_attr(docsrs, doc(cfg(tokio_unstable)))]
pub fn build_local(&mut self, options: LocalOptions) -> io::Result<LocalRuntime> {
match &self.kind {
Kind::CurrentThread => self.build_current_thread_local_runtime(),
@@ -1571,7 +1646,6 @@ impl Builder {
))
}
#[cfg(tokio_unstable)]
fn build_current_thread_local_runtime(&mut self) -> io::Result<LocalRuntime> {
use crate::runtime::local_runtime::LocalRuntimeScheduler;
@@ -1629,10 +1703,15 @@ impl Builder {
#[cfg(tokio_unstable)]
unhandled_panic: self.unhandled_panic.clone(),
disable_lifo_slot: self.disable_lifo_slot,
// This setting never makes sense for a current thread runtime,
// as it only configures how the I/O driver is stolen across
// workers.
enable_eager_driver_handoff: false,
seed_generator: seed_generator_1,
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
},
local_tid,
self.name.clone(),
);
let handle = Handle {
@@ -1810,10 +1889,12 @@ cfg_rt_multi_thread! {
#[cfg(tokio_unstable)]
unhandled_panic: self.unhandled_panic.clone(),
disable_lifo_slot: self.disable_lifo_slot,
enable_eager_driver_handoff: self.enable_eager_driver_handoff,
seed_generator: seed_generator_1,
metrics_poll_count_histogram: self.metrics_poll_count_histogram_builder(),
},
self.timer_flavor,
self.name.clone(),
);
let handle = Handle { inner: scheduler::Handle::MultiThread(handle) };
@@ -1829,7 +1910,13 @@ cfg_rt_multi_thread! {
impl fmt::Debug for Builder {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("Builder")
let mut debug = fmt.debug_struct("Builder");
if let Some(name) = &self.name {
debug.field("name", name);
}
debug
.field("worker_threads", &self.worker_threads)
.field("max_blocking_threads", &self.max_blocking_threads)
.field(
@@ -1841,6 +1928,15 @@ impl fmt::Debug for Builder {
.field("before_stop", &self.before_stop.as_ref().map(|_| "..."))
.field("before_park", &self.before_park.as_ref().map(|_| "..."))
.field("after_unpark", &self.after_unpark.as_ref().map(|_| "..."))
.finish()
.field(
"enable_eager_driver_handoff",
&self.enable_eager_driver_handoff,
);
if self.name.is_none() {
debug.finish_non_exhaustive()
} else {
debug.finish()
}
}
}
+5
View File
@@ -51,4 +51,9 @@ pub(crate) struct Config {
#[cfg(tokio_unstable)]
/// How to respond to unhandled task panics.
pub(crate) unhandled_panic: crate::runtime::UnhandledPanic,
/// If `true`, an idle worker is woken whenever a worker thread transitions
/// from polling the I/O driver to polling its own tasks (requires
/// `tokio_unstable`).
pub(crate) enable_eager_driver_handoff: bool,
}
+5
View File
@@ -159,6 +159,11 @@ cfg_rt! {
CONTEXT.try_with(|ctx| ctx.current_task_id.get()).unwrap_or(None)
}
#[cfg(tokio_unstable)]
pub(crate) fn worker_index() -> Option<usize> {
with_scheduler(|ctx| ctx.and_then(|c| c.worker_index()))
}
#[track_caller]
pub(crate) fn defer(waker: &Waker) {
with_scheduler(|maybe_scheduler| {
+6 -1
View File
@@ -1,6 +1,9 @@
use crate::io::blocking::Buf;
use crate::io::uring::open::Open;
use crate::io::uring::read::Read;
use crate::io::uring::utils::ArcFd;
use crate::io::uring::write::Write;
use crate::runtime::Handle;
use io_uring::cqueue;
@@ -8,6 +11,7 @@ use io_uring::squeue::Entry;
use std::future::Future;
use std::io::{self, Error};
use std::mem;
use std::os::fd::OwnedFd;
use std::pin::Pin;
use std::task::{Context, Poll, Waker};
@@ -18,7 +22,8 @@ use std::task::{Context, Poll, Waker};
pub(crate) enum CancelData {
Open(Open),
Write(Write),
Read(Read),
ReadVec(Read<Vec<u8>, OwnedFd>),
ReadBuf(Read<Buf, ArcFd>),
}
#[derive(Debug)]
+1 -1
View File
@@ -5,7 +5,7 @@
use crate::task::Id;
use std::{fmt, future::Future, path::Path};
pub use crate::runtime::task::trace::Root;
pub use crate::runtime::task::trace::{trace_with, Root, TraceMeta};
/// A snapshot of a runtime's state.
///
+21
View File
@@ -474,6 +474,27 @@ impl Handle {
runtime::Id::new(owned_id)
}
/// Returns the name of the current `Runtime`.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Handle;
///
/// #[tokio::main(flavor = "current_thread", name = "my-runtime")]
/// async fn main() {
/// println!("Current runtime name: {}", Handle::current().name().unwrap());
/// }
/// ```
///
pub fn name(&self) -> Option<&str> {
match &self.inner {
scheduler::Handle::CurrentThread(handle) => handle.name(),
#[cfg(feature = "rt-multi-thread")]
scheduler::Handle::MultiThread(handle) => handle.name(),
}
}
/// Returns a view that lets you get information about how the runtime
/// is performing.
pub fn metrics(&self) -> RuntimeMetrics {
+51 -4
View File
@@ -2,12 +2,14 @@ use io_uring::{squeue::Entry, IoUring, Probe};
use mio::unix::SourceFd;
use slab::Slab;
use crate::runtime::driver::op::CancelData;
use crate::runtime::driver::op::CqeResult;
use crate::runtime::driver::op::{Cancellable, Lifecycle};
use crate::{io::Interest, loom::sync::Mutex};
use super::{Handle, TOKEN_WAKEUP};
use std::os::fd::{AsRawFd, RawFd};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::{io, mem, task::Waker};
const DEFAULT_RING_SIZE: u32 = 256;
@@ -77,9 +79,16 @@ impl UringContext {
waker.wake_by_ref();
*ops.get_mut(idx).unwrap() = Lifecycle::Completed(cqe);
}
Some(Lifecycle::Cancelled(_)) => {
Some(Lifecycle::Cancelled(cancel_data)) => {
if let CancelData::Open(_) = cancel_data {
if let Ok(fd) = CqeResult::from(cqe).result {
// SAFETY: the successful CQE result provides
// a non-negative integer, and the event is
// related to an open operation.
unsafe { OwnedFd::from_raw_fd(fd as i32) };
}
}
// Op future was cancelled, so we discard the result.
// We just remove the entry from the slab.
ops.remove(idx);
}
Some(other) => {
@@ -147,6 +156,16 @@ impl Drop for UringContext {
for cqe in self.ring_mut().completion() {
let idx = cqe.user_data() as usize;
if let Some(Lifecycle::Cancelled(CancelData::Open(_))) = ops.get_mut(idx) {
if let Ok(fd) = CqeResult::from(cqe).result {
// SAFETY: the successful CQE result provides
// a non-negative integer, and the event is
// related to an open operation.
unsafe { OwnedFd::from_raw_fd(fd as i32) };
}
};
ops.remove(idx);
}
}
@@ -164,6 +183,26 @@ impl Handle {
&self.uring_context
}
/// Returns `true` if io_uring has already been initialized and the given
/// opcode is supported. Returns `false` if io_uring hasn't been
/// initialized yet or is unsupported. Unlike `check_and_init`, this
/// doesn't attempt initialization.
#[cfg_attr(test, allow(dead_code))]
pub(crate) fn is_uring_ready(&self, opcode: u8) -> bool {
self.uring_probe
.get()
.and_then(|opt| opt.as_ref())
.is_some_and(|probe| probe.is_supported(opcode))
}
/// Returns `true` if the io_uring probe has already been attempted
/// (regardless of whether io_uring is supported). Returns `false` if
/// no probe has been attempted yet.
#[cfg_attr(test, allow(dead_code))]
pub(crate) fn is_uring_probed(&self) -> bool {
self.uring_probe.get().is_some()
}
/// Check if the io_uring context is initialized. If not, it will try to initialize it.
/// Then, check if the provided opcode is supported.
///
@@ -272,7 +311,15 @@ impl Handle {
match mem::replace(lifecycle, Lifecycle::Cancelled(cancel_data)) {
Lifecycle::Submitted | Lifecycle::Waiting(_) => (),
// The driver saw the completion, but it was never polled.
Lifecycle::Completed(_) => {
Lifecycle::Completed(cqe) => {
if let Lifecycle::Cancelled(CancelData::Open(_)) = lifecycle {
if let Ok(fd) = CqeResult::from(cqe).result {
// SAFETY: the successful CQE result provides
// a non-negative integer, and the event is
// related to an open operation.
unsafe { OwnedFd::from_raw_fd(fd as i32) };
}
}
// We can safely remove the entry from the slab, as it has already been completed.
ops.remove(index);
}
+1 -1
View File
@@ -118,7 +118,7 @@ impl Registration {
// Uses the poll path, requiring the caller to ensure mutual exclusion for
// correctness. Only the last task to call this function is notified.
#[cfg(not(target_os = "wasi"))]
#[cfg(not(all(target_os = "wasi", target_env = "p1")))]
pub(crate) fn poll_read_io<R>(
&self,
cx: &mut Context<'_>,
@@ -29,7 +29,6 @@ use std::time::Duration;
/// [runtime]: crate::runtime::Runtime
/// [module]: crate::runtime
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(tokio_unstable)))]
pub struct LocalRuntime {
/// Task scheduler
scheduler: LocalRuntimeScheduler,
+50 -5
View File
@@ -36,9 +36,9 @@
//! | Yes | No
//! | |
//! V |
//! +--------------------------+ |
//! | Local Runtime (unstable) | |
//! +--------------------------+ |
//! +---------------+ |
//! | Local Runtime | |
//! +---------------+ |
//! |
//! v
//! +------------------------+
@@ -373,6 +373,13 @@
//! When a task is woken from a thread that is not a worker thread, then the
//! task is placed in the global queue.
//!
//! # Performance tuning
//!
//! ## File descriptor table pre-warming
//!
//! On Linux, file descriptor table growth can stall worker threads. See the
//! [`prewarm-fd-table`] example.
//!
//! [`poll`]: std::future::Future::poll
//! [`wake`]: std::task::Waker::wake
//! [`yield_now`]: crate::task::yield_now
@@ -385,6 +392,7 @@
//! [the lifo slot optimization]: crate::runtime::Builder::disable_lifo_slot
//! [coop budget]: crate::task::coop#cooperative-scheduling
//! [`worker_mean_poll_time`]: crate::runtime::RuntimeMetrics::worker_mean_poll_time
//! [`prewarm-fd-table`]: https://github.com/tokio-rs/tokio/blob/master/examples/prewarm-fd-table.rs
// At the top due to macros
#[cfg(test)]
@@ -567,8 +575,42 @@ cfg_rt! {
pub use self::builder::UnhandledPanic;
pub use crate::util::rand::RngSeed;
mod local_runtime;
pub use local_runtime::{LocalRuntime, LocalOptions};
/// Returns the index of the current worker thread, if called from a
/// runtime worker thread.
///
/// The returned value is a 0-based index matching the worker indices
/// used by [`RuntimeMetrics`] methods such as
/// [`worker_total_busy_duration`](RuntimeMetrics::worker_total_busy_duration).
///
/// Returns `None` when called from outside a runtime worker thread
/// (for example, from a blocking thread or a non-Tokio thread). On the
/// multi-thread runtime, the thread that calls [`Runtime::block_on`] is
/// not a worker thread, so this also returns `None` there.
///
/// For the current-thread runtime and [`LocalRuntime`], this always
/// returns `Some(0)` (including inside `block_on`, since the calling
/// thread *is* the worker thread).
///
/// Note that the result may change across `.await` points, as the
/// task may be moved to a different worker thread by the scheduler.
///
/// # Examples
///
/// ```
/// # #[cfg(not(target_family = "wasm"))]
/// # {
/// #[tokio::main(flavor = "multi_thread", worker_threads = 4)]
/// async fn main() {
/// let index = tokio::spawn(async {
/// tokio::runtime::worker_index()
/// }).await.unwrap();
/// println!("Task ran on worker {:?}", index);
/// }
/// # }
/// ```
pub fn worker_index() -> Option<usize> {
context::worker_index()
}
}
cfg_taskdump! {
@@ -590,6 +632,9 @@ cfg_rt! {
mod runtime;
pub use runtime::{Runtime, RuntimeFlavor, is_rt_shutdown_err};
mod local_runtime;
pub use local_runtime::{LocalRuntime, LocalOptions};
mod id;
pub use id::Id;
@@ -34,6 +34,9 @@ pub(crate) struct CurrentThread {
/// Handle to the current thread scheduler
pub(crate) struct Handle {
/// The name of the runtime
name: Option<String>,
/// Scheduler state shared across threads
shared: Shared,
@@ -132,6 +135,7 @@ impl CurrentThread {
seed_generator: RngSeedGenerator,
config: Config,
local_tid: Option<ThreadId>,
name: Option<String>,
) -> (CurrentThread, Arc<Handle>) {
let worker_metrics = WorkerMetrics::from_config(&config);
worker_metrics.set_thread_id(thread::current().id());
@@ -142,6 +146,7 @@ impl CurrentThread {
.unwrap_or(DEFAULT_GLOBAL_QUEUE_INTERVAL);
let handle = Arc::new(Handle {
name,
task_hooks: TaskHooks {
task_spawn_callback: config.before_spawn.clone(),
task_terminate_callback: config.after_termination.clone(),
@@ -380,8 +385,6 @@ impl Context {
core = c;
}
// If `before_park` spawns a task (or otherwise schedules work for us), then we should not
// park the thread.
if !self.has_pending_work(&core) {
// Park until the thread is signaled
core.metrics.about_to_park();
@@ -391,6 +394,15 @@ impl Context {
core.metrics.unparked();
core.submit_metrics(handle);
} else {
// `before_park` scheduled work (e.g. an `on_thread_park` hook that woke the
// `block_on` future), so we don't block. We must still poll the driver once
// without blocking, or timer and I/O events would stall under a runtime driven
// by repeated short `block_on` calls. See
// <https://github.com/tokio-rs/tokio/issues/8212>.
core.submit_metrics(handle);
core = self.park_internal(core, handle, &mut driver, Some(Duration::from_millis(0)));
}
if let Some(f) = &handle.shared.config.after_unpark {
@@ -639,6 +651,10 @@ impl Handle {
pub(crate) fn owned_id(&self) -> NonZeroU64 {
self.shared.owned.id
}
pub(crate) fn name(&self) -> Option<&str> {
self.name.as_deref()
}
}
impl fmt::Debug for Handle {
+9
View File
@@ -293,6 +293,15 @@ cfg_rt! {
match_flavor!(self, Context(context) => context.defer(waker));
}
#[cfg(tokio_unstable)]
pub(crate) fn worker_index(&self) -> Option<usize> {
match self {
Context::CurrentThread(_) => Some(0),
#[cfg(feature = "rt-multi-thread")]
Context::MultiThread(context) => Some(context.worker_index()),
}
}
#[cfg(all(tokio_unstable, feature = "time", feature = "rt-multi-thread"))]
pub(crate) fn with_time_temp_local_context<F, R>(&self, f: F) -> R
where
@@ -23,6 +23,9 @@ use crate::loom::sync::atomic::{AtomicBool, Ordering::SeqCst};
/// Handle to the multi thread scheduler
pub(crate) struct Handle {
/// The name of the runtime
pub(super) name: Option<String>,
/// Task spawner
pub(super) shared: worker::Shared,
@@ -123,6 +126,10 @@ impl Handle {
pub(crate) fn owned_id(&self) -> NonZeroU64 {
self.shared.owned.id
}
pub(crate) fn name(&self) -> Option<&str> {
self.name.as_deref()
}
}
impl fmt::Debug for Handle {
@@ -54,6 +54,7 @@ pub(crate) struct MultiThread;
// ===== impl MultiThread =====
impl MultiThread {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
size: usize,
driver: Driver,
@@ -62,6 +63,7 @@ impl MultiThread {
seed_generator: RngSeedGenerator,
config: Config,
timer_flavor: TimerFlavor,
name: Option<String>,
) -> (MultiThread, Arc<Handle>, Launch) {
let parker = Parker::new(driver);
let (handle, launch) = worker::create(
@@ -72,6 +74,7 @@ impl MultiThread {
seed_generator,
config,
timer_flavor,
name,
);
(MultiThread, handle, launch)
@@ -21,6 +21,13 @@ pub(crate) struct Unparker {
inner: Arc<Inner>,
}
/// Represents how a worker thread was parked
#[derive(Copy, Clone, Eq, PartialEq)]
pub(crate) enum HadDriver {
Yes,
No,
}
struct Inner {
/// Avoids entering the park if possible
state: AtomicUsize,
@@ -66,8 +73,8 @@ impl Parker {
}
}
pub(crate) fn park(&mut self, handle: &driver::Handle) {
self.inner.park(handle);
pub(crate) fn park(&mut self, handle: &driver::Handle) -> HadDriver {
self.inner.park(handle)
}
/// Parks the current thread for up to `duration`.
@@ -75,11 +82,16 @@ impl Parker {
/// This function tries to acquire the driver lock. If it succeeds, it
/// parks using the driver. Otherwise, it fails back to using a condvar,
/// unless the duration is zero, in which case it returns immediately.
pub(crate) fn park_timeout(&mut self, handle: &driver::Handle, duration: Duration) {
pub(crate) fn park_timeout(
&mut self,
handle: &driver::Handle,
duration: Duration,
) -> HadDriver {
if let Some(mut driver) = self.inner.shared.driver.try_lock() {
self.inner.park_driver(&mut driver, handle, Some(duration));
self.inner.park_driver(&mut driver, handle, Some(duration))
} else if !duration.is_zero() {
self.inner.park_condvar(Some(duration));
HadDriver::No
} else {
// https://github.com/tokio-rs/tokio/issues/6536
// Hacky, but it's just for loom tests. The counter gets incremented during
@@ -87,6 +99,7 @@ impl Parker {
// lock.
#[cfg(loom)]
CURRENT_THREAD_PARK_COUNT.with(|count| count.fetch_add(1, SeqCst));
HadDriver::No
}
}
@@ -116,7 +129,7 @@ impl Unparker {
impl Inner {
/// Parks the current thread for at most `dur`.
fn park(&self, handle: &driver::Handle) {
fn park(&self, handle: &driver::Handle) -> HadDriver {
// If we were previously notified then we consume this notification and
// return quickly.
if self
@@ -124,13 +137,14 @@ impl Inner {
.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
.is_ok()
{
return;
return HadDriver::No;
}
if let Some(mut driver) = self.shared.driver.try_lock() {
self.park_driver(&mut driver, handle, None);
self.park_driver(&mut driver, handle, None)
} else {
self.park_condvar(None);
HadDriver::No
}
}
@@ -216,12 +230,12 @@ impl Inner {
driver: &mut Driver,
handle: &driver::Handle,
duration: Option<Duration>,
) {
) -> HadDriver {
if duration.as_ref().is_some_and(Duration::is_zero) {
// zero duration doesn't actually park the thread, it just
// polls the I/O events, timers, etc.
driver.park_timeout(handle, Duration::ZERO);
return;
return HadDriver::Yes;
}
match self
@@ -239,7 +253,7 @@ impl Inner {
let old = self.state.swap(EMPTY, SeqCst);
debug_assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
return;
return HadDriver::No;
}
Err(actual) => panic!("inconsistent park state; actual = {actual}"),
}
@@ -256,6 +270,8 @@ impl Inner {
PARKED_DRIVER => {} // no notification, alas
n => panic!("inconsistent park_timeout state: {n}"),
}
HadDriver::Yes
}
fn unpark(&self, driver: &driver::Handle) {
@@ -274,9 +274,7 @@ impl<T> Local<T> {
"queue is not full; tail = {tail}; head = {head}"
);
let prev = pack(head, head);
// Claim a bunch of tasks
// Claim all tasks.
//
// We are claiming the tasks **before** reading them out of the buffer.
// This is safe because only the **current** thread is able to push new
@@ -289,15 +287,7 @@ impl<T> Local<T> {
if self
.inner
.head
.compare_exchange(
prev,
pack(
head.wrapping_add(NUM_TASKS_TAKEN),
head.wrapping_add(NUM_TASKS_TAKEN),
),
Release,
Relaxed,
)
.compare_exchange_weak(pack(head, head), pack(tail, tail), Release, Relaxed)
.is_err()
{
// We failed to claim the tasks, losing the race. Return out of
@@ -306,6 +296,29 @@ impl<T> Local<T> {
return Err(task);
}
// Add back the first half of tasks.
//
// We are doing it this way instead of just taking half of the tasks because we want the
// *second* half of the tasks, and if you just incremented `head` by `NUM_TASKS_TAKEN`,
// then you would be taking the first half instead of the second half.
//
// Pushing the second half of the local queue to the injection queue is better because when
// we take tasks *out* of the injection queue, we always place them in the first half. This
// means that if a task is in the second half, then we know for sure that this task is not
// a task we just got from the injection queue. This ensures that when we take a task out
// of the injection queue, then it will not be moved back into the injection queue (at
// least not until after we have polled it at least once).
//
// Note that if a concurrent worker tries to steal from us between these two operations and
// sees that the worker queue is empty, then that worker may go to sleep, and we do not
// notify it about these tasks becoming available for stealing again. Ordinarily this would
// be a problem, but it isn't in this case because the worker will be notified about the
// tasks we are adding to the injection queue instead, which ensures that the stealer wakes
// up again to take the tasks from the injection queue.
self.inner
.tail
.store(tail.wrapping_add(NUM_TASKS_TAKEN), Release);
/// An iterator that takes elements out of the run queue.
struct BatchTaskIter<'a, T: 'static> {
buffer: &'a [UnsafeCell<MaybeUninit<task::Notified<T>>>; LOCAL_QUEUE_CAPACITY],
@@ -337,7 +350,7 @@ impl<T> Local<T> {
// values again, and we are the only producer.
let batch_iter = BatchTaskIter {
buffer: &self.inner.buffer,
head: head as UnsignedLong,
head: head.wrapping_add(NUM_TASKS_TAKEN) as UnsignedLong,
i: 0,
};
overflow.push_batch(batch_iter.chain(std::iter::once(task)));
@@ -378,7 +391,7 @@ impl<T> Local<T> {
let res = self
.inner
.head
.compare_exchange(head, next, AcqRel, Acquire);
.compare_exchange_weak(head, next, AcqRel, Acquire);
match res {
Ok(_) => break real as usize & MASK,
@@ -494,7 +507,7 @@ impl<T> Steal<T> {
let res = self
.0
.head
.compare_exchange(prev_packed, next_packed, AcqRel, Acquire);
.compare_exchange_weak(prev_packed, next_packed, AcqRel, Acquire);
match res {
Ok(_) => break n,
@@ -543,17 +556,11 @@ impl<T> Steal<T> {
let res = self
.0
.head
.compare_exchange(prev_packed, next_packed, AcqRel, Acquire);
.compare_exchange_weak(prev_packed, next_packed, AcqRel, Acquire);
match res {
Ok(_) => return n,
Err(actual) => {
let (actual_steal, actual_real) = unpack(actual);
assert_ne!(actual_steal, actual_real);
prev_packed = actual;
}
Err(actual) => prev_packed = actual,
}
}
}
@@ -59,7 +59,7 @@
use crate::loom::sync::{Arc, Mutex};
use crate::runtime;
use crate::runtime::scheduler::multi_thread::{
idle, queue, Counters, Handle, Idle, Overflow, Parker, Stats, TraceStatus, Unparker,
idle, park, queue, Counters, Handle, Idle, Overflow, Parker, Stats, TraceStatus, Unparker,
};
use crate::runtime::scheduler::{inject, Defer, Lock};
use crate::runtime::task::OwnedTasks;
@@ -139,6 +139,15 @@ struct Core {
/// True if the scheduler is being traced
is_traced: bool,
/// Whether or not the worker has just returned from a park in which we
/// parked on the I/O driver.
had_driver: park::HadDriver,
/// If `true`, the worker should eagerly notify another worker when polling
/// the first task after returning from a park in which it parked on the I/O
/// or time driver.
enable_eager_driver_handoff: bool,
/// Parker
///
/// Stored in an `Option` as the parker is added / removed to make the
@@ -254,6 +263,7 @@ type Notified = task::Notified<Arc<Handle>>;
/// improvements.
const MAX_LIFO_POLLS_PER_TICK: usize = 3;
#[allow(clippy::too_many_arguments)]
pub(super) fn create(
size: usize,
park: Parker,
@@ -262,6 +272,7 @@ pub(super) fn create(
seed_generator: RngSeedGenerator,
config: Config,
timer_flavor: TimerFlavor,
name: Option<String>,
) -> (Arc<Handle>, Launch) {
let mut cores = Vec::with_capacity(size);
let mut remotes = Vec::with_capacity(size);
@@ -286,6 +297,8 @@ pub(super) fn create(
is_searching: false,
is_shutdown: false,
is_traced: false,
enable_eager_driver_handoff: config.enable_eager_driver_handoff,
had_driver: park::HadDriver::No,
park: Some(park),
global_queue_interval: stats.tuned_global_queue_interval(&config),
stats,
@@ -301,6 +314,7 @@ pub(super) fn create(
let remotes_len = remotes.len();
let handle = Arc::new(Handle {
name,
task_hooks: TaskHooks::from_config(&config),
shared: Shared {
remotes: remotes.into_boxed_slice(),
@@ -621,7 +635,30 @@ impl Context {
// Make sure the worker is not in the **searching** state. This enables
// another idle worker to try to steal work.
core.transition_from_searching(&self.worker);
let notified_parked_worker = core.transition_from_searching(&self.worker);
// If the setting to wake eagerly when releasing the I/O driver is
// enabled, and this worker had the driver, wake a parked worker to come
// grab it from us.
//
// Note that this is only done when we are *actually* about to poll a
// task, rather than whenever the worker has unparked. When the worker
// has been unparked, it may not actually have any tasks to poll, and if
// it's still holding the I/O driver, it should just go back to polling
// the driver again, rather than trying to wake someone else spuriously.
//
// Note that this explicitly checks `cfg!(tokio_unstable)` in addition,
// as that should result in this whole expression being eliminated at
// compile-time when unstable features are disabled.
if cfg!(tokio_unstable)
&& core.enable_eager_driver_handoff
&& core.had_driver == park::HadDriver::Yes
&& !notified_parked_worker
// don't do it a second time
{
core.had_driver = park::HadDriver::No;
self.worker.handle.notify_parked_local();
}
self.assert_lifo_enabled_is_correct(&core);
@@ -830,11 +867,11 @@ impl Context {
};
// Park thread
if let Some(timeout) = duration {
park.park_timeout(&self.worker.handle.driver, timeout);
let had_driver = if let Some(timeout) = duration {
park.park_timeout(&self.worker.handle.driver, timeout)
} else {
park.park(&self.worker.handle.driver);
}
park.park(&self.worker.handle.driver)
};
self.defer.wake();
@@ -859,6 +896,8 @@ impl Context {
// Place `park` back in `core`
core.park = Some(park);
core.had_driver = had_driver;
if core.should_notify_others() {
self.worker.handle.notify_parked_local();
}
@@ -1006,6 +1045,11 @@ impl Context {
None => f(None),
})
}
#[cfg(tokio_unstable)]
pub(crate) fn worker_index(&self) -> usize {
self.worker.index
}
}
impl Core {
@@ -1035,12 +1079,27 @@ impl Core {
return None;
}
// Other threads can only **remove** tasks from the current worker's
// `run_queue`. So, we can be confident that by the time we call
// `run_queue.push_back` below, there will be *at least* `cap`
// available slots in the queue.
let cap = usize::min(
// Other threads can only **remove** tasks from the current
// worker's `run_queue`. So, we can be confident that by the
// time we call `run_queue.push_back` below, there will be *at
// least* `cap` available slots in the queue.
//
// Note that even though `next_local_task()` just returned
// `None`, this may be different from `max_capacity()` if
// another worker is currently stealing tasks from us.
self.run_queue.remaining_slots(),
// We want to make sure that all of the tasks we take end up in
// the first half of the local queue. This ensures that the
// tasks do not get pushed to the inject queue again if overflow
// occurs, as overflow only affects tasks in the second half of
// the local queue.
//
// Note that even if there are concurrent stealers, we do not
// need to consider the value of `remaining_slots()` because a
// future call to `push_overflow()` can only succeed once that
// concurrent stealer has finished stealing, so at that point
// the tasks we are adding now will be in the first half.
self.run_queue.max_capacity() / 2,
);
@@ -1117,13 +1176,13 @@ impl Core {
self.is_searching
}
fn transition_from_searching(&mut self, worker: &Worker) {
fn transition_from_searching(&mut self, worker: &Worker) -> bool {
if !self.is_searching {
return;
return false;
}
self.is_searching = false;
worker.handle.transition_worker_from_searching();
worker.handle.transition_worker_from_searching()
}
fn has_tasks(&self) -> bool {
@@ -1376,12 +1435,18 @@ impl Handle {
}
}
fn notify_parked_local(&self) {
/// Notify a parked worker.
///
/// Returns `true` if a worker was notified, `false` otherwise.
fn notify_parked_local(&self) -> bool {
super::counters::inc_num_inc_notify_local();
if let Some(index) = self.shared.idle.worker_to_notify(&self.shared) {
super::counters::inc_num_unparks_local();
self.shared.remotes[index].unpark.unpark(&self.driver);
true
} else {
false
}
}
@@ -1410,11 +1475,14 @@ impl Handle {
}
}
fn transition_worker_from_searching(&self) {
/// Returns `true` if another parked worker was notified, `false` otherwise.
fn transition_worker_from_searching(&self) -> bool {
if self.shared.idle.transition_worker_from_searching() {
// We are the final searching worker. Because work was found, we
// need to notify another worker.
self.notify_parked_local();
self.notify_parked_local()
} else {
false
}
}
+3 -4
View File
@@ -67,16 +67,15 @@ impl fmt::Display for Id {
impl Id {
pub(crate) fn next() -> Self {
use crate::loom::sync::atomic::Ordering::Relaxed;
use crate::loom::sync::atomic::StaticAtomicU64;
use crate::loom::sync::atomic::{AtomicU64, Ordering::Relaxed};
#[cfg(all(test, loom))]
crate::loom::lazy_static! {
static ref NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(1);
static ref NEXT_ID: AtomicU64 = AtomicU64::new(1);
}
#[cfg(not(all(test, loom)))]
static NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(1);
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
loop {
let id = NEXT_ID.fetch_add(1, Relaxed);
+166 -64
View File
@@ -10,10 +10,11 @@ use std::ffi::c_void;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::ptr::{self, NonNull};
use std::ptr::NonNull;
use std::task::{self, Poll};
mod symbol;
mod trace_impl;
mod tree;
use symbol::Symbol;
@@ -29,8 +30,12 @@ pub(crate) struct Context {
/// The address of [`Trace::root`] establishes an upper unwinding bound on
/// the backtraces in `Trace`.
active_frame: Cell<Option<NonNull<Frame>>>,
/// The place to stash backtraces.
collector: Cell<Option<Trace>>,
/// The function that is invoked at each leaf future inside of Tokio
///
/// For example, within tokio::time:sleep, sockets. etc.
#[allow(clippy::type_complexity)]
trace_leaf_fn: Cell<Option<NonNull<dyn FnMut(&TraceMeta)>>>,
}
/// A [`Frame`] in an intrusive, doubly-linked tree of [`Frame`]s.
@@ -39,6 +44,8 @@ struct Frame {
inner_addr: *const c_void,
/// The parent frame, if any.
///
/// Tracking parent allows nested `Root` futures to correctly manage their boundaries
parent: Option<NonNull<Frame>>,
}
@@ -72,7 +79,7 @@ impl Context {
pub(crate) const fn new() -> Self {
Context {
active_frame: Cell::new(None),
collector: Cell::new(None),
trace_leaf_fn: Cell::new(None),
}
}
@@ -96,28 +103,152 @@ impl Context {
}
}
fn with_current_collector<F, R>(f: F) -> R
where
F: FnOnce(&Cell<Option<Trace>>) -> R,
{
// SAFETY: This call can only access the collector field, so it cannot
// break the trace frame linked list.
fn current_frame_addr() -> Option<*const c_void> {
// SAFETY: This call does not modify the linked list structure
unsafe {
Self::try_with_current(|context| f(&context.collector)).expect(FAIL_NO_THREAD_LOCAL)
Context::try_with_current(|ctx| {
ctx.active_frame
.get()
.map(|frame| frame.as_ref().inner_addr)
})
.flatten()
}
}
/// Calls the provided closure if we are being traced.
fn try_with_current_trace_leaf_fn<F, R>(f: F) -> Option<R>
where
F: for<'a> FnOnce(&'a mut dyn FnMut(&TraceMeta)) -> R,
{
let mut ret = None;
let inner = |context: &Context| {
if let Some(mut trace_leaf_fn) = context.trace_leaf_fn.replace(None) {
let _restore = defer(move || {
context.trace_leaf_fn.set(Some(trace_leaf_fn));
});
// SAFETY: The trace leaf fn is valid for the duration in which it's stored in the
// context. Furthermore, re-entrant calls are not possible because we store `None` for
// the duration in which we hold a mutable reference, so access is exclusive for that
// duration.
ret = Some(f(unsafe { trace_leaf_fn.as_mut() }));
}
};
// SAFETY: This call can only access the trace_leaf_fn field, so it cannot break the trace
// frame linked list.
unsafe { Self::try_with_current(inner) };
ret
}
/// Produces `true` if the current task is being traced; otherwise false.
pub(crate) fn is_tracing() -> bool {
Self::with_current_collector(|maybe_collector| {
let collector = maybe_collector.take();
let result = collector.is_some();
maybe_collector.set(collector);
result
})
// SAFETY: This call can only access the trace_leaf_fn field, so it cannot break the trace
// frame linked list.
unsafe { Self::try_with_current(|ctx| ctx.trace_leaf_fn.get().is_some()).unwrap_or(false) }
}
}
/// Metadata passed into the `trace_leaf` callback for [`trace_with`]
#[non_exhaustive]
#[derive(Debug)]
pub struct TraceMeta {
/// The root boundary address set by [`Root::poll`] if any.
///
/// When using unwinding the stack, this is the address at which
/// stack walking should stop. It corresponds to the `Root::poll` function pointer.
pub root_addr: Option<*const c_void>,
/// The address of the internal `trace_leaf` function that triggered this callback.
///
/// When capturing a backtrace, use this as the lower bound — frames at or below
/// this address are internal implementation details and should be excluded.
pub trace_leaf_addr: *const c_void,
}
/// Runs `f`. If `f` hits a Tokio yield point `trace_leaf` will be invoked.
///
/// This allows taking a task dump with caller-provided task dump machinery. If `f` is the poll
/// function of a future and that future returns `Poll::Pending`, then `trace_leaf` will be
/// invoked. `trace_leaf` can then take a backtrace to determine exactly where the yield occurred.
///
/// # Example
///
/// ```
/// use std::future::Future;
/// use std::task::Poll;
/// use tokio::runtime::dump::{trace_with, Trace, TraceMeta};
///
/// fn my_trace_leaf(_meta: &TraceMeta, count: &mut u32) {
/// *count += 1;
/// }
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let mut fut = std::pin::pin!(async {
/// tokio::task::yield_now().await;
/// });
///
/// let mut leaf_count = 0;
///
/// Trace::root(std::future::poll_fn(|cx| {
/// trace_with(
/// || { let _ = fut.as_mut().poll(cx); },
/// |meta| my_trace_leaf(meta, &mut leaf_count),
/// );
/// Poll::Ready(())
/// })).await;
///
/// assert!(leaf_count > 0);
/// # }
/// ```
pub fn trace_with<FN, FT, R>(f: FN, mut trace_leaf: FT) -> R
where
FN: FnOnce() -> R,
FT: FnMut(&TraceMeta),
{
let trace_leaf_dyn = (&mut trace_leaf) as &mut (dyn FnMut(&TraceMeta) + '_);
// SAFETY: The raw pointer is removed from the thread local before `trace_leaf` is dropped, so
// this transmute cannot lead to the violation of any lifetime requirements.
let trace_leaf_dyn = unsafe {
std::mem::transmute::<
*mut (dyn FnMut(&TraceMeta) + '_),
*mut (dyn FnMut(&TraceMeta) + 'static),
>(trace_leaf_dyn)
};
// SAFETY: Pointer comes from reference, so not null.
let trace_leaf_dyn = unsafe { NonNull::new_unchecked(trace_leaf_dyn) };
let mut old_trace_leaf_fn = None;
// Even if this access fails, that's okay. In that case, we still call the closure without
// actually performing any tracing.
//
// SAFETY: This call can only access the trace_leaf_fn field, so it cannot break the trace
// frame linked list.
unsafe {
Context::try_with_current(|ctx| {
old_trace_leaf_fn = ctx.trace_leaf_fn.replace(Some(trace_leaf_dyn));
})
};
let _restore = defer(move || {
// This ensures that `trace_leaf_fn` cannot be accessed after this call returns.
//
// SAFETY: This call can only access the trace_leaf_fn field, so it cannot
// break the trace frame linked list.
unsafe {
Context::try_with_current(|ctx| {
ctx.trace_leaf_fn.set(old_trace_leaf_fn);
})
};
});
f()
}
impl Trace {
/// Invokes `f`, returning both its result and the collection of backtraces
/// captured at each sub-invocation of [`trace_leaf`].
@@ -126,16 +257,15 @@ impl Trace {
where
F: FnOnce() -> R,
{
let collector = Trace { backtraces: vec![] };
trace_impl::capture(f)
}
let previous = Context::with_current_collector(|current| current.replace(Some(collector)));
pub(crate) fn empty() -> Self {
Self { backtraces: vec![] }
}
let result = f();
let collector =
Context::with_current_collector(|current| current.replace(previous)).unwrap();
(result, collector)
fn push_backtrace(&mut self, bt: Vec<BacktraceFrame>) {
self.backtraces.push(bt);
}
/// The root of a trace.
@@ -160,44 +290,15 @@ impl Trace {
// internal implementation details of this crate).
#[inline(never)]
pub(crate) fn trace_leaf(cx: &mut task::Context<'_>) -> Poll<()> {
// Safety: We don't manipulate the current context's active frame.
let did_trace = unsafe {
Context::try_with_current(|context_cell| {
if let Some(mut collector) = context_cell.collector.take() {
let mut frames = vec![];
let mut above_leaf = false;
let root_addr = Context::current_frame_addr();
if let Some(active_frame) = context_cell.active_frame.get() {
let active_frame = active_frame.as_ref();
let ret = Context::try_with_current_trace_leaf_fn(|leaf_fn| {
let meta = TraceMeta {
root_addr,
trace_leaf_addr: trace_leaf as *const c_void,
};
leaf_fn(&meta);
backtrace::trace(|frame| {
let below_root = !ptr::eq(frame.symbol_address(), active_frame.inner_addr);
// only capture frames above `Trace::leaf` and below
// `Trace::root`.
if above_leaf && below_root {
frames.push(frame.to_owned().into());
}
if ptr::eq(frame.symbol_address(), trace_leaf as *const _) {
above_leaf = true;
}
// only continue unwinding if we're below `Trace::root`
below_root
});
}
collector.backtraces.push(frames);
context_cell.collector.set(Some(collector));
true
} else {
false
}
})
.unwrap_or(false)
};
if did_trace {
// Use the same logic that `yield_now` uses to send out wakeups after
// the task yields.
context::with_scheduler(|scheduler| {
@@ -209,10 +310,11 @@ pub(crate) fn trace_leaf(cx: &mut task::Context<'_>) -> Poll<()> {
}
}
});
});
Poll::Pending
} else {
Poll::Ready(())
match ret {
Some(()) => Poll::Pending,
None => Poll::Ready(()),
}
}
+2 -1
View File
@@ -65,7 +65,8 @@ impl Eq for Symbol {}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(name) = self.symbol.name() {
let name = name.to_string();
// Printing this using :# formatting omits crate disambiguators.
let name = format!("{name:#}");
let name = if let Some((name, _)) = name.rsplit_once("::") {
name
} else {
@@ -0,0 +1,43 @@
//! Current `backtrace::trace` + collector based backtrace implementation
//!
//! This implementation may eventually be extracted into a separate `tokio-taskdump` crate.
use std::ptr;
use crate::runtime::task::trace::{trace_with, Trace, TraceMeta};
/// Capture using the default `backtrace::trace`-based implementation.
#[inline(never)]
pub(super) fn capture<F, R>(f: F) -> (R, Trace)
where
F: FnOnce() -> R,
{
let mut trace = Trace::empty();
let result = trace_with(f, |meta| trace_leaf(meta, &mut trace));
(result, trace)
}
/// Capture a backtrace via `backtrace::trace` and collect it into `trace`.
pub(crate) fn trace_leaf(meta: &TraceMeta, trace: &mut Trace) {
let mut frames: Vec<backtrace::BacktraceFrame> = vec![];
let mut above_leaf = false;
if let Some(root_addr) = meta.root_addr {
backtrace::trace(|frame| {
let below_root = !ptr::eq(frame.symbol_address(), root_addr);
if above_leaf && below_root {
frames.push(frame.to_owned().into());
}
if ptr::eq(frame.symbol_address(), meta.trace_leaf_addr) {
above_leaf = true;
}
below_root
});
}
trace.push_backtrace(frames);
}
+8 -2
View File
@@ -5,9 +5,15 @@ pub(crate) struct ThreadId(NonZeroU64);
impl ThreadId {
pub(crate) fn next() -> Self {
use crate::loom::sync::atomic::{Ordering::Relaxed, StaticAtomicU64};
use crate::loom::sync::atomic::{AtomicU64, Ordering::Relaxed};
static NEXT_ID: StaticAtomicU64 = StaticAtomicU64::new(0);
#[cfg(all(test, loom))]
crate::loom::lazy_static! {
static ref NEXT_ID: AtomicU64 = AtomicU64::new(0);
}
#[cfg(not(all(test, loom)))]
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
let mut last = NEXT_ID.load(Relaxed);
loop {
+6 -2
View File
@@ -5,7 +5,7 @@ mod level;
pub(crate) use self::level::Expiration;
use self::level::Level;
use std::{array, ptr::NonNull};
use std::ptr::NonNull;
use super::entry::STATE_DEREGISTERED;
use super::EntryList;
@@ -50,9 +50,13 @@ pub(super) const MAX_DURATION: u64 = (1 << (6 * NUM_LEVELS)) - 1;
impl Wheel {
/// Creates a new timing wheel.
pub(crate) fn new() -> Wheel {
let mut levels = Vec::with_capacity(NUM_LEVELS);
for i in 0..NUM_LEVELS {
levels.push(Level::new(i));
}
Wheel {
elapsed: 0,
levels: Box::new(array::from_fn(Level::new)),
levels: levels.into_boxed_slice().try_into().unwrap(),
pending: EntryList::new(),
}
}
+5 -3
View File
@@ -5,8 +5,6 @@ use self::level::Level;
use super::cancellation_queue::Sender;
use super::{EntryHandle, EntryList, WakeQueue};
use std::array;
/// Hashed timing wheel implementation.
///
/// See [`Driver`] documentation for some implementation notes.
@@ -41,9 +39,13 @@ pub(super) const MAX_DURATION: u64 = (1 << (6 * NUM_LEVELS)) - 1;
impl Wheel {
/// Creates a new timing wheel.
pub(crate) fn new() -> Wheel {
let mut levels = Vec::with_capacity(NUM_LEVELS);
for i in 0..NUM_LEVELS {
levels.push(Level::new(i));
}
Wheel {
elapsed: 0,
levels: Box::new(array::from_fn(Level::new)),
levels: levels.into_boxed_slice().try_into().unwrap(),
}
}
+1 -1
View File
@@ -384,7 +384,7 @@ impl Semaphore {
let new = curr.saturating_sub(n);
match self.permits.compare_exchange_weak(
curr_bits,
new << Self::PERMIT_SHIFT,
(new << Self::PERMIT_SHIFT) | (curr_bits & Self::CLOSED),
AcqRel,
Acquire,
) {
-5
View File
@@ -220,11 +220,6 @@ impl<T> Block<T> {
self.header.ready_slots.fetch_or(TX_CLOSED, Release);
}
pub(crate) unsafe fn is_closed(&self) -> bool {
let ready_bits = self.header.ready_slots.load(Acquire);
is_tx_closed(ready_bits)
}
/// Resets the block to a blank state. This enables reusing blocks in the
/// channel.
///
+3 -16
View File
@@ -1853,14 +1853,12 @@ impl<T> OwnedPermit<T> {
///
/// [`Sender`]: Sender
pub fn release(mut self) -> Sender<T> {
use chan::Semaphore;
let chan = self.chan.take().unwrap_or_else(|| {
unreachable!("OwnedPermit channel is only taken when the permit is moved")
});
// Add the permit back to the semaphore
chan.semaphore().add_permit();
drop(Permit { chan: &chan });
Sender { chan }
}
@@ -1919,21 +1917,10 @@ impl<T> OwnedPermit<T> {
impl<T> Drop for OwnedPermit<T> {
fn drop(&mut self) {
use chan::Semaphore;
// Are we still holding onto the sender?
if let Some(chan) = self.chan.take() {
let semaphore = chan.semaphore();
// Add the permit back to the semaphore
semaphore.add_permit();
// If this `OwnedPermit` is holding the last sender for this
// channel, wake the receiver so that it can be notified that the
// channel is closed.
if semaphore.is_closed() && semaphore.is_idle() {
chan.wake_rx();
}
// Reuse Drop impl of non-owned Permit.
drop(Permit { chan: &chan });
}
// Otherwise, do nothing.
+8 -10
View File
@@ -306,13 +306,11 @@ impl<T, S: Semaphore> Rx<T, S> {
return Ready(Some(value));
}
Some(Read::Closed) => {
// TODO: This check may not be required as it most
// likely can only return `true` at this point. A
// channel is closed when all tx handles are
// A channel is closed when all tx handles are
// dropped. Dropping a tx handle releases memory,
// which ensures that if dropping the tx handle is
// visible, then all messages sent are also visible.
assert!(self.inner.semaphore.is_idle());
debug_assert!(self.inner.semaphore.is_idle());
coop.made_progress();
return Ready(None);
}
@@ -380,13 +378,11 @@ impl<T, S: Semaphore> Rx<T, S> {
if number_added > 0 {
self.inner.semaphore.add_permits(number_added);
}
// TODO: This check may not be required as it most
// likely can only return `true` at this point. A
// channel is closed when all tx handles are
// A channel is closed when all tx handles are
// dropped. Dropping a tx handle releases memory,
// which ensures that if dropping the tx handle is
// visible, then all messages sent are also visible.
assert!(self.inner.semaphore.is_idle());
debug_assert!(self.inner.semaphore.is_idle());
coop.made_progress();
return Ready(number_added);
}
@@ -415,7 +411,7 @@ impl<T, S: Semaphore> Rx<T, S> {
try_recv!();
if rx_fields.rx_closed && self.inner.semaphore.is_idle() {
assert!(buffer.is_empty());
debug_assert_eq!(buffer.len(), initial_length);
coop.made_progress();
Ready(0usize)
} else {
@@ -440,7 +436,9 @@ impl<T, S: Semaphore> Rx<T, S> {
}
TryPopResult::Closed => return Err(TryRecvError::Disconnected),
// If close() was called, an empty queue should report Disconnected.
TryPopResult::Empty if rx_fields.rx_closed => {
TryPopResult::Empty
if rx_fields.rx_closed && self.inner.semaphore.is_idle() =>
{
return Err(TryRecvError::Disconnected)
}
TryPopResult::Empty => return Err(TryRecvError::Empty),
+50 -12
View File
@@ -239,15 +239,6 @@ impl<T> Tx<T> {
let _ = unsafe { Box::from_raw(block.as_ptr()) };
}
}
pub(crate) fn is_closed(&self) -> bool {
let tail = self.block_tail.load(Acquire);
unsafe {
let tail_block = &*tail;
tail_block.is_closed()
}
}
}
impl<T> fmt::Debug for Tx<T> {
@@ -271,11 +262,58 @@ impl<T> Rx<T> {
self.len(tx) == 0
}
// Guaranteed to return true if `slot_index` is the fake message sent on channel close.
// Guaranteed to return false if `slot_index` is a fully sent message.
//
// For messages that are partially sent, may return either true or false.
fn is_maybe_closed(&self, tx: &Tx<T>, slot_index: usize) -> bool {
let start_index = block::start_index(slot_index);
let tail = tx.block_tail.load(Acquire);
// SAFETY: Only the receiver frees blocks, so since we are the receiver, this will not be
// freed right now.
let tail_ref = unsafe { &*tail };
if tail_ref.is_at_index(start_index) {
return !tail_ref.has_value(slot_index);
}
// This method is optimized for checking whether the last value is present, so most of the
// time it is in `block_tail`. However, this isn't always the case since it's possible
// that the list was grown with an empty block, in which case `block_tail` points one block
// too far. To handle this case, we walk the list from the head.
let mut block_ptr = Some(self.head);
while let Some(block) = block_ptr {
// SAFETY: Only the receiver frees blocks, so since we are the receiver, this will not
// be freed right now.
let block_ref = unsafe { block.as_ref() };
if block_ref.is_at_index(start_index) {
return !block_ref.has_value(slot_index);
}
block_ptr = block_ref.load_next(Acquire);
}
true
}
pub(crate) fn len(&self, tx: &Tx<T>) -> usize {
// When all the senders are dropped, there will be a last block in the tail position,
// but it will be closed
let tail_position = tx.tail_position.load(Acquire);
tail_position - self.index - (tx.is_closed() as usize)
let mut len = tail_position.wrapping_sub(self.index);
debug_assert!(0 <= len as isize);
if len == 0 {
return 0;
}
// There are messages present in the queue. However, it's possible that the last message is
// a fake "closed" message that we do not wish to count. To avoid counting it, we do not
// count the last message if the ready bit is unset.
//
// Note that it is also possible for the ready bit to be unset on a normal message, but
// this happens only if that message is currently being sent *right now* in parallel on
// another thread. That is okay because it is optional to count messages that are currently
// being sent.
if self.is_maybe_closed(tx, tail_position.wrapping_sub(1)) {
len -= 1;
}
len
}
/// Pops the next value off the queue.
+3 -3
View File
@@ -137,10 +137,10 @@ pub mod error;
/// This value must be a power of 2. It also must be smaller than the number of
/// bits in `usize`.
#[cfg(all(target_pointer_width = "64", not(loom)))]
const BLOCK_CAP: usize = 32;
pub(crate) const BLOCK_CAP: usize = 32;
#[cfg(all(not(target_pointer_width = "64"), not(loom)))]
const BLOCK_CAP: usize = 16;
pub(crate) const BLOCK_CAP: usize = 16;
#[cfg(loom)]
const BLOCK_CAP: usize = 2;
pub(crate) const BLOCK_CAP: usize = 2;
+8
View File
@@ -1118,6 +1118,14 @@ impl NotifiedProject<'_> {
State::Init => {
let curr = notify.state.load(SeqCst);
// Check if `notify_waiters` was called before attempting to acquire
// the `NOTIFIED` state. If a broadcast occurred, we will be woken by it,
// leaving the `notify_one` permit for other waiters.
if get_num_notify_waiters_calls(curr) != *notify_waiters_calls {
*state = State::Done;
continue 'outer_loop;
}
// Optimistically try acquiring a pending notification
let res = notify.state.compare_exchange(
set_state(curr, NOTIFIED),
+1 -1
View File
@@ -583,7 +583,7 @@ impl<T> Sender<T> {
/// not be sent.
///
/// This method consumes `self` as only one value may ever be sent on a `oneshot`
/// channel. It is not marked async because sending a message to an `oneshot`
/// channel. It is not marked async because sending a message to a `oneshot`
/// channel never requires any form of waiting. Because of this, the `send`
/// method can be used in both synchronous and asynchronous code without
/// problems.
+11 -1
View File
@@ -265,12 +265,13 @@ impl<T: ?Sized> RwLock<T> {
///
/// # Panics
///
/// Panics if `max_reads` is more than `u32::MAX >> 3`.
/// Panics if `max_reads` is `0` or is bigger than `u32::MAX >> 3`.
#[track_caller]
pub fn with_max_readers(value: T, max_reads: u32) -> RwLock<T>
where
T: Sized,
{
assert_ne!(max_reads, 0, "a RwLock may not be created with 0 readers");
assert!(
max_reads <= MAX_READS,
"a RwLock may not be created with more than {MAX_READS} readers"
@@ -366,11 +367,16 @@ impl<T: ?Sized> RwLock<T> {
///
/// static LOCK: RwLock<i32> = RwLock::const_with_max_readers(5, 1024);
/// ```
///
/// # Panics
///
/// Panics if `max_reads` is `0` or is bigger than `u32::MAX >> 3`.
#[cfg(not(all(loom, test)))]
pub const fn const_with_max_readers(value: T, max_reads: u32) -> RwLock<T>
where
T: Sized,
{
assert!(max_reads != 0, "a RwLock may not be created with 0 readers");
assert!(max_reads <= MAX_READS);
RwLock {
@@ -773,6 +779,7 @@ impl<T: ?Sized> RwLock<T> {
/// ```
pub async fn write(&self) -> RwLockWriteGuard<'_, T> {
let acquire_fut = async {
debug_assert_ne!(self.mr, 0);
self.s.acquire(self.mr as usize).await.unwrap_or_else(|_| {
// The semaphore was closed. but, we never explicitly close it, and we have a
// handle to it through the Arc, which means that this can never happen.
@@ -911,6 +918,7 @@ impl<T: ?Sized> RwLock<T> {
let resource_span = self.resource_span.clone();
let acquire_fut = async {
debug_assert_ne!(self.mr, 0);
self.s.acquire(self.mr as usize).await.unwrap_or_else(|_| {
// The semaphore was closed. but, we never explicitly close it, and we have a
// handle to it through the Arc, which means that this can never happen.
@@ -975,6 +983,7 @@ impl<T: ?Sized> RwLock<T> {
/// # }
/// ```
pub fn try_write(&self) -> Result<RwLockWriteGuard<'_, T>, TryLockError> {
debug_assert_ne!(self.mr, 0);
match self.s.try_acquire(self.mr as usize) {
Ok(permit) => permit,
Err(TryAcquireError::NoPermits) => return Err(TryLockError(())),
@@ -1033,6 +1042,7 @@ impl<T: ?Sized> RwLock<T> {
/// # }
/// ```
pub fn try_write_owned(self: Arc<Self>) -> Result<OwnedRwLockWriteGuard<T>, TryLockError> {
debug_assert_ne!(self.mr, 0);
match self.s.try_acquire(self.mr as usize) {
Ok(permit) => permit,
Err(TryAcquireError::NoPermits) => return Err(TryLockError(())),
+54 -1
View File
@@ -1,4 +1,4 @@
use crate::sync::mpsc;
use crate::sync::mpsc::{self, BLOCK_CAP};
use loom::future::block_on;
use loom::sync::Arc;
@@ -222,3 +222,56 @@ fn nonempty_after_send() {
join.join().unwrap();
});
}
#[test]
fn is_empty_during_close() {
loom::model(|| {
let (tx, rx) = mpsc::channel::<()>(1);
let th1 = thread::spawn(move || {
assert!(rx.is_empty());
});
drop(tx);
th1.join().unwrap();
});
}
fn len_during_close_helper(n: usize) {
loom::model(move || {
let (tx, rx) = mpsc::channel::<()>(n + 1);
for _ in 0..n {
tx.try_send(()).unwrap();
}
let th1 = thread::spawn(move || {
assert_eq!(rx.len(), n);
});
drop(tx);
th1.join().unwrap();
});
}
#[test]
fn len_during_close_0() {
len_during_close_helper(0);
}
#[test]
fn len_during_close_1() {
len_during_close_helper(1);
}
#[test]
fn len_during_close_block_cap() {
len_during_close_helper(BLOCK_CAP);
}
#[test]
fn len_during_close_block_cap_plus_1() {
len_during_close_helper(BLOCK_CAP + 1);
}
+1 -1
View File
@@ -111,7 +111,7 @@
//! ```
//! use tokio::sync::watch;
//!
//! #[tokio::main(flavor = "current_thread")]
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! let (tx, mut rx) = watch::channel("hello");
//! tx.send("goodbye").unwrap();
+3 -7
View File
@@ -328,9 +328,8 @@ impl<'a> Drop for LocalDataEnterGuard<'a> {
cfg_rt! {
/// Spawns a `!Send` future on the current [`LocalSet`] or [`LocalRuntime`].
///
/// This is possible when either using one of these types
/// explicitly, or (with `tokio_unstable`) by opting to use the
/// `"local"` runtime flavor in `tokio::main`:
/// This is possible when either using one of these types explicitly, or by
/// opting to use the `"local"` runtime flavor in `tokio::main`:
///
/// ```ignore
/// #[tokio::main(flavor = "local")]
@@ -374,10 +373,9 @@ cfg_rt! {
/// }).await;
/// # }
/// ```
/// With local runtime flavor ([Unstable API][unstable] only).
/// With local runtime flavor.
///
/// ```rust
/// # #[cfg(tokio_unstable)]
/// #[tokio::main(flavor = "local")]
/// async fn main() {
/// let join = tokio::task::spawn_local(async {
@@ -386,8 +384,6 @@ cfg_rt! {
///
/// join.await.unwrap()
/// }
/// # #[cfg(not(tokio_unstable))]
/// # fn main() {}
///
/// ```
///
+20 -5
View File
@@ -142,16 +142,31 @@ where
/// }
/// # }
/// ```
///
/// # Panics
///
/// This function panics if there is no current timer set.
///
/// It can be triggered when [`Builder::enable_time`] or
/// [`Builder::enable_all`] are not included in the builder.
///
/// It can also panic whenever a timer is created outside of a
/// Tokio runtime. That is why `rt.block_on(sleep(...))` will panic,
/// since the function is executed outside of the runtime.
/// Whereas `rt.block_on(async {sleep(...).await})` doesn't panic.
/// And this is because wrapping the function on an async makes it lazy,
/// and so gets executed inside the runtime successfully without
/// panicking.
///
/// [`Builder::enable_time`]: crate::runtime::Builder::enable_time
/// [`Builder::enable_all`]: crate::runtime::Builder::enable_all
#[track_caller]
pub fn timeout_at<F>(deadline: Instant, future: F) -> Timeout<F::IntoFuture>
where
F: IntoFuture,
{
let delay = sleep_until(deadline);
Timeout {
value: future.into_future(),
delay,
}
Timeout::new_with_delay(future.into_future(), delay)
}
pin_project! {
+1 -1
View File
@@ -6,7 +6,7 @@ use tempfile::tempdir;
use tokio::fs;
#[tokio::test]
async fn symlink_file_windows() {
async fn symlink_dir_windows() {
const FILE_NAME: &str = "abc.txt";
let temp_dir = tempdir().unwrap();
+71
View File
@@ -0,0 +1,71 @@
//! Uring file operations tests.
#![cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux"
))]
use futures::future::FutureExt;
use std::fs;
use std::future::poll_fn;
use std::task::Poll;
use tempfile::NamedTempFile;
// see: https://github.com/tokio-rs/tokio/issues/7979
#[tokio::test]
async fn file_descriptors_are_closed_when_cancelling_open_op() {
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_path_buf();
let fd_count_before_opens = fs::read_dir("/proc/self/fd").unwrap().count();
for _ in 0..128 {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let path = path.clone();
let handle = tokio::spawn(async move {
poll_fn(|cx| {
let opt = {
let mut opt = tokio::fs::OpenOptions::new();
opt.read(true);
opt
};
let fut = opt.open(&path);
// If io_uring is enabled (and not falling back to the thread pool),
// the first poll should return Pending. We don't check if the result
// is actually pending because we run some checks on old kernel that
// do not support uring.
let _pending = Box::pin(fut).poll_unpin(cx);
tx.send(()).unwrap();
Poll::<()>::Pending
})
.await;
});
// Wait for the first poll
rx.recv().await.unwrap();
handle.abort();
let res = handle.await.unwrap_err();
assert!(res.is_cancelled());
}
let fd_count_after_cancel = fs::read_dir("/proc/self/fd").unwrap().count();
let leaked = fd_count_after_cancel.saturating_sub(fd_count_before_opens);
// Since we are opening 128 files, we expect that the related fds
// related to this operation will be closed. Since some other fds
// can be opened in the meantime, we expect this number to be higher
// than the counter before opening the files. This number could be
// lower, but to avoid test flakiness we check that this is at most
// half the number of the file we opened to check if there's a leak.
assert!(leaked <= 64);
}
@@ -0,0 +1,130 @@
#![cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux"
))]
use std::fs;
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use tempfile::NamedTempFile;
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
use tokio::time::timeout;
/// Count currently-open fds in this process.
fn fd_count() -> usize {
fs::read_dir("/proc/self/fd").unwrap().count()
}
/// First poll:
/// - polls the inner `tokio::fs::OpenOptions::open()` future once,
/// - expects `Pending` so we know we took the io_uring path,
/// - registers the task waker with Tokio's uring machinery.
///
/// Second poll:
/// - happens after the kernel completes the open and Tokio stores the CQE as
/// `Lifecycle::Completed(cqe)` and wakes the task,
/// - **intentionally does not poll the inner open future again**,
/// - stays pending forever so the task can be aborted.
///
/// Aborting the task here drops the inner `open()` future while Tokio still has
/// a completed CQE sitting in the slab.
struct PollOpenOnceThenNeverRepoll<F> {
inner: Pin<Box<F>>,
first_poll_tx: Option<UnboundedSender<()>>,
second_poll_tx: Option<UnboundedSender<()>>,
polled_once: bool,
}
impl<F: Future> Future for PollOpenOnceThenNeverRepoll<F> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if !self.polled_once {
// We don't check if the result is actually pending because
// we run some checks on old kernel that do not support uring.
let _pending = self.inner.as_mut().poll(cx);
self.polled_once = true;
self.first_poll_tx.take().unwrap().send(()).unwrap();
return Poll::Pending;
}
// We were polled again after the inner open completed and woke the task.
// Crucially, we do *not* re-poll the inner future here.
if let Some(tx) = self.second_poll_tx.take() {
tx.send(()).unwrap();
}
Poll::Pending
}
}
async fn completed_then_dropped_before_repoll(path: PathBuf) {
let (first_tx, mut first_rx) = unbounded_channel();
let (second_tx, mut second_rx) = unbounded_channel();
let handle = tokio::spawn(async move {
let mut opt = tokio::fs::OpenOptions::new();
opt.read(true);
PollOpenOnceThenNeverRepoll {
inner: Box::pin(opt.open(&path)),
first_poll_tx: Some(first_tx),
second_poll_tx: Some(second_tx),
polled_once: false,
}
.await;
});
// Wait until the inner open has been polled once and registered with io_uring.
first_rx.recv().await.unwrap();
// Wait until Tokio wakes the task because the open completed. At this point
// the CQE should already be stored as `Lifecycle::Completed(cqe)`.
let _ = timeout(Duration::from_secs(2), second_rx.recv()).await;
// Abort now, before the inner open future gets re-polled and consumes the CQE.
handle.abort();
let err = handle.await.unwrap_err();
assert!(err.is_cancelled(), "task was not cancelled as expected");
}
#[test]
fn uring_completed_then_dropped() {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.unwrap();
rt.block_on(async {
let before = fd_count();
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_path_buf();
for _ in 0..128 {
completed_then_dropped_before_repoll(path.clone()).await;
}
// Give completions a moment to settle before counting fds.
tokio::time::sleep(Duration::from_millis(250)).await;
let after = fd_count();
let leaked = after.saturating_sub(before);
// Since we are opening 128 files, we expect that the related fds
// related to this operation will be closed. Since some other fds
// can be opened in the meantime, we expect this number to be higher
// than the counter before opening the files. This number could be
// lower, but to avoid test flakiness we check that this is at most
// half the number of the file we opened to check if there's a leak.
assert!(leaked <= 64);
});
}
+471
View File
@@ -0,0 +1,471 @@
//! Tests for AsyncRead on tokio::fs::File via io-uring.
#![cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux"
))]
use std::io::Write;
use std::path::PathBuf;
use std::sync::mpsc;
use std::time::Duration;
use tempfile::NamedTempFile;
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use tokio::runtime::{Builder, Runtime};
use tokio_util::task::TaskTracker;
fn multi_rt(n: usize) -> Box<dyn Fn() -> Runtime> {
Box::new(move || {
Builder::new_multi_thread()
.worker_threads(n)
.enable_all()
.build()
.unwrap()
})
}
fn current_rt() -> Box<dyn Fn() -> Runtime> {
Box::new(|| Builder::new_current_thread().enable_all().build().unwrap())
}
fn rt_combinations() -> Vec<Box<dyn Fn() -> Runtime>> {
vec![current_rt(), multi_rt(1), multi_rt(4), multi_rt(64)]
}
fn create_temp_file(data: &[u8]) -> (NamedTempFile, PathBuf) {
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(data).unwrap();
tmp.flush().unwrap();
let path = tmp.path().to_path_buf();
(tmp, path)
}
#[tokio::test]
async fn test_file_read() {
let data = b"hello io-uring";
let (_tmp, path) = create_temp_file(data);
let mut file = File::open(&path).await.unwrap();
let mut buf = vec![0u8; data.len()];
let n = file.read(&mut buf).await.unwrap();
assert_eq!(n, data.len());
assert_eq!(&buf[..n], data);
}
#[tokio::test]
async fn test_file_read_exact() {
let data = b"exact read test data";
let (_tmp, path) = create_temp_file(data);
let mut file = File::open(&path).await.unwrap();
let mut buf = vec![0u8; data.len()];
file.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, data);
}
#[tokio::test]
async fn test_file_read_to_end() {
// Empty file
{
let (_tmp, path) = create_temp_file(b"");
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
let n = file.read_to_end(&mut buf).await.unwrap();
assert_eq!(n, 0);
assert!(buf.is_empty());
}
// Small file
{
let data: Vec<u8> = (0..100u8).collect();
let (_tmp, path) = create_temp_file(&data);
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, data);
}
// File larger than typical buffer
{
let data: Vec<u8> = (0..100_000u32).map(|i| (i % 256) as u8).collect();
let (_tmp, path) = create_temp_file(&data);
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, data);
}
}
#[tokio::test]
async fn test_file_read_to_string() {
let text = "hello, io-uring world! 🦀";
let (_tmp, path) = create_temp_file(text.as_bytes());
let mut file = File::open(&path).await.unwrap();
let mut s = String::new();
file.read_to_string(&mut s).await.unwrap();
assert_eq!(s, text);
}
#[tokio::test]
async fn test_file_read_empty() {
let (_tmp, path) = create_temp_file(b"");
let mut file = File::open(&path).await.unwrap();
let mut buf = vec![0u8; 100];
let n = file.read(&mut buf).await.unwrap();
assert_eq!(n, 0);
}
#[tokio::test]
async fn test_file_read_large() {
let data: Vec<u8> = (0..3_000_000u32).map(|i| (i % 256) as u8).collect();
let (_tmp, path) = create_temp_file(&data);
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf.len(), data.len());
assert_eq!(buf, data);
}
#[tokio::test]
async fn test_file_read_custom_buf_size() {
let data: Vec<u8> = (0..1000u16).map(|i| (i % 256) as u8).collect();
let (_tmp, path) = create_temp_file(&data);
let mut file = File::open(&path).await.unwrap();
file.set_max_buf_size(64);
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, data);
}
#[tokio::test]
async fn test_file_read_partial_buf() {
let data: Vec<u8> = (0..100u8).collect();
let (_tmp, path) = create_temp_file(&data);
let mut file = File::open(&path).await.unwrap();
// Read only 5 bytes
let mut buf = vec![0u8; 5];
file.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, &data[..5]);
// Read the rest
let mut rest = Vec::new();
file.read_to_end(&mut rest).await.unwrap();
assert_eq!(rest, data[5..]);
}
#[tokio::test]
async fn test_file_read_seek() {
let data = b"hello world";
let (_tmp, path) = create_temp_file(data);
let mut file = File::open(&path).await.unwrap();
file.seek(std::io::SeekFrom::Start(6)).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, b"world");
}
#[tokio::test]
async fn test_file_read_seek_back() {
let data = b"hello world";
let (_tmp, path) = create_temp_file(data);
let mut file = File::open(&path).await.unwrap();
// Read first 5 bytes
let mut buf = vec![0u8; 5];
file.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"hello");
// Seek back to start
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
// Read full content
let mut full = Vec::new();
file.read_to_end(&mut full).await.unwrap();
assert_eq!(full, data);
}
#[tokio::test]
async fn test_file_read_after_write() {
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_path_buf();
let mut file = tokio::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.await
.unwrap();
file.write_all(b"hello uring").await.unwrap();
file.flush().await.unwrap();
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, b"hello uring");
}
#[tokio::test]
async fn test_file_read_cancel() {
let data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
let (_tmp, path) = create_temp_file(&data);
let path2 = path.clone();
let handle = tokio::spawn(async move {
let mut file = File::open(&path2).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
buf
});
handle.abort();
let res = handle.await;
assert!(res.unwrap_err().is_cancelled());
// Verify runtime still works
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, data);
}
#[tokio::test]
async fn test_file_read_concurrent() {
const NUM_FILES: usize = 100;
let files: Vec<_> = (0..NUM_FILES)
.map(|i| {
let data: Vec<u8> = (0..1024).map(|j| ((i as u16 + j) % 256) as u8).collect();
create_temp_file(&data)
})
.collect();
let tracker = TaskTracker::new();
for (i, (_tmp, path)) in files.iter().enumerate() {
let path = path.clone();
let expected: Vec<u8> = (0..1024).map(|j| ((i as u16 + j) % 256) as u8).collect();
tracker.spawn(async move {
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, expected);
});
}
tracker.close();
tracker.wait().await;
}
#[test]
fn test_file_read_multi_runtime() {
for rt_factory in rt_combinations() {
let rt = rt_factory();
let data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
let (_tmp, path) = create_temp_file(&data);
let result = rt.block_on(async {
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
buf
});
assert_eq!(result, data);
}
}
#[test]
fn shutdown_runtime_with_pending_reads() {
for rt_factory in rt_combinations() {
let rt = rt_factory();
let (done_tx, done_rx) = mpsc::channel();
let data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
let (_tmp, path) = create_temp_file(&data);
for _ in 0..50 {
let path = path.clone();
rt.spawn(async move {
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
let _ = file.read_to_end(&mut buf).await;
});
}
std::thread::spawn(move || {
rt.shutdown_timeout(Duration::from_millis(500));
done_tx.send(()).unwrap();
});
done_rx.recv().unwrap();
}
}
#[test]
fn test_file_read_from_std() {
let mut tmp = NamedTempFile::new().unwrap();
let data = b"hello from_std io-uring";
tmp.write_all(data).unwrap();
tmp.flush().unwrap();
let path = tmp.path().to_owned();
let rt = Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap();
rt.block_on(async {
let std_file = std::fs::File::open(&path).unwrap();
let mut file = File::from_std(std_file);
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, data);
});
}
/// Read with a buffer smaller than the file content. Verifies internal
/// buffering serves subsequent small reads without issuing new underlying read
/// operations.
#[tokio::test]
async fn test_file_read_with_smaller_buf() {
let data: Vec<u8> = (0..1024u16).map(|i| (i % 256) as u8).collect();
let (_tmp, path) = create_temp_file(&data);
let mut file = File::open(&path).await.unwrap();
// triggers an underlying read that fills the internal buffer with more data
// than we consume here
let mut buf = vec![0u8; 4];
let n = file.read(&mut buf).await.unwrap();
assert_eq!(n, 4);
assert_eq!(&buf, &data[..4]);
// Second read: still smaller than what's buffered internally
let mut buf = vec![0u8; 32];
let n = file.read(&mut buf).await.unwrap();
assert!(n > 0);
assert_eq!(&buf[..n], &data[4..4 + n]);
// Read the rest
let mut rest = Vec::new();
file.read_to_end(&mut rest).await.unwrap();
let total = 4 + n + rest.len();
assert_eq!(total, data.len());
}
#[tokio::test]
async fn test_file_read_with_bigger_buf() {
let data = b"hello io-uring";
let (_tmp, path) = create_temp_file(data);
let mut file = File::open(&path).await.unwrap();
// Read with buffer larger than file's contents
let mut buf = vec![0u8; 1024];
let n = file.read(&mut buf).await.unwrap();
assert!(n > 0 && n <= data.len());
assert_eq!(&buf[..n], &data[..n]);
if n < data.len() {
let mut rest = vec![0u8; 1024];
let n2 = file.read(&mut rest).await.unwrap();
assert_eq!(&rest[..n2], &data[n..n + n2]);
}
}
/// Read a file larger than DEFAULT_MAX_BUF_SIZE (2 MiB). Verifies that
/// chunked reads across multiple underlying operations produce correct data.
#[tokio::test]
async fn test_file_read_buffer_larger_than_max() {
// 4 MiB + 1000 bytes to cross multiple chunk boundaries.
let size = (4 << 20) + 1000;
let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
let (_tmp, path) = create_temp_file(&data);
let mut file = File::open(&path).await.unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf.len(), data.len());
assert_eq!(buf, data);
}
/// Read some bytes from a file, then write, verifying the implicit seek-back
/// works correctly.
#[tokio::test]
async fn test_file_read_then_write() {
let original = b"hello world, io-uring!";
let (_tmp, path) = create_temp_file(original);
let mut file = tokio::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.await
.unwrap();
let mut buf = vec![0u8; 5];
file.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"hello");
// Write at the current position
file.write_all(b" REPLACED").await.unwrap();
file.flush().await.unwrap();
// Re-read the full file and verify the write landed correctly
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
let mut result = Vec::new();
file.read_to_end(&mut result).await.unwrap();
assert_eq!(&result[..5], b"hello");
assert_eq!(&result[5..14], b" REPLACED");
}
/// Partial read followed by write at a different position. Verifies the
/// seek-back accounts for partially consumed internal buffer.
#[tokio::test]
async fn test_file_partial_read_then_write() {
let data = b"abcdefghijklmnopqrstuvwxyz";
let (_tmp, path) = create_temp_file(data);
let mut file = tokio::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.await
.unwrap();
// First read fills internal buffer.
let mut buf = vec![0u8; 10];
file.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"abcdefghij");
// Second read served from internal buffer
let mut buf2 = vec![0u8; 3];
file.read_exact(&mut buf2).await.unwrap();
assert_eq!(&buf2, b"klm");
// Write should seek back past unconsumed buffered bytes, then write
file.write_all(b"NOP").await.unwrap();
file.flush().await.unwrap();
file.seek(std::io::SeekFrom::Start(0)).await.unwrap();
let mut result = Vec::new();
file.read_to_end(&mut result).await.unwrap();
assert_eq!(&result[..13], b"abcdefghijklm");
assert_eq!(&result[13..16], b"NOP");
assert_eq!(&result[16..], &data[16..]);
}
+78
View File
@@ -0,0 +1,78 @@
//! Uring file operations tests.
#![cfg(all(
tokio_unstable,
feature = "io-uring",
feature = "rt",
feature = "fs",
target_os = "linux"
))]
use futures::FutureExt;
use std::fs;
use std::future::poll_fn;
use std::task::Poll;
use tempfile::NamedTempFile;
use tokio::runtime::Builder;
// see: https://github.com/tokio-rs/tokio/issues/7979
#[test]
fn shutdown_runtime_while_performing_io_uring_ops() {
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_path_buf();
let fd_count_before_opens = fs::read_dir("/proc/self/fd").unwrap().count();
let rt = Builder::new_multi_thread().enable_all().build().unwrap();
rt.block_on(async {
for _ in 0..128 {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let path = path.clone();
let handle = tokio::spawn(async move {
poll_fn(|cx| {
let opt = {
let mut opt = tokio::fs::OpenOptions::new();
opt.read(true);
opt
};
let fut = opt.open(&path);
// If io_uring is enabled (and not falling back to the thread pool),
// the first poll should return Pending. We don't check if the result
// is actually a pending because we run some CI checks based on old
// kernels that don't support uring.
let _pending = Box::pin(fut).poll_unpin(cx);
tx.send(()).unwrap();
Poll::<()>::Pending
})
.await;
});
// Wait for the first poll
rx.recv().await.unwrap();
handle.abort();
let res = handle.await.unwrap_err();
assert!(res.is_cancelled());
}
});
rt.shutdown_background();
let fd_count_after_cancel = fs::read_dir("/proc/self/fd").unwrap().count();
let leaked = fd_count_after_cancel.saturating_sub(fd_count_before_opens);
// Since we are opening 128 files, we expect that the related fds
// related to this operation will be closed. Since some other fds
// can be opened in the meantime, we expect this number to be higher
// than the counter before opening the files. This number could be
// lower, but to avoid test flakiness we check that this is at most
// half the number of the file we opened to check if there's a leak.
assert!(leaked <= 64);
}
+7 -6
View File
@@ -10,6 +10,7 @@ use std::time::Duration;
use std::{
future::Future,
io::{self, ErrorKind, Read, Write},
os::fd::OwnedFd,
task::{Context, Waker},
};
@@ -69,7 +70,7 @@ impl AsRawFd for FileDescriptor {
impl Read for &FileDescriptor {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
read(self.fd.as_raw_fd(), buf).map_err(io::Error::from)
read(&self.fd, buf).map_err(io::Error::from)
}
}
@@ -99,10 +100,10 @@ impl Write for FileDescriptor {
}
}
fn set_nonblocking(fd: RawFd) {
fn set_nonblocking(fd: &OwnedFd) {
use nix::fcntl::{OFlag, F_GETFL, F_SETFL};
let flags = nix::fcntl::fcntl(fd, F_GETFL).expect("fcntl(F_GETFD)");
let flags = nix::fcntl::fcntl(fd, F_GETFL).expect("fcntl(F_GETFL)");
if flags < 0 {
panic!(
@@ -114,7 +115,7 @@ fn set_nonblocking(fd: RawFd) {
let flags = OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK;
nix::fcntl::fcntl(fd, F_SETFL(flags)).expect("fcntl(F_SETFD)");
nix::fcntl::fcntl(fd, F_SETFL(flags)).expect("fcntl(F_SETFL)");
}
fn socketpair() -> (FileDescriptor, FileDescriptor) {
@@ -129,8 +130,8 @@ fn socketpair() -> (FileDescriptor, FileDescriptor) {
.expect("socketpair");
let fds = (FileDescriptor { fd: fd_a }, FileDescriptor { fd: fd_b });
set_nonblocking(fds.0.fd.as_raw_fd());
set_nonblocking(fds.1.fd.as_raw_fd());
set_nonblocking(&fds.0.fd);
set_nonblocking(&fds.1.fd);
fds
}

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