Compare commits

...
Author SHA1 Message Date
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 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 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 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 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
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
ADD-SP 0273e45ead chore: prepare Tokio v1.50.0 (#7934) 2026-03-03 10:39:42 +01:00
ADD-SP e3ee4e58dc chore: prepare tokio-macros v2.6.1 (#7943) 2026-03-02 19:17:41 +01:00
Evan Cameron 8c980ea75a io: add write_all_vectored to tokio-util (#7768) 2026-02-27 11:16:43 +01:00
Mattia Pitossi e35fd6d6b7 ci: fix patch during clippy step (#7935)
* fix the ci issue

* fix readme

* fix ci
2026-02-27 09:40:05 +02:00
LIParadise 03fe44c103 runtime: fix event_interval doc (#7932) 2026-02-24 16:30:53 +01:00
ADD-SP d18e5dfbb0 io: fix race in Mock::poll_write (#7882) 2026-02-23 20:49:37 -08:00
ADD-SP f21f2693f0 runtime: fix race condition during the blocking pool shutdown (#7922) 2026-02-23 22:50:27 +01:00
Andrew Lin d81e8f0acb macros: remove (most) local use declarations in tokio::select! (#7929) 2026-02-23 10:46:18 +01:00
Phong Chuong 25e7f2641e rt: fix missing quotation in docs (#7925) 2026-02-20 23:41:12 +02:00
Phong Chuong e1a91ef114 util: fix typo in docs (#7926) 2026-02-20 23:36:27 +02:00
0rlych1kk4 1b11840f53 task: clarify when to use spawn_blocking vs dedicated threads (#7923) 2026-02-20 14:19:27 +00:00
Maxime Grenu 09f92b5aed sync: clarify that recv returns None once closed and no more messages (#7920)
The previous documentation stated:
  'As such, Receiver::poll returns Ok(Ready(None))'

This was misleading: when all Sender handles are dropped, recv does NOT
immediately return None. Buffered messages already in the channel can
still be received. Only after all senders are dropped AND the channel
has been fully drained does recv return None.

Also update the method reference from the internal Receiver::poll to the
public API: Receiver::recv and Receiver::poll_recv.

Closes #6053
2026-02-20 08:34:11 +00:00
Maxime Grenu e65040f061 sync: clarify RwLock fairness documentation (#7919)
The previous wording 'if a task that wishes to acquire the write lock is
at the head of the queue, read locks will not be given out' was
misleading: it implied that readers are only blocked when the writer is
first in the queue. In reality, due to the FIFO ordering, any write
request queued *before* a read request will block that reader.

Replace with a more accurate description: 'a read lock will not be given
out until all write lock requests that were queued before it have been
acquired and released.'

Closes #6901
2026-02-19 15:57:42 +01:00
ADD-SP c23735d43b runtime: fix TOCTOU issue when decreasing num_idle_threads (#7918) 2026-02-17 21:50:26 -08:00
Varun Chawla d83921bc51 runtime: fix double increment of num_idle_threads on shutdown (#7910) 2026-02-16 23:19:50 -08:00
cui 96f64f4ee2 codec: fix is_readable should be buffer empty or not (#7912) 2026-02-16 17:33:35 +01:00
Zen 00d10c22f8 task: fix two typos (#7913) 2026-02-16 10:51:10 +00:00
Zen a25d95a8c4 io: clarify the behavior of AsyncWriteExt::shutdown() (#7908) 2026-02-14 11:42:24 -08:00
Stepan Koltsov 9e7e1ef7ad io: Explain how to flush stdout/stderr (#7904) 2026-02-12 14:06:48 +00:00
Mattia Pitossi 5bcd7c1d09 task: fix task module feature flags in docs (#7891) 2026-02-12 12:27:25 +01:00
winlogon 2486d49778 tests: skip issue_7144 if strace is missing (#7903) 2026-02-12 12:26:20 +01:00
Tim Vilgot Mikael Fredenberg 9dacb1c53e tokio: replace some futures with poll_fn (#7895) 2026-02-09 13:30:31 +01:00
Alex H 8167f87137 task: add AbortOnDrop (#7855) 2026-02-09 13:23:46 +01:00
Mattia Pitossi 159f70bc55 ci: fix ambiguity issue during tokio releases (#7848) 2026-02-09 11:04:59 +01:00
Jack Kleeman d1e4db1018 sync: drop rx waker when oneshot receiver is dropped (#7886) 2026-02-09 10:17:45 +01:00
DaniPopes 530af3f331 runtime: correct the default thread name in docs (#7896) 2026-02-08 03:34:44 -08:00
George Burgess IV d89e998922 net: fix GET_BUF_SIZE constant for target_os = "android" (#7889)
We recently tried to upgrade Android to the newest tokio, but tests that
use these constants fail, since Android is provided the non-Linux
constants.
2026-02-06 15:08:04 +01:00
tsyrulb 306ed1c30b stream: bump minimum tokio version to 1.38 (#7887)
tokio-stream 0.1.18 added `Stream::size_hint` for
`ReceiverStream` and `UnboundedReceiverStream` (PR #7492),
which calls `Receiver::is_closed()` and `Receiver::len()`
(added in tokio 1.37.0) and `Receiver::capacity()` and
`Receiver::max_capacity()` (added in tokio 1.38.0).

The declared minimum of tokio 1.15.0 is no longer sufficient,
causing compilation failures when resolved via
`-Z direct-minimal-versions`.

Refs: #7492
2026-02-06 11:45:30 +01:00
Tim Vilgot Mikael Fredenberg 7e952697e8 signal: specialize windows Registry (#7885) 2026-02-06 11:29:34 +01:00
n4n5 c0943f99f0 rt: clarify the documentation of Runtime::spawn (#7803) 2026-02-04 11:27:46 +01:00
DaniPopes 187a2146a7 runtime: shorten default thread name to fit in Linux limit (#7880)
Linux thread names are truncated at 15 characters.
Currently, Tokio threads show up as "tokio-runtime-w", whereas
shortening "runtime" to "rt" would make the thread name perfectly fit
in the 15 character limit.
2026-02-03 16:23:30 +01:00
Muzzaiyyan Hussain f61374f931 io: fix incorrect and confusing AsyncWrite documentation (#7875) 2026-02-03 16:22:34 +01:00
ADD-SP 0d6c7af3e4 runtime: wake deferred tasks before entering block_in_place (#7879) 2026-02-03 14:07:09 +01:00
Alice Ryhl 9dc9b53ae1 io: implement vectored writes for write_buf (#7871) 2026-01-30 19:52:57 +01:00
Tim Vilgot Mikael Fredenberg c3b31ba2ab signal: guarantee that listeners never return None (#7869) 2026-01-29 19:24:47 -08:00
Alice Ryhl b68ea4156a io: hardcode platform list for poll_write (#7872) 2026-01-29 15:12:10 +01:00
Chinmoy Das 8f6d0864c3 macros: improve error message for return type mismatch in #[tokio::main] (#7856) 2026-01-25 01:09:05 +09:00
mu001999 63abec0525 macros: use call_site hygiene to avoid unused qualification (#7866) 2026-01-22 14:54:43 +01:00
Mattia Pitossi 8fd44d9bdc docs: fix link to tokio::select! (#7867) 2026-01-20 23:05:01 +02:00
Shawn 450fa2d09d fs: add tests for when fs::hard_link fails (#7863) 2026-01-19 12:51:47 +00:00
Andrea Bozzo 8cfa309126 time: add docs about auto-advance and when to use sleep (#7858) 2026-01-19 09:12:37 +01:00
Mattia PitossiandMartin Grigorov bf185b61ff docs: fix broken links of select! (#7860)
Co-authored-by: Martin Grigorov <[email protected]>
2026-01-17 10:10:59 -08:00
Mattia Pitossi 27d1383581 deps: bump tokio to 1.47.0 (#7862) 2026-01-17 10:06:00 -08:00
Alice Ryhl 11f9d4db73 macros: add test for special return types (#7857) 2026-01-15 10:25:45 +01:00
F4RAN 1280cf81de io: always cleanup AsyncFd registration list on deregister (#7773) 2026-01-14 15:02:59 +01:00
vrtgs 67682ac2e8 io: add optimizer hint that memchr returns in-bounds pointer (#7792) 2026-01-14 14:58:33 +01:00
Finn Sheng b88c02c55e time: implement FusedStream for IntervalStream (#7854) 2026-01-14 14:49:03 +01:00
Tim Vilgot Mikael Fredenberg 240cc44da8 signal: remember the result of SetConsoleCtrlHandler (#7833)
The unix implementation remembers whether it failed or not, so the
windows implementation should do so as well. This PR also replaces the
`(Once, AtomicState)` pair with `OnceLock` and tries to remember the
original errno value.
2026-01-14 13:20:41 +01:00
IgorErin 7ed6da6733 runtime: add comments to schedule_option_task_without_yield (#7851) 2026-01-13 10:56:50 -08:00
Tahmid 7a2135f426 runtime: avoid lock acquisition after uring init (#7850) 2026-01-09 17:06:14 -08:00
Alice Ryhl 0913cad381 runtime: revert "avoid lock acquisition after uring init" (#7849)
This reverts commit ff9681b3c6.
2026-01-09 10:15:06 +01:00
Tahmid ff9681b3c6 runtime: avoid lock acquisition after uring init (#7843) 2026-01-08 19:24:29 -08:00
Alice Ryhl f1cb007a28 net: add TcpStream::set_zero_linger (#7837) 2026-01-08 08:18:04 +00:00
Zachary Becker 2d4853ea16 examples: use select! instead of join! in connect_(tcp|udp) examples (#7842) 2026-01-07 22:52:10 -08:00
Marc-Antoine Perennou d65165f7b5 rt: make is_rt_shutdown_err method public (#7771) 2026-01-07 08:26:01 +00:00
Mattia Pitossi 71a1a3da7a tokio: update outdated unstable features section (#7839) 2026-01-07 09:03:12 +01:00
Mattia Pitossi df73fa2188 runtime: panic when event_interval is set to 0 (#7838) 2026-01-06 09:31:56 +00:00
Alice Ryhl 09ad5367b8 runtime: avoid redundant unpark in current_thread scheduler (#7834) 2026-01-05 11:12:31 +01:00
Alice Ryhl 934f68d91c runtime: don't park in current_thread if before_park defers waker (#7835) 2026-01-05 10:13:07 +01:00
Alice Ryhl 41d1877689 chore: prepare tokio-test 0.4.5 (#7831) 2026-01-04 13:53:43 +01:00
Alice Ryhl 60b083b630 chore: prepare tokio-stream 0.1.18 (#7830) 2026-01-04 13:53:30 +01:00
Alice Ryhl 9cc02cc88d chore: prepare tokio-util 0.7.18 (#7829) 2026-01-04 13:53:14 +01:00
Mattia Pitossi d2799d791b task: improve the docs of Builder::spawn_local (#7828) 2026-01-04 19:58:03 +08:00
Stepan Koltsov 4d4870f291 task: doc that task drops before JoinHandle completion (#7825) 2026-01-03 13:41:03 +00:00
Tahmid fdb150901a fs: check for io-uring opcode support (#7815) 2026-01-03 13:39:07 +01:00
Mattia Pitossi 426a562780 rt: remove allow(dead_code) after JoinSet stabilization (#7826) 2026-01-03 11:01:57 +01:00
Alice Ryhl e3b89bbefa chore: prepare Tokio v1.49.0 (#7824) 2026-01-03 10:58:44 +01:00
Alice Ryhl 4f577b84e9 Merge 'tokio-1.47.3' into 'master' 2026-01-02 21:31:26 +01:00
Mattia Pitossi 16f20c34ed rt: mention LocalRuntime in new_current_thread docs (#7820) 2026-01-02 16:45:51 +01:00
Tim Vilgot Mikael Fredenberg 46674789ab signal: optimize unix signal storage to skip zero (#7819) 2026-01-02 14:24:55 +00:00
Tim Vilgot Mikael Fredenberg 8f4ebfd2f7 signal: specialize unix OsStorage (#7818) 2026-01-01 22:18:29 +01:00
Aman Gupta bd3940c14f docs: fix typos in bounded.rs and park.rs (#7817) 2026-01-01 19:22:17 +01:00
Mattia Pitossi c27bed36ac examples: improve the style of chat.rs (#7812) 2025-12-31 22:19:43 +08:00
Stepan Koltsov 6d3feb581f task: Better typed RawTask::try_read_output (#7806) 2025-12-30 16:53:20 +02:00
n4n5 a708ad19cb chore: fix minor typos (#7804) 2025-12-30 16:09:38 +09:00
Andrea Bozzo 4bc2a15d28 io: add SyncIoBridge cross-references to copy and copy_buf (#7798) 2025-12-29 14:29:56 +00:00
Joe Thomas 33566434bb metrics: clarify that num_alive_tasks is not strongly consistent (#7614) 2025-12-29 21:45:58 +08:00
Jan TojnarandThomas de Zeeuw 7388f2d2ea net: add support for TCLASS option on IPv6 (#7781)
Co-authored-by: Thomas de Zeeuw <[email protected]>
2025-12-29 12:38:26 +08:00
Tim Vilgot Mikael Fredenberg 0a3e386269 time: improve the readability of alternative timer (#7801) 2025-12-28 19:01:05 +08:00
vrtgs d666068be7 fs: handle EINTR in fs::write for io-uring (#7786) 2025-12-24 23:18:25 +08:00
Qi 1b17a7e241 ci: fix wasm32-wasip1 tests (#7788) 2025-12-24 10:07:28 +08:00
xibeiyoumian 5b91709edf chore: fix some minor typos in the comments (#7785)
Signed-off-by: xibeiyoumian <[email protected]>
2025-12-23 20:24:13 +08:00
Aaron Chen 08f40652aa macros: remove extern crate proc_macro (#7783) 2025-12-21 18:42:14 +08:00
Clara Engler 6403b5370e readme: remove TokioConf 2026 CFP announcement (#7774) 2025-12-20 10:09:00 +01:00
Qi 064181f386 io: add tokio_util::io::simplex (#7565)
Signed-off-by: ADD-SP <[email protected]>
2025-12-18 20:35:01 +08:00
QiandAlice Ryhl 009a2567d0 sync: clarify the cancellation safety of oneshot::Receiver (#7780)
Signed-off-by: ADD-SP <[email protected]>
Co-authored-by: Alice Ryhl <[email protected]>
2025-12-18 19:21:59 +08:00
Qi 231a3a69f9 task: stabilize the LocalSet::id() (#7776)
Signed-off-by: ADD-SP <[email protected]>
2025-12-16 19:02:09 +08:00
Chinedu Francis Nwafili 0fa7755e97 runtime: stabilize runtime::id::Id (#7125) 2025-12-16 00:29:10 +08:00
Clara Engler d3fe35593d net: clarify the cancellation safety of the TcpStream::peek (#7305) 2025-12-15 10:02:13 +08:00
Owen GriffithsandQi 0ec0a85461 io: document the default capacity of the ReaderStream (#7147)
Signed-off-by: ADD-SP <[email protected]>
Co-authored-by: Qi <[email protected]>
2025-12-12 23:22:17 +08:00
Qi 97d06ae1a6 macros: fix the hygiene issue of join! and try_join! (#7766)
Signed-off-by: ADD-SP <[email protected]>
2025-12-08 18:39:29 +08:00
Mattia Pitossi b5054e1dff docs: break up CONTRIBUTING.md into several parts (#7762) 2025-12-07 19:10:54 +08:00
Daksh 398eef8120 fs: support io_uring with tokio::fs::read (#7696) 2025-12-05 15:57:32 +08:00
Tethys Svensson c8116ecd7b stream: work around the rustc bug in StreamExt::collect (#7754) 2025-12-05 10:24:29 +08:00
Martin Grigorov 5471a5835e ci: upgrade FreeBSD from 14.2 to 14.3 (#7758)
14.2 is no more available:

```
$ gcloud compute images list --project freebsd-org-cloud-dev --no-standard-images
NAME                                             PROJECT                FAMILY                       DEPRECATED  STATUS
freebsd-13-5-release-amd64-gce                   freebsd-org-cloud-dev  freebsd-13-5                             READY
freebsd-13-5-stable-amd64-v20251030              freebsd-org-cloud-dev  freebsd-13-5-snap                        READY
freebsd-13-5-stable-amd64-v20251107              freebsd-org-cloud-dev  freebsd-13-5-snap                        READY
freebsd-14-3-release-amd64-ufs-gce               freebsd-org-cloud-dev  freebsd-14-3                             READY
freebsd-14-3-stable-amd64-ufs-20251120           freebsd-org-cloud-dev  freebsd-14-3-snap                        READY
freebsd-14-3-stable-amd64-ufs-20251127           freebsd-org-cloud-dev  freebsd-14-3-snap                        READY
freebsd-14-3-stable-amd64-zfs-20251113           freebsd-org-cloud-dev  freebsd-14-3-snap                        READY
freebsd-14-3-stable-amd64-zfs-20251120           freebsd-org-cloud-dev  freebsd-14-3-snap                        READY
freebsd-14-3-stable-amd64-zfs-20251127           freebsd-org-cloud-dev  freebsd-14-3-snap                        READY
freebsd-15-0-release-amd64-ufs                   freebsd-org-cloud-dev  freebsd-15-0-amd64-ufs                   READY
freebsd-15-0-release-amd64-zfs                   freebsd-org-cloud-dev  freebsd-15-0-amd64-zfs                   READY
freebsd-15-0-stable-amd64-ufs-20251120           freebsd-org-cloud-dev  freebsd-15-0-amd64-ufs-snap              READY
freebsd-15-0-stable-amd64-ufs-20251127           freebsd-org-cloud-dev  freebsd-15-0-amd64-ufs-snap              READY
freebsd-15-0-stable-amd64-zfs-20251120           freebsd-org-cloud-dev  freebsd-15-0-amd64-zfs-snap              READY
freebsd-15-0-stable-amd64-zfs-20251127           freebsd-org-cloud-dev  freebsd-15-0-amd64-zfs-snap              READY
freebsd-16-0-current-amd64-ufs-20251110          freebsd-org-cloud-dev  freebsd-16-0-snap                        READY
freebsd-16-0-current-amd64-zfs-20251110          freebsd-org-cloud-dev  freebsd-16-0-snap                        READY
freebsd-16-0-current-arm64-aarch64-ufs-20251111  freebsd-org-cloud-dev  freebsd-16-0-snap                        READY
freebsd-16-0-current-arm64-aarch64-zfs-20251111  freebsd-org-cloud-dev  freebsd-16-0-snap                        READY
```
2025-12-04 16:52:29 +08:00
Alex Gaynor be99e7aa04 benches: add spawn_blocking concurrency benchmark (#7748) 2025-12-03 10:20:50 +01:00
Alice Ryhl 3bf2e53f0b net: deprecate {TcpStream,TcpSocket}::set_linger (#7752) 2025-12-02 13:15:48 +01:00
Mattia Pitossi ab3996a6dd time: update outdated docs of Wheel (#7749) 2025-11-29 18:06:33 +08:00
Ralf Jung c03a37fa0b tokio: enable more tests in Miri (#7734) 2025-11-29 15:08:47 +08:00
Qi 73d733a341 time: add alternative timer for better multicore scalability (#7467)
This change introduces per-worker timer wheels in the time subsystem
to reduce the lock contention.

Key changes:
- Each worker now maintains a local timer wheel.
- Timer insertions are performed locally.
- Timer cancellations are forwarded via a
  dedicated cross-worker cancellation queue.

Relevant RFC: https://github.com/tokio-rs/tokio/issues/7384

---------

Signed-off-by: ADD-SP <[email protected]>
2025-11-27 09:29:28 +08:00
Elichai Turkel 749322d351 task: implement Extend for JoinSet (#7195) 2025-11-25 11:31:21 +01:00
jinronga 963b631754 refactor: introduce constants for default addresses and improve error handling in TCP examples (#7741)
- Added `DEFAULT_ADDR` constant to `chat.rs` and `echo-tcp.rs` for better maintainability.
- Enhanced error logging in `connect-tcp.rs` and `echo-tcp.rs` to include connection addresses.
- Improved peer management in `chat.rs` by automatically cleaning up disconnected peers.
2025-11-25 08:44:41 +02:00
Paolo Barbolini 9a1b076c00 io: replace Result<T, io::Error> with io::Result<T> in AsyncWrite (#7740) 2025-11-23 17:26:48 +08:00
Mohamed Macow c434ed7865 net: clarify the drop behavior of unix::OwnedWriteHalf (#7742) 2025-11-23 17:18:24 +08:00
Seaker 4714ca168d net: clarify the platform-dependent backlog in TcpSocket docs (#7738) 2025-11-16 18:44:35 +08:00
Mattia Pitossi 5e3ad02fb1 sync: fix a typo in the docs of PollSender::is_closed (#7737) 2025-11-15 18:43:02 +08:00
Qi 12412afea4 deps: bump tokio to 1.44.0 (#7733) 2025-11-13 11:47:34 +02:00
Mattia Pitossi cae083a26f docs: fix typos in README (#7731) 2025-11-12 12:33:24 +01:00
Carl Lerche fd7a8d7c65 chore: add TokioConf 2026 CFP announcement (#7730)
* chore: add TokioConf 2026 CFP announcement

* sync readmes
2025-11-12 08:53:40 +02:00
Qi d709df2571 ci: bump miri to nightly-2025-11-09 (#7726) 2025-11-09 18:53:16 +02:00
Qi 665f08b5ad tokio: enable the unsafe_op_in_unsafe_fn lint at the crate level (#7711)
Signed-off-by: ADD-SP <[email protected]>
2025-11-09 12:35:08 +01:00
Mattia Pitossi d4641ba9fc util: use <ptr>::addr instead of unsafe impl (#7725) 2025-11-08 23:49:09 +01:00
Motoyuki Kimura 2bf80f0ac6 runtime: disable io-uring on EPERM (#7724) 2025-11-08 17:13:12 +08:00
Muhamad Awad 62ecff895a stream: add ChunksTimeout::into_remainder (#7715) 2025-11-06 20:15:10 +08:00
Benjamin RanandBenjamin Ran d84a9e9af3 util: enable loom tests (#7644)
Co-authored-by: Benjamin Ran <[email protected]>
2025-11-06 19:20:08 +08:00
Ari Seyhun 0671c205cc sync: improve the docs for the errors of mpsc (#7722)
* docs: fix documentation comments for mpsc error enums

* docs: improve code docs for `TryRecvError`

* docs: improve code docs for mpsc `SendError`
2025-11-05 12:45:13 +00:00
Qi 1ece2f1fa7 task: remove unnecessary trait bounds on the Debug implementation (#7720)
Remove the trait bounds of the `Debug` impl for `JoinQueue`
and `AbortOnDropHandle`.

Signed-off-by: ADD-SP <[email protected]>
2025-11-04 11:15:29 +01:00
Ari Seyhun 12319f26d0 sync: add missing period to mpsc::Sender::try_send docs (#7721) 2025-11-03 21:20:25 +08:00
Qi 454fd8c347 chore: prepare tokio-util v0.7.17 (#7719)
Signed-off-by: ADD-SP <[email protected]>
2025-11-02 15:33:25 +01:00
Conrad Ludgate 4421022c25 codec: remove unnecessary trait bounds on all Framed constructors (#7716) 2025-10-29 20:08:13 +08:00
Daksh 5a709e391b io_uring: change Completable to not return io::Result (#7702) 2025-10-24 22:17:17 +02:00
Alice Ryhl 5efb1c3b16 io: doc that AsyncWrite does not inherit from Write (#7705) 2025-10-23 12:07:02 +02:00
Alice Ryhl f490029b8f runtime: revert "replace manual vtable definitions with Wake" (#7699)
This reverts commit 4380de9fe9.
2025-10-21 12:59:05 +02:00
Mattia Pitossi d25778f67d task: add tests for task::Builder::spawn_local (#7697) 2025-10-20 20:43:41 +08:00
Qi b8318fa172 task: add tests for spawn_local in panic scenarios (#7694)
Signed-off-by: ADD-SP <[email protected]>
2025-10-20 20:25:00 +08:00
Alice Ryhl acfdb87e2b task: use #[tokio::test] explicitly in tests/task_builder.rs (#7698) 2025-10-20 11:15:04 +00:00
KR-bluejay d060401f6c sync: return TryRecvError::Disconnected from Receiver::try_recv after Receiver::close (#7686) 2025-10-18 12:57:07 +02:00
FrancescoV1985 5dacc2e2a8 task: add tests for spawn_local and spawn_local_on (#7609)
Add tests for task collections (TaskTracker, JoinSet, JoinMap).
2025-10-16 23:44:57 +08:00
Mattia Pitossi 444d3f5c49 task: add example for spawn_local usage on local runtime (#7689) 2025-10-16 23:13:16 +08:00
Mattia Pitossi d23a838732 runtime: add tests for spawn local on multi and current runtimes (#7687) 2025-10-15 22:09:46 +03:00
Alice Ryhl 2137f7d953 process: remove obsolete allow(deprecated) from is_rt_shutdown_err (#7685) 2025-10-15 14:17:56 +02:00
Alice Ryhl 51e9dc0943 Merge 'tokio-1.47.2' into 'master' (#7683) 2025-10-14 20:30:15 +02:00
Alice Ryhl 556820ff84 chore: prepare Tokio v1.48.0 (#7677) 2025-10-14 15:07:25 +02:00
Alice Ryhl fd1659a052 chore: prepare tokio-macros v2.6.0 (#7676) 2025-10-14 14:24:54 +02:00
Alice Ryhl 53e8acac64 ci: update nightly version to 2025-10-12 (#7670) 2025-10-14 12:56:46 +02:00
Alice Ryhl 9e5527d1d5 process: fix error when runtime is shut down on nightly-2025-10-12 (#7672) 2025-10-14 12:56:41 +02:00
Sean McArthur 25a24de0e6 net: remove PollEvented noise from Debug formats (#7675) 2025-10-13 21:02:37 +00:00
Mattia Pitossi c1fa25f300 task: clarify the behavior of several spawn_local methods (#7669) 2025-10-12 10:46:23 +08:00
Denis Davydov e7e02fcf0f fs: use FileOptions inside fs::File to support uring (#7617) 2025-10-10 17:06:13 +02:00
tottoto f7a7f62959 ci: remove cargo-deny Unicode-DFS-2016 license exception config (#7619) 2025-10-10 16:55:48 +02:00
QiandAlice Ryhl d1f1499f63 tokio: use cargo feature for taskdump support instead of cfg (#7655)
Signed-off-by: ADD-SP <[email protected]>
Co-authored-by: Alice Ryhl <[email protected]>
2025-10-10 11:31:41 +08:00
Samuele ad6f618952 runtime: clarify the behavior of Handle::block_on (#7665) 2025-10-10 10:01:17 +08:00
tison 0f9ae13c31 task: add LocalKey::try_get (#7666)
Signed-off-by: tison <[email protected]>
2025-10-09 10:19:27 +02:00
whollins 9255d96b1b deps: bump windows-sys to version 0.61 (#7645) 2025-10-07 10:40:42 +02:00
Qi ffcc9f7c95 tokio: fix the docs of feature flag (#7663)
Signed-off-by: ADD-SP <[email protected]>
2025-10-06 19:34:56 +08:00
Mattia 1a4cf319b5 sync: improve the docs of UnboundedSender::send (#7661) 2025-10-05 15:14:38 +08:00
Motoyuki Kimura 3698a6f153 fs: support io_uring in fs::write (#7567) 2025-10-02 11:01:18 +00:00
Alice Ryhl 5b4cbbc39e tokio: raise MSRV to 1.71 (#7658) 2025-10-02 11:14:17 +02:00
Ruiyang Sun c1f0c76fa0 macros: suppress clippy::unwrap_in_result in #[tokio::main] (#7651) 2025-10-02 16:53:25 +08:00
Tudyx d0953e833d task: simplify the example of TaskTracker (#7657) 2025-10-02 15:07:28 +08:00
Qi b157f5da76 runtime: add guide for choosing between runtime types (#7635)
Signed-off-by: ADD-SP <[email protected]>
2025-10-01 22:00:46 +08:00
Xinye TaoandXinye Tao 35470bfc6e sync: clarify bounded channel panic behavior (#7641)
Signed-off-by: Xinye Tao <[email protected]>
Co-authored-by: Xinye Tao <[email protected]>
2025-10-01 10:38:04 +02:00
Alice Ryhl 95edd8515e docs: fix some docs links (#7654) 2025-09-30 12:35:46 +00:00
Qi a0f7f5c94a fs: emit compilation error without tokio_unstable for io-uring (#7634)
Signed-off-by: ADD-SP <[email protected]>
2025-09-30 19:53:19 +08:00
Qi 02486978d1 ci: freeze rustc on nightly-2025-01-25 in netlify.toml (#7652)
Signed-off-by: ADD-SP <[email protected]>
2025-09-29 23:51:20 +08:00
Lucas Black 8ccf2fb92e ci: unfreeze wasm tests from rustc 1.88.0 (#7537) 2025-09-26 21:29:01 +08:00
Nikolai Kuklin bce76c515f task: add try_join_next and try_join_next_with_id on JoinQueue (#7636) 2025-09-25 20:31:31 +08:00
Martin Grigorov b48586f560 tokio: fix typos in tokio/CHANGELOG.md (#7643) 2025-09-23 22:12:41 +08:00
Jess Izen eb99e476e6 macros: fix the hygiene issue of join! and try_join! (#7638) 2025-09-21 13:47:22 +08:00
Daniel Sharifi b9b532485b sync: clarify the behavior of tokio::sync::watch::Receiver (#7584) 2025-09-20 22:02:56 +08:00
Nikolai Kuklin 1b98d5ad85 task: add tokio_util::task::JoinQueue (#7590) 2025-09-20 15:38:59 +08:00
Martin Grigorov 6d1ae62868 sync: close the broadcast::Sender in broadcast::Sender::new() (#7629) 2025-09-20 14:44:55 +08:00
Motoyuki KimuraandEmile Fugulin 3b5a15dfdf fs: use the Cargo feature for io-uring support instead of cfg (#7621)
Co-authored-by: Emile Fugulin <[email protected]>
2025-09-19 23:22:44 +08:00
Qi 2af3e4430a ci: update macros_type_mismatch for Rust 1.90.0 (#7630)
Signed-off-by: ADD-SP <[email protected]>
2025-09-19 16:11:21 +02:00
Adam Ning 67869be3d7 runtime: use release in wake_by_ref() even if already woken (#7622) 2025-09-18 13:41:23 +02:00
Vaibhav Gupta c6b16cc861 net: clarify the supported platform of set_reuseport() and reuseport() (#7628) 2025-09-17 21:10:10 +08:00
Martin Grigorov 7c197c7784 runtime: clarify the edge case of Builder::global_queue_interval() (#7605) 2025-09-16 21:41:18 +08:00
Martin GrigorovandAlice Ryhl 5f3f5b0be4 sync: improve the docs of sync::watch (#7601)
Co-authored-by: Alice Ryhl <[email protected]>
2025-09-15 19:56:29 +08:00
tottoto 32a1acc85f examples: bump http crate from 0.2 to 1 (#7618) 2025-09-15 18:22:57 +08:00
Sean Oxley 7f455b2d93 task: clarify the task ID reuse guarantees (#7577) 2025-09-15 18:18:16 +08:00
Martin Grigorov 86de2e306b util: fix pending_only_on_first_poll_with_cancellation_token_owned_test to use an owned cancellation token (#7613)
The name of the test suggests that it should test the
with_cancellation_token_owned() extension method
2025-09-15 16:22:42 +09:00
Martin Grigorov 6dc4f85f0b net: clarify the behavior of UCred::pid() on Cygwin (#7611) 2025-09-15 00:05:05 +08:00
Martin Grigorov 637fc1d103 tokio: fix minor errors in tokio/CHANGELOG.md (#7608) 2025-09-11 10:02:34 +00:00
Martin Grigorov 7a0ca807be time: add #[track_caller] to FutureExt::timeout (#7588)
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
2025-09-11 09:18:40 +02:00
Martin Grigorov d8e8037de5 net: fix copy/paste errors in udp peek methods (#7604) 2025-09-10 16:33:35 +02:00
Martin Grigorov de978c47e0 task: remove duplicated code in JoinMap::remove_by_id (#7603) 2025-09-10 22:16:15 +08:00
Qi 024bd60933 task: improve the example of poll_proceed (#7586)
Signed-off-by: ADD-SP <[email protected]>
2025-09-10 20:57:03 +08:00
Aatif Syed 7127e257a7 io: export Chain of AsyncReadExt::chain (#7599) 2025-09-10 10:45:42 +02:00
Martin Grigorov 94b6df699b fs: fill the destination buffer with 0s for MockFile::read() (#7596) 2025-09-10 10:41:20 +02:00
Martin Grigorov 510b9ea9dc macros: Update the version used for Git tag sample for release steps (#7598)
Change 1.x.y to x.y.z so that it does not become obsolete again when
tokio-macros 3.x is released
2025-09-09 15:52:58 +02:00
Martin Grigorov 0fc23971d9 macros: add missing local flavor to tokio::main error message (#7597) 2025-09-09 21:26:16 +08:00
unvalley f07233f742 test: add tests for time in wasm32-unknown-unknown (#7510) 2025-09-09 12:31:14 +02:00
Martin Grigorov 8efd04e382 sync: reword allocation failure paragraph in broadcast docs (#7595) 2025-09-09 09:05:14 +00:00
Martin Grigorov 044eaa1a41 fs: preserve max_buf_size when cloning a File (#7593) 2025-09-09 08:34:35 +00:00
Martin Grigorov 03bb6e29b1 fs: add File::max_buf_size (#7594) 2025-09-09 08:12:07 +00:00
Martin Grigorov ac4c95972e sync: fix implementation of unused RwLock::try_* methods (#7587)
bd4ccae184 introduced a wrapper for the
RwLock to get rid of poisoning aspects.

By mistake (?!) its try_read/write methods actually delegate to
read/write() and this would lead to blocking

Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
2025-09-09 09:39:01 +02:00
Martin Grigorov 14e739c306 readme: fix the version used as an example how to use the latest minor of LTS (#7592) 2025-09-09 09:37:47 +02:00
Qi 9f59c6952e ci: remove the job test-pass (#7575)
Signed-off-by: ADD-SP <[email protected]>
2025-09-08 12:07:40 +02:00
Martin Grigorov 86400a1920 io: fix typos in the docs of AsyncFd readiness guards (#7583)
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
2025-09-06 15:58:41 +08:00
Martin Grigorov c9b4e4c110 examples: fix the write length in the connect-tcp example (#7581)
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
2025-09-06 15:49:07 +08:00
Martin Grigorov 3eb515e1a7 examples: update outdated example name connect to connect-tcp (#7582)
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
2025-09-06 14:01:40 +08:00
Martin Grigorov 1ed2a1436f process: fix unit test for trailing LF in uname -r (#7579)
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
2025-09-06 13:52:16 +08:00
Martin Grigorov c6aceed643 io: clarify the zero capacity case of AsyncRead::poll_read (#7580)
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
2025-09-06 13:42:35 +08:00
Martin Grigorov 4590828fb4 stream: improve the the docs of TcpListenerStream (#7578)
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
2025-09-06 13:36:41 +08:00
Roman a99a351802 sync: use UnsafeCell::get_mut in Mutex::get_mut and RwLock::get_mut (#7569) 2025-09-04 22:39:28 +08:00
Aatif Syed d1e06f831e net: implement AsRef<Self> for TcpStream and UnixStream (#7573) 2025-09-04 10:31:49 +00:00
Sam 37ca2f049c sync: remove inner mutex in SetOnce (#7554) 2025-09-03 17:37:50 +02:00
Varun Doshi c8371d45bc codec: add {FramedRead,FramedWrite}::into_parts() (#7566) 2025-09-03 13:38:51 +02:00
Daniel Sharifi adc3e19ba7 time: clarify the cancellation safety of the DelayQueue (#7564) 2025-08-31 20:57:17 +08:00
Alex Bakon 925c614c89 time: reduce the generated code size of Timeout<T>::poll (#7535) 2025-08-19 13:42:55 +02:00
Asger Hautop Drewsen dd74c7c1bf task: implement Ord for task::Id (#7530) 2025-08-15 09:52:12 +02:00
Logan Praneis 23263231fd net: qualify that SO_REUSEADDR is only set on Unix (#7533) 2025-08-14 09:25:55 +08:00
CrazyFrog 86528741f9 ci: update GitHub actions/checkout to v5 (#7529) 2025-08-13 12:57:45 +02:00
Noam Soloveichik 131afd3c53 net: clarify socket gets closed on drop (#7526) 2025-08-11 16:26:01 +00:00
Qi 9ed6f70b81 ci: remove a typo from spellcheck.dic (#7524)
Signed-off-by: ADD-SP <[email protected]>
2025-08-10 20:31:09 +08:00
mxsm 46f7d87962 runtime: fix a typo in comment of MAX_LIFO_POLLS_PER_TICK (#7520) 2025-08-09 22:29:43 +08:00
Motoyuki Kimura 987675e843 ci: pin the rust version for wasm tests (#7518) 2025-08-08 10:09:31 +00:00
Qi 11d7c0486a task: inline the docs of TaskTracker while re-exporting it (#7516)
Signed-off-by: ADD-SP <[email protected]>
2025-08-08 09:54:25 +02:00
Motoyuki Kimura 3e84a198e4 fs: add io_uring open operation (#7321) 2025-08-08 09:51:42 +02:00
Qi 7497561fed net: render the cygwin in the docs of quickack and set_quickack (#7515)
Signed-off-by: ADD-SP <[email protected]>
2025-08-06 09:01:27 +08:00
Luca Bruno ef5b6af7f6 future: clarify the fairness of FutureExt for cancellation adapters (#7512)
This fixes the docstrings on `FutureExt` so that the bias and fairness
notes are correct and consistent in all cases.
All cancellation-related wrappers are biased towards the completion of
the inner future, but they do initially check if the token is
already cancelled at construction time.
2025-08-04 21:26:11 +08:00
Motoyuki Kimura 0922aa2a0b ci: fix clippy warnings triggered under specific cfg (#7495) 2025-08-04 10:36:17 +00:00
Michael Zhao 2403b91d75 process: upgrade Command::spawn_with to use FnOnce (#7511) 2025-08-03 18:27:37 +00:00
Conrad Ludgate f1d3b065b6 task: fix flaky joinmap test during abort (#7509) 2025-08-03 20:00:33 +02:00
Alice Ryhl cf6b50a3fd chore: prepare tokio-util v0.7.16 (#7507) 2025-08-03 11:12:41 +02:00
Conrad Ludgate 416e36b0df task: stabilise JoinMap (#7075) 2025-08-03 07:58:28 +00:00
Alice Ryhl 9741c90f9f sync: document cancel safety on SetOnce::wait (#7506) 2025-08-03 07:42:30 +00:00
Lucas Black 4e3f17bce3 codec: also apply capacity to read buffer in Framed::with_capacity (#7500) 2025-08-01 21:26:33 +02:00
Alice Ryhl 86cbf81e15 Merge 'tokio-1.47.1' into 'master' 2025-08-01 13:21:45 +02:00
Alice Ryhl e47565b086 blocking: clarify that spawn_blocking is aborted if not yet started (#7501) 2025-08-01 10:32:37 +00:00
Lucas Black 1bc50825f3 codec: add FramedWrite::with_capacity (#7493) 2025-08-01 11:23:22 +02:00
Alice Ryhl ad2e19ffe1 readme: add 1.47 as LTS release (#7497) 2025-07-31 16:45:10 +02:00
Qi 5f04d14d81 net: add TcpStream::quickack and TcpStream::set_quickack (#7490)
Signed-off-by: ADD-SP <[email protected]>
2025-07-31 19:58:01 +08:00
Motoyuki Kimura 01ea8f22ea ci: add kernel-version-test workflow for io_uring tests (#7486) 2025-07-31 20:52:28 +09:00
Łukasz Sobczak 9f423053fb sync: umplement Stream::size_hint for ReceiverStream and UnboundedReceiverStream (#7492) 2025-07-29 15:35:09 +00:00
yanyuxing 0e5c5d64f5 future: add adapters of CancellationToken for FutureExt (#7475) 2025-07-29 18:09:13 +08:00
Luca Bruno 1b27e17ff8 net: add SocketAddr::as_abstract_name (#7491) 2025-07-29 11:07:58 +02:00
Jess Izen 8fc62c06c7 metrics: reorder metrics to be grouped by cfg-gates (#7453) 2025-07-29 07:40:57 +02:00
James Kay 4b96af6040 macros: add "local" runtime flavor (#7375) 2025-07-28 13:59:00 +02:00
378 changed files with 19435 additions and 8355 deletions
+7
View File
@@ -1,3 +1,6 @@
R-loom-blocking:
- tokio/src/runtime/blocking/*
- tokio/src/runtime/blocking/**/*
R-loom-sync:
- tokio/src/sync/*
@@ -19,3 +22,7 @@ R-loom-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/**/*
+1 -1
View File
@@ -20,5 +20,5 @@ jobs:
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: EmbarkStudios/cargo-deny-action@v2
+267 -206
View File
@@ -15,10 +15,10 @@ env:
RUST_BACKTRACE: 1
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
# Change to specific Rust release to pin
rust_stable: stable
rust_nightly: nightly-2025-01-25
rust_stable: 1.94
rust_nightly: nightly-2025-10-12
# Pin a specific miri version
rust_miri_nightly: nightly-2025-06-02
rust_miri_nightly: nightly-2025-11-13
rust_clippy: '1.88'
# When updating this, also update:
# - README.md
@@ -28,7 +28,10 @@ env:
# - tokio-util/Cargo.toml
# - tokio-test/Cargo.toml
# - tokio-stream/Cargo.toml
rust_min: '1.70'
rust_min: '1.71'
# This excludes unstable features like io_uring,
# which require '--cfg tokio_unstable'.
TOKIO_STABLE_FEATURES: "full,test-util"
defaults:
run:
@@ -38,48 +41,6 @@ permissions:
contents: read
jobs:
# Depends on all actions that are required for a "successful" CI run.
tests-pass:
name: all systems go
runs-on: ubuntu-latest
needs:
- test-tokio-full
- test-workspace-all-features
- test-integration-tests-per-feature
- test-parking_lot
- valgrind
- test-unstable
- miri-lib
- miri-test
- miri-doc
- asan
- cross-check
- cross-check-tier3
- cross-test-with-parking_lot
- cross-test-without-parking_lot
- no-atomic-u64-test
- no-atomic-u64-check
- features
- minrust
- minimal-versions
- fmt
- clippy
- docs
- loom-compile
- check-readme
- test-hyper
- test-quinn
- x86_64-fortanix-unknown-sgx
- check-redox
- wasm32-unknown-unknown
- wasm32-wasip1
- check-external-types
- check-fuzzing
- check-unstable-mt-counters
- check-spelling
steps:
- run: exit 0
# Basic actions that must pass before we kick off more expensive tests.
basics:
name: basic checks
@@ -103,7 +64,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -115,7 +76,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
# Run `tokio` with `full` features. This excludes testing utilities which
# Run `tokio` with stable features. This excludes testing utilities which
# can alter the runtime behavior of Tokio.
- name: test tokio full
run: |
@@ -135,7 +96,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -147,12 +108,21 @@ jobs:
- uses: Swatinem/rust-cache@v2
# Test **all** crates in the workspace with all features.
- name: test all --all-features
- name: test --features ${{ env.TOKIO_STABLE_FEATURES }}
run: |
set -euxo pipefail
cargo nextest run --workspace --all-features
cargo test --doc --workspace --all-features
cargo nextest run --workspace --features $TOKIO_STABLE_FEATURES
# Removing workspace patches to run tests without path dependencies
# (if not specified differently in the crate)
perl -0 -i -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
cargo nextest run \
--workspace \
--exclude tokio \
--exclude examples \
--features $TOKIO_STABLE_FEATURES
# Cargo nextest does not support doctest, so we run them separately
# (see https://github.com/nextest-rs/nextest/issues/16)
cargo test --doc --workspace --features $TOKIO_STABLE_FEATURES
test-workspace-all-features-panic-abort:
needs: basics
@@ -165,7 +135,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -177,10 +147,15 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: test all --all-features panic=abort
- name: test --features ${{ env.TOKIO_STABLE_FEATURES }} panic=abort
run: |
set -euxo pipefail
RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run --workspace --exclude tokio-macros --exclude tests-build --all-features --tests
RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run \
--workspace \
--exclude tokio-macros \
--exclude tests-build \
--features $TOKIO_STABLE_FEATURES \
--tests
test-integration-tests-per-feature:
needs: basics
@@ -193,7 +168,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -233,7 +208,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -244,15 +219,22 @@ jobs:
run: sed -i '/\[features\]/a plsend = ["parking_lot/send_guard"]' tokio/Cargo.toml
- uses: Swatinem/rust-cache@v2
- name: Check tests with all features enabled
run: cargo check --workspace --all-features --tests
- name: Check tests --features ${{ env.TOKIO_STABLE_FEATURES }}
run: |
set -euxo pipefail
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)
perl -0 -i -pe 's/\[patch\.crates-io\].+\n\[/[/s' Cargo.toml
cargo check --workspace --exclude tokio --tests --features $TOKIO_STABLE_FEATURES
valgrind:
name: valgrind
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -287,11 +269,13 @@ jobs:
strategy:
matrix:
include:
- os: windows-latest
- os: ubuntu-latest
- os: macos-latest
- { os: windows-latest, extra_features: "" }
- { os: ubuntu-latest, extra_features: "" }
# only Linux supports io_uring
- { os: ubuntu-latest, extra_features: io-uring }
- { os: macos-latest, extra_features: "" }
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -307,8 +291,8 @@ jobs:
- name: test tokio full --cfg unstable
run: |
set -euxo pipefail
cargo nextest run --all-features
cargo test --doc --all-features
cargo nextest run --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }}
cargo test --doc --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }}
working-directory: tokio
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
@@ -325,7 +309,7 @@ jobs:
include:
- os: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -341,47 +325,14 @@ jobs:
- name: test tokio full --cfg unstable --cfg taskdump
run: |
set -euxo pipefail
cargo nextest run --all-features
cargo test --doc --all-features
cargo nextest run --features $TOKIO_STABLE_FEATURES,taskdump
cargo test --doc --features $TOKIO_STABLE_FEATURES
working-directory: tokio
env:
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
# in order to run doctests for unstable features, we must also pass
# the unstable cfg to RustDoc
RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_taskdump
test-uring:
name: test tokio full --cfg tokio_uring
needs: basics
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_stable }}
- name: Install cargo-nextest
uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- uses: Swatinem/rust-cache@v2
- name: test tokio full --cfg tokio_uring
run: |
set -euxo pipefail
cargo nextest run --all-features
cargo test --doc --all-features
working-directory: tokio
env:
RUSTFLAGS: --cfg tokio_uring -Dwarnings
# in order to run doctests for unstable features, we must also pass
# the unstable cfg to RustDoc
RUSTDOCFLAGS: --cfg tokio_uring
RUSTDOCFLAGS: --cfg tokio_unstable
check-unstable-mt-counters:
name: check tokio full --internal-mt-counters
@@ -392,7 +343,7 @@ jobs:
include:
- os: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -402,7 +353,8 @@ jobs:
with:
tool: cargo-nextest
- uses: Swatinem/rust-cache@v2
# Run `tokio` with "unstable" and "taskdump" cfg flags.
# Since the internal-mt-counters feature is only for debugging purposes,
# we can enable all features including unstable.
- name: check tokio full --cfg unstable --cfg internal-mt-counters
run: |
set -euxo pipefail
@@ -420,7 +372,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -436,14 +388,14 @@ jobs:
cargo miri nextest run --features full --lib --no-fail-fast
working-directory: tokio
env:
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
miri-test:
name: miri-test
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -459,14 +411,14 @@ jobs:
cargo miri nextest run --features full --test '*' --no-fail-fast
working-directory: tokio
env:
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
miri-doc:
name: miri-doc
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_miri_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -475,17 +427,17 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: miri-doc-test
run: |
cargo miri test --doc --all-features --no-fail-fast
cargo miri test --doc --features $TOKIO_STABLE_FEATURES --no-fail-fast
working-directory: tokio
env:
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
asan:
name: asan
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install llvm
# Required to resolve symbols in sanitizer output
run: sudo apt-get install -y llvm
@@ -496,7 +448,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: asan
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1 --nocapture
run: cargo test --workspace --features $TOKIO_STABLE_FEATURES --target x86_64-unknown-linux-gnu --tests -- --test-threads 1 --nocapture
env:
RUSTFLAGS: -Z sanitizer=address --cfg tokio_no_tuning_tests
# Ignore `trybuild` errors as they are irrelevant and flaky on nightly
@@ -507,13 +459,16 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Check `tokio` semver
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
rust-toolchain: ${{ env.rust_stable }}
package: tokio
release-type: minor
feature-group: only-explicit-features
# We don't care about the semver of unstable tokio features.
features: ${{ env.TOKIO_STABLE_FEATURES }}
- name: Check semver for rest of the workspace
if: ${{ !startsWith(github.event.pull_request.base.ref, 'tokio-1.') }}
uses: obi1kenobi/cargo-semver-checks-action@v2
@@ -533,7 +488,7 @@ jobs:
- powerpc64-unknown-linux-gnu
- arm-linux-androideabi
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -541,7 +496,8 @@ jobs:
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- run: cargo check --workspace --all-features --target ${{ matrix.target }}
# We don't use --all-features since io-uring will be enabled and is not supported on those targets.
- run: cargo check --workspace --features $TOKIO_STABLE_FEATURES --target ${{ matrix.target }}
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
@@ -553,10 +509,11 @@ jobs:
matrix:
target:
- name: x86_64-unknown-haiku
- name: armv7-sony-vita-newlibeabihf
exclude_features: "process,signal,rt-process-signal,full"
exclude_features: "taskdump" # taskdump is only available on Linux
# - name: armv7-sony-vita-newlibeabihf
# exclude_features: "process,signal,rt-process-signal,full,taskdump"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@nightly
with:
@@ -579,14 +536,14 @@ jobs:
include:
- target: i686-unknown-linux-gnu
os: ubuntu-latest
rustflags: --cfg tokio_taskdump
extra_features: "taskdump"
- target: armv5te-unknown-linux-gnueabi
os: ubuntu-latest
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-24.04-arm
- target: aarch64-unknown-linux-gnu
os: ubuntu-24.04-arm
rustflags: --cfg tokio_taskdump
extra_features: "io-uring,taskdump"
- target: aarch64-pc-windows-msvc
os: windows-11-arm
steps:
@@ -611,11 +568,15 @@ jobs:
- name: Tests run with all features (including parking_lot)
run: |
set -euxo pipefail
cargo nextest run -p tokio --all-features --target ${{ matrix.target }}
cargo test --doc -p tokio --all-features --target ${{ matrix.target }}
# We use `--features "$TOKIO_STABLE_FEATURES"` instead of `--all-features` since
# `--all-features` includes `io_uring` and `taskdump`,
# which is not available on all targets.
cargo nextest run -p tokio --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }} --target ${{ matrix.target }}
cargo test --doc -p tokio --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }} --target ${{ matrix.target }}
env:
RUST_TEST_THREADS: 1
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_tuning_tests
RUSTDOCFLAGS: --cfg tokio_unstable -Dwarnings
cross-test-without-parking_lot:
needs: basics
@@ -625,14 +586,14 @@ jobs:
include:
- target: i686-unknown-linux-gnu
os: ubuntu-latest
rustflags: --cfg tokio_taskdump
extra_features: "taskdump"
- target: armv5te-unknown-linux-gnueabi
os: ubuntu-latest
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-24.04-arm
- target: aarch64-unknown-linux-gnu
os: ubuntu-24.04-arm
rustflags: --cfg tokio_taskdump
extra_features: "io-uring,taskdump"
- target: aarch64-pc-windows-msvc
os: windows-11-arm
steps:
@@ -661,11 +622,15 @@ jobs:
- name: Tests run with all features (without parking_lot)
run: |
set -euxo pipefail
cargo nextest run -p tokio --features full,test-util --target ${{ matrix.target }}
cargo test --doc -p tokio --features full,test-util --target ${{ matrix.target }}
# We use `--features "$TOKIO_STABLE_FEATURES"` instead of `--all-features` since
# `--all-features` includes `io_uring` and `taskdump`,
# which is not available on all targets.
cargo nextest run -p tokio --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }} --target ${{ matrix.target }}
cargo test --doc -p tokio --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }} --target ${{ matrix.target }}
env:
RUST_TEST_THREADS: 1
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_parking_lot --cfg tokio_no_tuning_tests ${{ matrix.rustflags }}
RUSTDOCFLAGS: --cfg tokio_unstable -Dwarnings
# See https://github.com/tokio-rs/tokio/issues/5187
no-atomic-u64-test:
@@ -673,7 +638,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -693,18 +658,19 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: test tokio --all-features
run: |
cargo nextest run -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
cargo test --doc -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --all-features
cargo nextest run -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --features $TOKIO_STABLE_FEATURES,taskdump
cargo test --doc -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --features $TOKIO_STABLE_FEATURES,taskdump
env:
RUST_TEST_THREADS: 1
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings --cfg tokio_no_tuning_tests
RUSTDOCFLAGS: --cfg tokio_unstable
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_tuning_tests
no-atomic-u64-check:
name: Check tokio --feature-powerset --depth 2 on i686-unknown-linux-gnu without AtomicU64
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -720,26 +686,29 @@ jobs:
# https://github.com/tokio-rs/tokio/pull/5356
# https://github.com/tokio-rs/tokio/issues/5373
- name: Check
run: cargo hack check -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --feature-powerset --depth 2 --keep-going
# We use `--skip io-uring` since io-uring crate doesn't provide a binding for the i686 target.
run: cargo hack check -Zbuild-std --target target-specs/i686-unknown-linux-gnu.json -p tokio --feature-powerset --skip io-uring --depth 2 --keep-going
env:
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
features:
name: features ${{ matrix.name }}
name: features exclude ${{ matrix.name }}
needs: basics
runs-on: ubuntu-latest
strategy:
matrix:
include:
- { name: "", rustflags: "" }
# Try with unstable feature flags
- { name: "--unstable", rustflags: "--cfg tokio_unstable -Dwarnings" }
# Try with unstable and taskdump feature flags
- { name: "--unstable --taskdump", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump" }
- { name: "--tokio_uring", rustflags: "-Dwarnings --cfg tokio_uring" }
- { name: "--unstable --taskdump --tokio_uring", rustflags: "--cfg tokio_unstable -Dwarnings --cfg tokio_taskdump --cfg tokio_uring" }
- name: ""
rustflags: ""
exclude_features: "io-uring,taskdump"
- name: "--unstable"
rustflags: "--cfg tokio_unstable -Dwarnings"
exclude_features: "io-uring,taskdump"
- name: "--unstable io-uring,taskdump"
rustflags: "--cfg tokio_unstable -Dwarnings"
exclude_features: ""
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -750,7 +719,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: check --feature-powerset ${{ matrix.name }}
run: cargo hack check --all --feature-powerset --depth 2 --keep-going
run: cargo hack check --all --feature-powerset --exclude-features "${{ matrix.exclude_features }}" --depth 2 --keep-going
env:
RUSTFLAGS: ${{ matrix.rustflags }}
@@ -758,30 +727,36 @@ jobs:
name: minrust
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_min }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_min }}
- name: Install cargo-hack
uses: taiki-e/install-action@v2
with:
tool: cargo-hack
- uses: Swatinem/rust-cache@v2
- name: "check --workspace --all-features"
- name: "cargo check"
run: |
if [[ "${{ github.event.pull_request.base.ref }}" =~ ^tokio-1\..* ]]; then
# Only check `tokio` crate as the PR is backporting to an earlier tokio release.
cargo check -p tokio --all-features
cargo check -p tokio --features $TOKIO_STABLE_FEATURES
else
# Check all crates in the workspace
cargo check --workspace --all-features
cargo check -p tokio --features $TOKIO_STABLE_FEATURES
# Other crates doesn't have unstable features, so we can use --all-features.
cargo hack check -p tokio-macros -p tokio-stream -p tokio-util -p tokio-test --all-features
fi
env:
RUSTFLAGS: "" # remove -Dwarnings
minimal-versions:
name: minimal-versions
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -790,17 +765,18 @@ jobs:
uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v2
- name: "check --all-features -Z minimal-versions"
- name: "check -Z minimal-versions"
run: |
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
# from determining minimal versions based on dev-dependencies.
cargo hack --remove-dev-deps --workspace
# Update Cargo.lock to minimal version dependencies.
cargo update -Z minimal-versions
cargo hack check --all-features --ignore-private
cargo hack check -p tokio --features $TOKIO_STABLE_FEATURES --ignore-private
cargo hack check -p tokio-macros -p tokio-stream -p tokio-util -p tokio-test --all-features --ignore-private
- name: "check --all-features --unstable -Z minimal-versions"
env:
RUSTFLAGS: --cfg tokio_unstable --cfg tokio_taskdump --cfg tokio_uring -Dwarnings
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
run: |
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
# from determining minimal versions based on dev-dependencies.
@@ -813,7 +789,7 @@ jobs:
name: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -833,7 +809,7 @@ jobs:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_clippy }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -841,8 +817,25 @@ jobs:
components: clippy
- uses: Swatinem/rust-cache@v2
# Run clippy
- name: "clippy --all"
run: cargo clippy --all --tests --all-features --no-deps
- name: "clippy --workspace --features ${{ env.TOKIO_STABLE_FEATURES }}"
run: |
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)
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
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
docs:
name: docs
@@ -851,30 +844,29 @@ jobs:
matrix:
run:
- os: windows-latest
extra_features: "tracing"
- os: ubuntu-latest
RUSTFLAGS: --cfg tokio_taskdump --cfg tokio_uring
RUSTDOCFLAGS: --cfg tokio_taskdump --cfg tokio_uring
extra_features: "tracing,io-uring,taskdump"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_nightly }}
- uses: Swatinem/rust-cache@v2
- name: "doc --lib --all-features"
run: |
cargo doc --lib --no-deps --all-features --document-private-items
run: cargo doc --lib --no-deps --document-private-items --features $TOKIO_STABLE_FEATURES,${{ matrix.run.extra_features }}
env:
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable ${{ matrix.run.RUSTFLAGS }}
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings ${{ matrix.run.RUSTDOCFLAGS }}
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
loom-compile:
name: build loom tests
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -890,7 +882,7 @@ jobs:
name: Check README
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Verify that both READMEs are identical
run: diff README.md tokio/README.md
@@ -909,7 +901,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -961,7 +953,7 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1005,7 +997,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1022,32 +1014,41 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_nightly }}
target: x86_64-unknown-redox
- name: check tokio on redox
run: cargo check --target x86_64-unknown-redox --all-features
run: cargo check --target x86_64-unknown-redox --features $TOKIO_STABLE_FEATURES
working-directory: tokio
wasm32-unknown-unknown:
name: test tokio for wasm32-unknown-unknown
name: test tokio for wasm32-unknown-unknown (${{ matrix.name }})
needs: basics
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: macros sync
features: "macros sync"
- name: macros sync rt
features: "macros sync rt"
- name: macros sync time rt
features: "macros sync time rt"
steps:
- uses: actions/checkout@v4
- name: Install Rust 1.88.0
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.88.0
toolchain: ${{ env.rust_stable }}
- name: Install wasm-pack
uses: taiki-e/install-action@wasm-pack
- uses: Swatinem/rust-cache@v2
- name: test tokio
run: wasm-pack test --node -- --features "macros sync"
- name: test tokio (${{ matrix.name }})
run: wasm-pack test --node -- --features "${{ matrix.features }}"
working-directory: tokio
wasm32-wasip1:
@@ -1061,10 +1062,10 @@ jobs:
- wasm32-wasip1-threads
steps:
- uses: actions/checkout@v4
- name: Install Rust 1.88.0
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.88.0
toolchain: ${{ env.rust_stable }}
targets: ${{ matrix.target }}
# Install dependencies
@@ -1075,11 +1076,14 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: WASI test tokio full
run: cargo test -p tokio --target ${{ matrix.target }} --features full
run: cargo test -p tokio --target ${{ matrix.target }} --features "sync,macros,io-util,rt,time"
env:
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
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
# in order to run doctests for unstable features, we must also pass
# the unstable cfg to RustDoc
RUSTDOCFLAGS: --cfg tokio_unstable
- name: WASI test tokio-util full
run: cargo test -p tokio-util --target ${{ matrix.target }} --features full
@@ -1087,9 +1091,10 @@ jobs:
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
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
RUSTDOCFLAGS: -C link-args=--max-memory=67108864
- name: WASI test tokio-stream
run: cargo test -p tokio-stream --target ${{ matrix.target }} --features time,net,io-util,sync
run: cargo test --manifest-path=tokio-stream/Cargo.toml --target ${{ matrix.target }} --features time,net,io-util,sync
env:
CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --"
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER: "wasmtime run -W bulk-memory=y -W threads=y -W shared-memory=y -S threads=y --"
@@ -1112,32 +1117,63 @@ 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@v4
- 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
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- windows-latest
- ubuntu-latest
rust:
# `check-external-types` requires a specific Rust nightly version. See
# the README for details: https://github.com/awslabs/cargo-check-external-types
- nightly-2024-06-30
include:
- os: windows-latest
# Windows neither supports io-uring nor taskdump.
extra_features: "tracing"
- os: ubuntu-latest
# includes all unstable features.
extra_features: "tracing,io-uring,taskdump"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ matrix.rust }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
# `check-external-types` requires a specific Rust nightly version. See
# the README for details: https://github.com/awslabs/cargo-check-external-types
toolchain: nightly-2025-08-06
- uses: Swatinem/rust-cache@v2
- name: Install cargo-check-external-types
uses: taiki-e/cache-cargo-install-action@v1
with:
tool: cargo-check-external-types@0.1.13
tool: cargo-check-external-types@0.3.0
- name: check-external-types
run: cargo check-external-types --all-features
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
RUSTDOCFLAGS: --cfg tokio_unstable
run: cargo check-external-types --features $TOKIO_STABLE_FEATURES,${{ matrix.extra_features }}
working-directory: tokio
check-fuzzing:
@@ -1165,7 +1201,7 @@ jobs:
needs: basics
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@stable
with:
@@ -1232,6 +1268,31 @@ jobs:
exit 1
fi
get-latest-kernel-version:
runs-on: ubuntu-latest
outputs:
kernel_version: ${{ steps.fetch.outputs.kernel_version }}
steps:
- name: Fetch latest stable kernel
id: fetch
run: |
KERNEL_VERSION=$(curl -s https://www.kernel.org/releases.json | jq -r '.latest_stable.version')
echo "kernel_version=$KERNEL_VERSION" >> $GITHUB_OUTPUT
test-io-uring-on-specific-kernel-versions:
name: Test io_uring on Linux ${{ matrix.kernel_version }}
needs: [get-latest-kernel-version, basics]
strategy:
matrix:
kernel_version:
# A latest stable kernel version
- ${{ needs.get-latest-kernel-version.outputs.kernel_version }}
# A kernel version that doesn't support io_uring
- '4.19.325'
uses: ./.github/workflows/uring-kernel-version-test.yml
with:
kernel_version: ${{ matrix.kernel_version }}
freebsd-x86_64:
name: FreeBSD x86_64
needs: basics
@@ -1242,19 +1303,19 @@ jobs:
uses: vmactions/freebsd-vm@v1
with:
release: '14.4'
envs: "RUSTFLAGS"
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 full,test-util
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 full,test-util,tracing
--features $TOKIO_STABLE_FEATURES,tracing
freebsd-docs:
name: FreeBSD docs
@@ -1269,17 +1330,17 @@ jobs:
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
with:
release: '14.4'
envs: "RUST_NIGHTLY RUSTDOCFLAGS RUSTFLAGS"
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 full,test-util,io-uring,tracing` instead of
# 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 full,test-util,tracing \
cargo doc --lib --no-deps --features $TOKIO_STABLE_FEATURES,tracing \
--document-private-items
freebsd-i686:
@@ -1292,7 +1353,7 @@ jobs:
uses: vmactions/freebsd-vm@v1
with:
release: '14.4'
envs: "RUSTFLAGS"
envs: "TOKIO_STABLE_FEATURES RUSTFLAGS"
prepare: |
pkg install -y curl
curl https://sh.rustup.rs -sSf --output rustup.sh
@@ -1300,5 +1361,5 @@ jobs:
run: |
. $HOME/.cargo/env
rustup target add i686-unknown-freebsd
cargo test --workspace --features full,test-util \
cargo test --workspace --features $TOKIO_STABLE_FEATURES \
--target i686-unknown-freebsd
+37 -5
View File
@@ -23,13 +23,29 @@ permissions:
contents: read
jobs:
loom-blocking:
name: loom tokio::runtime::spawn_blocking
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-blocking') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture loom_blocking
working-directory: tokio
loom-sync:
name: loom tokio::sync
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-sync') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -45,14 +61,14 @@ 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@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture runtime::time::tests
run: cargo test --lib --release --features full -- --nocapture runtime::time
working-directory: tokio
loom-current-thread:
@@ -61,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@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -84,7 +100,7 @@ jobs:
- scope: loom_multi_thread::group_c
- scope: loom_multi_thread::group_d
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -95,3 +111,19 @@ jobs:
working-directory: tokio
env:
SCOPE: ${{ matrix.scope }}
loom-util:
name: loom tokio-util
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-util') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture
working-directory: tokio-util
+1 -1
View File
@@ -19,5 +19,5 @@ jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: EmbarkStudios/cargo-deny-action@v2
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
stress-test:
- simple_echo_tcp
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
@@ -0,0 +1,103 @@
name: Uring Kernel Version Test
on:
workflow_call:
inputs:
kernel_version:
description: 'Version of the Linux kernel to build'
required: true
type: string
jobs:
build:
runs-on: ubuntu-latest
env:
KERNEL_VERSION: ${{ inputs.kernel_version }}
steps:
- uses: actions/checkout@v5
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential bison flex libssl-dev libelf-dev \
qemu-system-x86 busybox-static cpio xz-utils wget
- name: Cache Linux source
id: cache-kernel
uses: actions/cache@v4
with:
path: linux-${{ env.KERNEL_VERSION }}
key: kernel-${{ env.KERNEL_VERSION }}
- name: Download & build Linux kernel
if: steps.cache-kernel.outputs.cache-hit != 'true'
run: |
MAJOR=${KERNEL_VERSION%%.*}
wget https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KERNEL_VERSION}.tar.xz
tar xf linux-${KERNEL_VERSION}.tar.xz
cd linux-${KERNEL_VERSION}
make defconfig
make -j$(nproc)
- name: Generate test binaries with io_uring enabled
run: |
# Build both integration (tokio/tests/) and unit (e.g., tokio/src/fs/file/tests.rs) tests with io_uring enabled
rustup target add x86_64-unknown-linux-musl
RUSTFLAGS="--cfg tokio_unstable" \
cargo test -p tokio --features full,io-uring \
--target x86_64-unknown-linux-musl --test 'fs*' --lib --no-run
- name: Prepare initramfs + tests binaries
run: |
set -e
rm -rf initramfs
mkdir -p initramfs/{bin,bin/tests,sbin,proc,sys,tmp}
# Copy test binaries into initramfs
for bin in target/x86_64-unknown-linux-musl/debug/deps/{fs_*,tokio-*}; do
if [ -f "$bin" ] && [ -x "$bin" ]; then
cp "$bin" initramfs/bin/tests
fi
done
# Add BusyBox & symlinks
cp /usr/bin/busybox initramfs/bin/
for cmd in sh mount uname true sleep; do ln -sf busybox initramfs/bin/$cmd; done
ln -sf ../bin/busybox initramfs/sbin/poweroff
# Generate init script
cat > initramfs/init << 'EOF'
#!/bin/sh
set -e
mkdir -p /dev
# create device nodes, as some tests require them
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/tty c 5 0
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mkdir -p /tmp && mount -t tmpfs -o mode=1777 tmpfs /tmp
for f in /bin/tests/*; do RUST_BACKTRACE=1 "$f" ; done
EOF
chmod +x initramfs/init
# Pack into a CPIO archive
(cd initramfs && find . -print0 \
| cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz)
- name: Run tests in QEMU
run: |
qemu-system-x86_64 \
-kernel linux-${{ env.KERNEL_VERSION }}/arch/x86/boot/bzImage \
-initrd initramfs.cpio.gz \
-append "console=ttyS0 rootfstype=ramfs panic=1" \
-nographic -no-reboot -m 1024 -action panic=exit-failure 2>&1 | tee qemu-output.log
# qemu always exits with 0, so we check if the tests passed by using grep.
if grep -q "test result: FAILED" qemu-output.log; then
echo "tests failed (QEMU exited abnormally)"
exit 1
else
echo "all tests passed"
fi
+1
View File
@@ -3,3 +3,4 @@ Cargo.lock
.cargo/config.toml
.cargo/config
+18 -698
View File
@@ -1,7 +1,6 @@
# Contributing to Tokio
:balloon: Thanks for your help improving the project! We are so happy to have
you!
Thanks for your help improving Tokio! We are so happy to have you!
There are opportunities to contribute to Tokio at any level. It doesn't matter if
you are just getting started with Rust or are the most weathered expert, we can
@@ -9,15 +8,11 @@ use your help.
**No contribution is too small and all contributions are valued.**
This guide will help you get started. **Do not let this guide intimidate you**.
It should be considered a map to help you navigate the process.
See the [contributing guidelines] to get started.
The [dev channel][dev] is available for any concerns not covered in this guide, please join
us!
[contributing guidelines]: docs/contributing/README.md
[dev]: https://discord.gg/tokio
## Conduct
## Code of Conduct
The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes
the _minimum_ behavior expected from all contributors. Instances of violations of the
@@ -26,709 +21,34 @@ Code of Conduct can be reported by contacting the project team at
[coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md
## Contributing in Issues
## Need Help?
For any issue, there are fundamentally three ways an individual can contribute:
Reach out to us on the [Discord server] for any concern not covered in this guide.
1. By opening the issue for discussion: For instance, if you believe that you
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
issue tracker][issue] is the way to report it.
2. By helping to triage the issue: This can be done by providing
supporting details (a test case that demonstrates a bug), providing
suggestions on how to address the issue, or ensuring that the issue is tagged
correctly.
3. By helping to resolve the issue: Typically this is done either in the form of
demonstrating that the issue reported is not a problem after all, or more
often, by opening a Pull Request that changes some bit of something in
Tokio in a concrete and reviewable manner.
[issue]: https://github.com/tokio-rs/tokio/issues
**Anybody can participate in any stage of contribution**. We urge you to
participate in the discussion around bugs and participate in reviewing PRs.
### Asking for General Help
If you have reviewed existing documentation and still have questions or are
having problems, you can [open a discussion] asking for help.
In exchange for receiving help, we ask that you contribute back a documentation
PR that helps others avoid the problems that you encountered.
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new
### Submitting a Bug Report
When opening a new issue in the Tokio issue tracker, you will be presented
with a basic template that should be filled in. If you believe that you have
uncovered a bug, please fill out this form, following the template to the best
of your ability. Do not worry if you cannot answer every detail, just fill in
what you can.
The two most important pieces of information we need in order to properly
evaluate the report is a description of the behavior you are seeing and a simple
test case we can use to recreate the problem on our own. If we cannot recreate
the issue, it becomes impossible for us to fix.
In order to rule out the possibility of bugs introduced by userland code, test
cases should be limited, as much as possible, to using only Tokio APIs.
See [How to create a Minimal, Complete, and Verifiable example][mcve].
[mcve]: https://stackoverflow.com/help/mcve
### Triaging a Bug Report
Once an issue has been opened, it is not uncommon for there to be discussion
around it. Some contributors may have differing opinions about the issue,
including whether the behavior being seen is a bug or a feature. This discussion
is part of the process and should be kept focused, helpful, and professional.
Short, clipped responses—that provide neither additional context nor supporting
detail—are not helpful or professional. To many, such responses are simply
annoying and unfriendly.
Contributors are encouraged to help one another make forward progress as much as
possible, empowering one another to solve issues collaboratively. If you choose
to comment on an issue that you feel either is not a problem that needs to be
fixed, or if you encounter information in an issue that you feel is incorrect,
explain why you feel that way with additional supporting context, and be willing
to be convinced that you may be wrong. By doing so, we can often reach the
correct outcome much faster.
### Resolving a Bug Report
In the majority of cases, issues are resolved by opening a Pull Request. The
process for opening and reviewing a Pull Request is similar to that of opening
and triaging issues, but carries with it a necessary review and approval
workflow that ensures that the proposed changes meet the minimal quality and
functional guidelines of the Tokio project.
## Pull Requests
Pull Requests are the way concrete changes are made to the code, documentation,
and dependencies in the Tokio repository.
Even tiny pull requests (e.g., one character pull request fixing a typo in API
documentation) are greatly appreciated. Before making a large change, it is
usually a good idea to first open an issue describing the change to solicit
feedback and guidance. This will increase the likelihood of the PR getting
merged.
### Cargo Commands
Due to the extensive use of features in Tokio, you will often need to add extra
arguments to many common cargo commands. This section lists some commonly needed
commands.
Some commands just need the `--all-features` argument:
```
cargo build --all-features
cargo check --all-features
cargo test --all-features
```
Ideally, you should use the same version of clippy as the one used in CI
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
might have new lints:
[ci.yml]: .github/workflows/ci.yml
<!--
When updating this, also update:
- .github/workflows/ci.yml
- README.md
- tokio/README.md
- tokio/Cargo.toml
- tokio-util/Cargo.toml
- tokio-test/Cargo.toml
- tokio-stream/Cargo.toml
-->
```
cargo +1.88 clippy --all --tests --all-features
```
When building documentation, a simple `cargo doc` is not sufficient. To produce
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
docs, please use:
```
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
```
This turns on indicators to display the Cargo features required for
conditionally compiled APIs in Tokio, and it enables documentation of unstable
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
*and* rustc.
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
as docs.rs itself does.
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
```
cargo install --locked cargo-docs-rs
cargo +nightly docs-rs [--open]
```
The `cargo fmt` command does not work on the Tokio codebase. You can use the
command below instead:
```
# Mac or Linux
rustfmt --check --edition 2021 $(git ls-files '*.rs')
# Powershell
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
```
The `--check` argument prints the things that need to be fixed. If you remove
it, `rustfmt` will update your files locally instead.
You can run loom tests with
```
cd tokio # tokio crate in workspace
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
cargo test --lib --release --features full -- --test-threads=1 --nocapture
```
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
run loom tests that test unstable features.
You can run miri tests with
```
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields" \
cargo +nightly miri test --features full --lib --tests
```
### Performing spellcheck on tokio codebase
You can perform spell-check on tokio codebase. For details of how to use the spellcheck tool, feel free to visit
https://github.com/drahnr/cargo-spellcheck
```
# First install the spell-check plugin
cargo install --locked cargo-spellcheck
# Then run the cargo spell check command
cargo spellcheck check
```
if the command rejects a word, you should backtick the rejected word if it's code related. If not, the
rejected word should be put into `spellcheck.dic` file.
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
the total number of words included in the file
### Tests
If the change being proposed alters code (as opposed to only documentation for
example), it is either adding new functionality to Tokio or it is fixing
existing, broken functionality. In both of these cases, the pull request should
include one or more tests to ensure that Tokio does not regress in the future.
There are two ways to write tests: [integration tests][integration-tests]
and [documentation tests][documentation-tests].
(Tokio avoids [unit tests][unit-tests] as much as possible).
Tokio uses [conditional compilation attributes][conditional-compilation]
throughout the codebase, to modify rustc's behavior. Code marked with such
attributes can be enabled using RUSTFLAGS and RUSTDOCFLAGS environment
variables. One of the most prevalent flags passed in these variables is
the `--cfg` option. To run tests in a particular file, check first what
options #![cfg] declaration defines for that file.
For instance, to run a test marked with the 'tokio_unstable' cfg option,
you must pass this flag to the compiler when running the test.
```
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
```
#### Integration tests
Integration tests go in the same crate as the code they are testing. Each sub
crate should have a `dev-dependency` on `tokio` itself. This makes all Tokio
utilities available to use in tests, no matter the crate being tested.
The best strategy for writing a new integration test is to look at existing
integration tests in the crate and follow the style.
#### Fuzz tests
Some of our crates include a set of fuzz tests, this will be marked by a
directory `fuzz`. It is a good idea to run fuzz tests after each change.
To get started with fuzz testing you'll need to install
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
`cargo install --locked cargo-fuzz`
To list the available fuzzing harnesses you can run;
```bash
$ cd tokio
$ cargo fuzz list
fuzz_linked_list
```
Running a fuzz test is as simple as;
`cargo fuzz run fuzz_linked_list`
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
harness will run forever and will only exit if you `ctrl-c` or it finds
a bug.
#### Documentation tests
Ideally, every API has at least one [documentation test] that demonstrates how to
use the API. Documentation tests are run with `cargo test --doc`. This ensures
that the example is correct and provides additional test coverage.
The trick to documentation tests is striking a balance between being succinct
for a reader to understand and actually testing the API.
Same as with integration tests, when writing a documentation test, the full
`tokio` crate is available. This is especially useful for getting access to the
runtime to run the example.
The documentation tests will be visible from both the crate specific
documentation **and** the `tokio` facade documentation via the re-export. The
example should be written from the point of view of a user that is using the
`tokio` crate. As such, the example should use the API via the facade and not by
directly referencing the crate.
The type level example for `tokio_timer::Timeout` provides a good example of a
documentation test:
```
/// // import the `timeout` function, usually this is done
/// // with `use tokio::prelude::*`
/// use tokio::prelude::FutureExt;
/// use futures::Stream;
/// use futures::sync::mpsc;
/// use std::time::Duration;
///
/// # fn main() {
/// let (tx, rx) = mpsc::unbounded();
/// # tx.unbounded_send(()).unwrap();
/// # drop(tx);
///
/// let process = rx.for_each(|item| {
/// // do something with `item`
/// # drop(item);
/// # Ok(())
/// });
///
/// # tokio::runtime::current_thread::block_on_all(
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
/// process.timeout(Duration::from_millis(10))
/// # ).unwrap();
/// # }
```
Given that this is a *type* level documentation test and the primary way users
of `tokio` will create an instance of `Timeout` is by using
`FutureExt::timeout`, this is how the documentation test is structured.
Lines that start with `/// #` are removed when the documentation is generated.
They are only there to get the test to run. The `block_on_all` function is the
easiest way to execute a future from a test.
If this were a documentation test for the `Timeout::new` function, then the
example would explicitly use `Timeout::new`. For example:
```
/// use tokio::timer::Timeout;
/// use futures::Future;
/// use futures::sync::oneshot;
/// use std::time::Duration;
///
/// # fn main() {
/// let (tx, rx) = oneshot::channel();
/// # tx.send(()).unwrap();
///
/// # tokio::runtime::current_thread::block_on_all(
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
/// Timeout::new(rx, Duration::from_millis(10))
/// # ).unwrap();
/// # }
```
### Benchmarks
You can run benchmarks locally for the changes you've made to the tokio codebase.
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
against the changes you have made, for example, you can run;
```bash
cd benches
# Run all benchmarks.
cargo bench
# Run all tests in the `benches/fs.rs` file
cargo bench --bench fs
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
cargo bench async_read_buf
# After running benches, you can check the statistics under `tokio/target/criterion/`
```
You can also refer to Criterion docs for additional options and details.
### Commits
It is a recommended best practice to keep your changes as logically grouped as
possible within individual commits. There is no limit to the number of commits
any single Pull Request may have, and many contributors find it easier to review
changes that are split across multiple commits.
That said, if you have a number of commits that are "checkpoints" and don't
represent a single logical change, please squash those together.
Note that multiple commits often get squashed when they are landed (see the
notes about [commit squashing](#commit-squashing)).
#### Commit message guidelines
A good commit message should describe what changed and why.
1. The first line should:
* contain a short description of the change (preferably 50 characters or less,
and no more than 72 characters)
* be entirely in lowercase with the exception of proper nouns, acronyms, and
the words that refer to code, like function/variable names
* start with an imperative verb
* not have a period at the end
* be prefixed with the name of the module being changed; usually this is the
same as the M-* label on the PR
Examples:
* time: introduce `Timeout` and deprecate `Deadline`
* codec: export `Encoder`, `Decoder`, `Framed*`
* ci: fix the FreeBSD ci configuration
2. Keep the second line blank.
3. Wrap all other lines at 72 columns (except for long URLs).
4. If your patch fixes an open issue, you can add a reference to it at the end
of the log. Use the `Fixes: #` prefix and the issue number. For other
references use `Refs: #`. `Refs` may include multiple issues, separated by a
comma.
Examples:
- `Fixes: #1337`
- `Refs: #1234`
Sample complete commit message:
```txt
module: explain the commit in one line
Body of commit message is a few lines of text, explaining things
in more detail, possibly giving some background about the issue
being fixed, etc.
The body of the commit message can be several paragraphs, and
please do proper word-wrap and keep columns shorter than about
72 characters or so. That way, `git log` will show things
nicely even when it is indented.
Fixes: #1337
Refs: #453, #154
```
### Opening the Pull Request
From within GitHub, opening a new Pull Request will present you with a
[template] that should be filled out. Please try to do your best at filling out
the details, but feel free to skip parts if you're not sure what to put.
[template]: .github/PULL_REQUEST_TEMPLATE.md
### Discuss and update
You will probably get feedback or requests for changes to your Pull Request.
This is a big part of the submission process so don't be discouraged! Some
contributors may sign off on the Pull Request right away, others may have
more detailed comments or feedback. This is a necessary part of the process
in order to evaluate whether the changes are correct and necessary.
**Any community member can review a PR and you might get conflicting feedback**.
Keep an eye out for comments from code owners to provide guidance on conflicting
feedback.
**Once the PR is open, do not rebase the commits**. See [Commit Squashing](#commit-squashing) for
more details.
### Commit Squashing
In most cases, **do not squash commits that you add to your Pull Request during
the review process**. When the commits in your Pull Request land, they may be
squashed into one commit per logical change. Metadata will be added to the
commit message (including links to the Pull Request, links to relevant issues,
and the names of the reviewers). The commit history of your Pull Request,
however, will stay intact on the Pull Request page.
## Reviewing Pull Requests
**Any Tokio community member is welcome to review any pull request**.
All Tokio contributors who choose to review and provide feedback on Pull
Requests have a responsibility to both the project and the individual making the
contribution. Reviews and feedback must be helpful, insightful, and geared
towards improving the contribution as opposed to simply blocking it. If there
are reasons why you feel the PR should not land, explain what those are. Do not
expect to be able to block a Pull Request from advancing simply because you say
"No" without giving an explanation. Be open to having your mind changed. Be open
to working with the contributor to make the Pull Request better.
Reviews that are dismissive or disrespectful of the contributor or any other
reviewers are strictly counter to the Code of Conduct.
When reviewing a Pull Request, the primary goals are for the codebase to improve
and for the person submitting the request to succeed. **Even if a Pull Request
does not land, the submitters should come away from the experience feeling like
their effort was not wasted or unappreciated**. Every Pull Request from a new
contributor is an opportunity to grow the community.
### Review a bit at a time.
Do not overwhelm new contributors.
It is tempting to micro-optimize and make everything about relative performance,
perfect grammar, or exact style matches. Do not succumb to that temptation.
Focus first on the most significant aspects of the change:
1. Does this change make sense for Tokio?
2. Does this change make Tokio better, even if only incrementally?
3. Are there clear bugs or larger scale issues that need attending to?
4. Is the commit message readable and correct? If it contains a breaking change
is it clear enough?
Note that only **incremental** improvement is needed to land a PR. This means
that the PR does not need to be perfect, only better than the status quo. Follow
up PRs may be opened to continue iterating.
When changes are necessary, *request* them, do not *demand* them, and **do not
assume that the submitter already knows how to add a test or run a benchmark**.
Specific performance optimization techniques, coding styles and conventions
change over time. The first impression you give to a new contributor never does.
Nits (requests for small changes that are not essential) are fine, but try to
avoid stalling the Pull Request. Most nits can typically be fixed by the Tokio
Collaborator landing the Pull Request but they can also be an opportunity for
the contributor to learn a bit more about the project.
It is always good to clearly indicate nits when you comment: e.g.
`Nit: change foo() to bar(). But this is not blocking.`
If your comments were addressed but were not folded automatically after new
commits or if they proved to be mistaken, please, [hide them][hiding-a-comment]
with the appropriate reason to keep the conversation flow concise and relevant.
### Be aware of the person behind the code
Be aware that *how* you communicate requests and reviews in your feedback can
have a significant impact on the success of the Pull Request. Yes, we may land
a particular change that makes Tokio better, but the individual might just not
want to have anything to do with Tokio ever again. The goal is not just having
good code.
### Abandoned or Stalled Pull Requests
If a Pull Request appears to be abandoned or stalled, it is polite to first
check with the contributor to see if they intend to continue the work before
checking if they would mind if you took it over (especially if it just has nits
left). When doing so, it is courteous to give the original contributor credit
for the work they started (either by preserving their name and email address in
the commit log, or by using an `Author: ` meta-data tag in the commit.
_Adapted from the [Node.js contributing guide][node]_.
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
[documentation test]: https://doc.rust-lang.org/rustdoc/documentation-tests.html
## Keeping track of issues and PRs
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
section explains the meaning of various labels, as well as our [GitHub
project][project]. The section is primarily targeted at maintainers. Most
contributors aren't able to set these labels.
### Area
The area label describes the crates relevant to this issue or PR.
- **A-tokio** This issue concerns the main Tokio crate.
- **A-tokio-util** This issue concerns the `tokio-util` crate.
- **A-tokio-tls** This issue concerns the `tokio-tls` crate. Only used for
older issues, as the crate has been moved to another repository.
- **A-tokio-test** The issue concerns the `tokio-test` crate.
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
be used for the procedural macros, and not `join!` or `select!`.
- **A-ci** This issue concerns our GitHub Actions setup.
### Category
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
- **C-enhancement** This is a PR that adds a new features.
- **C-maintenance** This is an issue or PR about stuff such as documentation,
GitHub Actions or code quality.
- **C-feature-request** This is a feature request. Implementations of feature
requests use `C-enhancement` instead.
- **C-feature-accepted** If you submit a PR for this feature request, we wont
close it with the reason "we don't want this". Issues with this label should
also have the `C-feature-request` label.
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
world"
- **C-proposal** A proposal of some kind, and a request for comments.
- **C-question** A user question. Large overlap with GitHub discussions.
- **C-request** A non-feature request, e.g. "please add deprecation notices to
`-alpha.*` versions of crates"
### Calls for participation
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
or `C-feature-accepted`.
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
can do after reading the tutorial on our website.
- **E-medium** This is not `E-easy` or `E-hard`.
- **E-hard** This either involves very tricky code, is something we don't know
how to solve, or is difficult for some other reason.
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
example.
The "E-" prefix is the same as used in the Rust compiler repository. Some
issues are missing a difficulty rating, but feel free to ask on our Discord
server if you want to know how difficult an issue likely is.
### Module
The module label provides a more fine grained categorization than **Area**.
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
- **M-codec** The `tokio_util::codec` module.
- **M-compat** The `tokio_util::compat` module.
- **M-coop** Things relevant to coop.
- **M-fs** The `tokio::fs` module.
- **M-io** The `tokio::io` module.
- **M-macros** Issues about any kind of macro.
- **M-net** The `tokio::net` module.
- **M-process** The `tokio::process` module.
- **M-runtime** The `tokio::runtime` module.
- **M-signal** The `tokio::signal` module.
- **M-sync** The `tokio::sync` module.
- **M-task** The `tokio::task` module.
- **M-time** The `tokio::time` module.
- **M-tracing** Tracing support in Tokio.
### Topic
Some extra information.
- **T-docs** This is about documentation.
- **T-performance** This is about performance.
- **T-v0.1.x** This is about old Tokio.
Any label not listed here is not in active use.
[project]: https://github.com/orgs/tokio-rs/projects/1
[Discord server]: https://discord.gg/tokio
## LTS guarantees
Tokio ≥1.0.0 comes with LTS guarantees:
* A minimum of 5 years of maintenance.
* A minimum of 3 years before a hypothetical 2.0 release.
In Tokio ≥1.0.0, each LTS release comes with the guarantee of at least one year of
backported fixes.
The goal of these guarantees is to provide stability to the ecosystem.
## Minimum Supported Rust Version (MSRV)
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
compiler release.
* The MSRV will only be increased on 1.x releases.
* All Tokio ≥1.0.0 releases will support at least a 6-month old Rust
compiler release.
* The MSRV will only be increased on 1.x releases.
## Versioning Policy
With Tokio ≥1.0.0:
* Patch (1.\_.x) releases _should only_ contain bug fixes or documentation
changes. Besides this, these releases should not substantially change
runtime behavior.
* Minor (1.x) releases may contain new functionality, MSRV increases (see
above), minor dependency updates, deprecations, and larger internal
implementation changes.
* Patch (1.\_.x) releases _should only_ contain bug fixes or documentation
changes. Besides this, these releases should not substantially change
runtime behavior.
* Minor (1.x) releases may contain new functionality, MSRV increases (see
above), minor dependency updates, deprecations, and larger internal
implementation changes.
This is as defined by [Semantic Versioning 2.0](https://semver.org/).
## Releasing
Since the Tokio project consists of a number of crates, many of which depend on
each other, releasing new versions to crates.io can involve some complexities.
When releasing a new version of a crate, follow these steps:
1. **Ensure that the release crate has no path dependencies.** When the HEAD
version of a Tokio crate requires unreleased changes in another Tokio crate,
the crates.io dependency on the second crate will be replaced with a path
dependency. Crates with path dependencies cannot be published, so before
publishing the dependent crate, any path dependencies must also be published.
This should be done through a form of depth-first tree traversal:
1. Starting with the first path dependency in the crate to be released,
inspect the `Cargo.toml` for the dependency. If the dependency has any
path dependencies of its own, repeat this step with the first such
dependency.
2. Begin the release process for the path dependency.
3. Once the path dependency has been published to crates.io, update the
dependent crate to depend on the crates.io version.
4. When all path dependencies have been published, the dependent crate may
be published.
To verify that a crate is ready to publish, run:
```bash
bin/publish --dry-run <CRATE NAME> <CRATE VERSION>
```
2. **Update Cargo metadata.** After releasing any path dependencies, update the
`version` field in `Cargo.toml` to the new version, and the `documentation`
field to the docs.rs URL of the new version.
3. **Update other documentation links.** Update the "Documentation" link in the
crate's `README.md` to point to the docs.rs URL of the new version.
4. **Update the changelog for the crate.** Each crate in the Tokio repository
has its own `CHANGELOG.md` in that crate's subdirectory. Any changes to that
crate since the last release should be added to the changelog. Change
descriptions may be taken from the Git history, but should be edited to
ensure a consistent format, based on [Keep A Changelog][keep-a-changelog].
Other entries in that crate's changelog may also be used for reference.
5. **Perform a final audit for breaking changes.** Compare the HEAD version of
crate with the Git tag for the most recent release version. If there are any
breaking API changes, determine if those changes can be made without breaking
existing APIs. If so, resolve those issues. Otherwise, if it is necessary to
make a breaking release, update the version numbers to reflect this.
6. **Open a pull request with your changes.** Once that pull request has been
approved by a maintainer and the pull request has been merged, continue to
the next step.
7. **Release the crate.** Run the following command:
```bash
bin/publish <NAME OF CRATE> <VERSION>
```
Your editor and prompt you to edit a message for the tag. Copy the changelog
entry for that release version into your editor and close the window.
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog/blob/master/CHANGELOG.md
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
Generated
-2100
View File
File diff suppressed because it is too large Load Diff
+7 -2
View File
@@ -15,6 +15,13 @@ members = [
"tests-integration",
]
[patch.crates-io]
tokio = { path = "tokio" }
tokio-macros = { path = "tokio-macros" }
tokio-stream = { path = "tokio-stream" }
tokio-test = { path = "tokio-test" }
tokio-util = { path = "tokio-util" }
[workspace.metadata.spellcheck]
config = "spellcheck.toml"
@@ -27,8 +34,6 @@ unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(tokio_internal_mt_counters)',
'cfg(tokio_no_parking_lot)',
'cfg(tokio_no_tuning_tests)',
'cfg(tokio_taskdump)',
'cfg(tokio_unstable)',
'cfg(tokio_uring)',
'cfg(target_os, values("cygwin"))',
] }
+16 -10
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
@@ -52,11 +56,11 @@ an asynchronous application.
A basic TCP echo server with Tokio.
Make sure you activated the full features of the tokio crate on Cargo.toml:
Make sure you enable the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.47.5", features = ["full"] }
tokio = { version = "1.51.4", features = ["full"] }
```
Then, on your main.rs:
@@ -103,7 +107,7 @@ More examples can be found [here][examples]. For a larger "real world" example,
[examples]: https://github.com/tokio-rs/tokio/tree/master/examples
[mini-redis]: https://github.com/tokio-rs/mini-redis/
To see a list of the available features flags that can be enabled, check our
To see a list of the available feature flags that can be enabled, check our
[docs][feature-flag-docs].
## Getting Help
@@ -125,7 +129,7 @@ question. You can also ask your question on [the discussions page][discussions].
you! We have a [contributing guide][guide] to help you get involved in the Tokio
project.
[guide]: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
[guide]: https://github.com/tokio-rs/tokio/blob/master/docs/contributing/README.md
## Related Projects
@@ -186,12 +190,13 @@ When updating this, also update:
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
least** 6 months. When increasing the MSRV, the new Rust version must have been
released at least six months ago. The current MSRV is 1.70.
released at least six months ago. The current MSRV is 1.71.
Note that the MSRV is not increased automatically, and only as part of a minor
release. The MSRV history for past minor releases can be found below:
* 1.39 to now - Rust 1.70
* 1.48 to now - Rust 1.71
* 1.39 to 1.47 - Rust 1.70
* 1.30 to 1.38 - Rust 1.63
* 1.27 to 1.29 - Rust 1.56
* 1.17 to 1.26 - Rust 1.49
@@ -216,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.32.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
@@ -240,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
@@ -250,5 +256,5 @@ This project is licensed under the [MIT license].
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Tokio by you, shall be licensed as MIT, without any additional
for inclusion in Tokio by you shall be licensed as MIT, without any additional
terms or conditions.
+10
View File
@@ -96,5 +96,15 @@ name = "time_timeout"
path = "time_timeout.rs"
harness = false
[[bench]]
name = "spawn_blocking"
path = "spawn_blocking.rs"
harness = false
[[bench]]
name = "remote_spawn"
path = "remote_spawn.rs"
harness = false
[lints]
workspace = true
+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);
+73
View File
@@ -0,0 +1,73 @@
//! Benchmark spawn_blocking at different concurrency levels on the multi-threaded scheduler.
//!
//! For each parallelism level N (1, 2, 4, 8, 16, 32, 64, capped at available parallelism):
//! - Spawns N regular async tasks
//! - Each task spawns M batches of B spawn_blocking tasks (no-ops)
//! - Each batch is awaited to completion before starting the next
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use tokio::runtime::{self, Runtime};
use tokio::task::JoinSet;
/// Number of batches per task
const NUM_BATCHES: usize = 100;
/// Number of spawn_blocking calls per batch
const BATCH_SIZE: usize = 16;
fn spawn_blocking_concurrency(c: &mut Criterion) {
let max_parallelism = std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(1);
let parallelism_levels: Vec<usize> = [1, 2, 4, 8, 16, 32, 64]
.into_iter()
.filter(|&n| n <= max_parallelism)
.collect();
let mut group = c.benchmark_group("spawn_blocking");
for num_tasks in parallelism_levels {
group.bench_with_input(
BenchmarkId::new("concurrency", num_tasks),
&num_tasks,
|b, &num_tasks| {
let rt = rt();
b.iter(|| {
rt.block_on(async {
let mut tasks = JoinSet::new();
for _ in 0..num_tasks {
tasks.spawn(async {
for _ in 0..NUM_BATCHES {
let mut batch = JoinSet::new();
for _ in 0..BATCH_SIZE {
batch.spawn_blocking(|| black_box(0));
}
batch.join_all().await;
}
});
}
tasks.join_all().await;
});
});
},
);
}
group.finish();
}
fn rt() -> Runtime {
runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
}
criterion_group!(spawn_blocking_benches, spawn_blocking_concurrency);
criterion_main!(spawn_blocking_benches);
+1 -1
View File
@@ -9,7 +9,7 @@ allow = [
"Apache-2.0",
]
exceptions = [
{ allow = ["Unicode-3.0", "Unicode-DFS-2016"], crate = "unicode-ident" },
{ allow = ["Unicode-3.0"], crate = "unicode-ident" },
]
[bans]
+46
View File
@@ -0,0 +1,46 @@
# Contributing
This guide will help you get started. **Do not let this guide intimidate you**.
It should be considered a map to help you navigate the process.
## Quick start
If you are unsure where to begin, use the following guides:
- Want to report or triage a bug? Start with [Contributing in Issues](contributing-in-issues.md).
- Looking for something to work on? Filter issues by [`E-help-wanted`](https://github.com/tokio-rs/tokio/labels/E-help-wanted).
- Planning to submit a PR? Read [Pull Requests](pull-requests.md) for the full workflow and required checks.
- Want to understand what the labels on issues mean? See [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md).
- Interested in code review? See [Reviewing Pull Requests](reviewing-pull-requests.md).
## Table of Contents
- [Contributing in Issues](contributing-in-issues.md)
- [Asking for General Help](contributing-in-issues.md#asking-for-general-help)
- [Submitting a Bug Report](contributing-in-issues.md#submitting-a-bug-report)
- [Triaging a Bug Report](contributing-in-issues.md#triaging-a-bug-report)
- [Resolving a Bug Report](contributing-in-issues.md#resolving-a-bug-report)
- [Pull Requests](pull-requests.md)
- [Cargo Commands](pull-requests.md#cargo-commands)
- [Performing spellcheck on tokio codebase](pull-requests.md#performing-spellcheck-on-tokio-codebase)
- [Tests](pull-requests.md#tests)
- [Integration tests](pull-requests.md#integration-tests)
- [Fuzz tests](pull-requests.md#fuzz-tests)
- [Documentation tests](pull-requests.md#documentation-tests)
- [Benchmarks](pull-requests.md#benchmarks)
- [Commits](pull-requests.md#commits)
- [Commit message guidelines](pull-requests.md#commit-message-guidelines)
- [Opening the Pull Request](pull-requests.md#opening-the-pull-request)
- [Discuss and update](pull-requests.md#discuss-and-update)
- [Commit Squashing](pull-requests.md#commit-squashing)
- [Reviewing Pull Requests](reviewing-pull-requests.md)
- [Review a bit at a time](reviewing-pull-requests.md#review-a-bit-at-a-time)
- [Be aware of the person behind the code](reviewing-pull-requests.md#be-aware-of-the-person-behind-the-code)
- [Abandoned or Stalled Pull Requests](reviewing-pull-requests.md#abandoned-or-stalled-pull-requests)
- [How to specify crates dependencies versions](how-to-specify-crates-dependencies-versions.md)
- [Keeping track of issues and PRs](keeping-track-of-issues-and-prs.md)
- [Area](keeping-track-of-issues-and-prs.md#area)
- [Category](keeping-track-of-issues-and-prs.md#category)
- [Calls for participation](keeping-track-of-issues-and-prs.md#calls-for-participation)
- [Module](keeping-track-of-issues-and-prs.md#module)
- [Topic](keeping-track-of-issues-and-prs.md#topic)
@@ -0,0 +1,79 @@
## Contributing in Issues
For any issue, there are fundamentally three ways an individual can contribute:
1. By opening the issue for discussion: For instance, if you believe that you
have discovered a bug in Tokio, creating a new issue in [the tokio-rs/tokio
issue tracker][issue] is the way to report it.
2. By helping to triage the issue: This can be done by providing
supporting details (a test case that demonstrates a bug), providing
suggestions on how to address the issue, or ensuring that the issue is tagged
correctly.
3. By helping to resolve the issue: Typically this is done either in the form of
demonstrating that the issue reported is not a problem after all, or more
often, by opening a Pull Request that changes some bit of something in
Tokio in a concrete and reviewable manner.
[issue]: https://github.com/tokio-rs/tokio/issues
**Anybody can participate in any stage of contribution**. We urge you to
participate in the discussion around bugs and participate in reviewing PRs.
### Asking for General Help
If you have reviewed existing documentation and still have questions or are
having problems, you can [open a discussion] asking for help.
In exchange for receiving help, we ask that you contribute back a documentation
PR that helps others avoid the problems that you encountered.
[open a discussion]: https://github.com/tokio-rs/tokio/discussions/new/choose
### Submitting a Bug Report
When opening a new issue in the Tokio issue tracker, you will be presented
with a basic template that should be filled in. If you believe that you have
uncovered a bug, please fill out this form, following the template to the best
of your ability. Do not worry if you cannot answer every detail, just fill in
what you can.
The two most important pieces of information we need in order to properly
evaluate the report is a description of the behavior you are seeing and a simple
test case we can use to recreate the problem on our own. If we cannot recreate
the issue, it becomes impossible for us to fix.
In order to rule out the possibility of bugs introduced by userland code, test
cases should be limited, as much as possible, to using only Tokio APIs.
See [How to create a Minimal, Complete, and Verifiable example][mcve].
[mcve]: https://stackoverflow.com/help/mcve
### Triaging a Bug Report
Once an issue has been opened, it is not uncommon for there to be discussion
around it. Some contributors may have differing opinions about the issue,
including whether the behavior being seen is a bug or a feature. This discussion
is part of the process and should be kept focused, helpful, and professional.
Short, clipped responses—that provide neither additional context nor supporting
detail—are not helpful or professional. To many, such responses are simply
annoying and unfriendly.
Contributors are encouraged to help one another make forward progress as much as
possible, empowering one another to solve issues collaboratively. If you choose
to comment on an issue that you feel either is not a problem that needs to be
fixed, or if you encounter information in an issue that you feel is incorrect,
explain why you feel that way with additional supporting context, and be willing
to be convinced that you may be wrong. By doing so, we can often reach the
correct outcome much faster.
### Resolving a Bug Report
In the majority of cases, issues are resolved by opening a Pull Request. The
process for opening and reviewing a Pull Request is similar to that of opening
and triaging issues, but carries with it a necessary review and approval
workflow that ensures that the proposed changes meet the minimal quality and
functional guidelines of the Tokio project.
@@ -0,0 +1,32 @@
# How to specify crates dependencies versions
Each crate (e.g., `tokio-util`, `tokio-stream`, etc.) should specify dependencies
according to the following rules:
1. The listed version should be the oldest version that the crate works with
(e.g., if `tokio-util` works with `tokio` version `1.44` but not `1.43`, then
`tokio-util` should specify version `1.44` for its `tokio` dependency).
We don't require users to use the latest version unnecessarily.
2. When a crate starts using a newer feature in a dependency, the version
should be bumped to the version that introduced it.
3. If a crate depends on an unreleased feature in a dependency, it may use
`path =` dependency to specify this. Since path dependencies must be removed
during the release of the crate, this ensures that it can't be released until
the dependency has a new version.
Consider the following example from `tokio-stream`:
```toml
[dependencies]
futures-core = { version = "0.3.0" }
pin-project-lite = "0.2.11"
tokio = { version = "1.38.0", path = "../tokio", features = ["sync"] }
```
In this case, local development of `tokio-stream` uses the local version
of `tokio` via the `path` dependency. This means that it's currently not
possible to release `tokio-stream`, and `tokio` should be released first.
Once a new version of `tokio` is released (in this example the `1.38.0`),
the path dependency should be removed.
As mentioned before, this version should only be bumped when adding a new
feature in the crate that relies on a newer version.
@@ -0,0 +1,89 @@
## Keeping track of issues and PRs
The Tokio GitHub repository has a lot of issues and PRs to keep track of. This
section explains the meaning of various labels, as well as our [GitHub
project][project]. The section is primarily targeted at maintainers. Most
contributors aren't able to set these labels.
### Area
The area label describes the crates relevant to this issue or PR.
- **A-ci** This issue concerns our GitHub Actions setup.
- **A-tokio** This issue concerns the main Tokio crate.
- **A-readme** This issue is related to documentation such as README.md.
- **A-benches** This issue concerns the benchmarks.
- **A-examples** This issue concerns the examples.
- **A-tokio-test** The issue concerns the `tokio-test` crate.
- **A-tokio-util** This issue concerns the `tokio-util` crate.
- **A-tokio-macros** This issue concerns the `tokio-macros` crate. Should only
be used for the procedural macros, and not `join!` or `select!`.
- **A-tokio-stream** This issue concerns the `tokio-stream` crate.
### Category
- **C-bug** This is a bug-report. Bug-fix PRs use `C-enhancement` instead.
- **C-enhancement** This is a PR that adds a new features.
- **C-maintenance** This is an issue or PR about stuff such as documentation,
GitHub Actions or code quality.
- **C-feature-request** This is a feature request. Implementations of feature
requests use `C-enhancement` instead.
- **C-feature-accepted** If you submit a PR for this feature request, we won't
close it with the reason "we don't want this". Issues with this label should
also have the `C-feature-request` label.
- **C-musing** Stuff like tracking issues or roadmaps. "musings about a better
world"
- **C-proposal** A proposal of some kind, and a request for comments.
- **C-question** A user question. Large overlap with GitHub discussions.
- **C-request** A non-feature request, e.g. "please add deprecation notices to
`-alpha.*` versions of crates"
### Calls for participation
- **E-help-wanted** Stuff where we want help. Often seen together with `C-bug`
or `C-feature-accepted`.
- **E-easy** This is easy, ranging from quick documentation fixes to stuff you
can do after reading the tutorial on our website.
- **E-medium** This is not `E-easy` or `E-hard`.
- **E-hard** This either involves very tricky code, is something we don't know
how to solve, or is challenging for some other reason.
- **E-needs-mvce** This bug is missing a minimal complete and verifiable
example.
The "E-" prefix is the same as used in the Rust compiler repository. Some
issues are missing a difficulty rating, but feel free to ask on our Discord
server if you want to know how challenging an issue likely is.
### Module
The module label provides a more fine grained categorization than **Area**.
- **M-blocking** Things relevant to `spawn_blocking`, `block_in_place`.
- **M-codec** The `tokio_util::codec` module.
- **M-compat** The `tokio_util::compat` module.
- **M-coop** Things relevant to coop.
- **M-fs** The `tokio::fs` module.
- **M-io** The `tokio::io` module.
- **M-macros** Issues about any kind of macro.
- **M-metrics** Things relevant to `tokio::runtime::metrics`.
- **M-net** The `tokio::net` module.
- **M-process** The `tokio::process` module.
- **M-runtime** The `tokio::runtime` module.
- **M-signal** The `tokio::signal` module.
- **M-sync** The `tokio::sync` module.
- **M-task** The `tokio::task` module.
- **M-time** The `tokio::time` module.
- **M-tracing** Tracing support in Tokio.
- **M-taskdump** Things relevant to taskdump.
### Topic
Some extra information.
- **T-docs** This is about documentation.
- **T-performance** This is about performance.
- **T-v0.1.x** This is about old Tokio.
Any label not listed here is not in active use.
[project]: https://github.com/orgs/tokio-rs/projects/1
+346
View File
@@ -0,0 +1,346 @@
## Pull Requests
Pull Requests are the way concrete changes are made to the code, documentation,
and dependencies in the Tokio repository.
Even tiny pull requests (e.g., one-character pull request fixing a typo in API
documentation) are greatly appreciated. Before making a large change, it is
usually a good idea to first open an issue describing the change to solicit
feedback and guidance. This will increase the likelihood of the PR getting
merged.
### Cargo Commands
Due to the extensive use of features in Tokio, you will often need to add extra
arguments to many common cargo commands. This section lists some commonly needed
commands.
Some commands just need the `--all-features` argument:
```
cargo build --all-features
cargo check --all-features
cargo test --all-features
```
**NOTE**: there are some features that are not supported in every system, so you might
need to specify which features you want to pass to cargo (e.g., `cargo check --features=full,io-uring`)
Ideally, you should use the same version of clippy as the one used in CI
(defined by `env.rust_clippy` in [ci.yml][ci.yml]), because newer versions
might have new lints:
[ci.yml]: ../../.github/workflows/ci.yml
<!--
When updating this, also update:
- .github/workflows/ci.yml
- README.md
- tokio/README.md
- tokio/Cargo.toml
- tokio-util/Cargo.toml
- tokio-test/Cargo.toml
- tokio-stream/Cargo.toml
-->
```
cargo +1.88 clippy --all --tests --all-features
```
When building documentation, a simple `cargo doc` is not sufficient. To produce
documentation equivalent to what will be produced in docs.rs's builds of Tokio's
docs, please use:
```
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg docsrs --cfg tokio_unstable" cargo +nightly doc --all-features [--open]
```
This turns on indicators to display the Cargo features required for
conditionally compiled APIs in Tokio, and it enables documentation of unstable
Tokio features. Notice that it is necessary to pass cfg flags to both RustDoc
*and* rustc.
There is a more concise way to build docs.rs-equivalent docs by using [`cargo
docs-rs`], which reads the above documentation flags out of Tokio's Cargo.toml
as docs.rs itself does.
[`cargo docs-rs`]: https://github.com/dtolnay/cargo-docs-rs
```
cargo install --locked cargo-docs-rs
cargo +nightly docs-rs [--open]
```
The `cargo fmt` command does not work on the Tokio codebase. You can use the
command below instead:
```
# Mac or Linux
rustfmt --check --edition 2021 $(git ls-files '*.rs')
# Powershell
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2021 $_.FullName }
```
The `--check` argument prints the things that need to be fixed. If you remove
it, `rustfmt` will update your files locally instead.
You can run loom tests with
```
cd tokio # tokio crate in workspace
LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_assertions" \
cargo test --lib --release --features full -- --test-threads=1 --nocapture
```
Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to
run loom tests that test unstable features.
You can run miri tests with
```
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance" \
cargo +nightly miri test --features full --lib --tests
```
### Performing spellcheck on tokio codebase
You can perform a spell-check on the Tokio codebase. For details of how to use the spellcheck tool, feel free to visit
https://github.com/drahnr/cargo-spellcheck
```
# First install the spell-check plugin
cargo install --locked cargo-spellcheck
# Then run the cargo spell check command
cargo spellcheck check
```
If the command rejects a word, you should backtick the rejected word if it's code related. If not, the
rejected word should be put into `spellcheck.dic` file.
Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool
the total number of words included in the file
### Tests
If the change being proposed alters code (as opposed to only documentation for
example), it is either adding new functionality to Tokio or it is fixing
existing, broken functionality. In both of these cases, the pull request should
include one or more tests to ensure that Tokio does not regress in the future.
There are two ways to write tests: [integration tests][integration-tests]
and [documentation tests][documentation-tests].
(Tokio avoids [unit tests][unit-tests] as much as possible).
Tokio uses [conditional compilation attributes][conditional-compilation]
throughout the codebase, to modify rustc's behavior. Code marked with such
attributes can be enabled using `RUSTFLAGS` and `RUSTDOCFLAGS` environment
variables. One of the most prevalent flags passed in these variables is
the `--cfg` option. To run tests in a particular file, check first what
options #![cfg] declaration defines for that file.
For instance, to run a test marked with the 'tokio_unstable' cfg option,
you must pass this flag to the compiler when running the test.
```
$ RUSTFLAGS="--cfg tokio_unstable" cargo test -p tokio --all-features --test rt_metrics
```
#### Integration tests
Integration tests go in the same crate as the code they are testing. Each sub
crate should have a `dev-dependency` on `tokio` itself. This makes all Tokio
utilities available to use in tests, no matter the crate being tested.
The best strategy for writing a new integration test is to look at existing
integration tests in the crate and follow the style.
#### Fuzz tests
Some of our crates include a set of fuzz tests, this will be marked by a
directory `fuzz`. It is a good idea to run fuzz tests after each change.
To get started with fuzz testing you'll need to install
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
`cargo install --locked cargo-fuzz`
To list the available fuzzing harnesses you can run;
```bash
$ cd tokio
$ cargo fuzz list
fuzz_linked_list
```
Running a fuzz test is as simple as;
`cargo fuzz run fuzz_linked_list`
**NOTE**: Keep in mind that by default when running a fuzz test the fuzz
harness will run forever and will only exit if you `ctrl-c` or it finds
a bug.
#### Documentation tests
Ideally, every API has at least one [documentation test] that demonstrates how to
use the API. Documentation tests are run with `cargo test --doc`. This ensures
that the example is correct and provides additional test coverage.
The trick to documentation tests is striking a balance between being succinct
for a reader to understand and actually testing the API.
Same as with integration tests, when writing a documentation test, the full
`tokio` crate is available. This is especially useful for getting access to the
runtime to run the example.
The documentation tests will be visible from both the crate-specific
documentation **and** the `tokio` facade documentation via the re-export. The
example should be written from the point of view of a user that is using the
`tokio` crate. As such, the example should use the API via the facade and not by
directly referencing the crate.
The type level example for `tokio::time::timeout` provides a good example of a
documentation test:
```
/// Create a new `Timeout` set to expire in 10 milliseconds.
///
/// ```rust
/// use tokio::time::timeout;
/// use tokio::sync::oneshot;
///
/// use std::time::Duration;
///
/// # async fn dox() {
/// let (tx, rx) = oneshot::channel();
/// # tx.send(()).unwrap();
///
/// // Wrap the future with a `Timeout` set to expire in 10 milliseconds.
/// if let Err(_) = timeout(Duration::from_millis(10), rx).await {
/// println!("did not receive value within 10 ms");
/// }
/// # }
/// ```
```
Lines that start with `/// #` are removed when the documentation is generated.
### Benchmarks
You can run benchmarks locally for the changes you've made to the tokio codebase.
Tokio currently uses [Criterion](https://github.com/bheisler/criterion.rs) as its benchmarking tool. To run a benchmark
against the changes you have made, for example, you can run;
```bash
cd benches
# Run all benchmarks.
cargo bench
# Run all tests in the `benches/fs.rs` file
cargo bench --bench fs
# Run the `async_read_buf` benchmark in `benches/fs.rs` specifically.
cargo bench async_read_buf
# After running benches, you can check the statistics under `tokio/target/criterion/`
```
You can also refer to [Criterion] docs for additional options and details.
[Criterion]: https://docs.rs/criterion/latest/criterion/
### Commits
It is a recommended best practice to keep your changes as logically grouped as
possible within individual commits. There is no limit to the number of commits
any single Pull Request may have, and many contributors find it easier to review
changes that are split across multiple commits.
That said, if you have a number of commits that are "checkpoints" and don't
represent a single logical change, please squash those together.
Note that multiple commits often get squashed when they are landed (see the
notes about [commit squashing](#commit-squashing)).
#### Commit message guidelines
A good commit message should describe what changed and why.
1. The first line should:
* contain a short description of the change (preferably 50 characters or less,
and no more than 72 characters)
* be entirely in lowercase with the exception of proper nouns, acronyms, and
the words that refer to code, like function/variable names
* start with an imperative verb
* not have a period at the end
* be prefixed with the name of the module being changed; usually this is the
same as the M-* label on the PR
Examples:
* time: introduce `Timeout` and deprecate `Deadline`
* codec: export `Encoder`, `Decoder`, `Framed*`
* ci: fix the FreeBSD ci configuration
2. Keep the second line blank.
3. Wrap all other lines at 72 columns (except for long URLs).
4. If your patch fixes an open issue, you can add a reference to it at the end
of the log. Use the `Fixes: #` prefix and the issue number. For other
references use `Refs: #`. `Refs` may include multiple issues, separated by a
comma.
Examples:
- `Fixes: #1337`
- `Refs: #1234`
Sample complete commit message:
```txt
module: explain the commit in one line
Body of commit message is a few lines of text, explaining things
in more detail, possibly giving some background about the issue
being fixed, etc.
The body of the commit message can be several paragraphs, and
please do proper word-wrap and keep columns shorter than about
72 characters or so. That way, `git log` will show things
nicely even when it is indented.
Fixes: #1337
Refs: #453, #154
```
### Opening the Pull Request
From within GitHub, opening a new Pull Request will present you with a
[template] that should be filled out. Please try to do your best at filling out
the details, but feel free to skip parts if you're not sure what to put.
[template]: ../../.github/PULL_REQUEST_TEMPLATE.md
### Discuss and update
You will probably get feedback or requests for changes to your Pull Request.
This is a big part of the submission process so don't be discouraged! Some
contributors may sign off on the Pull Request right away, others may have
more detailed comments or feedback. This is a necessary part of the process
in order to evaluate whether the changes are correct and necessary.
**Any community member can review a PR and you might get conflicting feedback**.
Keep an eye out for comments from code owners to provide guidance on conflicting
feedback.
**Once the PR is open, do not rebase the commits**. See [Commit Squashing](#commit-squashing) for
more details.
### Commit Squashing
In most cases, **do not squash commits that you add to your Pull Request during
the review process**. When the commits in your Pull Request land, they may be
squashed into one commit per logical change. Metadata will be added to the
commit message (including links to the Pull Request, links to relevant issues,
and the names of the reviewers). The commit history of your Pull Request,
however, will stay intact on the Pull Request page.
[integration-tests]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
[unit-tests]: https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html
[documentation-tests]: https://doc.rust-lang.org/rust-by-example/testing/doc_testing.html
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
@@ -0,0 +1,80 @@
## Reviewing Pull Requests
**Any Tokio community member is welcome to review any pull request**.
All Tokio contributors who choose to review and provide feedback on Pull
Requests have a responsibility to both the project and the individual making the
contribution. Reviews and feedback must be helpful, insightful, and geared
towards improving the contribution as opposed to simply blocking it. If there
are reasons why you feel the PR should not land, explain what those are. Do not
expect to be able to block a Pull Request from advancing simply because you say
"No" without giving an explanation. Be open to having your mind changed. Be open
to working with the contributor to make the Pull Request better.
Reviews that are dismissive or disrespectful of the contributor or any other
reviewers are strictly counter to the Code of Conduct.
When reviewing a Pull Request, the primary goals are for the codebase to improve
and for the person submitting the request to succeed. **Even if a Pull Request
does not land, the submitters should come away from the experience feeling like
their effort was not wasted or unappreciated**. Every Pull Request from a new
contributor is an opportunity to grow the community.
### Review a bit at a time
Do not overwhelm new contributors.
It is tempting to micro-optimize and make everything about relative performance,
perfect grammar, or exact style matches. Do not succumb to that temptation.
Focus first on the most significant aspects of the change:
1. Does this change make sense for Tokio?
2. Does this change make Tokio better, even if only incrementally?
3. Are there clear bugs or larger scale issues that need attending to?
4. Is the commit message readable and correct? If it contains a breaking change
is it clear enough?
Note that only **incremental** improvement is needed to land a PR. This means
that the PR does not need to be perfect, only better than the status quo. Follow
up PRs may be opened to continue iterating.
When changes are necessary, *request* them, do not *demand* them, and **do not
assume that the submitter already knows how to add a test or run a benchmark**.
Specific performance optimization techniques, coding styles and conventions
change over time. The first impression you give to a new contributor never does.
Nits (requests for small changes that are not essential) are fine, but try to
avoid stalling the Pull Request. Most nits can typically be fixed by the Tokio
Collaborator landing the Pull Request but they can also be an opportunity for
the contributor to learn a bit more about the project.
It is always good to clearly indicate nits when you comment: e.g.
`Nit: change foo() to bar(). But this is not blocking.`
If your comments were addressed but were not folded automatically after new
commits or if they proved to be mistaken, please, [hide them][hiding-a-comment]
with the appropriate reason to keep the conversation flow concise and relevant.
### Be aware of the person behind the code
Be aware that *how* you communicate requests and reviews in your feedback can
have a significant impact on the success of the Pull Request. Yes, we may land
a particular change that makes Tokio better, but the individual might just not
want to have anything to do with Tokio ever again. The goal is not just having
good code.
### Abandoned or Stalled Pull Requests
If a Pull Request appears to be abandoned or stalled, it is polite to first
check with the contributor to see if they intend to continue the work before
checking if they would mind if you took it over (especially if it just has nits
left). When doing so, it is courteous to give the original contributor credit
for the work they started (either by preserving their name and email address in
the commit log, or by using an `Author: ` meta-data tag in the commit.
_Adapted from the [Node.js contributing guide][node]_.
[node]: https://github.com/nodejs/node/blob/master/CONTRIBUTING.md
[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment
+16 -2
View File
@@ -16,7 +16,7 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3.1", default-features = false, features = ["fmt", "ansi", "env-filter", "tracing-log"] }
bytes = "1.0.0"
futures = { version = "0.3.0", features = ["thread-pool"]}
http = "0.2"
http = "1"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
@@ -24,8 +24,14 @@ httparse = "1.0"
httpdate = "1.0"
once_cell = "1.5.2"
[target.'cfg(target_os = "linux")'.dev-dependencies]
libc = "0.2"
[target.'cfg(all(tokio_unstable, target_os = "linux"))'.dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing", "taskdump"] }
[target.'cfg(windows)'.dev-dependencies.windows-sys]
version = "0.59"
version = "0.61"
[[example]]
name = "chat"
@@ -43,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"
@@ -99,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
+32 -19
View File
@@ -39,6 +39,8 @@ use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
const DEFAULT_ADDR: &str = "127.0.0.1:6142";
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter};
@@ -70,14 +72,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
let addr = env::args()
.nth(1)
.unwrap_or_else(|| "127.0.0.1:6142".to_string());
.unwrap_or_else(|| DEFAULT_ADDR.to_string());
// Bind a TCP listener to the socket address.
//
// Note that this is the Tokio TcpListener, which is fully async.
let listener = TcpListener::bind(&addr).await?;
tracing::info!("server running on {}", addr);
tracing::info!("server running on {addr}");
loop {
// Asynchronously wait for an inbound TcpStream.
@@ -88,9 +90,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
// Spawn our handler to be run asynchronously.
tokio::spawn(async move {
tracing::debug!("accepted connection");
tracing::debug!("accepted connection from {addr}");
if let Err(e) = process(state, stream, addr).await {
tracing::info!("an error occurred; error = {:?}", e);
tracing::warn!("Connection from {addr} failed: {e:?}");
}
});
}
@@ -138,12 +140,24 @@ impl Shared {
/// Send a `LineCodec` encoded message to every peer, except
/// for the sender.
///
/// This function also cleans up disconnected peers automatically.
async fn broadcast(&mut self, sender: SocketAddr, message: &str) {
for peer in self.peers.iter_mut() {
if *peer.0 != sender {
let _ = peer.1.send(message.into());
let mut failed_peers = Vec::new();
let message = message.to_string(); // Clone once for all sends
for (addr, tx) in self.peers.iter() {
if *addr != sender && tx.send(message.clone()).is_err() {
// Receiver has been dropped, mark for removal
failed_peers.push(*addr);
}
}
// Clean up disconnected peers
for addr in failed_peers {
self.peers.remove(&addr);
tracing::debug!("Removed disconnected peer: {addr}");
}
}
}
@@ -178,13 +192,10 @@ async fn process(
lines.send("Please enter your username:").await?;
// Read the first line from the `LineCodec` stream to get the username.
let username = match lines.next().await {
Some(Ok(line)) => line,
let Some(Ok(username)) = lines.next().await else {
// We didn't get a line so we return early here.
_ => {
tracing::error!("Failed to get username from {}. Client disconnected.", addr);
return Ok(());
}
tracing::error!("Failed to get username from {addr}. Client disconnected.");
return Ok(());
};
// Register our peer with state which internally sets up some channels.
@@ -194,7 +205,7 @@ async fn process(
{
let mut state = state.lock().await;
let msg = format!("{username} has joined the chat");
tracing::info!("{}", msg);
tracing::info!("{msg}");
state.broadcast(addr, &msg).await;
}
@@ -203,7 +214,10 @@ async fn process(
tokio::select! {
// A message was received from a peer. Send it to the current user.
Some(msg) = peer.rx.recv() => {
peer.lines.send(&msg).await?;
if let Err(e) = peer.lines.send(&msg).await {
tracing::error!("Failed to send message to {username}: {e:?}");
break;
}
}
result = peer.lines.next() => match result {
// A message was received from the current user, we should
@@ -217,10 +231,9 @@ async fn process(
// An error occurred.
Some(Err(e)) => {
tracing::error!(
"an error occurred while processing messages for {}; error = {:?}",
username,
e
"an error occurred while processing messages for {username}; error = {e:?}"
);
break;
}
// The stream has been exhausted.
None => break,
@@ -235,7 +248,7 @@ async fn process(
state.peers.remove(&addr);
let msg = format!("{username} has left the chat");
tracing::info!("{}", msg);
tracing::info!("{msg}");
state.broadcast(addr, &msg).await;
}
+6 -4
View File
@@ -58,14 +58,16 @@ pub async fn connect(
//BytesMut into Bytes
Ok(i) => future::ready(Some(i.freeze())),
Err(e) => {
println!("failed to read from socket; error={e}");
eprintln!("failed to read from socket; error={e}");
future::ready(None)
}
})
.map(Ok);
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
(Err(e), _) | (_, Err(e)) => Err(e.into()),
_ => Ok(()),
tokio::select! {
r = sink.send_all(&mut stdin) => r?,
r = stdout.send_all(&mut stream) => r?,
}
Ok(())
}
+5 -2
View File
@@ -59,7 +59,10 @@ pub async fn connect(
let socket = UdpSocket::bind(&bind_addr).await?;
socket.connect(addr).await?;
tokio::try_join!(send(stdin, &socket), recv(stdout, &socket))?;
tokio::select! {
r = send(stdin, &socket) => r?,
r = recv(stdout, &socket) => r?,
}
Ok(())
}
@@ -85,7 +88,7 @@ async fn recv(
let n = reader.recv(&mut buf[..]).await?;
if n > 0 {
stdout.send(Bytes::from(buf)).await?;
stdout.send(Bytes::copy_from_slice(&buf[..n])).await?;
}
}
}
-2
View File
@@ -4,7 +4,6 @@
#[cfg(all(
tokio_unstable,
tokio_taskdump,
target_os = "linux",
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
))]
@@ -82,7 +81,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(not(all(
tokio_unstable,
tokio_taskdump,
target_os = "linux",
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
)))]
+23 -16
View File
@@ -16,7 +16,7 @@
//! cargo run --example connect-tcp 127.0.0.1:8080
//!
//! Each line you type in to the `connect-tcp` terminal should be echo'd back to
//! you! If you open up multiple terminals running the `connect` example you
//! you! If you open up multiple terminals running the `connect-tcp` example you
//! should be able to see them all make progress simultaneously.
#![warn(rust_2018_idioms)]
@@ -27,6 +27,9 @@ use tokio::net::TcpListener;
use std::env;
use std::error::Error;
const DEFAULT_ADDR: &str = "127.0.0.1:8080";
const BUFFER_SIZE: usize = 4096;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Allow passing an address to listen on as the first argument of this
@@ -34,7 +37,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
// 127.0.0.1:8080 for connections.
let addr = env::args()
.nth(1)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
.unwrap_or_else(|| DEFAULT_ADDR.to_string());
// Next up we create a TCP listener which will listen for incoming
// connections. This TCP listener is bound to the address we determined
@@ -44,7 +47,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
loop {
// Asynchronously wait for an inbound socket.
let (mut socket, _) = listener.accept().await?;
let (mut socket, addr) = listener.accept().await?;
// And this is where much of the magic of this server happens. We
// crucially want all clients to make progress concurrently, rather than
@@ -55,23 +58,27 @@ async fn main() -> Result<(), Box<dyn Error>> {
// which will allow all of our clients to be processed concurrently.
tokio::spawn(async move {
let mut buf = vec![0; 1024];
let mut buf = vec![0; BUFFER_SIZE];
// In a loop, read data from the socket and write the data back.
loop {
let n = socket
.read(&mut buf)
.await
.expect("failed to read data from socket");
if n == 0 {
return;
match socket.read(&mut buf).await {
Ok(0) => {
// Connection closed by peer
return;
}
Ok(n) => {
// Write the data back. If writing fails, log the error and exit.
if let Err(e) = socket.write_all(&buf[0..n]).await {
eprintln!("Failed to write to socket {}: {}", addr, e);
return;
}
}
Err(e) => {
eprintln!("Failed to read from socket {}: {}", addr, e);
return;
}
}
socket
.write_all(&buf[0..n])
.await
.expect("failed to write data to socket");
}
});
}
+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 {});
}
+1 -1
View File
@@ -19,7 +19,7 @@
//! is:
//!
//!
//! $ cargo run --example connect 127.0.0.1:8080
//! $ cargo run --example connect-tcp 127.0.0.1:8080
//! GET foo
//! foo = bar
//! GET FOOBAR
+5 -12
View File
@@ -1,15 +1,6 @@
[build]
# TODO: unfreeze toolchain
# error[E0557]: feature has been removed
# --> /opt/buildhome/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lock_api-0.4.13/src/lib.rs:89:29
# |
# 89 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
# | ^^^^^^^^^^^^ feature has been removed
# |
# = note: removed in 1.58.0; see <https://github.com/rust-lang/rust/pull/138907; for more information
# = note: merged into `doc_cfg`
command = """
rustup install nightly-2025-01-25 --profile minimal && cargo doc --no-deps --all-features
rustup install nightly --profile minimal && cargo doc --no-deps --all-features
"""
publish = "target/doc"
@@ -17,9 +8,11 @@
RUSTDOCFLAGS="""
--cfg docsrs \
--cfg tokio_unstable \
--cfg tokio_taskdump \
"""
RUSTFLAGS="--cfg tokio_unstable --cfg tokio_taskdump --cfg docsrs"
RUSTFLAGS="""
--cfg docsrs \
--cfg tokio_unstable
"""
[[redirects]]
from = "/"
+10 -2
View File
@@ -1,4 +1,4 @@
306
314
&
+
<
@@ -64,6 +64,7 @@ codec
codecs
combinator
combinators
condvar
config
Config
connectionless
@@ -75,6 +76,7 @@ CQE
cqe's
customizable
Customizable
Cygwin
datagram
Datagram
datagrams
@@ -162,6 +164,7 @@ Lauck
libc
lifecycle
lifo
LLVM
lookups
macOS
MacOS
@@ -184,15 +187,17 @@ mut
mutex
Mutex
Nagle
namespace
nonblocking
nondecreasing
noop
ntasks
NUMA
ok
oneshot
opcode
ORed
os
overweighing
parker
parsers
peekable
@@ -204,6 +209,7 @@ POSIX
proxied
qos
RAII
RCU
reallocations
recv's
refactors
@@ -304,4 +310,6 @@ wakers
Wakers
wakeup
wakeups
WASI
workstealing
ZST
+7 -5
View File
@@ -4,6 +4,7 @@
"crt-objects-fallback": "false",
"crt-static-respected": true,
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"default-uwtable": true,
"dynamic-linking": true,
"env": "gnu",
"has-rpath": true,
@@ -12,10 +13,10 @@
"llvm-target": "i686-unknown-linux-gnu",
"max-atomic-width": 32,
"metadata": {
"description": null,
"host_tools": null,
"std": null,
"tier": null
"description": "32-bit Linux (kernel 3.2, glibc 2.17+)",
"host_tools": true,
"std": true,
"tier": 1
},
"os": "linux",
"position-independent-executables": true,
@@ -28,6 +29,7 @@
]
},
"relro-level": "full",
"rustc-abi": "x86-sse2",
"stack-probes": {
"kind": "inline"
},
@@ -42,5 +44,5 @@
"target-family": [
"unix"
],
"target-pointer-width": "32"
"target-pointer-width": 32
}
@@ -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)]
@@ -40,7 +40,7 @@ error: Failed to parse value of `flavor` as string.
23 | #[tokio::test(flavor = 123)]
| ^^^
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
error: No such runtime flavor `foo`. The runtime flavors are `current_thread`, `local`, and `multi_thread`.
--> tests/fail/macros_invalid_input.rs:26:24
|
26 | #[tokio::test(flavor = "foo")]
@@ -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)]
| ^^^
+42
View File
@@ -0,0 +1,42 @@
use tests_build::tokio;
#[tokio::main]
async fn main() {
// do not leak `RotatorSelect`
let _ = tokio::join!(async {
fn foo(_: impl RotatorSelect) {}
});
// do not leak `std::task::Poll::Pending`
let _ = tokio::join!(async { Pending });
// do not leak `std::task::Poll::Ready`
let _ = tokio::join!(async { Ready(0) });
// do not leak `std::future::Future`
let _ = tokio::join!(async {
struct MyFuture;
impl Future for MyFuture {
type Output = ();
fn poll(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
todo!()
}
}
});
// do not leak `std::pin::Pin`
let _ = tokio::join!(async {
let mut x = 5;
let _ = Pin::new(&mut x);
});
// do not leak `std::future::poll_fn`
let _ = tokio::join!(async {
let _ = poll_fn(|_cx| todo!());
});
}
+60
View File
@@ -0,0 +1,60 @@
error[E0405]: cannot find trait `RotatorSelect` in this scope
--> tests/fail/macros_join.rs:7:24
|
7 | fn foo(_: impl RotatorSelect) {}
| ^^^^^^^^^^^^^ not found in this scope
error[E0425]: cannot find value `Pending` in this scope
--> tests/fail/macros_join.rs:11:34
|
11 | let _ = tokio::join!(async { Pending });
| ^^^^^^^ not found in this scope
|
help: consider importing this unit variant
|
1 + use std::task::Poll::Pending;
|
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
--> tests/fail/macros_join.rs:14:34
|
14 | let _ = tokio::join!(async { Ready(0) });
| ^^^^^ not found in this scope
|
help: consider importing this tuple variant
|
1 + use std::task::Poll::Ready;
|
error[E0405]: cannot find trait `Future` in this scope
--> tests/fail/macros_join.rs:20:14
|
20 | impl Future for MyFuture {
| ^^^^^^ not found in this scope
|
help: consider importing this trait
|
1 + use std::future::Future;
|
error[E0433]: failed to resolve: use of undeclared type `Pin`
--> tests/fail/macros_join.rs:35:17
|
35 | let _ = Pin::new(&mut x);
| ^^^ use of undeclared type `Pin`
|
help: consider importing this struct
|
1 + use std::pin::Pin;
|
error[E0425]: cannot find function `poll_fn` in this scope
--> tests/fail/macros_join.rs:40:17
|
40 | let _ = poll_fn(|_cx| todo!());
| ^^^^^^^ not found in this scope
|
help: consider importing this function
|
1 + use std::future::poll_fn;
|
+42
View File
@@ -0,0 +1,42 @@
use tests_build::tokio;
#[tokio::main]
async fn main() {
// do not leak `RotatorSelect`
let _ = tokio::try_join!(async {
fn foo(_: impl RotatorSelect) {}
});
// do not leak `std::task::Poll::Pending`
let _ = tokio::try_join!(async { Pending });
// do not leak `std::task::Poll::Ready`
let _ = tokio::try_join!(async { Ready(0) });
// do not leak `std::future::Future`
let _ = tokio::try_join!(async {
struct MyFuture;
impl Future for MyFuture {
type Output = ();
fn poll(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
todo!()
}
}
});
// do not leak `std::pin::Pin`
let _ = tokio::try_join!(async {
let mut x = 5;
let _ = Pin::new(&mut x);
});
// do not leak `std::future::poll_fn`
let _ = tokio::try_join!(async {
let _ = poll_fn(|_cx| todo!());
});
}
@@ -0,0 +1,60 @@
error[E0405]: cannot find trait `RotatorSelect` in this scope
--> tests/fail/macros_try_join.rs:7:24
|
7 | fn foo(_: impl RotatorSelect) {}
| ^^^^^^^^^^^^^ not found in this scope
error[E0425]: cannot find value `Pending` in this scope
--> tests/fail/macros_try_join.rs:11:38
|
11 | let _ = tokio::try_join!(async { Pending });
| ^^^^^^^ not found in this scope
|
help: consider importing this unit variant
|
1 + use std::task::Poll::Pending;
|
error[E0425]: cannot find function, tuple struct or tuple variant `Ready` in this scope
--> tests/fail/macros_try_join.rs:14:38
|
14 | let _ = tokio::try_join!(async { Ready(0) });
| ^^^^^ not found in this scope
|
help: consider importing this tuple variant
|
1 + use std::task::Poll::Ready;
|
error[E0405]: cannot find trait `Future` in this scope
--> tests/fail/macros_try_join.rs:20:14
|
20 | impl Future for MyFuture {
| ^^^^^^ not found in this scope
|
help: consider importing this trait
|
1 + use std::future::Future;
|
error[E0433]: failed to resolve: use of undeclared type `Pin`
--> tests/fail/macros_try_join.rs:35:17
|
35 | let _ = Pin::new(&mut x);
| ^^^ use of undeclared type `Pin`
|
help: consider importing this struct
|
1 + use std::pin::Pin;
|
error[E0425]: cannot find function `poll_fn` in this scope
--> tests/fail/macros_try_join.rs:40:17
|
40 | let _ = poll_fn(|_cx| todo!());
| ^^^^^^^ not found in this scope
|
help: consider importing this function
|
1 + use std::future::poll_fn;
|
@@ -1,3 +1,14 @@
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:3:1: 3:15}` to be a future that resolves to `()`, but it resolves to `Result<(), _>`
--> tests/fail/macros_type_mismatch.rs:3:1
|
3 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
= note: expected unit type `()`
found enum `Result<(), _>`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:3:1: 3:15}` to `&dyn Future<Output = ()>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:5:5
|
@@ -15,6 +26,17 @@ help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panic
5 | Ok(()).expect("REASON")
| +++++++++++++++++
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:8:1: 8:15}` to be a future that resolves to `()`, but it resolves to `Result<(), _>`
--> tests/fail/macros_type_mismatch.rs:8:1
|
8 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
= note: expected unit type `()`
found enum `Result<(), _>`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:8:1: 8:15}` to `&dyn Future<Output = ()>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:10:5
|
@@ -32,6 +54,17 @@ help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panic
10 | return Ok(());.expect("REASON")
| +++++++++++++++++
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:13:1: 13:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
--> tests/fail/macros_type_mismatch.rs:13:1
|
13 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:13:1: 13:15}` to `&dyn Future<Output = Result<(), ()>>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:23:5
|
@@ -58,6 +91,17 @@ error[E0277]: the `?` operator can only be used in an async block that returns `
40 | None?;
| ^ cannot use the `?` operator in an async block that returns `()`
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to be a future that resolves to `Option<()>`, but it resolves to `()`
--> tests/fail/macros_type_mismatch.rs:38:1
|
38 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `Option<()>`, found `()`
|
= note: expected enum `Option<()>`
found unit type `()`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:38:1: 38:15}` to `&dyn Future<Output = Option<()>>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:40:5
|
@@ -86,6 +130,17 @@ error[E0277]: the `?` operator can only be used in an async block that returns `
57 | Ok(())?;
| ^ cannot use the `?` operator in an async block that returns `()`
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to be a future that resolves to `Result<(), ()>`, but it resolves to `()`
--> tests/fail/macros_type_mismatch.rs:55:1
|
55 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `Result<(), ()>`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:55:1: 55:15}` to `&dyn Future<Output = Result<(), ()>>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:57:5
|
@@ -102,6 +157,15 @@ help: try adding an expression at the end of the block
58 + Ok(())
|
error[E0271]: expected `{async block@$DIR/tests/fail/macros_type_mismatch.rs:63:1: 63:15}` to be a future that resolves to `()`, but it resolves to `{integer}`
--> tests/fail/macros_type_mismatch.rs:63:1
|
63 | #[tokio::main]
| ^^^^^^^^^^^^^^ expected `()`, found integer
|
= note: required for the cast from `&{async block@$DIR/tests/fail/macros_type_mismatch.rs:63:1: 63:15}` to `&dyn Future<Output = ()>`
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:66:5
|
+12
View File
@@ -12,12 +12,24 @@ fn compile_fail_full() {
#[cfg(feature = "full")]
t.pass("tests/pass/macros_main_loop.rs");
#[cfg(feature = "full")]
t.pass("tests/pass/impl_trait.rs");
#[cfg(feature = "full")]
t.pass("tests/pass/use_builder_outer.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_invalid_input.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_dead_code.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_join.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_try_join.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_type_mismatch.rs");
+23
View File
@@ -0,0 +1,23 @@
use tests_build::tokio;
#[tokio::main]
async fn never() -> ! {
loop {}
}
#[tokio::main]
async fn impl_trait() -> impl Iterator<Item = impl core::fmt::Debug> {
[()].into_iter()
}
#[tokio::main]
async fn impl_trait2() -> Result<(), impl core::fmt::Debug> {
Err(())
}
fn main() {
if impl_trait().count() == 10 {
never();
}
let _ = impl_trait2();
}
@@ -0,0 +1,9 @@
#![deny(unused_qualifications)]
use tests_build::tokio;
pub use tokio::runtime;
#[tokio::main]
async fn main() {
if true {}
}
+11 -1
View File
@@ -16,6 +16,13 @@ async fn spawning() -> usize {
join.await.unwrap()
}
#[cfg(tokio_unstable)]
#[tokio::main(flavor = "local")]
async fn local_main() -> usize {
let join = tokio::task::spawn_local(async { 1 });
join.await.unwrap()
}
#[test]
fn main_with_spawn() {
assert_eq!(1, spawning());
@@ -24,5 +31,8 @@ fn main_with_spawn() {
#[test]
fn shell() {
assert_eq!(1, basic_main());
assert_eq!(bool::default(), generic_fun::<bool>())
assert_eq!(bool::default(), generic_fun::<bool>());
#[cfg(tokio_unstable)]
assert_eq!(1, local_main());
}
+29
View File
@@ -1,3 +1,32 @@
# 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])
- macros: use call_site hygiene to avoid unused qualification ([#7866])
[#7856]: https://github.com/tokio-rs/tokio/pull/7856
[#7866]: https://github.com/tokio-rs/tokio/pull/7866
# 2.6.0 (Oct 14th, 2025)
The MSRV is raised to 1.71.
- msrv: increase MSRV to 1.71 ([#7658])
- macros: add `local` runtime flavor ([#7375], [#7597])
- macros: suppress `clippy::unwrap_in_result` in `#[tokio::main]` ([#7651])
[#7375]: https://github.com/tokio-rs/tokio/pull/7375
[#7597]: https://github.com/tokio-rs/tokio/pull/7597
[#7651]: https://github.com/tokio-rs/tokio/pull/7651
[#7658]: https://github.com/tokio-rs/tokio/pull/7658
# 2.5.0 (Jan 8th, 2025)
- macros: suppress `clippy::needless_return` in `#[tokio::main]` ([#6874])
+5 -5
View File
@@ -1,12 +1,12 @@
[package]
name = "tokio-macros"
# When releasing to crates.io:
# - Remove path dependencies
# - Remove path dependencies (if any)
# - Update CHANGELOG.md.
# - Create "tokio-macros-1.x.y" git tag.
version = "2.5.0"
# - Create "tokio-macros-x.y.z" git tag.
version = "2.7.0"
edition = "2021"
rust-version = "1.70"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
@@ -27,7 +27,7 @@ quote = "1"
syn = { version = "2.0", features = ["full"] }
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
tokio = { version = "1.0.0", features = ["full", "test-util"] }
[package.metadata.docs.rs]
all-features = true
+116 -22
View File
@@ -10,6 +10,7 @@ type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
enum RuntimeFlavor {
CurrentThread,
Threaded,
Local,
}
impl RuntimeFlavor {
@@ -17,10 +18,11 @@ impl RuntimeFlavor {
match s {
"current_thread" => Ok(RuntimeFlavor::CurrentThread),
"multi_thread" => Ok(RuntimeFlavor::Threaded),
"local" => Ok(RuntimeFlavor::Local),
"single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()),
"basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()),
"threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()),
_ => Err(format!("No such runtime flavor `{s}`. The runtime flavors are `current_thread` and `multi_thread`.")),
_ => Err(format!("No such runtime flavor `{s}`. The runtime flavors are `current_thread`, `local`, and `multi_thread`.")),
}
}
}
@@ -51,6 +53,7 @@ impl UnhandledPanic {
}
struct FinalConfig {
name: Option<String>,
flavor: RuntimeFlavor,
worker_threads: Option<usize>,
start_paused: Option<bool>,
@@ -60,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,
@@ -68,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>,
@@ -81,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,
@@ -95,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."));
@@ -177,15 +193,16 @@ impl Configuration {
use RuntimeFlavor as F;
let flavor = self.flavor.unwrap_or(self.default_flavor);
let worker_threads = match (flavor, self.worker_threads) {
(F::CurrentThread, Some((_, worker_threads_span))) => {
(F::CurrentThread | F::Local, Some((_, worker_threads_span))) => {
let msg = format!(
"The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`",
self.macro_name(),
);
return Err(syn::Error::new(worker_threads_span, msg));
}
(F::CurrentThread, None) => None,
(F::CurrentThread | F::Local, None) => None,
(F::Threaded, worker_threads) if self.rt_multi_thread_available => {
worker_threads.map(|(val, _span)| val)
}
@@ -207,7 +224,7 @@ impl Configuration {
);
return Err(syn::Error::new(start_paused_span, msg));
}
(F::CurrentThread, Some((start_paused, _))) => Some(start_paused),
(F::CurrentThread | F::Local, Some((start_paused, _))) => Some(start_paused),
(_, None) => None,
};
@@ -219,11 +236,12 @@ impl Configuration {
);
return Err(syn::Error::new(unhandled_panic_span, msg));
}
(F::CurrentThread, Some((unhandled_panic, _))) => Some(unhandled_panic),
(F::CurrentThread | F::Local, Some((unhandled_panic, _))) => Some(unhandled_panic),
(_, None) => None,
};
Ok(FinalConfig {
name: self.name.clone(),
crate_name: self.crate_name.clone(),
flavor,
worker_threads,
@@ -290,6 +308,35 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
}
}
fn contains_impl_trait(ty: &syn::Type) -> bool {
match ty {
syn::Type::ImplTrait(_) => true,
syn::Type::Array(t) => contains_impl_trait(&t.elem),
syn::Type::Ptr(t) => contains_impl_trait(&t.elem),
syn::Type::Reference(t) => contains_impl_trait(&t.elem),
syn::Type::Slice(t) => contains_impl_trait(&t.elem),
syn::Type::Tuple(t) => t.elems.iter().any(contains_impl_trait),
syn::Type::Paren(t) => contains_impl_trait(&t.elem),
syn::Type::Group(t) => contains_impl_trait(&t.elem),
syn::Type::Path(t) => match t.path.segments.last() {
Some(segment) => match &segment.arguments {
syn::PathArguments::AngleBracketed(args) => args.args.iter().any(|arg| match arg {
syn::GenericArgument::Type(t) => contains_impl_trait(t),
syn::GenericArgument::AssocType(t) => contains_impl_trait(&t.ty),
_ => false,
}),
syn::PathArguments::Parenthesized(args) => {
args.inputs.iter().any(contains_impl_trait)
|| matches!(&args.output, syn::ReturnType::Type(_, t) if contains_impl_trait(t))
}
syn::PathArguments::None => false,
},
None => false,
},
_ => false,
}
}
fn build_config(
input: &ItemFn,
args: AttributeArgs,
@@ -340,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));
}
@@ -365,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));
@@ -405,16 +456,32 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
let crate_path = config
.crate_name
.map(ToTokens::into_token_stream)
.unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream());
.unwrap_or_else(|| {
Ident::new("tokio", Span::call_site().located_at(last_stmt_start_span))
.into_token_stream()
});
let use_builder = quote_spanned! {Span::call_site().located_at(last_stmt_start_span)=>
use #crate_path::runtime::Builder;
};
let mut rt = match config.flavor {
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
#crate_path::runtime::Builder::new_current_thread()
},
RuntimeFlavor::CurrentThread | RuntimeFlavor::Local => {
quote_spanned! {last_stmt_start_span=>
Builder::new_current_thread()
}
}
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
#crate_path::runtime::Builder::new_multi_thread()
Builder::new_multi_thread()
},
};
let build = if let RuntimeFlavor::Local = config.flavor {
quote_spanned! {last_stmt_start_span=> build_local(Default::default())}
} else {
quote_spanned! {last_stmt_start_span=> build()}
};
if let Some(v) = config.worker_threads {
rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) };
}
@@ -425,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! {
@@ -437,14 +507,18 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
let body_ident = quote! { body };
// This explicit `return` is intentional. See tokio-rs/tokio#4636
let last_block = quote_spanned! {last_stmt_end_span=>
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return)]
#[allow(clippy::expect_used, clippy::diverging_sub_expression, clippy::needless_return, clippy::unwrap_in_result)]
{
#use_builder
return #rt
.enable_all()
.build()
.#build
.expect("Failed building the Runtime")
.block_on(#body_ident);
}
};
let body = input.body();
@@ -458,22 +532,42 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
//
// We don't do this for the main function as it should only be used once so
// there will be no benefit.
let output_type = match &input.sig.output {
// For functions with no return value syn doesn't print anything,
// but that doesn't work as `Output` for our boxed `Future`, so
// default to `()` (the same type as the function output).
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
};
let body = if is_test {
let output_type = match &input.sig.output {
// For functions with no return value syn doesn't print anything,
// but that doesn't work as `Output` for our boxed `Future`, so
// default to `()` (the same type as the function output).
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
};
quote! {
let body = async #body;
#crate_path::pin!(body);
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
}
} else {
// force typecheck without runtime overhead
let check_block = match &input.sig.output {
syn::ReturnType::Type(_, t)
if matches!(**t, syn::Type::Never(_)) || contains_impl_trait(t) =>
{
quote! {}
}
_ => quote! {
if false {
let _: &dyn ::core::future::Future<Output = #output_type> = &body;
}
},
};
quote! {
let body = async #body;
// Compile-time assertion that the future's output matches the return type.
let body = {
#check_block
body
};
}
};
+106 -20
View File
@@ -12,11 +12,6 @@
//! Macros for use with Tokio
// This `extern` is required for older `rustc` versions but newer `rustc`
// versions warn about the unused `extern crate`.
#[allow(unused_extern_crates)]
extern crate proc_macro;
mod entry;
mod select;
@@ -46,7 +41,12 @@ use proc_macro::TokenStream;
/// Awaiting on other futures from the function provided here will not
/// perform as fast as those spawned as workers.
///
/// # Multi-threaded runtime
/// # Runtime flavors
///
/// The macro can be configured with a `flavor` parameter to select
/// different runtime configurations.
///
/// ## Multi-threaded
///
/// To use the multi-threaded runtime, the macro can be configured using
///
@@ -61,23 +61,56 @@ use proc_macro::TokenStream;
/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
/// flag.
///
/// # Current thread runtime
/// ## Current-thread
///
/// To use the single-threaded runtime known as the `current_thread` runtime,
/// the macro can be configured using
///
/// ```
/// ```rust
/// #[tokio::main(flavor = "current_thread")]
/// # async fn main() {}
/// ```
///
/// ## Function arguments:
/// ## Local
///
/// Arguments are allowed for any functions aside from `main` which is special
/// To use the [local runtime], the macro can be configured using
///
/// ## Usage
/// ```rust
/// #[tokio::main(flavor = "local")]
/// # async fn main() {}
/// ```
///
/// ### Using the multi-thread runtime
/// # Function arguments
///
/// Arguments are allowed for any functions, aside from `main` which is special.
///
/// # 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
/// #[tokio::main]
@@ -100,7 +133,7 @@ use proc_macro::TokenStream;
/// }
/// ```
///
/// ### Using current thread runtime
/// ## Using the current-thread runtime
///
/// The basic scheduler is single-threaded.
///
@@ -125,7 +158,34 @@ use proc_macro::TokenStream;
/// }
/// ```
///
/// ### Set number of worker threads
/// ## Using the local runtime
///
/// The [local runtime] is similar to the current-thread runtime but
/// supports [`task::spawn_local`](../tokio/task/fn.spawn_local.html).
///
/// ```rust
/// #[tokio::main(flavor = "local")]
/// async fn main() {
/// println!("Hello world");
/// }
/// ```
///
/// Equivalent code not using `#[tokio::main]`
///
/// ```rust
/// fn main() {
/// tokio::runtime::Builder::new_current_thread()
/// .enable_all()
/// .build_local(tokio::runtime::LocalOptions::default())
/// .unwrap()
/// .block_on(async {
/// println!("Hello world");
/// })
/// }
/// ```
///
///
/// ## Set number of worker threads
///
/// ```rust
/// #[tokio::main(worker_threads = 2)]
@@ -149,7 +209,7 @@ use proc_macro::TokenStream;
/// }
/// ```
///
/// ### Configure the runtime to start with time paused
/// ## Configure the runtime to start with time paused
///
/// ```rust
/// #[tokio::main(flavor = "current_thread", start_paused = true)]
@@ -175,7 +235,7 @@ use proc_macro::TokenStream;
///
/// Note that `start_paused` requires the `test-util` feature to be enabled.
///
/// ### Rename package
/// ## Rename package
///
/// ```rust
/// use tokio as tokio1;
@@ -202,7 +262,7 @@ use proc_macro::TokenStream;
/// }
/// ```
///
/// ### Configure unhandled panic behavior
/// ## Configure unhandled panic behavior
///
/// Available options are `shutdown_runtime` and `ignore`. For more details, see
/// [`Builder::unhandled_panic`].
@@ -228,7 +288,7 @@ use proc_macro::TokenStream;
/// fn main() {
/// tokio::runtime::Builder::new_current_thread()
/// .enable_all()
/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
/// .unhandled_panic(tokio::runtime::UnhandledPanic::ShutdownRuntime)
/// .build()
/// .unwrap()
/// .block_on(async {
@@ -247,6 +307,7 @@ use proc_macro::TokenStream;
///
/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
/// [unstable]: ../tokio/index.html#unstable-features
/// [local runtime]: ../tokio/runtime/struct.LocalRuntime.html
#[proc_macro_attribute]
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
entry::main(args.into(), item.into(), true).into()
@@ -358,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
@@ -484,7 +570,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// panic!("This panic will shutdown the runtime.");
/// }).await;
/// }
/// # #[cfg(not(tokio_unstable))]
///
/// # fn main() { }
/// ```
///
@@ -505,7 +591,7 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
/// }).await;
/// })
/// }
/// # #[cfg(not(tokio_unstable))]
///
/// # fn main() { }
/// ```
///
+22
View File
@@ -1,3 +1,25 @@
# 0.1.18 (January 4th, 2026)
### Added
- stream: add `ChunksTimeout::into_remainder` ([#7715])
- stream: add examples to wrapper types ([#7024])
- sync: implement `Stream::size_hint` for `ReceiverStream` and `UnboundedReceiverStream` ([#7492])
### Fixed
- stream: work around the rustc bug in `StreamExt::collect` ([#7754])
### Documented
- stream: improve the the docs of `TcpListenerStream` ([#7578])
[#7024]: https://github.com/tokio-rs/tokio/pull/7024
[#7492]: https://github.com/tokio-rs/tokio/pull/7492
[#7578]: https://github.com/tokio-rs/tokio/pull/7578
[#7715]: https://github.com/tokio-rs/tokio/pull/7715
[#7754]: https://github.com/tokio-rs/tokio/pull/7754
# 0.1.17 (December 6th, 2024)
- deps: fix dev-dependency on tokio-test ([#6931], [#7019])
+7 -7
View File
@@ -1,12 +1,12 @@
[package]
name = "tokio-stream"
# When releasing to crates.io:
# - Remove path dependencies
# - Remove path dependencies (if any)
# - Update CHANGELOG.md.
# - Create "tokio-stream-0.1.x" git tag.
version = "0.1.17"
version = "0.1.18"
edition = "2021"
rust-version = "1.70"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
@@ -38,14 +38,14 @@ signal = ["tokio/signal"]
[dependencies]
futures-core = { version = "0.3.0" }
pin-project-lite = "0.2.11"
tokio = { version = "1.15.0", path = "../tokio", features = ["sync"] }
tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
tokio = { version = "1.38.0", features = ["sync"] }
tokio-util = { version = "0.7.0", optional = true }
[dev-dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
tokio = { version = "1.38.0", features = ["full", "test-util"] }
async-stream = "0.3"
parking_lot = "0.12.0"
tokio-test = { version = "0.4", path = "../tokio-test" }
tokio-test = "0.4"
futures = { version = "0.3", default-features = false }
[package.metadata.docs.rs]
+5 -5
View File
@@ -26,12 +26,12 @@ unsafe impl<T> Sync for Empty<T> {}
/// ```
/// use tokio_stream::{self as stream, StreamExt};
///
/// #[tokio::main]
/// async fn main() {
/// let mut none = stream::empty::<i32>();
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let mut none = stream::empty::<i32>();
///
/// assert_eq!(None, none.next().await);
/// }
/// assert_eq!(None, none.next().await);
/// # }
/// ```
pub const fn empty<T>() -> Empty<T> {
Empty(PhantomData)
+6 -6
View File
@@ -34,14 +34,14 @@
//! ```rust
//! use tokio_stream::{self as stream, StreamExt};
//!
//! #[tokio::main]
//! async fn main() {
//! let mut stream = stream::iter(vec![0, 1, 2]);
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! let mut stream = stream::iter(vec![0, 1, 2]);
//!
//! while let Some(value) = stream.next().await {
//! println!("Got {}", value);
//! }
//! while let Some(value) = stream.next().await {
//! println!("Got {}", value);
//! }
//! # }
//! ```
//!
//! # Returning a Stream from a function
+8 -8
View File
@@ -22,16 +22,16 @@ impl<I> Unpin for Once<I> {}
/// ```
/// use tokio_stream::{self as stream, StreamExt};
///
/// #[tokio::main]
/// async fn main() {
/// // one is the loneliest number
/// let mut one = stream::once(1);
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// // one is the loneliest number
/// let mut one = stream::once(1);
///
/// assert_eq!(Some(1), one.next().await);
/// assert_eq!(Some(1), one.next().await);
///
/// // just one, that's all we get
/// assert_eq!(None, one.next().await);
/// }
/// // just one, that's all we get
/// assert_eq!(None, one.next().await);
/// # }
/// ```
pub fn once<T>(value: T) -> Once<T> {
Once {
+13 -13
View File
@@ -17,20 +17,20 @@ pin_project! {
/// ```
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
///
/// #[tokio::main]
/// async fn main() {
/// let mut map = StreamMap::new();
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
/// map.insert(0, stream);
/// map.insert(1, stream2);
/// while let Some((key, val)) = map.next().await {
/// match val {
/// Some(val) => println!("got {val:?} from stream {key:?}"),
/// None => println!("stream {key:?} closed"),
/// }
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let mut map = StreamMap::new();
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
/// map.insert(0, stream);
/// map.insert(1, stream2);
/// while let Some((key, val)) = map.next().await {
/// match val {
/// Some(val) => println!("got {val:?} from stream {key:?}"),
/// None => println!("stream {key:?} closed"),
/// }
/// }
/// # }
/// ```
#[must_use = "streams do nothing unless polled"]
pub struct StreamNotifyClose<S> {
@@ -83,7 +83,7 @@ where
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if let Some(inner) = &self.inner {
// We always return +1 because when there's stream there's atleast one more item.
// We always return +1 because when there's a stream there's at least one more item.
let (l, u) = inner.size_hint();
(l.saturating_add(1), u.and_then(|u| u.checked_add(1)))
} else {
+61 -57
View File
@@ -129,7 +129,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -171,8 +171,9 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
///
/// use tokio_stream::{self as stream, StreamExt};
///
/// let mut stream = stream::iter(vec![Ok(1), Ok(2), Err("nope")]);
@@ -203,7 +204,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -239,7 +240,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -283,7 +284,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -418,7 +419,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -454,7 +455,7 @@ pub trait StreamExt: Stream {
///
/// # Examples
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -514,7 +515,10 @@ pub trait StreamExt: Stream {
/// }
/// }
///
/// # /*
/// #[tokio::main]
/// # */
/// # #[tokio::main(flavor = "current_thread")]
/// async fn main() {
/// let mut stream = Alternate { state: 0 };
///
@@ -551,7 +555,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -580,7 +584,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -606,7 +610,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -637,7 +641,7 @@ pub trait StreamExt: Stream {
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
/// let mut stream = stream::iter(vec![1,2,3,4,1]).skip_while(|x| *x < 3);
@@ -680,7 +684,7 @@ pub trait StreamExt: Stream {
/// Basic usage:
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -695,7 +699,7 @@ pub trait StreamExt: Stream {
/// Stopping at the first `false`:
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -739,7 +743,7 @@ pub trait StreamExt: Stream {
/// Basic usage:
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -754,7 +758,7 @@ pub trait StreamExt: Stream {
/// Stopping at the first `true`:
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
@@ -787,21 +791,21 @@ pub trait StreamExt: Stream {
/// ```
/// use tokio_stream::{self as stream, StreamExt};
///
/// #[tokio::main]
/// async fn main() {
/// let one = stream::iter(vec![1, 2, 3]);
/// let two = stream::iter(vec![4, 5, 6]);
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let one = stream::iter(vec![1, 2, 3]);
/// let two = stream::iter(vec![4, 5, 6]);
///
/// let mut stream = one.chain(two);
/// let mut stream = one.chain(two);
///
/// assert_eq!(stream.next().await, Some(1));
/// assert_eq!(stream.next().await, Some(2));
/// assert_eq!(stream.next().await, Some(3));
/// assert_eq!(stream.next().await, Some(4));
/// assert_eq!(stream.next().await, Some(5));
/// assert_eq!(stream.next().await, Some(6));
/// assert_eq!(stream.next().await, None);
/// }
/// assert_eq!(stream.next().await, Some(1));
/// assert_eq!(stream.next().await, Some(2));
/// assert_eq!(stream.next().await, Some(3));
/// assert_eq!(stream.next().await, Some(4));
/// assert_eq!(stream.next().await, Some(5));
/// assert_eq!(stream.next().await, Some(6));
/// assert_eq!(stream.next().await, None);
/// # }
/// ```
fn chain<U>(self, other: U) -> Chain<Self, U>
where
@@ -823,7 +827,7 @@ pub trait StreamExt: Stream {
/// # Examples
/// Basic usage:
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, *};
///
@@ -874,16 +878,16 @@ pub trait StreamExt: Stream {
/// ```
/// use tokio_stream::{self as stream, StreamExt};
///
/// #[tokio::main]
/// async fn main() {
/// let doubled: Vec<i32> =
/// stream::iter(vec![1, 2, 3])
/// .map(|x| x * 2)
/// .collect()
/// .await;
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let doubled: Vec<i32> =
/// stream::iter(vec![1, 2, 3])
/// .map(|x| x * 2)
/// .collect()
/// .await;
///
/// assert_eq!(vec![2, 4, 6], doubled);
/// }
/// assert_eq!(vec![2, 4, 6], doubled);
/// # }
/// ```
///
/// Collecting a stream of `Result` values
@@ -891,28 +895,28 @@ pub trait StreamExt: Stream {
/// ```
/// use tokio_stream::{self as stream, StreamExt};
///
/// #[tokio::main]
/// async fn main() {
/// // A stream containing only `Ok` values will be collected
/// let values: Result<Vec<i32>, &str> =
/// stream::iter(vec![Ok(1), Ok(2), Ok(3)])
/// .collect()
/// .await;
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// // A stream containing only `Ok` values will be collected
/// let values: Result<Vec<i32>, &str> =
/// stream::iter(vec![Ok(1), Ok(2), Ok(3)])
/// .collect()
/// .await;
///
/// assert_eq!(Ok(vec![1, 2, 3]), values);
/// assert_eq!(Ok(vec![1, 2, 3]), values);
///
/// // A stream containing `Err` values will return the first error.
/// let results = vec![Ok(1), Err("no"), Ok(2), Ok(3), Err("nein")];
/// // A stream containing `Err` values will return the first error.
/// let results = vec![Ok(1), Err("no"), Ok(2), Ok(3), Err("nein")];
///
/// let values: Result<Vec<i32>, &str> =
/// stream::iter(results)
/// .collect()
/// .await;
/// let values: Result<Vec<i32>, &str> =
/// stream::iter(results)
/// .collect()
/// .await;
///
/// assert_eq!(Err("no"), values);
/// }
/// assert_eq!(Err("no"), values);
/// # }
/// ```
fn collect<T>(self) -> Collect<Self, T>
fn collect<T>(self) -> Collect<Self, T, T::InternalCollection>
where
T: FromStream<Self::Item>,
Self: Sized,
@@ -945,7 +949,7 @@ pub trait StreamExt: Stream {
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
/// use std::time::Duration;
@@ -1031,7 +1035,7 @@ pub trait StreamExt: Stream {
/// Suppose we have a stream `int_stream` that yields 3 numbers (1, 2, 3):
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
/// use std::time::Duration;
@@ -33,6 +33,12 @@ impl<S: Stream> ChunksTimeout<S> {
cap: max_size,
}
}
/// Consumes the [`ChunksTimeout`] and then returns all buffered items.
pub fn into_remainder(mut self: Pin<&mut Self>) -> Vec<S::Item> {
let me = self.as_mut().project();
std::mem::take(me.items)
}
}
impl<S: Stream> Stream for ChunksTimeout<S> {
+143 -9
View File
@@ -1,25 +1,25 @@
use crate::Stream;
use core::future::Future;
use core::marker::PhantomPinned;
use core::marker::{PhantomData, PhantomPinned};
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! {
/// Future returned by the [`collect`](super::StreamExt::collect) method.
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[derive(Debug)]
pub struct Collect<T, U>
where
T: Stream,
U: FromStream<T::Item>,
pub struct Collect<T, U, C>
{
#[pin]
stream: T,
collection: U::InternalCollection,
collection: C,
_output: PhantomData<U>,
// Make this future `!Unpin` for compatibility with async trait methods.
#[pin]
_pin: PhantomPinned,
@@ -38,24 +38,25 @@ pin_project! {
/// enhancements to the Rust language.
pub trait FromStream<T>: sealed::FromStreamPriv<T> {}
impl<T, U> Collect<T, U>
impl<T, U> Collect<T, U, U::InternalCollection>
where
T: Stream,
U: FromStream<T::Item>,
{
pub(super) fn new(stream: T) -> Collect<T, U> {
pub(super) fn new(stream: T) -> Collect<T, U, U::InternalCollection> {
let (lower, upper) = stream.size_hint();
let collection = U::initialize(sealed::Internal, lower, upper);
Collect {
stream,
collection,
_output: PhantomData,
_pin: PhantomPinned,
}
}
}
impl<T, U> Future for Collect<T, U>
impl<T, U> Future for Collect<T, U, U::InternalCollection>
where
T: Stream,
U: FromStream<T::Item>,
@@ -136,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]> {
+67 -72
View File
@@ -65,57 +65,57 @@ use std::task::{ready, Context, Poll};
/// use tokio::sync::mpsc;
/// use std::pin::Pin;
///
/// #[tokio::main]
/// async fn main() {
/// let (tx1, mut rx1) = mpsc::channel::<usize>(10);
/// let (tx2, mut rx2) = mpsc::channel::<usize>(10);
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let (tx1, mut rx1) = mpsc::channel::<usize>(10);
/// let (tx2, mut rx2) = mpsc::channel::<usize>(10);
///
/// // Convert the channels to a `Stream`.
/// let rx1 = Box::pin(async_stream::stream! {
/// while let Some(item) = rx1.recv().await {
/// yield item;
/// }
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
///
/// let rx2 = Box::pin(async_stream::stream! {
/// while let Some(item) = rx2.recv().await {
/// yield item;
/// }
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
///
/// tokio::spawn(async move {
/// tx1.send(1).await.unwrap();
///
/// // This value will never be received. The send may or may not return
/// // `Err` depending on if the remote end closed first or not.
/// let _ = tx1.send(2).await;
/// });
///
/// tokio::spawn(async move {
/// tx2.send(3).await.unwrap();
/// let _ = tx2.send(4).await;
/// });
///
/// let mut map = StreamMap::new();
///
/// // Insert both streams
/// map.insert("one", rx1);
/// map.insert("two", rx2);
///
/// // Read twice
/// for _ in 0..2 {
/// let (key, val) = map.next().await.unwrap();
///
/// if key == "one" {
/// assert_eq!(val, 1);
/// } else {
/// assert_eq!(val, 3);
/// }
///
/// // Remove the stream to prevent reading the next value
/// map.remove(key);
/// // Convert the channels to a `Stream`.
/// let rx1 = Box::pin(async_stream::stream! {
/// while let Some(item) = rx1.recv().await {
/// yield item;
/// }
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
///
/// let rx2 = Box::pin(async_stream::stream! {
/// while let Some(item) = rx2.recv().await {
/// yield item;
/// }
/// }) as Pin<Box<dyn Stream<Item = usize> + Send>>;
///
/// tokio::spawn(async move {
/// tx1.send(1).await.unwrap();
///
/// // This value will never be received. The send may or may not return
/// // `Err` depending on if the remote end closed first or not.
/// let _ = tx1.send(2).await;
/// });
///
/// tokio::spawn(async move {
/// tx2.send(3).await.unwrap();
/// let _ = tx2.send(4).await;
/// });
///
/// let mut map = StreamMap::new();
///
/// // Insert both streams
/// map.insert("one", rx1);
/// map.insert("two", rx2);
///
/// // Read twice
/// for _ in 0..2 {
/// let (key, val) = map.next().await.unwrap();
///
/// if key == "one" {
/// assert_eq!(val, 1);
/// } else {
/// assert_eq!(val, 3);
/// }
///
/// // Remove the stream to prevent reading the next value
/// map.remove(key);
/// }
/// # }
/// ```
///
/// This example models a read-only client to a chat system with channels. The
@@ -185,20 +185,20 @@ use std::task::{ready, Context, Poll};
/// ```
/// use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose};
///
/// #[tokio::main]
/// async fn main() {
/// let mut map = StreamMap::new();
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
/// map.insert(0, stream);
/// map.insert(1, stream2);
/// while let Some((key, val)) = map.next().await {
/// match val {
/// Some(val) => println!("got {val:?} from stream {key:?}"),
/// None => println!("stream {key:?} closed"),
/// }
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let mut map = StreamMap::new();
/// let stream = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
/// let stream2 = StreamNotifyClose::new(tokio_stream::iter(vec![0, 1]));
/// map.insert(0, stream);
/// map.insert(1, stream2);
/// while let Some((key, val)) = map.next().await {
/// match val {
/// Some(val) => println!("got {val:?} from stream {key:?}"),
/// None => println!("stream {key:?} closed"),
/// }
/// }
/// # }
/// ```
#[derive(Debug)]
@@ -579,9 +579,11 @@ where
/// # Cancel safety
///
/// This method is cancel safe. If `next_many` is used as the event in a
/// [`tokio::select!`](tokio::select) statement and some other branch
/// completes first, it is guaranteed that no items were received on any of
/// the underlying streams.
/// [`tokio::select!`] statement and some other branch completes first,
/// it is guaranteed that no items were received on any of the underlying
/// streams.
///
/// [`tokio::select!`]: https://docs.rs/tokio/latest/tokio/macro.select.html
pub async fn next_many(&mut self, buffer: &mut Vec<(K, V::Item)>, limit: usize) -> usize {
poll_fn(|cx| self.poll_next_many(cx, buffer, limit)).await
}
@@ -734,22 +736,15 @@ mod rand {
#[cfg(not(loom))]
pub(crate) mod rand {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hash, Hasher};
use std::hash::BuildHasher;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering::Relaxed;
static COUNTER: AtomicU32 = AtomicU32::new(1);
pub(crate) fn seed() -> u64 {
let rand_state = RandomState::new();
let mut hasher = rand_state.build_hasher();
// Hash some unique-ish data to generate some new state
COUNTER.fetch_add(1, Relaxed).hash(&mut hasher);
// Get the seed
hasher.finish()
RandomState::new().hash_one(COUNTER.fetch_add(1, Relaxed))
}
}
+8 -4
View File
@@ -22,9 +22,9 @@ cfg_sync! {
}
cfg_signal! {
#[cfg(unix)]
#[cfg(all(unix, not(loom)))]
mod signal_unix;
#[cfg(unix)]
#[cfg(all(unix, not(loom)))]
pub use signal_unix::SignalStream;
#[cfg(any(windows, docsrs))]
@@ -39,12 +39,14 @@ cfg_time! {
}
cfg_net! {
#[cfg(not(loom))]
mod tcp_listener;
#[cfg(not(loom))]
pub use tcp_listener::TcpListenerStream;
#[cfg(unix)]
#[cfg(all(unix, not(loom)))]
mod unix_listener;
#[cfg(unix)]
#[cfg(all(unix, not(loom)))]
pub use unix_listener::UnixListenerStream;
}
@@ -57,6 +59,8 @@ cfg_io_util! {
}
cfg_fs! {
#[cfg(not(loom))]
mod read_dir;
#[cfg(not(loom))]
pub use read_dir::ReadDirStream;
}
+7
View File
@@ -1,4 +1,5 @@
use crate::Stream;
use futures_core::stream::FusedStream;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::time::{Instant, Interval};
@@ -57,6 +58,12 @@ impl Stream for IntervalStream {
}
}
impl FusedStream for IntervalStream {
fn is_terminated(&self) -> bool {
false
}
}
impl AsRef<Interval> for IntervalStream {
fn as_ref(&self) -> &Interval {
&self.inner
+19
View File
@@ -67,6 +67,25 @@ impl<T> Stream for ReceiverStream<T> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.inner.poll_recv(cx)
}
/// Returns the bounds of the stream based on the underlying receiver.
///
/// For open channels, it returns `(receiver.len(), None)`.
///
/// For closed channels, it returns `(receiver.len(), Some(used_capacity))`
/// where `used_capacity` is calculated as `receiver.max_capacity() -
/// receiver.capacity()`. This accounts for any [`Permit`] that is still
/// able to send a message.
///
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
fn size_hint(&self) -> (usize, Option<usize>) {
if self.inner.is_closed() {
let used_capacity = self.inner.max_capacity() - self.inner.capacity();
(self.inner.len(), Some(used_capacity))
} else {
(self.inner.len(), None)
}
}
}
impl<T> AsRef<Receiver<T>> for ReceiverStream<T> {
@@ -61,6 +61,20 @@ impl<T> Stream for UnboundedReceiverStream<T> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.inner.poll_recv(cx)
}
/// Returns the bounds of the stream based on the underlying receiver.
///
/// For open channels, it returns `(receiver.len(), None)`.
///
/// For closed channels, it returns `(receiver.len(), receiver.len())`.
fn size_hint(&self) -> (usize, Option<usize>) {
if self.inner.is_closed() {
let len = self.inner.len();
(len, Some(len))
} else {
(self.inner.len(), None)
}
}
}
impl<T> AsRef<UnboundedReceiver<T>> for UnboundedReceiverStream<T> {
+6 -3
View File
@@ -11,6 +11,8 @@ use tokio::net::{TcpListener, TcpStream};
/// Accept connections from both IPv4 and IPv6 listeners in the same loop:
///
/// ```no_run
/// # #[cfg(not(target_family = "wasm"))]
/// # {
/// use std::net::{Ipv4Addr, Ipv6Addr};
///
/// use tokio::net::TcpListener;
@@ -18,12 +20,12 @@ use tokio::net::{TcpListener, TcpStream};
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// let ipv4_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 8080)).await?;
/// let ipv6_listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 8080)).await?;
/// let ipv4_listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 8080)).await?;
/// let ipv6_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 8080)).await?;
/// let ipv4_connections = TcpListenerStream::new(ipv4_listener);
/// let ipv6_connections = TcpListenerStream::new(ipv6_listener);
///
/// let mut connections = ipv4_connections.chain(ipv6_connections);
/// let mut connections = ipv4_connections.merge(ipv6_connections);
/// while let Some(tcp_stream) = connections.next().await {
/// let stream = tcp_stream?;
/// let peer_addr = stream.peer_addr()?;
@@ -31,6 +33,7 @@ use tokio::net::{TcpListener, TcpStream};
/// }
/// # Ok(())
/// # }
/// # }
/// ```
///
/// [`TcpListener`]: struct@tokio::net::TcpListener
+3 -3
View File
@@ -17,7 +17,7 @@ use tokio::sync::watch::error::RecvError;
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
/// use tokio::sync::watch;
@@ -33,7 +33,7 @@ use tokio::sync::watch::error::RecvError;
/// ```
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use tokio_stream::{StreamExt, wrappers::WatchStream};
/// use tokio::sync::watch;
@@ -51,7 +51,7 @@ use tokio::sync::watch::error::RecvError;
/// Example with [`WatchStream<T>::from_changes`]:
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use futures::future::FutureExt;
/// use tokio::sync::watch;
+109
View File
@@ -0,0 +1,109 @@
use futures::{Stream, StreamExt};
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
#[tokio::test]
async fn size_hint_stream_open() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
tx.send(2).await.unwrap();
let mut stream = ReceiverStream::new(rx);
assert_eq!(stream.size_hint(), (2, None));
stream.next().await;
assert_eq!(stream.size_hint(), (1, None));
stream.next().await;
assert_eq!(stream.size_hint(), (0, None));
}
#[tokio::test]
async fn size_hint_stream_closed() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
tx.send(2).await.unwrap();
let mut stream = ReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn size_hint_sender_dropped() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
tx.send(2).await.unwrap();
let mut stream = ReceiverStream::new(rx);
drop(tx);
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[test]
fn size_hint_stream_instantly_closed() {
let (_tx, rx) = mpsc::channel::<i32>(4);
let mut stream = ReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn size_hint_stream_closed_permits_send() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
let permit1 = tx.reserve().await.unwrap();
let permit2 = tx.reserve().await.unwrap();
let mut stream = ReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (1, Some(3)));
permit1.send(2);
assert_eq!(stream.size_hint(), (2, Some(3)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(1)));
permit2.send(3);
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(stream.next().await, None);
}
#[tokio::test]
async fn size_hint_stream_closed_permits_drop() {
let (tx, rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
let permit1 = tx.reserve().await.unwrap();
let permit2 = tx.reserve().await.unwrap();
let mut stream = ReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (1, Some(3)));
drop(permit1);
assert_eq!(stream.size_hint(), (1, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(1)));
drop(permit2);
assert_eq!(stream.size_hint(), (0, Some(0)));
assert_eq!(stream.next().await, None);
}
@@ -0,0 +1,63 @@
use futures::{Stream, StreamExt};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
#[tokio::test]
async fn size_hint_stream_open() {
let (tx, rx) = mpsc::unbounded_channel();
tx.send(1).unwrap();
tx.send(2).unwrap();
let mut stream = UnboundedReceiverStream::new(rx);
assert_eq!(stream.size_hint(), (2, None));
stream.next().await;
assert_eq!(stream.size_hint(), (1, None));
stream.next().await;
assert_eq!(stream.size_hint(), (0, None));
}
#[tokio::test]
async fn size_hint_stream_closed() {
let (tx, rx) = mpsc::unbounded_channel();
tx.send(1).unwrap();
tx.send(2).unwrap();
let mut stream = UnboundedReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[tokio::test]
async fn size_hint_sender_dropped() {
let (tx, rx) = mpsc::unbounded_channel();
tx.send(1).unwrap();
tx.send(2).unwrap();
let mut stream = UnboundedReceiverStream::new(rx);
drop(tx);
assert_eq!(stream.size_hint(), (2, Some(2)));
stream.next().await;
assert_eq!(stream.size_hint(), (1, Some(1)));
stream.next().await;
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[test]
fn size_hint_stream_instantly_closed() {
let (_tx, rx) = mpsc::unbounded_channel::<i32>();
let mut stream = UnboundedReceiverStream::new(rx);
stream.close();
assert_eq!(stream.size_hint(), (0, Some(0)));
}
@@ -0,0 +1,30 @@
#![warn(rust_2018_idioms)]
use futures::FutureExt;
use std::error::Error;
use tokio::time;
use tokio::time::Duration;
use tokio_stream::{self as stream, StreamExt};
use tokio_test::assert_pending;
use tokio_test::task;
#[tokio::test(start_paused = true)]
async fn stream_chunks_remainder() -> Result<(), Box<dyn Error>> {
let stream1 =
stream::iter([5]).then(move |n| time::sleep(Duration::from_secs(1)).map(move |_| n));
let inner = stream::iter([1, 2, 3, 4]).chain(stream1);
tokio::pin!(inner);
let chunked = (&mut inner).chunks_timeout(10, Duration::from_millis(20));
let mut chunked = task::spawn(chunked);
assert_pending!(chunked.poll_next());
let remainder = chunked.enter(|_, stream| stream.into_remainder());
assert_eq!(remainder, vec![1, 2, 3, 4]);
time::advance(Duration::from_secs(2)).await;
assert_eq!(inner.next().await, Some(5));
Ok(())
}
+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();
+12
View File
@@ -48,3 +48,15 @@ async fn basic_usage() {
assert_eq!(stream.next().await, None);
assert_eq!(stream.size_hint(), (0, Some(0)));
}
#[tokio::test]
#[cfg(feature = "time")]
async fn interval_stream_is_never_terminated() {
use futures_core::stream::FusedStream;
use tokio_stream::wrappers::IntervalStream;
let interval = tokio::time::interval(std::time::Duration::from_millis(1));
let stream = IntervalStream::new(interval);
assert!(!stream.is_terminated());
}
+18
View File
@@ -1,3 +1,21 @@
# 0.4.5 (January 4th, 2026)
### Added
- test: add `io::Builder::name` for better panic messages ([#7212])
### Fixed
- test: make `Spawn` forward `size_hint` ([#6607])
### Changed
- test: remove unused `async-stream` and `bytes` dependencies ([#7214])
[#6607]: https://github.com/tokio-rs/tokio/pull/6607
[#7212]: https://github.com/tokio-rs/tokio/pull/7212
[#7214]: https://github.com/tokio-rs/tokio/pull/7214
# 0.4.4 (March 14, 2024)
- task: mark `Spawn` as `#[must_use]` ([#6371])
+6 -6
View File
@@ -1,12 +1,12 @@
[package]
name = "tokio-test"
# When releasing to crates.io:
# - Remove path dependencies
# - Remove path dependencies (if any)
# - Update CHANGELOG.md.
# - Create "tokio-test-0.4.x" git tag.
version = "0.4.4"
version = "0.4.5"
edition = "2021"
rust-version = "1.70"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
@@ -17,12 +17,12 @@ Testing utilities for Tokio- and futures-based code
categories = ["asynchronous", "development-tools::testing"]
[dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
tokio-stream = { version = "0.1.1", path = "../tokio-stream" }
tokio = { version = "1.2.0", features = ["rt", "sync", "time", "test-util"] }
tokio-stream = "0.1.1"
futures-core = "0.3.0"
[dev-dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["full"] }
tokio = { version = "1.2.0", features = ["full"] }
futures-util = "0.3.0"
[package.metadata.docs.rs]
+24 -1
View File
@@ -438,7 +438,30 @@ impl AsyncWrite for Mock {
let until = Instant::now() + rem;
self.inner.sleep = Some(Box::pin(time::sleep_until(until)));
} else {
panic!("unexpected WouldBlock {}", self.pmsg());
// A race condition (TOCTOU) can occur if the
// timer expires between the `write()` call
// and `remaining_wait()` due to preemption or other
// delays. In this case, the `Wait` action is already popped by
// `action()`, so we continue to the next one.
//
// Consider the following sequence:
//
// poll_write Inner action()
// |--write()--->| |
// | |--action()---->| (returns Wait)
// |<-WouldBlk---| |
// | | |
// | <--- TIMEOUT! ---> |
// | (due to preemption, etc.) |
// | | |
// |-rem_wait()->| |
// | |--action()---->| (time's up, pop Wait)
// |<--None------| |
// | | |
// |---continue->| (process next action)
//
// See <https://github.com/tokio-rs/tokio/issues/7881>.
continue;
}
}
Ok(0) => {
+45 -11
View File
@@ -26,10 +26,11 @@
//! ```
use std::future::Future;
use std::mem;
use std::ops;
use std::pin::Pin;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, Wake, Waker};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use tokio_stream::Stream;
@@ -170,7 +171,7 @@ impl MockTask {
F: FnOnce(&mut Context<'_>) -> R,
{
self.waker.clear();
let waker = self.clone().into_waker();
let waker = self.waker();
let mut cx = Context::from_waker(&waker);
f(&mut cx)
@@ -189,8 +190,11 @@ impl MockTask {
Arc::strong_count(&self.waker)
}
fn into_waker(self) -> Waker {
self.waker.into()
fn waker(&self) -> Waker {
unsafe {
let raw = to_raw(self.waker.clone());
Waker::from_raw(raw)
}
}
}
@@ -222,14 +226,8 @@ impl ThreadWaker {
_ => unreachable!(),
}
}
}
impl Wake for ThreadWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
fn wake(&self) {
// First, try transitioning from IDLE -> NOTIFY, this does not require a lock.
let mut state = self.state.lock().unwrap();
let prev = *state;
@@ -249,3 +247,39 @@ impl Wake for ThreadWaker {
self.condvar.notify_one();
}
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
unsafe fn to_raw(waker: Arc<ThreadWaker>) -> RawWaker {
RawWaker::new(Arc::into_raw(waker) as *const (), &VTABLE)
}
unsafe fn from_raw(raw: *const ()) -> Arc<ThreadWaker> {
Arc::from_raw(raw as *const ThreadWaker)
}
unsafe fn clone(raw: *const ()) -> RawWaker {
let waker = from_raw(raw);
// Increment the ref count
mem::forget(waker.clone());
to_raw(waker)
}
unsafe fn wake(raw: *const ()) {
let waker = from_raw(raw);
waker.wake();
}
unsafe fn wake_by_ref(raw: *const ()) {
let waker = from_raw(raw);
waker.wake();
// We don't actually own a reference to the unparker
mem::forget(waker);
}
unsafe fn drop_waker(raw: *const ()) {
let _ = from_raw(raw);
}
+87
View File
@@ -1,3 +1,90 @@
# 0.7.18 (January 4th, 2026)
### Added
- io: add `tokio_util::io::simplex` ([#7565])
### Changed
- task: remove unnecessary trait bounds on the `Debug` implementation for `JoinQueue` and `AbortOnDropHandle` ([#7720])
### Fixed
- deps: bump `tokio` to `1.44.0` ([#7733])
### Documented
- io: document the default capacity of the `ReaderStream` ([#7147])
- sync: fix a typo in the docs of `PollSender::is_closed` ([#7737])
[#7147]: https://github.com/tokio-rs/tokio/pull/7147
[#7565]: https://github.com/tokio-rs/tokio/pull/7565
[#7720]: https://github.com/tokio-rs/tokio/pull/7720
[#7733]: https://github.com/tokio-rs/tokio/pull/7733
[#7737]: https://github.com/tokio-rs/tokio/pull/7737
# 0.7.17 (November 2nd, 2025)
The MSRV is increased to 1.71.
### Added
- codec: add `{FramedRead,FramedWrite}::into_parts()` ([#7566])
- time: add `#[track_caller]` to `FutureExt::timeout` ([#7588])
- task: add `tokio_util::task::JoinQueue` ([#7590])
### Changed
- codec: remove unnecessary trait bounds on all Framed constructors ([#7716])
### Documented
- time: clarify the cancellation safety of the `DelayQueue` ([#7564])
- docs: fix some docs links ([#7654])
- task: simplify the example of `TaskTracker` ([#7657])
- task: clarify the behavior of several `spawn_local` methods ([#7669])
[#7564]: https://github.com/tokio-rs/tokio/pull/7564
[#7566]: https://github.com/tokio-rs/tokio/pull/7566
[#7588]: https://github.com/tokio-rs/tokio/pull/7588
[#7590]: https://github.com/tokio-rs/tokio/pull/7590
[#7654]: https://github.com/tokio-rs/tokio/pull/7654
[#7657]: https://github.com/tokio-rs/tokio/pull/7657
[#7669]: https://github.com/tokio-rs/tokio/pull/7669
[#7716]: https://github.com/tokio-rs/tokio/pull/7716
# 0.7.16 (August 3rd, 2025)
### Added
- codec: add `FramedWrite::with_capacity` ([#7493])
- future: add adapters of `CancellationToken` for `FutureExt` ([#7475])
- sync: add `DropGuardRef` for `CancellationToken` ([#7407])
- task: add `AbortOnDropHandle::detach` ([#7400])
- task: stabilise `JoinMap` ([#7075])
### Changed
- codec: also apply capacity to read buffer in `Framed::with_capacity` ([#7500])
- sync: make `CancellationToken::run_until_cancelled` biased towards the token ([#7462])
- task: remove raw-entry feature from hashbrown dep ([#7252])
### Documented
- compat: add more documentation to `tokio_util::compat` ([#7279])
- sync: improve docs of `tokio_util::sync::CancellationToken` ([#7408])
[#7075]: https://github.com/tokio-rs/tokio/pull/7075
[#7252]: https://github.com/tokio-rs/tokio/pull/7252
[#7279]: https://github.com/tokio-rs/tokio/pull/7279
[#7400]: https://github.com/tokio-rs/tokio/pull/7400
[#7407]: https://github.com/tokio-rs/tokio/pull/7407
[#7408]: https://github.com/tokio-rs/tokio/pull/7408
[#7462]: https://github.com/tokio-rs/tokio/pull/7462
[#7475]: https://github.com/tokio-rs/tokio/pull/7475
[#7493]: https://github.com/tokio-rs/tokio/pull/7493
[#7500]: https://github.com/tokio-rs/tokio/pull/7500
# 0.7.15 (April 23rd, 2025)
### Fixed
+15 -13
View File
@@ -1,12 +1,12 @@
[package]
name = "tokio-util"
# When releasing to crates.io:
# - Remove path dependencies
# - Remove path dependencies (if any)
# - Update CHANGELOG.md.
# - Create "tokio-util-0.7.x" git tag.
version = "0.7.15"
version = "0.7.18"
edition = "2021"
rust-version = "1.70"
rust-version = "1.71"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
@@ -21,20 +21,21 @@ categories = ["asynchronous"]
default = []
# Shorthand for enabling everything
full = ["codec", "compat", "io-util", "time", "net", "rt"]
full = ["codec", "compat", "io-util", "time", "net", "rt", "join-map"]
net = ["tokio/net"]
compat = ["futures-io",]
compat = ["futures-io"]
codec = []
time = ["tokio/time","slab"]
time = ["tokio/time", "slab"]
io = []
io-util = ["io", "tokio/rt", "tokio/io-util"]
rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
rt = ["tokio/rt", "tokio/sync", "futures-util"]
join-map = ["rt", "hashbrown"]
__docs_rs = ["futures-util"]
[dependencies]
tokio = { version = "1.28.0", path = "../tokio", features = ["sync"] }
tokio = { version = "1.47.0", features = ["sync"] }
bytes = "1.5.0"
futures-core = "0.3.0"
futures-sink = "0.3.0"
@@ -43,14 +44,12 @@ futures-util = { version = "0.3.0", optional = true }
pin-project-lite = "0.2.11"
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
tracing = { version = "0.1.29", default-features = false, features = ["std"], optional = true }
[target.'cfg(tokio_unstable)'.dependencies]
hashbrown = { version = "0.15.0", default-features = false, optional = true }
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
tokio-test = { version = "0.4.0", path = "../tokio-test" }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
tokio = { version = "1.0.0", features = ["full"] }
tokio-test = "0.4.0"
tokio-stream = "0.1"
async-stream = "0.3.0"
futures = "0.3.0"
@@ -58,6 +57,9 @@ futures-test = "0.3.5"
parking_lot = "0.12.0"
tempfile = "3.1.0"
[target.'cfg(loom)'.dev-dependencies]
loom = { version = "0.7", features = ["futures", "checkpoint"] }
[package.metadata.docs.rs]
all-features = true
# enable unstable features in the documentation
+9
View File
@@ -60,6 +60,15 @@ macro_rules! cfg_rt {
}
}
macro_rules! cfg_not_rt {
($($item:item)*) => {
$(
#[cfg(not(feature = "rt"))]
$item
)*
}
}
macro_rules! cfg_time {
($($item:item)*) => {
$(
+6 -8
View File
@@ -41,10 +41,7 @@ pin_project! {
}
}
impl<T, U> Framed<T, U>
where
T: AsyncRead + AsyncWrite,
{
impl<T, U> Framed<T, U> {
/// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this
/// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data.
///
@@ -119,14 +116,15 @@ where
buffer: BytesMut::with_capacity(capacity),
has_errored: false,
},
write: WriteFrame::default(),
write: WriteFrame {
buffer: BytesMut::with_capacity(capacity),
backpressure_boundary: capacity,
},
},
},
}
}
}
impl<T, U> Framed<T, U> {
/// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this
/// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data.
///
@@ -374,7 +372,7 @@ pub struct FramedParts<T, U> {
/// This private field allows us to add additional fields in the future in a
/// backwards compatible way.
_priv: (),
pub(crate) _priv: (),
}
impl<T, U> FramedParts<T, U> {
+2 -1
View File
@@ -65,6 +65,7 @@ impl Default for WriteFrame {
impl From<BytesMut> for ReadFrame {
fn from(mut buffer: BytesMut) -> Self {
let is_readable = !buffer.is_empty();
let size = buffer.capacity();
if size < INITIAL_CAPACITY {
buffer.reserve(INITIAL_CAPACITY - size);
@@ -72,7 +73,7 @@ impl From<BytesMut> for ReadFrame {
Self {
buffer,
is_readable: size > 0,
is_readable,
eof: false,
has_errored: false,
}
+15 -7
View File
@@ -11,6 +11,8 @@ use std::fmt;
use std::pin::Pin;
use std::task::{Context, Poll};
use super::FramedParts;
pin_project! {
/// A [`Stream`] of messages decoded from an [`AsyncRead`].
///
@@ -34,11 +36,7 @@ pin_project! {
// ===== impl FramedRead =====
impl<T, D> FramedRead<T, D>
where
T: AsyncRead,
D: Decoder,
{
impl<T, D> FramedRead<T, D> {
/// Creates a new `FramedRead` with the given `decoder`.
pub fn new(inner: T, decoder: D) -> FramedRead<T, D> {
FramedRead {
@@ -66,9 +64,7 @@ where
},
}
}
}
impl<T, D> FramedRead<T, D> {
/// Returns a reference to the underlying I/O stream wrapped by
/// `FramedRead`.
///
@@ -153,6 +149,18 @@ impl<T, D> FramedRead<T, D> {
pub fn read_buffer_mut(&mut self) -> &mut BytesMut {
&mut self.inner.state.buffer
}
/// Consumes the `FramedRead`, returning its underlying I/O stream, the buffer
/// with unprocessed data, and the codec.
pub fn into_parts(self) -> FramedParts<T, D> {
FramedParts {
io: self.inner.inner,
codec: self.inner.codec,
read_buf: self.inner.state.buffer,
write_buf: BytesMut::new(),
_priv: (),
}
}
}
// This impl just defers to the underlying FramedImpl
+30 -6
View File
@@ -12,6 +12,8 @@ use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use super::FramedParts;
pin_project! {
/// A [`Sink`] of frames encoded to an `AsyncWrite`.
///
@@ -33,10 +35,7 @@ pin_project! {
}
}
impl<T, E> FramedWrite<T, E>
where
T: AsyncWrite,
{
impl<T, E> FramedWrite<T, E> {
/// Creates a new `FramedWrite` with the given `encoder`.
pub fn new(inner: T, encoder: E) -> FramedWrite<T, E> {
FramedWrite {
@@ -47,9 +46,22 @@ where
},
}
}
}
impl<T, E> FramedWrite<T, E> {
/// Creates a new `FramedWrite` with the given `encoder` and a buffer of `capacity`
/// initial size.
pub fn with_capacity(inner: T, encoder: E, capacity: usize) -> FramedWrite<T, E> {
FramedWrite {
inner: FramedImpl {
inner,
codec: encoder,
state: WriteFrame {
buffer: BytesMut::with_capacity(capacity),
backpressure_boundary: capacity,
},
},
}
}
/// Returns a reference to the underlying I/O stream wrapped by
/// `FramedWrite`.
///
@@ -144,6 +156,18 @@ impl<T, E> FramedWrite<T, E> {
pub fn set_backpressure_boundary(&mut self, boundary: usize) {
self.inner.state.backpressure_boundary = boundary;
}
/// Consumes the `FramedWrite`, returning its underlying I/O stream, the buffer
/// with unprocessed data, and the codec.
pub fn into_parts(self) -> FramedParts<T, E> {
FramedParts {
io: self.inner.inner,
codec: self.inner.codec,
read_buf: BytesMut::new(),
write_buf: self.inner.state.buffer,
_priv: (),
}
}
}
// This impl just defers to the underlying FramedImpl
+7 -7
View File
@@ -81,7 +81,7 @@
//! ```
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # #[tokio::main]
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x0BHello world";
//! let mut reader = LengthDelimitedCodec::builder()
@@ -117,7 +117,7 @@
//! ```
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # #[tokio::main]
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x0BHello world";
//! let mut reader = LengthDelimitedCodec::builder()
@@ -154,7 +154,7 @@
//! ```
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # #[tokio::main]
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x0DHello world";
//! let mut reader = LengthDelimitedCodec::builder()
@@ -190,7 +190,7 @@
//! ```
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # #[tokio::main]
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x00\x0B\xCA\xFEHello world";
//! let mut reader = LengthDelimitedCodec::builder()
@@ -237,7 +237,7 @@
//! ```
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # #[tokio::main]
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! # let io: &[u8] = b"\xCA\x00\x0B\xFEHello world";
//! let mut reader = LengthDelimitedCodec::builder()
@@ -286,7 +286,7 @@
//! ```
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # #[tokio::main]
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! # let io: &[u8] = b"\xCA\x00\x0F\xFEHello world";
//! let mut reader = LengthDelimitedCodec::builder()
@@ -329,7 +329,7 @@
//! ```
//! # use tokio_stream::StreamExt;
//! # use tokio_util::codec::LengthDelimitedCodec;
//! # #[tokio::main]
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! # let io: &[u8] = b"\x00\x00\x0B\xFFHello world";
//! let mut reader = LengthDelimitedCodec::builder()
+32 -32
View File
@@ -19,25 +19,25 @@
//! use tokio_util::codec::LinesCodec;
//! use tokio_util::codec::FramedWrite;
//!
//! #[tokio::main]
//! async fn main() {
//! let buffer = Vec::new();
//! let messages = vec!["Hello", "World"];
//! let encoder = LinesCodec::new();
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! let buffer = Vec::new();
//! let messages = vec!["Hello", "World"];
//! let encoder = LinesCodec::new();
//!
//! // FramedWrite is a sink which means you can send values into it
//! // asynchronously.
//! let mut writer = FramedWrite::new(buffer, encoder);
//! // FramedWrite is a sink which means you can send values into it
//! // asynchronously.
//! let mut writer = FramedWrite::new(buffer, encoder);
//!
//! // To be able to send values into a FramedWrite, you need to bring the
//! // `SinkExt` trait into scope.
//! writer.send(messages[0]).await.unwrap();
//! writer.send(messages[1]).await.unwrap();
//! // To be able to send values into a FramedWrite, you need to bring the
//! // `SinkExt` trait into scope.
//! writer.send(messages[0]).await.unwrap();
//! writer.send(messages[1]).await.unwrap();
//!
//! let buffer = writer.get_ref();
//! let buffer = writer.get_ref();
//!
//! assert_eq!(buffer.as_slice(), "Hello\nWorld\n".as_bytes());
//! }
//! assert_eq!(buffer.as_slice(), "Hello\nWorld\n".as_bytes());
//! # }
//!```
//!
//! # Example decoding using `LinesCodec`
@@ -51,25 +51,25 @@
//! use tokio_util::codec::LinesCodec;
//! use tokio_util::codec::FramedRead;
//!
//! #[tokio::main]
//! async fn main() {
//! let message = "Hello\nWorld".as_bytes();
//! let decoder = LinesCodec::new();
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() {
//! let message = "Hello\nWorld".as_bytes();
//! let decoder = LinesCodec::new();
//!
//! // FramedRead can be used to read a stream of values that are framed according to
//! // a codec. FramedRead will read from its input (here `buffer`) until a whole frame
//! // can be parsed.
//! let mut reader = FramedRead::new(message, decoder);
//! // FramedRead can be used to read a stream of values that are framed according to
//! // a codec. FramedRead will read from its input (here `buffer`) until a whole frame
//! // can be parsed.
//! let mut reader = FramedRead::new(message, decoder);
//!
//! // To read values from a FramedRead, you need to bring the
//! // `StreamExt` trait into scope.
//! let frame1 = reader.next().await.unwrap().unwrap();
//! let frame2 = reader.next().await.unwrap().unwrap();
//! // To read values from a FramedRead, you need to bring the
//! // `StreamExt` trait into scope.
//! let frame1 = reader.next().await.unwrap().unwrap();
//! let frame2 = reader.next().await.unwrap().unwrap();
//!
//! assert!(reader.next().await.is_none());
//! assert_eq!(frame1, "Hello");
//! assert_eq!(frame2, "World");
//! }
//! assert!(reader.next().await.is_none());
//! assert_eq!(frame1, "Hello");
//! assert_eq!(frame2, "World");
//! # }
//! ```
//!
//! # The Decoder trait
@@ -313,7 +313,7 @@
//! [`AsyncWrite`]: tokio::io::AsyncWrite
//! [`Stream`]: futures_core::Stream
//! [`Sink`]: futures_sink::Sink
//! [`SinkExt`]: futures::sink::SinkExt
//! [`SinkExt`]: https://docs.rs/futures/0.3/futures/sink/trait.SinkExt.html
//! [`SinkExt::close`]: https://docs.rs/futures/0.3/futures/sink/trait.SinkExt.html#method.close
//! [`FramedRead`]: struct@crate::codec::FramedRead
//! [`FramedWrite`]: struct@crate::codec::FramedWrite
+6
View File
@@ -34,6 +34,8 @@
//! stream via [`compat()`].
//!
//! ```no_run
//! # #[cfg(not(target_family = "wasm"))]
//! # {
//! use tokio::net::{TcpListener, TcpStream};
//! use tokio::io::AsyncWriteExt;
//! use tokio_util::compat::TokioAsyncReadCompatExt;
@@ -58,6 +60,7 @@
//!
//! Ok(())
//! }
//! # }
//! ```
//!
//! ## Example 2: Futures -> Tokio (`AsyncRead`)
@@ -66,6 +69,8 @@
//! adapt it to be used with [`tokio::io::AsyncReadExt::read_to_end`]
//!
//! ```
//! # #[cfg(not(target_family = "wasm"))]
//! # {
//! use futures::io::Cursor;
//! use tokio_util::compat::FuturesAsyncReadCompatExt;
//! use tokio::io::AsyncReadExt;
@@ -82,6 +87,7 @@
//! // Run the future inside a Tokio runtime
//! tokio::runtime::Runtime::new().unwrap().block_on(future);
//! }
//! # }
//! ```
//!
//! ## Common Use Cases
+9
View File
@@ -37,6 +37,8 @@ pin_project! {
/// them. It then uses the context of the runtime with the timer enabled to
/// execute a [`sleep`] future on the runtime with timing disabled.
/// ```
/// # #[cfg(not(target_family = "wasm"))]
/// # {
/// use tokio::time::{sleep, Duration};
/// use tokio_util::context::RuntimeExt;
///
@@ -56,6 +58,7 @@ pin_project! {
///
/// // Execute the future on rt2.
/// rt2.block_on(fut);
/// # }
/// ```
///
/// [`Handle`]: struct@tokio::runtime::Handle
@@ -88,6 +91,8 @@ impl<F> TokioContext<F> {
/// [`RuntimeExt::wrap`]: fn@RuntimeExt::wrap
///
/// ```
/// # #[cfg(not(target_family = "wasm"))]
/// # {
/// use tokio::time::{sleep, Duration};
/// use tokio_util::context::TokioContext;
///
@@ -109,6 +114,7 @@ impl<F> TokioContext<F> {
///
/// // Execute the future on rt2.
/// rt2.block_on(fut);
/// # }
/// ```
pub fn new(future: F, handle: Handle) -> TokioContext<F> {
TokioContext {
@@ -153,6 +159,8 @@ pub trait RuntimeExt {
/// execute a [`sleep`] future on the runtime with timing disabled.
///
/// ```
/// # #[cfg(not(target_family = "wasm"))]
/// # {
/// use tokio::time::{sleep, Duration};
/// use tokio_util::context::RuntimeExt;
///
@@ -172,6 +180,7 @@ pub trait RuntimeExt {
///
/// // Execute the future on rt2.
/// rt2.block_on(fut);
/// # }
/// ```
///
/// [`TokioContext`]: struct@crate::context::TokioContext
+12 -12
View File
@@ -46,18 +46,18 @@ use tokio::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf, Result}
/// # async fn some_async_function() -> u32 { 10 }
/// # async fn other_async_function() -> u32 { 20 }
///
/// #[tokio::main]
/// async fn main() {
/// let result = if some_condition() {
/// Either::Left(some_async_function())
/// } else {
/// Either::Right(other_async_function())
/// };
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let result = if some_condition() {
/// Either::Left(some_async_function())
/// } else {
/// Either::Right(other_async_function())
/// };
///
/// let value = result.await;
/// println!("Result is {}", value);
/// # assert_eq!(value, 10);
/// }
/// let value = result.await;
/// println!("Result is {}", value);
/// # assert_eq!(value, 10);
/// # }
/// ```
#[allow(missing_docs)] // Doc-comments for variants in this particular case don't make much sense.
#[derive(Debug, Clone)]
@@ -212,7 +212,7 @@ where
}
}
#[cfg(test)]
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use tokio::io::{repeat, AsyncReadExt, Repeat};
+137
View File
@@ -0,0 +1,137 @@
//! An extension trait for Futures that provides a variety of convenient adapters.
mod with_cancellation_token;
use with_cancellation_token::{WithCancellationTokenFuture, WithCancellationTokenFutureOwned};
use std::future::Future;
use crate::sync::CancellationToken;
/// A trait which contains a variety of convenient adapters and utilities for `Future`s.
pub trait FutureExt: Future {
cfg_time! {
/// A wrapper around [`tokio::time::timeout`], with the advantage that it is easier to write
/// fluent call chains.
///
/// # Examples
///
/// ```rust
/// use tokio::{sync::oneshot, time::Duration};
/// use tokio_util::future::FutureExt;
///
/// # async fn dox() {
/// let (_tx, rx) = oneshot::channel::<()>();
///
/// let res = rx.timeout(Duration::from_millis(10)).await;
/// assert!(res.is_err());
/// # }
/// ```
#[track_caller]
fn timeout(self, timeout: std::time::Duration) -> tokio::time::Timeout<Self>
where
Self: Sized,
{
tokio::time::timeout(timeout, self)
}
/// A wrapper around [`tokio::time::timeout_at`], with the advantage that it is easier to write
/// fluent call chains.
///
/// # Examples
///
/// ```rust
/// use tokio::{sync::oneshot, time::{Duration, Instant}};
/// use tokio_util::future::FutureExt;
///
/// # async fn dox() {
/// let (_tx, rx) = oneshot::channel::<()>();
/// let deadline = Instant::now() + Duration::from_millis(10);
///
/// let res = rx.timeout_at(deadline).await;
/// assert!(res.is_err());
/// # }
/// ```
fn timeout_at(self, deadline: tokio::time::Instant) -> tokio::time::Timeout<Self>
where
Self: Sized,
{
tokio::time::timeout_at(deadline, self)
}
}
/// Similar to [`CancellationToken::run_until_cancelled`],
/// but with the advantage that it is easier to write fluent call chains.
///
/// # Fairness
///
/// Calling this on an already-cancelled token directly returns `None`.
/// For all subsequent polls, in case of concurrent completion and
/// cancellation, this is biased towards the `self` future completion.
///
/// # Examples
///
/// ```rust
/// use tokio::sync::oneshot;
/// use tokio_util::future::FutureExt;
/// use tokio_util::sync::CancellationToken;
///
/// # async fn dox() {
/// let (_tx, rx) = oneshot::channel::<()>();
/// let token = CancellationToken::new();
/// let token_clone = token.clone();
/// tokio::spawn(async move {
/// tokio::time::sleep(std::time::Duration::from_millis(10)).await;
/// token.cancel();
/// });
/// assert!(rx.with_cancellation_token(&token_clone).await.is_none())
/// # }
/// ```
fn with_cancellation_token(
self,
cancellation_token: &CancellationToken,
) -> WithCancellationTokenFuture<'_, Self>
where
Self: Sized,
{
WithCancellationTokenFuture::new(cancellation_token, self)
}
/// Similar to [`CancellationToken::run_until_cancelled_owned`],
/// but with the advantage that it is easier to write fluent call chains.
///
/// # Fairness
///
/// Calling this on an already-cancelled token directly returns `None`.
/// For all subsequent polls, in case of concurrent completion and
/// cancellation, this is biased towards the `self` future completion.
///
/// # Examples
///
/// ```rust
/// use tokio::sync::oneshot;
/// use tokio_util::future::FutureExt;
/// use tokio_util::sync::CancellationToken;
///
/// # async fn dox() {
/// let (_tx, rx) = oneshot::channel::<()>();
/// let token = CancellationToken::new();
/// let token_clone = token.clone();
/// tokio::spawn(async move {
/// tokio::time::sleep(std::time::Duration::from_millis(10)).await;
/// token.cancel();
/// });
/// assert!(rx.with_cancellation_token_owned(token_clone).await.is_none())
/// # }
/// ```
fn with_cancellation_token_owned(
self,
cancellation_token: CancellationToken,
) -> WithCancellationTokenFutureOwned<Self>
where
Self: Sized,
{
WithCancellationTokenFutureOwned::new(cancellation_token, self)
}
}
impl<T: Future + ?Sized> FutureExt for T {}
@@ -0,0 +1,79 @@
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use crate::sync::{CancellationToken, RunUntilCancelledFuture, RunUntilCancelledFutureOwned};
pin_project! {
/// A [`Future`] that is resolved once the corresponding [`CancellationToken`]
/// is cancelled or a given [`Future`] gets resolved.
///
/// This future is immediately resolved if the corresponding [`CancellationToken`]
/// is already cancelled, otherwise, in case of concurrent completion and
/// cancellation, this is biased towards the future completion.
#[must_use = "futures do nothing unless polled"]
pub struct WithCancellationTokenFuture<'a, F: Future> {
#[pin]
run_until_cancelled: Option<RunUntilCancelledFuture<'a, F>>
}
}
impl<'a, F: Future> WithCancellationTokenFuture<'a, F> {
pub(crate) fn new(cancellation_token: &'a CancellationToken, future: F) -> Self {
Self {
run_until_cancelled: (!cancellation_token.is_cancelled())
.then(|| RunUntilCancelledFuture::new(cancellation_token, future)),
}
}
}
impl<'a, F: Future> Future for WithCancellationTokenFuture<'a, F> {
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
match this.run_until_cancelled.as_pin_mut() {
Some(fut) => fut.poll(cx),
None => Poll::Ready(None),
}
}
}
pin_project! {
/// A [`Future`] that is resolved once the corresponding [`CancellationToken`]
/// is cancelled or a given [`Future`] gets resolved.
///
/// This future is immediately resolved if the corresponding [`CancellationToken`]
/// is already cancelled, otherwise, in case of concurrent completion and
/// cancellation, this is biased towards the future completion.
#[must_use = "futures do nothing unless polled"]
pub struct WithCancellationTokenFutureOwned<F: Future> {
#[pin]
run_until_cancelled: Option<RunUntilCancelledFutureOwned<F>>
}
}
impl<F: Future> WithCancellationTokenFutureOwned<F> {
pub(crate) fn new(cancellation_token: CancellationToken, future: F) -> Self {
Self {
run_until_cancelled: (!cancellation_token.is_cancelled())
.then(|| RunUntilCancelledFutureOwned::new(cancellation_token, future)),
}
}
}
impl<F: Future> Future for WithCancellationTokenFutureOwned<F> {
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
match this.run_until_cancelled.as_pin_mut() {
Some(fut) => fut.poll(cx),
None => Poll::Ready(None),
}
}
}
+3
View File
@@ -14,8 +14,10 @@ mod copy_to_bytes;
mod inspect;
mod read_buf;
mod reader_stream;
pub mod simplex;
mod sink_writer;
mod stream_reader;
mod write_all_vectored;
cfg_io_util! {
mod read_arc;
@@ -31,4 +33,5 @@ pub use self::read_buf::read_buf;
pub use self::reader_stream::ReaderStream;
pub use self::sink_writer::SinkWriter;
pub use self::stream_reader::StreamReader;
pub use self::write_all_vectored::{write_all_vectored, WriteAllVectored};
pub use crate::util::{poll_read_buf, poll_write_buf};
+1 -1
View File
@@ -10,7 +10,7 @@ use tokio::io::{AsyncRead, AsyncReadExt};
/// # Example
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// use tokio_util::io::read_exact_arc;
///
+3 -19
View File
@@ -1,8 +1,7 @@
use bytes::BufMut;
use std::future::Future;
use std::future::poll_fn;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::AsyncRead;
/// Read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
@@ -16,7 +15,7 @@ use tokio::io::AsyncRead;
/// use tokio_stream as stream;
/// use tokio::io::Result;
/// use tokio_util::io::{StreamReader, read_buf};
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
///
/// // Create a reader from an iterator. This particular reader will always be
@@ -46,20 +45,5 @@ where
R: AsyncRead + Unpin,
B: BufMut,
{
return ReadBufFn(read, buf).await;
struct ReadBufFn<'a, R, B>(&'a mut R, &'a mut B);
impl<'a, R, B> Future for ReadBufFn<'a, R, B>
where
R: AsyncRead + Unpin,
B: BufMut,
{
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = &mut *self;
crate::util::poll_read_buf(Pin::new(this.0), cx, this.1)
}
}
poll_fn(|cx| crate::util::poll_read_buf(Pin::new(read), cx, buf)).await
}
+6 -1
View File
@@ -16,7 +16,7 @@ pin_project! {
/// # Example
///
/// ```
/// # #[tokio::main]
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
/// use tokio_stream::StreamExt;
/// use tokio_util::io::ReaderStream;
@@ -58,6 +58,11 @@ impl<R: AsyncRead> ReaderStream<R> {
/// Convert an [`AsyncRead`] into a [`Stream`] with item type
/// `Result<Bytes, std::io::Error>`.
///
/// Currently, the default capacity 4096 bytes (4 KiB).
/// This capacity is not part of the semver contract
/// and may be tweaked in future releases without
/// requiring a major version bump.
///
/// [`AsyncRead`]: tokio::io::AsyncRead
/// [`Stream`]: futures_core::Stream
pub fn new(reader: R) -> Self {
+360
View File
@@ -0,0 +1,360 @@
//! Unidirectional byte-oriented channel.
use crate::util::poll_proceed;
use bytes::Buf;
use bytes::BytesMut;
use futures_core::ready;
use std::io::Error as IoError;
use std::io::ErrorKind as IoErrorKind;
use std::io::IoSlice;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
type IoResult<T> = Result<T, IoError>;
const CLOSED_ERROR_MSG: &str = "simplex has been closed";
#[derive(Debug)]
struct Inner {
/// `poll_write` will return [`Poll::Pending`] if the backpressure boundary is reached
backpressure_boundary: usize,
/// either [`Sender`] or [`Receiver`] is closed
is_closed: bool,
/// Waker used to wake the [`Receiver`]
receiver_waker: Option<Waker>,
/// Waker used to wake the [`Sender`]
sender_waker: Option<Waker>,
/// Buffer used to read and write data
buf: BytesMut,
}
impl Inner {
fn with_capacity(capacity: usize) -> Self {
Self {
backpressure_boundary: capacity,
is_closed: false,
receiver_waker: None,
sender_waker: None,
buf: BytesMut::with_capacity(capacity),
}
}
fn register_receiver_waker(&mut self, waker: &Waker) -> Option<Waker> {
match self.receiver_waker.as_mut() {
Some(old) if old.will_wake(waker) => None,
_ => self.receiver_waker.replace(waker.clone()),
}
}
fn register_sender_waker(&mut self, waker: &Waker) -> Option<Waker> {
match self.sender_waker.as_mut() {
Some(old) if old.will_wake(waker) => None,
_ => self.sender_waker.replace(waker.clone()),
}
}
fn take_receiver_waker(&mut self) -> Option<Waker> {
self.receiver_waker.take()
}
fn take_sender_waker(&mut self) -> Option<Waker> {
self.sender_waker.take()
}
fn is_closed(&self) -> bool {
self.is_closed
}
fn close_receiver(&mut self) -> Option<Waker> {
self.is_closed = true;
self.take_sender_waker()
}
fn close_sender(&mut self) -> Option<Waker> {
self.is_closed = true;
self.take_receiver_waker()
}
}
/// Receiver of the simplex channel.
///
/// # Cancellation safety
///
/// The `Receiver` is cancel safe. If it is used as the event in a
/// [`tokio::select!`] statement and some other branch completes
/// first, it is guaranteed that no bytes were received on this
/// channel.
///
/// You can still read the remaining data from the buffer
/// even if the write half has been dropped.
/// See [`Sender::poll_shutdown`] and [`Sender::drop`] for more details.
///
/// [`tokio::select!`]: https://docs.rs/tokio/latest/tokio/macro.select.html
#[derive(Debug)]
pub struct Receiver {
inner: Arc<Mutex<Inner>>,
}
impl Drop for Receiver {
/// This also wakes up the [`Sender`].
fn drop(&mut self) {
let maybe_waker = {
let mut inner = self.inner.lock().unwrap();
inner.close_receiver()
};
if let Some(waker) = maybe_waker {
waker.wake();
}
}
}
impl AsyncRead for Receiver {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<IoResult<()>> {
let coop = ready!(poll_proceed(cx));
let mut inner = self.inner.lock().unwrap();
let to_read = buf.remaining().min(inner.buf.remaining());
if to_read == 0 {
if inner.is_closed() || buf.remaining() == 0 {
return Poll::Ready(Ok(()));
}
let old_waker = inner.register_receiver_waker(cx.waker());
let maybe_waker = inner.take_sender_waker();
// unlock before waking up and dropping old waker
drop(inner);
drop(old_waker);
if let Some(waker) = maybe_waker {
waker.wake();
}
return Poll::Pending;
}
// this is to avoid starving other tasks
coop.made_progress();
buf.put_slice(&inner.buf[..to_read]);
inner.buf.advance(to_read);
let waker = inner.take_sender_waker();
drop(inner); // unlock before waking up
if let Some(waker) = waker {
waker.wake();
}
Poll::Ready(Ok(()))
}
}
/// Sender of the simplex channel.
///
/// # Cancellation safety
///
/// The `Sender` is cancel safe. If it is used as the event in a
/// [`tokio::select!`] statement and some other branch completes
/// first, it is guaranteed that no bytes were sent on this channel.
///
/// # Shutdown
///
/// See [`Sender::poll_shutdown`].
///
/// [`tokio::select!`]: https://docs.rs/tokio/latest/tokio/macro.select.html
#[derive(Debug)]
pub struct Sender {
inner: Arc<Mutex<Inner>>,
}
impl Drop for Sender {
/// This also wakes up the [`Receiver`].
fn drop(&mut self) {
let maybe_waker = {
let mut inner = self.inner.lock().unwrap();
inner.close_sender()
};
if let Some(waker) = maybe_waker {
waker.wake();
}
}
}
impl AsyncWrite for Sender {
/// # Errors
///
/// This method will return [`IoErrorKind::BrokenPipe`]
/// if the channel has been closed.
fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<IoResult<usize>> {
let coop = ready!(poll_proceed(cx));
let mut inner = self.inner.lock().unwrap();
if inner.is_closed() {
return Poll::Ready(Err(IoError::new(IoErrorKind::BrokenPipe, CLOSED_ERROR_MSG)));
}
let free = inner
.backpressure_boundary
.checked_sub(inner.buf.len())
.expect("backpressure boundary overflow");
let to_write = buf.len().min(free);
if to_write == 0 {
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
let old_waker = inner.register_sender_waker(cx.waker());
let waker = inner.take_receiver_waker();
// unlock before waking up and dropping old waker
drop(inner);
drop(old_waker);
if let Some(waker) = waker {
waker.wake();
}
return Poll::Pending;
}
// this is to avoid starving other tasks
coop.made_progress();
inner.buf.extend_from_slice(&buf[..to_write]);
let waker = inner.take_receiver_waker();
drop(inner); // unlock before waking up
if let Some(waker) = waker {
waker.wake();
}
Poll::Ready(Ok(to_write))
}
/// # Errors
///
/// This method will return [`IoErrorKind::BrokenPipe`]
/// if the channel has been closed.
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<IoResult<()>> {
let inner = self.inner.lock().unwrap();
if inner.is_closed() {
Poll::Ready(Err(IoError::new(IoErrorKind::BrokenPipe, CLOSED_ERROR_MSG)))
} else {
Poll::Ready(Ok(()))
}
}
/// After returns [`Poll::Ready`], all the following call to
/// [`Sender::poll_write`] and [`Sender::poll_flush`]
/// will return error.
///
/// The [`Receiver`] can still be used to read remaining data
/// until all bytes have been consumed.
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<IoResult<()>> {
let maybe_waker = {
let mut inner = self.inner.lock().unwrap();
inner.close_sender()
};
if let Some(waker) = maybe_waker {
waker.wake();
}
Poll::Ready(Ok(()))
}
fn is_write_vectored(&self) -> bool {
true
}
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<Result<usize, IoError>> {
let coop = ready!(poll_proceed(cx));
let mut inner = self.inner.lock().unwrap();
if inner.is_closed() {
return Poll::Ready(Err(IoError::new(IoErrorKind::BrokenPipe, CLOSED_ERROR_MSG)));
}
let free = inner
.backpressure_boundary
.checked_sub(inner.buf.len())
.expect("backpressure boundary overflow");
if free == 0 {
let old_waker = inner.register_sender_waker(cx.waker());
let maybe_waker = inner.take_receiver_waker();
// unlock before waking up and dropping old waker
drop(inner);
drop(old_waker);
if let Some(waker) = maybe_waker {
waker.wake();
}
return Poll::Pending;
}
// this is to avoid starving other tasks
coop.made_progress();
let mut rem = free;
for buf in bufs {
if rem == 0 {
break;
}
let to_write = buf.len().min(rem);
if to_write == 0 {
assert_ne!(rem, 0);
assert_eq!(buf.len(), 0);
continue;
}
inner.buf.extend_from_slice(&buf[..to_write]);
rem -= to_write;
}
let waker = inner.take_receiver_waker();
drop(inner); // unlock before waking up
if let Some(waker) = waker {
waker.wake();
}
Poll::Ready(Ok(free - rem))
}
}
/// Create a simplex channel.
///
/// The `capacity` parameter specifies the maximum number of bytes that can be
/// stored in the channel without making the [`Sender::poll_write`]
/// return [`Poll::Pending`].
///
/// # Panics
///
/// This function will panic if `capacity` is zero.
pub fn new(capacity: usize) -> (Sender, Receiver) {
assert_ne!(capacity, 0, "capacity must be greater than zero");
let inner = Arc::new(Mutex::new(Inner::with_capacity(capacity)));
let tx = Sender {
inner: Arc::clone(&inner),
};
let rx = Receiver { inner };
(tx, rx)
}
+21 -18
View File
@@ -57,15 +57,15 @@ use tokio::io::{
/// let hash = blake3::hash(&data);
///
/// Ok(hash)
///}
///
/// #[tokio::main]
/// async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// hash_contents(reader).await
/// }
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// hash_contents(reader).await
/// # }
/// ```
///
/// When the data doesn't fit into memory, the hashing library will usually
@@ -88,7 +88,7 @@ use tokio::io::{
/// /// and hashes the data incrementally.
/// async fn hash_stream(mut reader: impl AsyncRead + Unpin, mut hasher: Hasher) -> Result<(), std::io::Error> {
/// // Create a buffer to read data into, sized for performance.
/// let mut data = vec![0; 64 * 1024];
/// let mut data = vec![0; 16 * 1024];
/// loop {
/// // Read data from the reader into the buffer.
/// let len = reader.read(&mut data).await?;
@@ -102,16 +102,16 @@ use tokio::io::{
/// let hash = hasher.finalize();
///
/// Ok(hash)
///}
///
/// #[tokio::main]
/// async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// let hasher = Hasher;
/// hash_stream(reader, hasher).await
/// }
///
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<(), std::io::Error> {
/// // Example: In-memory data.
/// let data = b"Hello, world!"; // A byte slice.
/// let reader = Cursor::new(data); // Create an in-memory AsyncRead.
/// let hasher = Hasher;
/// hash_stream(reader, hasher).await
/// # }
/// ```
///
///
@@ -218,6 +218,8 @@ use tokio::io::{
/// thread pool, preventing it from interfering with the async tasks.
///
/// ```rust
/// # #[cfg(not(target_family = "wasm"))]
/// # {
/// use tokio::task::spawn_blocking;
/// use tokio_util::io::SyncIoBridge;
/// use tokio::io::AsyncRead;
@@ -255,6 +257,7 @@ use tokio::io::{
///
/// Ok(())
/// }
/// # }
/// ```
///
#[derive(Debug)]
+165
View File
@@ -0,0 +1,165 @@
use tokio::io::AsyncWrite;
use pin_project_lite::pin_project;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
use std::{future::Future, io::IoSlice};
use std::{io, mem};
pin_project! {
/// A future that writes all data from multiple buffers to a writer.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct WriteAllVectored<'a, 'b, W: ?Sized> {
writer: &'a mut W,
bufs: &'a mut [IoSlice<'b>],
// Make this future `!Unpin` for compatibility with async trait methods.
#[pin]
_pin: PhantomPinned,
}
}
/// Like [`write_all`] but writes all data from multiple buffers into this writer.
///
/// This function writes multiple (possibly non-contiguous) buffers into the writer,
/// using the `writev` syscall to potentially write in a single system call.
///
/// Equivalent to:
///
/// ```ignore
/// async fn write_all_vectored<W: AsyncWrite + Unpin + ?Sized>(
/// writer: &mut W,
/// mut bufs: &mut [IoSlice<'_>]
/// ) -> io::Result<()> {
/// while !bufs.is_empty() {
/// let n = write_vectored(writer, bufs).await?;
/// if n == 0 {
/// return Err(io::ErrorKind::WriteZero.into());
/// }
/// IoSlice::advance_slices(&mut bufs, n);
/// }
/// Ok(())
/// }
/// ```
///
/// # Cancel safety
///
/// This method is not cancellation safe. If it is used as the event
/// in a `tokio::select!` statement and some other
/// branch completes first, then the provided buffer may have been
/// partially written, but future calls to `write_all_vectored` will
/// have lost its place in the buffer.
///
/// # Examples
///
/// ```rust
/// use tokio_util::io::write_all_vectored;
/// use std::io::IoSlice;
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> std::io::Result<()> {
///
/// let mut writer = Vec::new();
/// let bufs = &mut [
/// IoSlice::new(&[1]),
/// IoSlice::new(&[2, 3]),
/// IoSlice::new(&[4, 5, 6]),
/// ];
///
/// write_all_vectored(&mut writer, bufs).await?;
///
/// // Note: `bufs` has been modified by `IoSlice::advance_slices` and should not be reused.
/// assert_eq!(writer, &[1, 2, 3, 4, 5, 6]);
/// Ok(())
/// }
/// ```
///
/// # Notes
///
/// See the documentation for [`Write::write_all_vectored`] from std.
/// After calling this function, the buffer slices may have
/// been advanced and should not be reused.
///
/// [`Write::write_all_vectored`]: std::io::Write::write_all_vectored
/// [`write_all`]: tokio::io::AsyncWriteExt::write_all
/// [`writev`]: https://man7.org/linux/man-pages/man3/writev.3p.html
pub fn write_all_vectored<'a, 'b, W>(
writer: &'a mut W,
bufs: &'a mut [IoSlice<'b>],
) -> WriteAllVectored<'a, 'b, W>
where
W: AsyncWrite + Unpin + ?Sized,
{
WriteAllVectored {
writer,
bufs,
_pin: PhantomPinned,
}
}
impl<W> Future for WriteAllVectored<'_, '_, W>
where
W: AsyncWrite + Unpin + ?Sized,
{
type Output = io::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let me = self.project();
while !me.bufs.is_empty() {
// advance to first non-empty buffer
let non_empty = match me.bufs.iter().position(|b| !b.is_empty()) {
Some(pos) => pos,
None => return Poll::Ready(Ok(())),
};
// drop empty buffers at the start
*me.bufs = &mut mem::take(me.bufs)[non_empty..];
let n = ready!(Pin::new(&mut *me.writer).poll_write_vectored(cx, me.bufs))?;
if n == 0 {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
self::advance_slices(me.bufs, n);
}
Poll::Ready(Ok(()))
}
}
// copied from `std::IoSlice::advance_slices`
// replace with method when MSRV is 1.81.0
fn advance_slices<'a>(bufs: &mut &mut [IoSlice<'a>], n: usize) {
// Number of buffers to remove.
let mut remove = 0;
// Remaining length before reaching n. This prevents overflow
// that could happen if the length of slices in `bufs` were instead
// accumulated. Those slice may be aliased and, if they are large
// enough, their added length may overflow a `usize`.
let mut left = n;
for buf in bufs.iter() {
if let Some(remainder) = left.checked_sub(buf.len()) {
left = remainder;
remove += 1;
} else {
break;
}
}
*bufs = &mut std::mem::take(bufs)[remove..];
if let Some(first) = bufs.first_mut() {
let buf = &first[..left];
// necessary due to limitating in the borrow checker,
// when tokio MSRV reaches 1.81.0 this entire function
// can be replaced with `IoSlice::advance_slices`
//
// SAFETY: transmute a sub-slice of an IoSlice<'a> back to
// the lifetime `'a`. This is safe because the underlying memory
// is guaranteed to live for 'a, we have shared access, and no
// underlying data is reinterpreted to a different type.
unsafe {
*first = IoSlice::new(std::mem::transmute::<&[u8], &'a [u8]>(buf));
}
} else {
assert!(left == 0, "advancing io slices beyond their length");
}
}
+5 -1
View File
@@ -45,9 +45,11 @@ cfg_io! {
cfg_rt! {
pub mod context;
pub mod task;
}
#[cfg(feature = "rt")]
pub mod task;
cfg_time! {
pub mod time;
}
@@ -59,3 +61,5 @@ pub mod either;
pub use bytes;
mod util;
pub mod future;
+9 -1
View File
@@ -1 +1,9 @@
pub(crate) use std::sync;
//! This module abstracts over `loom` and `std::sync` types depending on whether we
//! are running loom tests or not.
pub(crate) mod sync {
#[cfg(all(test, loom))]
pub(crate) use loom::sync::{Arc, Mutex, MutexGuard};
#[cfg(not(all(test, loom)))]
pub(crate) use std::sync::{Arc, Mutex, MutexGuard};
}
+2
View File
@@ -1,3 +1,5 @@
#![cfg(not(loom))]
//! TCP/UDP/Unix helpers for tokio.
use crate::either::Either;
+74 -28
View File
@@ -281,34 +281,6 @@ impl CancellationToken {
where
F: Future,
{
pin_project! {
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// is cancelled or a given Future gets resolved. It is biased towards the
/// Future completion.
#[must_use = "futures do nothing unless polled"]
struct RunUntilCancelledFuture<'a, F: Future> {
#[pin]
cancellation: WaitForCancellationFuture<'a>,
#[pin]
future: F,
}
}
impl<'a, F: Future> Future for RunUntilCancelledFuture<'a, F> {
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Poll::Ready(res) = this.future.poll(cx) {
Poll::Ready(Some(res))
} else if this.cancellation.poll(cx).is_ready() {
Poll::Ready(None)
} else {
Poll::Pending
}
}
}
if self.is_cancelled() {
None
} else {
@@ -439,3 +411,77 @@ impl Future for WaitForCancellationFutureOwned {
}
}
}
pin_project! {
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// is cancelled or a given Future gets resolved. It is biased towards the
/// Future completion.
#[must_use = "futures do nothing unless polled"]
pub(crate) struct RunUntilCancelledFuture<'a, F: Future> {
#[pin]
cancellation: WaitForCancellationFuture<'a>,
#[pin]
future: F,
}
}
impl<'a, F: Future> RunUntilCancelledFuture<'a, F> {
pub(crate) fn new(cancellation_token: &'a CancellationToken, future: F) -> Self {
Self {
cancellation: cancellation_token.cancelled(),
future,
}
}
}
impl<'a, F: Future> Future for RunUntilCancelledFuture<'a, F> {
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Poll::Ready(res) = this.future.poll(cx) {
Poll::Ready(Some(res))
} else if this.cancellation.poll(cx).is_ready() {
Poll::Ready(None)
} else {
Poll::Pending
}
}
}
pin_project! {
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// is cancelled or a given Future gets resolved. It is biased towards the
/// Future completion.
#[must_use = "futures do nothing unless polled"]
pub(crate) struct RunUntilCancelledFutureOwned<F: Future> {
#[pin]
cancellation: WaitForCancellationFutureOwned,
#[pin]
future: F,
}
}
impl<F: Future> Future for RunUntilCancelledFutureOwned<F> {
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if let Poll::Ready(res) = this.future.poll(cx) {
Poll::Ready(Some(res))
} else if this.cancellation.poll(cx).is_ready() {
Poll::Ready(None)
} else {
Poll::Pending
}
}
}
impl<F: Future> RunUntilCancelledFutureOwned<F> {
pub(crate) fn new(cancellation_token: CancellationToken, future: F) -> Self {
Self {
cancellation: cancellation_token.cancelled_owned(),
future,
}
}
}
+4
View File
@@ -5,6 +5,7 @@ pub use cancellation_token::{
guard::DropGuard, guard_ref::DropGuardRef, CancellationToken, WaitForCancellationFuture,
WaitForCancellationFutureOwned,
};
pub(crate) use cancellation_token::{RunUntilCancelledFuture, RunUntilCancelledFutureOwned};
mod mpsc;
pub use mpsc::{PollSendError, PollSender};
@@ -14,3 +15,6 @@ pub use poll_semaphore::PollSemaphore;
mod reusable_box;
pub use reusable_box::ReusableBoxFuture;
#[cfg(test)]
mod tests;

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