Compare commits

...
Author SHA1 Message Date
Alice Ryhl 9c337ca1a3 chore: prepare Tokio v1.37.0 (#6435) 2024-03-28 17:34:42 +01:00
M.Amin Rayej e542501439 io: document cancel safety of AsyncBufReadExt::fill_buf (#6431) 2024-03-26 20:45:15 +03:30
M.Amin Rayej 4601c84718 stream: add next_many and poll_next_many to StreamMap (#6409) 2024-03-26 18:38:53 +03:30
M.Amin Rayej deff2524c3 util: document cancel safety of SinkExt::send and StreamExt::next (#6417) 2024-03-25 01:53:01 +03:30
Matthieu Le brazidec (r3v2d0g) 4565b81097 sync: add a rwlock() method to owned RwLock guards (#6418) 2024-03-25 00:37:05 +09:00
Ilson Balliego 3ce4720a45 sync: add is_closed, is_empty, and len to mpsc receivers (#6348)
Fixes: #4638
2024-03-24 14:46:02 +01:00
M.Amin Rayej 8342e4b524 util: assert compatibility between LengthDelimitedCodec options (#6414) 2024-03-24 01:12:24 +03:30
Motoyuki Kimura 4c453e9790 readme: add description about benchmarks (#6425) 2024-03-23 18:11:41 +03:30
M.Amin Rayej 1846483f19 sync: expose strong and weak counts of mpsc sender handles (#6405) 2024-03-22 23:25:33 +03:30
Christopher Acosta baad270b98 sync: add Semaphore example for limiting the number of outgoing requests (#6419) 2024-03-22 21:33:28 +03:30
Motoyuki Kimura f9d78fbe92 fs: add set_max_buf_size to tokio::fs::File (#6411) 2024-03-22 17:28:29 +03:30
M.Amin Rayej bb25a06f34 chore: fix dead code warnings (#6423) 2024-03-22 13:44:18 +03:30
M.Amin Rayej d51f16855b runtime: panic if unhandled_panic is enabled when not supported (#6410) 2024-03-20 09:48:09 +01:00
M.Amin Rayej 5baa8d58ec sync: remove stale comment (#6406) 2024-03-17 19:29:10 +03:30
M.Amin Rayej bd51feac47 readme: fix running loom tests guide (#6408) 2024-03-17 14:57:31 +03:30
M.Amin Rayej b2896feb5b io: use Mutex instead of spinlock (#6403) 2024-03-16 12:36:20 +03:30
Alice Ryhl 3d0d0fd2af chore: prepare tokio-test v0.4.4 (#6400) 2024-03-14 22:10:15 +01:00
Alice Ryhl 7cfb100796 chore: prepare tokio-stream v0.1.15 (#6401) 2024-03-14 22:10:05 +01:00
M.Amin Rayej e37bd63854 io: implement try_new and try_with_interest for AsyncFd (#6345) 2024-03-14 22:48:26 +09:00
Vincent Palancher c9e75785c8 sync: remove 'static bound on impl Sink for PollSender (#6397)
In PR #5665, the `'static` bound has been removed on values sent into
`PollSender`. One of this bound was remaining on the `PollSender`
implementation of `Sink`. This patch removes it and adds some tests on
the `Sink` interface for `PollSender`.
2024-03-14 10:23:59 +01:00
kim / Motoyuki Kimura 3141ed6228 sync: update watch channel docs (#6395) 2024-03-13 17:24:19 +01:00
Alice Ryhl ea1cfbdb97 sync: reorder const_new before new_with (#6392) 2024-03-12 14:22:25 +00:00
Alice Ryhl fb2dc97468 readme: update commit message guidelines (#6393) 2024-03-12 14:21:08 +00:00
kim / Motoyuki Kimura a3d2548789 sync: implement Clone for watch::Sender (#6388) 2024-03-12 10:25:43 +01:00
Yotam Ofek b4ab6472de signal: fix typo in argument name (#6389) 2024-03-10 16:24:07 +01:00
M.Amin Rayej 3133af42e1 runtime: make the enter example deterministic (#6351) 2024-03-10 11:37:18 +01:00
Patrick McGleenon 1f924f95f1 chore: fix deprecated circleci image (#6379) 2024-03-06 00:23:38 +09:00
kim / Motoyuki Kimura f6d061919f test: mark Spawn as #[must_use] (#6371) 2024-03-04 16:56:40 +01:00
Lev Kokotov f5ca423bf1 sync: fix missing period in broadcast docs (#6377) 2024-03-04 08:40:03 +00:00
Alice Ryhl e0d33c4a20 tokio: mark 1.36 as an LTS release (#6375) 2024-03-03 20:44:38 +00:00
Alice Ryhl 3125279697 ci: update FreeBSD image to 14 (#6376) 2024-03-03 20:12:58 +00:00
Kevin Reid 5658d7c503 runtime: add doc link from Runtime to #[tokio::main] (#6366) 2024-02-25 22:17:26 +01:00
Patrick McGleenon a2096049ee ci: downgrade to QEMU 7.2 for cross-compile builds (#6361) (#6363) 2024-02-23 12:08:57 +09:00
kim / Motoyuki Kimura 94db07b379 task: add TaskLocalFuture::take_value (#6340) 2024-02-21 11:23:54 +01:00
qj 099ee23b65 docs: fix docsrs build without net feature (#6360) 2024-02-19 11:37:09 +00:00
M.Amin Rayej b32826bc93 runtime: fix leaking registration entries when os registration fails (#6329) 2024-02-15 03:13:08 +03:30
Gil Shoshan 2ce1cee0f9 macros: allow select with only else branch (#6339) 2024-02-13 09:28:16 +01:00
M.Amin Rayej 84e41d4aff io: document cancel safety of AsyncReadExt's primitive read functions (#6337) 2024-02-11 20:45:13 +03:30
Val Lorentz db6929ad62 task: fix documentation links (#6336) 2024-02-11 15:08:01 +01:00
M.Amin Rayej e25d661e08 util: add examples for FramedRead and FramedWrite (#6310) 2024-02-11 02:38:44 +03:30
Patrick McGleenonandRafael Bachmann e392c4ff1e chore: update CI to clippy 1.76 (#6334)
Co-authored-by: Rafael Bachmann <[email protected]>
2024-02-10 10:45:40 +01:00
kim / Motoyuki Kimura 0fbde0e94b sync: add forget_permits method to semaphore (#6331) 2024-02-08 17:40:55 +03:30
Jack Wrenn 10c9eeb6c2 runtime: include task Id in taskdumps (#6328)
Task `Id`s provide a semi-stable identifier for monitoring task
state across task dumps.

Fixes #6313
2024-02-05 22:53:20 +01:00
Alice Ryhl 47a5fe3a12 metrics: fix worker_steal_count test hanging (#6327) 2024-02-05 15:59:04 +00:00
Alice Ryhl fbdf539ac2 sync: make downgrade must_use (#6326) 2024-02-05 08:46:04 +01:00
Alice Ryhl 63caced26f tokio: update CHANGELOG.md (#6324) 2024-02-03 21:57:58 +01:00
erikdesjardins 0b31b2a195 task: implement FromIterator for JoinSet (#6300) 2024-02-02 13:30:49 +01:00
Augusto Hack cb197def68 docs: transition_to_idle doesn't return boolean (#6320) 2024-02-02 13:29:35 +01:00
M.Amin Rayej eaf81ed324 chore: prepare Tokio v1.36.0 (#6312) 2024-02-02 11:13:46 +00:00
M.Amin Rayej 53f9e5a357 ci: make sure dictionary words are sorted and unique (#6316) 2024-01-31 16:11:54 +03:30
M.Amin Rayej 9077762545 net: expose keepalive option on TcpSocket (#6311) 2024-01-30 21:46:37 +03:30
Owen Leung 131e7b4e49 ci: add spellchecking (#6297) 2024-01-29 10:53:43 +01:00
M.Amin Rayej e53b92a993 io: clarify clear_ready docs (#6304) 2024-01-27 20:30:15 +00:00
Sergei Fomin 7536132065 sync: use AtomicBool in broadcast channel future (#6298) 2024-01-27 18:52:55 +00:00
JungChul Shin b6d0c9091d macros: fix trait_method breaking change detection (#6308) 2024-01-26 14:51:44 +01:00
kim / Motoyuki Kimura 4846959e8a runtime: remove obsolete comment (#6303) 2024-01-24 16:22:28 +01:00
M.Amin Rayej ec3038357f net: add UnixSocket (#6290) 2024-01-22 11:35:02 +01:00
Daniel Sedlak f80bbec28f io: simplify check for empty slice (#6293) 2024-01-18 10:59:22 +00:00
M.Amin Rayej eab26a662c net: document that *Fd traits on TcpSocket are unix-only (#6294) 2024-01-18 10:35:46 +01:00
M.Amin Rayej 58edfc61ab ci: verify that tests work with panic=abort (#6283) 2024-01-16 16:10:23 +01:00
Evan Rittenhouse bfd7b08067 time: add FutureExt::timeout (#6276) 2024-01-16 09:51:38 +01:00
M.Amin Rayej 12ce924fb9 task: add JoinSet::try_join_next (#6280) 2024-01-14 10:34:30 +00:00
Jiahao XU e4f9bcb577 process: use pidfd on Linux when available (#6152)
Signed-off-by: Jiahao XU <[email protected]>
2024-01-11 13:58:23 +01:00
zixuan zhao 8463af92af sync: document FIFO behavior of tokio::sync::Mutex (#6279)
Signed-off-by: azuredream <[email protected]>
2024-01-11 11:15:57 +01:00
Taiki Endo 84c5674c60 ci: update clippy version to 1.75 (#6273) 2024-01-07 02:33:37 +09:00
Rustin 3275cfb638 io: make copy cooperative (#6265) 2024-01-06 17:22:26 +01:00
Taiki Endo 9780bf491f ci: update cargo-check-external-types to 0.1.10 (#6274) 2024-01-07 00:53:08 +09:00
Taiki Endo d6ba535ceb ci: update actions/checkout action to v4 (#6272) 2024-01-06 20:30:28 +09:00
Alan Somers e929d0e8b9 tests: update mio-aio to 0.8 (#6269)
This is a test-only dependency.  The main reason for the update is to
avoid transitively depending on a Nix version with a CVE.

mio-aio 0.8.0 has a substantially different API than 0.7.0.  Notably, it
no longer includes any lio_listio functionality.  So to test Tokio's
handling of EVFILT_LIO events we must go low-level and call
libc::lio_listio directly.
2024-01-06 11:14:32 +00:00
Alan Somers 2f730d4d5a ci: update FreeBSD CI environment to 13.2 (#6270)
Because 13.1 is EoL
2024-01-06 11:59:04 +01:00
Alice Ryhl c7e7f203ee chore: typographic improvements (#6262) 2024-01-03 18:01:27 +00:00
Théodore Prévot 7c606ab44a sync: add Sender::{try_,}reserve_many (#6205) 2024-01-02 16:34:56 +00:00
Alice Ryhl 2d2faf6014 rt: improve robustness of wake_in_drop_after_panic test (#6238) 2024-01-02 17:25:51 +01:00
Jesse Schalken c029771247 task: fix typo (#6261) 2024-01-02 12:08:11 +01:00
Owen Leung 7341004535 sync: add {Receiver,UnboundedReceiver}::poll_recv_many (#6236) 2024-01-01 14:26:58 +01:00
Paul Olteanu 02b779e315 sync: add watch::Receiver::mark_unchanged (#6252) 2023-12-30 20:05:37 +00:00
Tymoteusz Wiśniewski 48345d6e48 net: add support for anonymous unix pipes (#6127) 2023-12-30 15:37:47 +00:00
kim / Motoyuki Kimura 581cd41d79 io: make repeat and sink cooperative (#6254) 2023-12-30 15:26:16 +01:00
kim / Motoyuki Kimura 5f7fe8fd0d ci: fix new warnings on 1.75.0 (#6255) 2023-12-29 17:22:54 +01:00
Alice Ryhl 52f28dcb4f benches: fix benchmarking conflicts for real this time (#6246) 2023-12-28 09:52:18 +00:00
Alice Ryhl d51308b035 ci: fix configured clippy msrv (#6244) 2023-12-23 00:28:14 +00:00
Alice Ryhl 7cae89af47 benches: fix benchmarking conflicts (#6243) 2023-12-22 22:43:05 +00:00
Eddy Oyieko 53ad102cf0 util: implement Sink for Either (#6239) 2023-12-22 18:23:04 +01:00
Miccah e7214e3670 io: implement AsyncWrite for Empty (#6235) 2023-12-20 00:05:53 +00:00
Alice Ryhl 433545782e tests: fix name of coop_budget.rs (#6234) 2023-12-19 19:02:02 +00:00
snek bd15bace35 io: add tokio::io::Join (#6220) 2023-12-19 19:51:48 +01:00
Alice Ryhl 410660fd54 Merge 'tokio-1.35.1' into 'master' (#6231) 2023-12-19 18:15:46 +01:00
Alice Ryhl 46ff36386d chore: prepare Tokio v1.35.1 (#6230) 2023-12-19 18:14:34 +01:00
Alice Ryhl 0a1695ebfb Merge 'tokio-1.32.1' into 'tokio-1.35.x' (#6229) 2023-12-19 18:11:16 +01:00
Alice Ryhl c4f0178b74 chore: prepare Tokio v1.32.1 (#6228) 2023-12-19 18:07:05 +01:00
Alice Ryhl 22b3a65934 Merge 'tokio-1.25.3' into 'tokio-1.32.x' (#6227) 2023-12-19 18:00:41 +01:00
Noah Kennedy 0d362339d5 chore: release v1.25.3 (#6223)
# 1.25.3 (December 17thm 2023)

### Fixed
- io: add budgeting to `tokio::runtime::io::registration::async_io` ([#6221])

[#6221]: https://github.com/tokio-rs/tokio/pull/6221
2023-12-19 17:09:44 +01:00
Noah Kennedy ab7313ff6b io: add budgeting to tokio::runtime::io::registration::async_io (#6221)
Fixes #5946.
Fixes #4782.

This change adds budgeting to most of the remaining unbudgeted IO operations which can complete instantly, including datagram send/recv operations and listener socket accepts.

This is particularly significant for scenarios in which resource limits are hit, as it can be common for things like listener tasks to spin when receiving errors and just log them, busy looping worker threads which might otherwise be handling existing connections and closing them.

This can also sometimes lead to complex failure scenarios within datagram systems experiencing resource exhaustion.
2023-12-17 22:34:51 +00:00
oliverandkwfn 4aa7bbff4c chore: typo fixes (#6213)
Co-authored-by: kwfn <[email protected]>
2023-12-11 19:22:55 +00:00
Bryan Baron c06a55088b coop: fix typo in docs for task::unconstrained (#6212) 2023-12-11 17:50:24 +01:00
Alice Ryhl 92a3455c66 chore: prepare Tokio v1.35.0 (#6197) 2023-12-08 23:35:27 +01:00
Alice Ryhl 1968565825 chore: use relaxed load for unsync_load (#6203) 2023-12-08 22:28:40 +00:00
Alice Ryhl c9273f1aee sync: improve safety comments for WakeList (#6200) 2023-12-08 18:57:01 +01:00
Alice Ryhl e05d0f8c2c changelog: fix missing link for 1.8.2 (#6199) 2023-12-08 17:51:46 +00:00
Alice Ryhl debcb2254a Revert "net: add SocketAddr::as_abstract_namespace (#6144)" (#6198)
This reverts commit 2400769b54.
2023-12-08 18:44:32 +01:00
tijsvd 83b7397e44 io: drop the Sized requirements from AsyncReadExt.read_buf (#6169) 2023-12-08 09:51:08 +01:00
NAHO 3991f9f9a4 docs: fix typo in 'tokio/src/sync/broadcast.rs' (#6182) 2023-12-08 09:48:36 +01:00
Alice Ryhl 48c0e6283f chore: use relaxed load for unsync_load on miri (#6179) 2023-12-08 09:48:15 +01:00
Jack Wrenn d561b5850a taskdump: skip notified tasks during taskdumps (#6194)
Fixes #6051
2023-12-08 09:47:46 +01:00
Weijia Jiang 3a4aef17b2 runtime: reduce the lock contention in task spawn (#6001) 2023-12-07 10:45:07 +00:00
Andrew Walbran a0a58d7edd tokio: update nix dependency to 0.27.1 (#6190) 2023-12-04 16:09:58 +01:00
fzyzcjy ed4f766c98 runtime: make Runtime unwind safe (#6189) 2023-12-04 13:07:31 +01:00
zhiqiangxu 3ac4cfb68a runtime: move comment to the right place (#6184) 2023-12-03 11:47:40 +01:00
Kamila Borowska 7232424a81 chore: remove uses of unsafe from maybe_done.rs (#6180) 2023-12-01 12:51:42 +01:00
simlay 4c33ed33f6 net: add Apple watchOS support (#6176) 2023-11-29 21:16:48 +01:00
Alice Ryhl 3468b4b72f runtime: document fairness guarantees and current behavior (#6145) 2023-11-29 12:32:58 +01:00
Hans Krutzer 2400769b54 net: add SocketAddr::as_abstract_namespace (#6144) 2023-11-25 14:44:34 +00:00
aliu a8e8fa6681 time: add DelayQueue::deadline (#6163) 2023-11-23 18:20:26 +01:00
Alice Ryhl 498288cd31 chore: fix docsrs without net feature (#6166) 2023-11-23 15:20:46 +01:00
Hayden Stainsby 7a30504fd4 task: make task span explicit root (#6158)
In Tokio, tasks are optionally instrumented with tracing spans to allow
analysis of the runtime behavior to be performed with tools like
tokio-console.

The span that is created for each task gets currently follows the
default tracing behavior and has a contextual parent attached to it
based on the span that is actual when `tokio::spawn` or similar is
called.

However, in tracing, a span will remain "alive" until all its children
spans are closed. This doesn't match how spawned tasks work. A task may
outlive the context in which is was spawned (and frequently does). This
causes tasks which spawn other - longer living - tasks to appear in
`tokio-console` as having lost their waker when instead they should be
shown as completed (tokio-rs/console#345). It can also cause undesired
behavior for unrelated tracing spans if a subscriber is receiving both
the other spans as well as Tokio's instrumentation.

To fix this mismatch in behavior, the task span has `parent: None` set
on it, making it an explicit root - it has no parent. The same was
already done for all spans representing resources in #6107. This change
is made within the scope of #5792.

Due to a defect in the currently available `tracing-mock` crate, it is
not possible to test this change at a tracing level
(tokio-rs/tracing#2440). Instead, a test for the `console-subscriber`
has been written which shows that this change fixes the defect as
observed in `tokio-console` (tokio-rs/console#490).
2023-11-19 19:21:19 +01:00
Daniel Sedlak 340d4e5238 runtime: fix comment typos (#6143) 2023-11-19 14:01:29 +01:00
Marc-Andre GirouxandDavid Barsky 7b555185ff sync: avoid creating resource spans with curernt parent, use a None parent instead (#6107)
A child span stored on sync primitives can keep the parent span open,
unable to be closed by subscribers due to the sync resource referencing it.

Fixes: #6106

Co-authored-by: David Barsky <[email protected]>
2023-11-14 16:24:21 -05:00
Hayden Stainsby 135d7ca38e taskdump: fix taskdump cargo config example (#6150)
The documentation for the `Handle::dump()` method includes a description
of the Rust flags needed. However, the sample `.cargo/config.toml` file
is incorrect. It gives the Rust flags as:

```
rustflags = ["--cfg tokio_unstable", "--cfg tokio_taskdump"]
```

However, each command line argument needs to be a separate element in
the array:

```
rustflags = ["--cfg", "tokio_unstable", "--cfg", "tokio_taskdump"]
```

This change corrects that line.
2023-11-14 20:17:05 +01:00
Alice Ryhl e6720f985d metrics: fix hang in worker_overflow_count test (#6146)
Closes: #6054
2023-11-14 20:13:56 +01:00
Alice Ryhl d44e995bb0 task: document cancel safety of LocalSet::run_until (#6147)
Closes: #6122
2023-11-14 20:13:43 +01:00
Alice Ryhl 06660ef00a io: flush in AsyncWriteExt examples (#6149)
Closes: #6068
2023-11-14 20:13:00 +01:00
Alice Ryhl 2e5773a6fe runtime: handle missing context on wake (#6148) 2023-11-14 14:01:10 +01:00
Carl Lerche 49eb26f159 chore: prepare Tokio v1.34.0 release (#6138)
This also includes:
  - tokio-macros v2.2.0
2023-11-09 11:22:54 -08:00
Carl Lerche 19d96c0674 io: increase ScheduledIo tick resolution (#6135)
While this is no current evidence that increasing the tick resolution is
necessary, since we have available bits, we might as well use them.
2023-11-08 15:11:18 -08:00
Carl Lerche 30b2eb17c8 io: fix possible I/O resource hang (#6134)
Use a per-resource tick instead of a single global tick counter. This
prevents the hang issue described by #6133.

Fixes: #6133
2023-11-08 14:05:06 -08:00
aliu 8ec3e0d94d metrics: update stats when unparking in multi-thread (#6131) 2023-11-06 17:14:51 +01:00
Yotam Ofek 161ecec156 stream: fix typo in peekable docs (#6130) 2023-11-05 22:51:09 +01:00
Tymoteusz Wiśniewski 61fcc3bc0b time: remove cached elapsed value from driver state (#6097) 2023-11-05 15:00:05 +01:00
Alice Ryhl 944024e8eb chore: update rust-version to 1.63 in all crates (#6126) 2023-11-04 09:07:22 +01:00
ad hoc 65f861f478 stream: add StreamExt::peekable (#6095) 2023-11-01 17:32:35 +00:00
Alice Ryhl 4c8580152d ci: fix docs on latest nightly (#6120) 2023-11-01 16:33:32 +00:00
Hayden Stainsby ed32cd194c task: add tests for tracing instrumentation of tasks (#6112)
Tokio is instrumented with traces which can be used to analyze the
behavior of the runtime during execution or post-mortem. The
instrumentation is optional. This is where tokio-console collections
information.

There are currently no tests for the instrumentation.

In order to provide more stability to the instrumentation and prepare
for future changes, tests are added to verify the current behavior. The
tests are written using the `tracing-mock` crate. As this crate is still
unreleased, a separate test create has been added under `tokio/tests`
which is outside the workspace. This allows us to pull in both `tracing`
and `tracing-mock` from the tracing repository on GitHub without
affecting the rest of the tokio repository.

This change adds initial tests for the task instrumentation. Further
tests will be added in subsequent commits.

Once `tracing-mock` is published on crates.io (tokio-rs/tracing#539),
these tests can be moved in with the "normal" tokio integration tests.
The decision to add these tests now is due to the release of
`tracing-mock` taking a while, so it would be better to have tests while
we wait.
2023-10-31 10:02:00 +01:00
Fritz Rehde 593dbf55d1 docs: fix typos (#6118) 2023-10-30 15:17:46 +01:00
Satyam1Vishwakarma d8a4a5f24b tokio: gate some panicking tests with #[cfg(panic = "unwind")] (#6115) 2023-10-30 11:51:39 +01:00
Alice Ryhl cc86fef9c0 tokio: gate some panicking tests with #[cfg(panic = "unwind")] (#6110) 2023-10-26 12:28:12 +02:00
Nikolay Arhipov f3949cc56d tokio: added vita target support (#6094) 2023-10-25 14:44:41 +02:00
Alice Ryhl 503fad7908 chore: prepare tokio-util v0.7.10 (#6104) 2023-10-25 12:05:47 +02:00
Alice Ryhl 58acb56a17 ci: update nightly to 2023-10-21 (#6103) 2023-10-24 14:15:25 +00:00
Friedel Ziegelmayer d22c549d97 deps: update hashbrown to 0.14 (#6102) 2023-10-24 12:27:39 +02:00
xianhua zhouandXianhua Zhou bc48a6fa8d sync: fix broadcast::channel link (#6100)
Co-authored-by: Xianhua Zhou <[email protected]>
2023-10-23 15:40:17 +00:00
Alice Ryhl 70410836ae task: add tokio_util::sync::TaskTracker (#6033) 2023-10-22 14:35:19 +00:00
Aaron Schweiger 881b510a07 sync: add mpsc::Receiver::recv_many (#6010) 2023-10-17 11:01:41 +02:00
Rafael Bachmann 6871084629 chore: clippy and doc fixes (#6081) 2023-10-16 17:37:51 +02:00
Alice Ryhl 1b8ebfcffb readme: remove rdbc and add axum to related projects (#6077) 2023-10-16 10:25:40 +02:00
Alice Ryhl 654a3d5acf io: fix integer overflow in take (#6080) 2023-10-15 20:54:27 +02:00
Alice Ryhl a08ad926b1 changelog: fix typo in quadratic (#6079) 2023-10-15 20:30:46 +02:00
Alice Ryhl 723934242b time: reorder comment in sleep.rs (#6076) 2023-10-15 19:21:57 +02:00
Alice Ryhl 944f769cd5 chore: remove bin directory (#6078) 2023-10-15 19:21:12 +02:00
inkyu f3ad6cffd9 task: fix missing wakeup when using LocalSet::enter (#6016) 2023-10-15 19:18:06 +02:00
Andrea StedileandAlice Ryhl f1e41a4ad4 task: add JoinMap::keys (#6046)
Co-authored-by: Alice Ryhl <[email protected]>
2023-10-15 16:47:41 +02:00
icedrocket f9335b8186 fs: update cfg attr in fs::read_dir (#6075) 2023-10-15 13:50:33 +02:00
Alice Ryhl 1134cbb168 net: fix flaky doctest for TcpStream::into_std (#6074) 2023-10-14 15:09:07 +00:00
Alice Ryhl 339c78a680 tokio: remove #5973 from changelog (#6073) 2023-10-14 12:24:45 +00:00
Tymoteusz Wiśniewski c00861210b chore: move 1.20.x to previous LTS releases (#6069) 2023-10-12 18:18:03 +02:00
Alice Ryhl 0f296d2089 io: allow clear_readiness after io driver shutdown (#6067) 2023-10-12 08:18:22 -07:00
Tymoteusz Wiśniewski d420d528f9 chore: render taskdump docs in Netlify previews (#6060) 2023-10-09 11:57:36 +02:00
Tymoteusz Wiśniewski 0457690d01 chore: prepare Tokio v1.33.0 release (#6059) 2023-10-09 11:55:17 +02:00
David Yamnitsky 4557451257 io: implement Seek for SyncIoBridge (#6058) 2023-10-07 14:43:35 +02:00
Jack Wrenn 2bd43765d9 rt: do not trace tasks while locking OwnedTasks (#6036) 2023-10-06 21:48:47 +02:00
Dan Kov f306bd02c3 sync: fix unclosed code block in example (#6056) 2023-10-06 14:33:26 +02:00
Alice Ryhl 6b010ac80f docs: fix new doc warnings in 1.73.0 (#6055) 2023-10-06 09:49:44 +00:00
Alice Ryhl 8cd3383913 time: reduce iteration count in short_sleeps test (#6052) 2023-10-05 22:03:27 +00:00
Alice Ryhl d6ed00c292 sync: Semaphore doc final cleanup (#6050) 2023-10-05 12:43:49 +00:00
songmuhan 5d29136a83 docs: add semaphore example for running tests sequentially (#6038)
Signed-off-by: Muhan Song <[email protected]>
2023-10-05 13:48:19 +02:00
Luís Cruz 52b29b33bb net: add apple tvos support (#6045) 2023-10-04 18:48:53 +02:00
Alice Ryhl eaba9712e8 sync: document that broadcast capacity is a lower bound (#6042) 2023-10-03 07:58:34 +00:00
Alice Ryhl 310adf7ca6 rt: fix flaky test test_disable_lifo_slot (#6043) 2023-10-02 15:05:48 +02:00
Marek Kuskowski 0700d6a7cd io: mark Interest::add with #[must_use] (#6037) 2023-09-29 16:43:38 +02:00
Tymoteusz Wiśniewski ca89c5b2ec benches: move sender to a spawned task in watch benchmark (#6034) 2023-09-28 11:28:12 +02:00
Uwe Klotz 453c720709 sync: use Acquire/Release instead of SeqCst in watch (#6018) 2023-09-24 18:13:43 +02:00
Uwe Klotz e76c06ba38 sync: prevent lock poisoning in watch::Receiver::wait_for (#6021) 2023-09-24 17:26:35 +02:00
Alice Ryhl 02aacf5110 sync: make TokenBucket::close into a destructor in example (#6032) 2023-09-24 14:16:14 +02:00
Weijia Jiang 707fb4d0df tokio: remove wildcard in match patterns (#5970) 2023-09-23 22:05:44 +02:00
Alice Ryhl b161633b5f sync: reorder Semaphore examples (#6031) 2023-09-23 19:53:44 +02:00
M.Amin Rayej f5b8cf9dac sync: add token bucket example to Semaphore (#5978) 2023-09-23 17:45:30 +02:00
Rebekah Kim aa36807c02 sync: fix docs typo (#6030) 2023-09-23 12:33:41 +02:00
Alice Ryhl 74c7a87985 Merge 'tokio-1.32.x' into 'master' (#6028) 2023-09-22 20:00:09 +02:00
Alice Ryhl ccb37c4f39 Merge 'tokio-1.25.2' into 'tokio-1.32.x' (#6027) 2023-09-22 19:58:58 +02:00
Alice Ryhl 9ab4ca68ac chore: prepare Tokio v1.25.2 (#6026) 2023-09-22 18:32:04 +02:00
Alice Ryhl 60a0ca58fa Merge 'tokio-1.20.6' into 'tokio-1.25.x' (#6025) 2023-09-22 18:26:10 +02:00
Alice Ryhl 938c7eb023 chore: prepare Tokio v1.20.6 (#6024) 2023-09-22 13:44:00 +02:00
Alice Ryhl bfa9ea8d9b io: use memchr from libc (#5960) 2023-09-22 13:40:01 +02:00
Uwe Klotz 9bc782acfc sync: fix incorrect comment (#6020) 2023-09-21 10:44:27 +02:00
Chris Constantine 3f6165d82e chore: prepare tokio-util v0.7.9 (#6019) 2023-09-20 19:30:12 +02:00
Uwe Klotz ad7f988da3 sync: fix mark_changed when version overflows (#6017) 2023-09-19 15:44:08 +00:00
nicflower 9d51b76d01 sync: add watch::Sender::new (#5998) 2023-09-19 16:01:36 +02:00
Uwe Klotz 804511822b sync: rename watch::mark_unseen to watch::mark_changed (#6014) 2023-09-19 13:13:46 +00:00
Alexander Kirilin e6553c4ee3 sync: add Semaphore example using an Arc<Semaphore> (#5956) 2023-09-19 14:13:19 +02:00
M.Amin Rayej 98bb3be094 ci: fix ci on tokio-1.20.x (#5999) 2023-09-19 00:16:15 +02:00
Hayden Stainsby d247e7f5df sync: document that const_new() is not instrumented (#6002) 2023-09-13 13:49:02 +02:00
Victor Timofei 65e7715909 util: replace sync::reusable_box::Pending with std::future::Pending (#6000) 2023-09-12 08:53:05 +09:00
Victor Timofei 61042b4d90 sync: add watch::Receiver::mark_unseen (#5962) 2023-09-11 13:50:29 +02:00
Icenowy Zheng 1c428cc558 tokio: fix cache line size for RISC-V (#5994) 2023-09-11 07:22:14 +00:00
Alexandre Bléron 61f095fdc1 sync: add ?Sized bound to {MutexGuard,OwnedMutexGuard}::map (#5997) 2023-09-10 20:08:12 +02:00
Marek Kuskowski 65027b60bc io: add Interest::remove method (#5906) 2023-09-10 16:46:07 +02:00
M.Amin Rayej b046c0dcbb benches: use criterion instead of bencher (#5981) 2023-09-10 16:42:53 +02:00
Adam Chalmers 737dff40cb task: rename generic paramter for spawn (#5993) 2023-09-10 11:21:51 +02:00
Weijia Jiang a6be73eecb codec: document the line ending used by LinesCodec (#5982) 2023-09-08 15:59:48 +02:00
M.Amin Rayej 9fafe783d3 io: support vectored writes for DuplexStream (#5985) 2023-09-08 12:57:18 +02:00
Joan Antoni RE fb3ae0a254 docs: fix worker_overflow_count (#5988) 2023-09-07 14:16:52 -05:00
Hayden Stainsby aad1892ab5 task: fix spawn_local source location (#5984)
The location of a spawned task, as shown in tokio console, is taken from
the location set on the tracing span that instruments the task. For this
location to work, there must be unbroken chain of functions instrumented
with `#[track_caller]`.

For `task::spawn_local`, there was a break in this chain and so the
span contained the location of an internal function in tokio.

This change adds the missing `#[track_caller]` attribute. It has been
tested locally as automated tests would really need `tracing-mock` to be
published so we can use it in the tokio tests.
2023-09-06 10:55:47 +02:00
Hayden Stainsby 8ea303e027 chore: list 1.32.x as LTS release (#5980) 2023-09-05 10:02:50 +02:00
M.Amin Rayej 84ed35ef70 process: document that Child::wait is cancel safe (#5977) 2023-09-04 10:24:55 +02:00
Jack Wrenn 95fb599664 tokio: render taskdump documentation on docs.rs (#5972)
Modifies `package.metadata.docs.r` so that `--cfg tokio_taskdump`
is used by docs.rs when building documentation.
2023-09-02 11:43:31 +02:00
ComplexSpaces 8b312ee571 macros: use ::core qualified imports instead of ::std inside tokio::test macro (#5973) 2023-09-02 03:44:08 +09:00
Colin Walters fd7d0ad5e5 io: add SyncIOBridge::into_inner (#5971) 2023-09-01 13:01:22 +00:00
M.Amin Rayej 37bb47c4a2 fs: add vectored writes to tokio::fs::File (#5958) 2023-08-29 14:18:16 +02:00
Rain cb1e10b745 sync: improve docs for watch channels (#5954)
## Motivation

I found the watch docs as written to be somewhat confusing.

* It wasn't clear to me whether values are marked seen or not at
  creation/subscribe time.
* The example also confused me a bit, suggesting a while loop when a
  do-while loop is generally more correct.
* I noticed a potential race with `borrow` that is no longer an issue
  with `borrow_and_update`.

## Solution

Update the documentation for the watch module to try and make all this
clearer.
2023-08-28 16:13:07 -07:00
Alice Ryhl fb3028f3a2 test: fix testing category slug (#5953) 2023-08-26 17:09:49 +02:00
Alice Ryhl b45f5831cf tokio: remove stats feature (#5952) 2023-08-26 17:09:33 +02:00
Rain 0fe24fcffa sync: improve cancel-safety documentation for mpsc::Sender::send (#5947)
This specific issue (data loss because a send got cancelled) has bitten
our team a couple of times over the last few months. We've switched to
recommending this kind of reserve pattern instead.
2023-08-25 22:03:23 +00:00
Eliza Weisman d1dae25cd2 ci: drop MIPS targets from cross-check (#5951)
Currently, Tokio runs cross-compilation checks for the
`mips-unknown-linux-gnu` and `mipsel-unknown-linux-musl` target triples.
However, Rust has recently demoted these targets from Tier 2 support to
Tier 3 (see rust-lang/compiler-team#648). Therefore, MIPS toolchains may
not always be available, even in stable releases. This is currently
[breaking our CI builds][1], as Rust 1.72.0 does not contain a standard
library for `mips-unknown-linux-gnu`.

This branch removes these builds from the cross-compilation check's
build matrix. Tokio may still build successfully for MIPS targets, but
we can't easily guarantee support when the stable Rust release train may
or may not be able to build for MIPS targets.

[1]: https://github.com/tokio-rs/tokio/actions/runs/5970263562/job/16197657405?pr=5947#step:3:80
2023-08-25 14:40:32 -07:00
nicflower 59c9364689 io: pass through IO traits for StreamReader and SinkWriter (#5941) 2023-08-23 17:46:39 +00:00
Alice Ryhl 3b79be624d chore: prepare tokio-test v0.4.3 (#5943) 2023-08-23 12:38:11 +02:00
Jiahao XU 8955ed5f85 sync: add const fn OnceCell::from_value (#5903)
Signed-off-by: Jiahao XU <[email protected]>
2023-08-22 13:32:56 +02:00
Matilda Smeds bc26934e3b sync: add Semaphore example that limits open files (#5939) 2023-08-20 12:14:41 +02:00
Hamza Jadid 3d64a06600 docs: added trailing backticks (#5938) 2023-08-17 17:22:34 -05:00
Carl Lerche a7d52c2fed chore: prepare Tokio v1.32.0 release (#5937) 2023-08-16 14:11:30 -07:00
Marek Kuskowski f5f2b58b8d rt: improve docs for Builder::max_blocking_threads (#5793)
Closes #5777
2023-08-16 12:12:53 -07:00
mahkoh 718dcc8dff docs: BytesMut::with_capacity does not guarantee exact capacity (#5870) 2023-08-16 10:29:29 -07:00
Folkert de Vries 10e141d211 io: add Ready::ERROR and report error readiness (#5781)
Add `Ready::ERROR` enabling callers to specify interest in error readiness. Some platforms use error
readiness notifications to notify of other events. For example, Linux uses error to notify receipt of
messages on a UDP socket's error queue.

Using error readiness is platform specific.

Closes #5716
2023-08-16 10:28:19 -07:00
Carl Lerche 6e42c26c80 rt(alt): tweak some constants to improve scalability (#5935)
This patch aims to reduce the number of threads that get no-op wakeups.
2023-08-16 08:20:48 -07:00
Folkert de Vries 82bef00db4 io: minor tweaks to AsyncFd (#5932) 2023-08-14 19:13:14 +02:00
Alice Ryhl 40633fc678 readme: list previous LTS releases (#5931) 2023-08-14 19:10:24 +02:00
Aspen Smith 3dd5f7ae2e sync: move broadcast waiters into separate list before waking (#5925)
Within `notify_rx`, looping while re-locking and re-reading from
`Shared.tail` as long as there are still available wakers causes a
quadratic slowdown as receivers which are looping receiving from the
channel are added. Instead of continually re-reading from the original
list, this commit modifies `notify_rx` to move the waiters into a
separate list immediately similar to how `Notify::notify_waiters` works,
using a new `WaitersList` struct modified after NotifyWaitersList.

Fixes #5923
2023-08-14 19:09:56 +02:00
Félix Saparelli 2c92cad9db process: stabilize Command::raw_arg (#5930) 2023-08-13 10:03:11 +02:00
joe thomas 197757d440 streams: create StreamMock for testing Streams (#5915)
Introduce a new mock type to tests streams and eventually
sinks. Only includes next() and wait() for now. Fixes #4106
2023-08-12 19:18:23 +00:00
Carl Lerche 8b8005ebdd chore: prepare Tokio v1.31.0 release (#5928) 2023-08-12 09:37:49 -07:00
Carl Lerche 6cb106c353 rt: unstable EWMA poll time metric (#5927)
Because the runtime uses this value as a tuning heuristic, it can be
useful to get its value. This patch exposes the value as an unstable
metric.
2023-08-10 19:43:44 +00:00
Carl Lerche dd23f08c3a rt(alt): fix memory leak and increase max preemption when running Loom CI tests (#5911)
The memory leak was caused by a bug during shutdown where some state was leaked.
2023-08-10 09:18:10 -07:00
Jakub Kubík 5d29bdfb0c io: delegate WriteHalf::poll_write_vectored (#5914) 2023-08-10 10:11:00 +02:00
Brian Cardarella 4c220af777 chore: prepare Tokio v1.30.0 release (#5917) 2023-08-09 15:16:11 +00:00
Jiahao XU 0a631f88e8 process: add {ChildStd*}::into_owned_{fd, handle} (#5899) 2023-08-09 16:04:53 +02:00
Carl Lerche ee44dc98d8 ci: fix MIRI tests (#5919)
A change to parking lot or miri resulted in CI breaking.
2023-08-08 12:34:19 -07:00
Consoli 51cffbb74f time: mark Sleep as !Unpin in docs (#5916) 2023-08-06 13:34:08 +02:00
Carl Lerche 8832e936b1 rt(alt): fix a number of concurrency bugs (#5907)
Expands loom coverage and fixes a number of bugs.

Closes #5888
2023-08-04 11:59:38 -07:00
Victor Timofei dbda2045f1 time: implement extra reset variants for Interval (#5878) 2023-08-04 19:12:51 +02:00
Carl Lerche 7c54fdce3e rt: pop at least one task from inject queue (#5908)
When attempting to pull a batch of tasks from the injection queue,
ensure we set the cap to at least one.
2023-08-04 08:24:01 -07:00
wathen 38d1bcd9df sync: avoid false sharing in mpsc channel (#5829) 2023-08-03 15:54:19 +02:00
Alice Ryhl 52e6510215 runtime: fix flaky test wake_while_rt_is_dropping (#5905) 2023-08-02 22:30:34 +02:00
Taiki Endo e5e88551d2 Update CI config (#5893) 2023-08-02 01:04:11 +09:00
Jiahao XU efe3ab679a sync: make const_new methods always available (#5885)
Since MSRV is bumped to 1.63, `Mutex::new` is now usable in const context.

Also use `assert!` in const function to ensure correctness instead of
silently truncating the value and remove cfg `tokio_no_const_mutex_new`.

Signed-off-by: Jiahao XU <[email protected]>
2023-07-28 13:46:21 +02:00
Jiahao XU fb08591b43 tokio: removed unused tokio_* cfgs (#5890)
Signed-off-by: Jiahao XU <[email protected]>
2023-07-28 12:54:55 +02:00
kiron1 6aca07bee7 example: use copy_bidirectional in proxy.rs (#5856) 2023-07-28 12:06:44 +02:00
Jiahao XU 5128601898 ci: fix clippy warnings (#5891)
Signed-off-by: Jiahao XU <[email protected]>
2023-07-28 12:05:32 +02:00
Jiahao XU c445e467ce tokio: bump MSRV to 1.63 (#5887) 2023-07-27 10:57:19 +02:00
Carl Lerche a58beb3aca rt(alt): track which workers are idle. (#5886)
The scheduler uses this map to avoid trying to steal from idle workers.
2023-07-21 14:00:33 -07:00
Carl Lerche 4165601b1b rt: initial implementation of new threaded runtime (#5823)
This patch includes an initial implementation of a new multi-threaded
runtime. The new runtime aims to increase the scheduler throughput by
speeding up how it dispatches work to peer worker threads. This
implementation improves most benchmarks by about ~10% when the number of
threads is below 16. As threads increase, mutex contention deteriorates
performance.

Because the new scheduler is not yet ready to replace the old one, the
patch introduces it as an unstable runtime flavor with a warning that it
isn't production ready. Work to improve the scalability of the runtime
will most likely require more intrusive changes across Tokio, so I am
opting to merge with master to avoid larger conflicts.
2023-07-21 11:56:34 -07:00
Hayden Stainsby 63577cd8d3 rt: add runtime ID (#5864)
There are a number of cases in which being able to identify a runtime is
useful.

When instrumenting an application, this is particularly true. For
example, we would like to be able to add traces for runtimes so that
tasks can be differentiated (#5792). It would also allow a way to
differentiate runtimes which are have their tasks dumped.

Outside of instrumentation, it may be useful to check whether 2 runtime
handles are pointing to the same runtime.

This change adds an opaque `runtime::Id` struct which serves this
purpose, initially behind the `tokio_unstable` cfg flag. 

The inner value of the ID is taken from the `OwnedTasks` or
`LocalOwnedTasks` struct which every runtime and local set already
has. This will mean that any use of the ID will align with the task
dump feature.

The ID is added within the scope of working towards closing #5545.
2023-07-19 12:53:40 +02:00
Josh Guilfoyle 02544540f1 net: implement UCred for espidf (#5868) 2023-07-19 11:24:13 +02:00
Josh Guilfoyle d64c8e3ae0 poll: Do not clear readiness on short read/writes. (#5881)
The new mio_unsupported_force_poll_poll behaviour works the same as
Windows (using level-triggered APIs to mimic edge-triggered ones) and it
depends on intercepting an EAGAIN result to start polling the fd again.
2023-07-18 20:28:30 -05:00
Hayden StainsbyandAlice Ryhl f24b9824e6 rt: use optional non-zero value for task owner_id (#5876)
We switch to using a `NonZeroU64` for the `id` field for `OwnedTasks`
and `LocalOwnedTasks` lists. This allows the task header to contain an
`Option<NonZeroU64>` instead of a `u64` with a special meaning for 0.

The size in memory will be the same thanks to Rust's niche optimization,
but this solution is clearer in its intent.

Co-authored-by: Alice Ryhl <[email protected]>
2023-07-18 09:43:25 +02:00
Alice Ryhl 267a231581 runtime: use Arc::increment_strong_count instead of mem::forget (#5872) 2023-07-17 13:46:41 +02:00
Hyeonu Park 05feb2b0bb fs: add File::options() (#5869) 2023-07-16 19:09:22 +02:00
avdb 33d6d4f63c io: use vec in example for AsyncReadExt::read_exact (#5863) 2023-07-16 16:11:05 +02:00
Joris Kleiber 6166e9bcad process: fix raw_arg not showing up in docs (#5865) 2023-07-16 14:10:42 +02:00
João Marcos e52d56e807 sync: add broadcast::Sender::new (#5824) 2023-07-15 18:54:06 +02:00
dullbananas 304d140361 tokio: reduce LLVM code generation (#5859) 2023-07-15 10:44:23 +02:00
Alice Ryhl 91ad76c00c runtime: expand on sharing runtime docs (#5858) 2023-07-10 16:24:55 +02:00
Marek Kuskowski 74a5a458ea util: fix broken intra-doc link (#5849) 2023-07-06 10:43:35 +02:00
João Marcos 0d382faa4e sync: mention lagging in docs for broadcast::send (#5820) 2023-07-03 08:27:46 +00:00
wathen d8847cf891 sync: fix import style for std::error::Error (#5818) 2023-07-03 08:11:00 +00:00
pbrenna 918cf08a5f test: fetch actions from mock handle before write (#5814) 2023-07-03 08:03:33 +00:00
Jiahao XU fc69666f8a Speedup CI (#5691)
- Pass `--no-deps` to `cargo-clippy`
 - Use `dtolnay/rust-toolchain@stale` instead of
   `dtolnay/rust-toolchain@master`
 - Use dtolnay/rust-toolchain instead of `rustup` directly
 - Use `cargo-nextest` in job test to speedup testing
 - Use `cargo-nextest` in job test-unstable to speedup testing
 - Use `cargo-nextest` in job test-unstable-taskdump to speedup testing
 - Use `cargo-nextest` in job no-atomic-u64 to speedup testing
 - Use `cargo-nextest` in job check-unstable-mt-counters
 - Run `cargo check --benches` for `benches/` in job test
   Since the benchmark is not run
 - Run `cargo-check` instead of `cargo-build` in job test-parking_lot
   since no test is run
 - Run `cargo-check` instead of `cargo-build` in job no-atomic-u64
 - Run `Swatinem/rust-cache@v2` after `taiki-e/install-action@v2` to
   avoid caching pre-built artifacts downloaded by it.
 - Use `Swatinem/rust-cache@v2` in job no-atomic-u64
 - Add concurrenty group to cancel outdated CI
 - Use `taiki-e/setup-cross-toolchain-action@v1` in job cross-test

   instead of cross, so that we can use `cargo-nextest` to run tests in
   parallel.

   Also use `Swatinem/rust-cache@v2` to cache artifacts.
 - Use `Swatinem/rust-cache@v2` in job cross-check to speedup ci.
 - Fix job `cross-test`: Use `armv5te-unknown-linux-gnueabi` for no-atomic-u64

   testing instead of `arm-unknown-linux-gnueabihf`, which actually has
   atomic-u64
 - Rm use of `cross` in job `cross-check`

   Since it does not run any test, it does not need the `cross-rs`
   toolchain as tokio does not use any external C/C++ library that require
   `gcc`/`clang` to compile.
 - Add more recognizable name for steps in job cross-test
 - Split job `test` into `test-{tokio-full, workspace-all-features,
   integration-tests-per-feature}`
 - Split job `no-atomic-u64` into `no-atomic-u64-{test, check}`
 - Parallelize job `features` by using matrix
 - Split `cross-test` into `cross-test-{with, without}-parking_lot`
 - Speedup job `cross-test-*` and `no-atomic-u64-test` by running
   `cargo-test` with `-- --test-threads 1` since `qemu` userspace
   emulation has problems running binaries with many threads.
 - Speedup workflow `stress-test.yml` and job `valgrind` in workflow `ci.yml`
   by passing `--fair-sched=yes` to `valgrind`.
 - Speedup job `test-hyper`: Cache `./hyper/target`
   instead of caching `./target`, which is non-existent.
 - Set `RUST_TEST_THREADS=1` to make sure `libtest` only use one thread
   so that qemu will be happy with the tests.
   This is applied to `cross-test-with(out)-parking_lot, no-atomic-u64-test`.
 - Apply suggestions from code review

Signed-off-by: Jiahao XU <[email protected]>
2023-07-02 16:56:38 +09:00
Alice Ryhl bb4512eae0 ci: reenable semver check (#5845) 2023-07-01 16:54:38 +02:00
Carl Lerche 9dbf1879ee Merge branch 'tokio-1.29.x' into merge-1.29.x 2023-06-29 15:06:16 -07:00
Carl Lerche 1b1b9dc7e3 chore: prepare Tokio v1.29.1 release 2023-06-29 14:27:28 -07:00
Carl Lerche 012c848401 rt: fix nesting block_in_place with block_on (#5837)
This patch fixes a bug where nesting `block_in_place` with a `block_on`
between could lead to a panic. This happened because the nested
`block_in_place` would try to acquire a core on return when it should
not attempt to do so. The `block_on` between the two nested
`block_in_place` altered the thread-local state to lead to the incorrect
behavior.

The fix is for each call to `block_in_place` to track if it needs to try
to steal a core back.

Fixes #5239
2023-06-29 14:23:46 -07:00
Carl Lerche 6e990eb2c8 rt: fix nesting block_in_place with block_on (#5837)
This patch fixes a bug where nesting `block_in_place` with a `block_on`
between could lead to a panic. This happened because the nested
`block_in_place` would try to acquire a core on return when it should
not attempt to do so. The `block_on` between the two nested
`block_in_place` altered the thread-local state to lead to the incorrect
behavior.

The fix is for each call to `block_in_place` to track if it needs to try
to steal a core back.

Fixes #5239
2023-06-29 13:47:14 -07:00
Carl Lerche b573adc733 io: remove slab in favor of Arc and allocations (#5833)
This patch removes the custom slab in favor of regular allocations an
`Arc`. Originally, the slab was used to be able to pass indexes as
tokens to the I/O driver when registering I/O resources. However, this
has the downside of having a more expensive token lookup path. It also
pins a `ScheduledIo` to a specific I/O driver. Additionally, the slab is
approaching custom allocator territory.

We plan to explore migrating I/O resources between I/O drivers. As a
step towards that, we need to decouple `ScheduledIo` from the I/O
driver. To do this, the patch uses plain-old allocation to allocate the
`ScheduledIo` and we use the pointer as the token. To use the token, we
need to be very careful about releasing the `ScheduledIo`. We need to
make sure that the associated I/O handle is deregistered from the I/O
driver **and** there are no polls. The strategy in this PR is to let the
I/O driver do the final release between polls, but I expect this
strategy to evolve over time.
2023-06-29 13:46:45 -07:00
Carl Lerche 0c7d8d10fb ci: disable tuning tests for cross tests (#5836) 2023-06-29 10:23:13 -07:00
Carl Lerche ec1f52e1d3 io: fix safety of LinkedList drain_filter API (#5832)
The `drain_filter` method on the internal `LinkedList` type passes a
`&mut` reference to the node type. However, the `LinkedList` is intended
to be used with nodes that are shared in other ways. For example
`task::Header` is accessible concurrently from multiple threads.

Currently, the only usage of `drain_filter` is in a case where `&mut`
access is safe, so this change is to help prevent future bugs and
tighten up the safety of internal utilities.
2023-06-28 12:23:08 -07:00
Consoli 1bfe778acb sync: handle possibly dangling reference safely (#5812) 2023-06-28 09:10:18 +02:00
Carl Lerche ce23db6bc7 rt: reorganize I/O driver source (#5828)
Moves `Driver` into its own file and eliminates a bunch of code defined
in macros.
2023-06-27 16:24:36 -07:00
Carl Lerche 48c55768fd chore: prepare Tokio v1.29.0 release (#5826) 2023-06-27 13:37:10 -07:00
Diggory Blake 657fd883d2 task: add guarantee about when a spawned task may be polled (#5816) 2023-06-27 19:05:09 +02:00
Dustin J. Mitchell 6b076a2743 fs: wait for in-flight ops before cloning File (#5803)
If there is an ongoing operation on a file, wait for that to complete
before performing the clone in `File::try_clone`. This avoids a race
between the ongoing operation and any subsequent operations performed on
the clone.

Fixes: #5759
2023-06-27 10:19:03 +02:00
Dhruv Vats 910a1e2fcf io: fix futures_io::AsyncSeek implementaion for Compat (#5783) 2023-06-25 13:04:35 +02:00
icedrocket 6d25a00145 fs: update cfg attr in fs::read_dir (#5806) 2023-06-25 10:54:14 +02:00
wjjiang 78bf8a9e5e sync: replace Poll::Ready with Ready (#5815) 2023-06-25 10:40:49 +02:00
tim gretler b8af5aad16 task: add spawn_blocking methods to JoinMap (#5797) 2023-06-24 12:13:56 +02:00
Carl Lerche 2e62374e4a rt: pad the task struct to avoid false sharing (#5809)
This change pads the task struct to avoid false sharing. It is possible
for these structs to overlap cache lines without this alignment.
2023-06-21 09:39:14 -07:00
Jack Wrenn 56c4365584 tokio: improve taskdump documentation (#5805)
- Add example trace output.
- Add note on enabling unstable features.
- Add note on performance overhead.
2023-06-19 13:34:48 -04:00
Andrew Mackenzie fb0d305a7a ci: build tokio for redox-os (#5800) 2023-06-19 19:33:17 +02:00
盏一 848482d2bb rt(threaded): adjust transition_from_parked behavior after introducing disable_lifo_slot feature (#5753) 2023-06-14 12:42:31 +02:00
Andrew Mackenzie 00af6eff77 net: add support for Redox OS (#5790) 2023-06-13 12:42:40 +02:00
icedrocket b7290910f7 sync: fix typo in batch semaphore (#5789) 2023-06-12 15:43:12 +02:00
Taiki Endo af6c87a045 chore: upgrade remaining 2018 edition crates to 2021 edition (#5788) 2023-06-12 02:21:50 +09:00
Taiki Endo 6257712d68 ci: update cargo-check-external-types to 0.1.7 (#5786) 2023-06-11 19:02:12 +09:00
Taiki Endo c5d52c17ae chore: enable cargo v2 resolver to prevent dev-deps from enabling log feature of mio (#5787) 2023-06-11 17:34:22 +09:00
Erk 2a54ad01d0 time: do not overflow to signal value (#5710) 2023-06-10 14:24:19 +02:00
Jack Wrenn cb18b0a231 tokio: improve task dump documentation (#5778)
Adds depth to the taskdump example, and documentation to Handle::dump.
2023-06-10 13:30:08 +02:00
nvartolomei 7ccd3e0c6d task: add JoinSet::poll_join_next (#5721) 2023-06-10 13:19:07 +02:00
Bugen Zhao e63d0f10bf task: use pin-project for TaskLocalFuture (#5758)
Signed-off-by: Bugen Zhao <[email protected]>
2023-06-10 12:38:52 +02:00
Alice Ryhl a2941e48be ci: temporarily disable semver check (#5774) 2023-06-08 10:36:25 +02:00
Carl Lerche 1c8d22c18b rt: reduce code defined in macros (#5773)
Instead of defining code in macros, move code definition to sub modules
and use the cfg_macro to declare the module.
2023-06-07 08:48:27 -07:00
Carl Lerche cbb3c155dd rt: panic if EnterGuard dropped incorrect order (#5772)
Calling `Handle::enter()` returns a `EnterGuard` value, which resets the
thread-local context on drop. The drop implementation assumes that
guards from nested `enter()` calls are dropped in reverse order.
However, there is no static enforcement of this requirement.

This patch checks that the guards are dropped in reverse order and
panics otherwise. A future PR will deprecate `Handle::enter()` in favor
of a method that takes a closure, ensuring the guard is dropped
appropriately.
2023-06-07 08:47:58 -07:00
Jack Wrenn 038c4d9999 rt: implement task dumps for multi-thread runtime (#5717)
This patch implements task dumps on the multi-thread runtime. It
complements #5608, which implemented task dumps on the current-thread
runtime.
2023-06-06 13:55:37 -07:00
Folkert de Vries 7b24b22901 io: support PRIORITY epoll events (#5566)
Add support for epoll priority events. The commit adds `Interest::PRIORITY`, `ready`, and `ready_mut` functions to `AsyncFd`.

Closes #4885
2023-06-06 11:57:20 -07:00
Carl Lerche 779b9c19d5 ci: disable tuning test when runing ASAN (#5770)
The tuning test relies on a predictable execution environment. It
assumes that spawning a new task can complete reasonably fast. When
running tests with ASAN, the tuning test will spurriously fail. After
investigating, I believe this is due to running tests with ASAN enabled
and without `release` in a low resource environment (CI) results in an
execution environment that is too slow for the tuning test to succeed.
2023-06-06 09:35:19 -07:00
Carl Lerche 1204da7300 rt: split runtime::context into multiple files (#5768)
This PR restructures `runtime::context` into multiple files by component and feature flag. The goal is to reduce code defined in macros and make each context component more manageable.

There should be no behavior changes except tweaking how the RNG seed is set. Instead of putting it in `set_current`, we set it when entering the runtime. This aligns better with the feature's original intent, enabling users to make a runtime's RNG deterministic. The seed should not be changed by `Handle::enter()`, so there is no need to have the code in `context::set_current`.
2023-06-06 08:37:11 -07:00
Carl Lerche e75ca93d30 rt: EnterGuard should not be Send (#5766)
Removes `Send` from `EnterGuard` (returned by `Handle::enter()`. The
guard type changes a thread-local variable on drop. If the guard is
moved to a different thread, it would modify the wrong thread-local.

This is a **breaking change** but it fixes a bug and prevents incorrect
user behavior. If user code breaks because of this, it is because they
(most likely) have a bug in their code.
2023-06-05 14:09:43 -07:00
476 changed files with 24073 additions and 6547 deletions
-8
View File
@@ -1,8 +0,0 @@
# See https://github.com/rustsec/rustsec/blob/59e1d2ad0b9cbc6892c26de233d4925074b4b97b/cargo-audit/audit.toml.example for example.
[advisories]
ignore = [
# We depend on nix 0.22 only via mio-aio, a dev-dependency.
# https://github.com/tokio-rs/tokio/pull/4255#issuecomment-974786349
"RUSTSEC-2021-0119",
]
+1 -1
View File
@@ -2,7 +2,7 @@ version: 2.1
jobs:
test-arm:
machine:
image: ubuntu-2004:202101-01
image: default
resource_class: arm.medium
environment:
# Change to pin rust version
+2 -2
View File
@@ -1,10 +1,10 @@
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master' || $CIRRUS_BRANCH =~ 'tokio-.*')
auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*'
freebsd_instance:
image_family: freebsd-13-1
image_family: freebsd-14-0
env:
RUST_STABLE: stable
RUST_NIGHTLY: nightly-2022-10-25
RUST_NIGHTLY: nightly-2023-10-21
RUSTFLAGS: -D warnings
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
-1
View File
@@ -1 +0,0 @@
msrv = "1.56"
+25 -5
View File
@@ -1,8 +1,28 @@
R-loom:
R-loom-sync:
- tokio/src/sync/*
- tokio/src/sync/**/*
- tokio-util/src/sync/*
- tokio-util/src/sync/**/*
- tokio/src/runtime/*
- tokio/src/runtime/**/*
R-loom-time-driver:
- tokio/src/runtime/time/*
- tokio/src/runtime/time/**/*
R-loom-current-thread:
- tokio/src/runtime/scheduler/*
- tokio/src/runtime/scheduler/current_thread/*
- tokio/src/runtime/task/*
- tokio/src/runtime/task/**
R-loom-multi-thread:
- tokio/src/runtime/scheduler/*
- tokio/src/runtime/scheduler/multi_thread/*
- tokio/src/runtime/scheduler/multi_thread/**
- tokio/src/runtime/task/*
- tokio/src/runtime/task/**
R-loom-multi-thread-alt:
- tokio/src/runtime/scheduler/*
- tokio/src/runtime/scheduler/multi_thread_alt/*
- tokio/src/runtime/scheduler/multi_thread_alt/**
- tokio/src/runtime/task/*
- tokio/src/runtime/task/**
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Audit Check
# https://github.com/rustsec/audit-check/issues/2
+471 -161
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -4,6 +4,10 @@ on:
# See .github/labeler.yml file
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
permissions:
contents: read
+89 -21
View File
@@ -7,8 +7,14 @@ on:
name: Loom
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
RUSTFLAGS: -Dwarnings
RUSTFLAGS: -Dwarnings --cfg loom --cfg tokio_unstable -C debug_assertions
LOOM_MAX_PREEMPTIONS: 2
LOOM_MAX_BRANCHES: 10000
RUST_BACKTRACE: 1
# Change to specific Rust release to pin
rust_stable: stable
@@ -17,38 +23,100 @@ permissions:
contents: read
jobs:
loom:
name: loom
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') || (github.base_ref == null))
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
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture sync::tests
working-directory: tokio
loom-time-driver:
name: loom time driver
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-time-driver') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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
working-directory: tokio
loom-current-thread:
name: loom current-thread scheduler
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-current-thread') || (github.base_ref == null))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: run tests
run: cargo test --lib --release --features full -- --nocapture loom_current_thread
working-directory: tokio
loom-multi-thread:
name: loom multi-thread scheduler
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread') || (github.base_ref == null))
runs-on: ubuntu-latest
strategy:
matrix:
include:
- scope: --skip loom_pool
max_preemptions: 2
- scope: loom_pool::group_a
max_preemptions: 2
- scope: loom_pool::group_b
max_preemptions: 2
- scope: loom_pool::group_c
max_preemptions: 2
- scope: loom_pool::group_d
max_preemptions: 2
- scope: time::driver
max_preemptions: 2
- scope: loom_multi_thread::group_a
- scope: loom_multi_thread::group_b
- scope: loom_multi_thread::group_c
- scope: loom_multi_thread::group_d
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: loom ${{ matrix.scope }}
run: cargo test --lib --release --features full -- --nocapture $SCOPE
run: cargo test --lib --release --features full -- $SCOPE
working-directory: tokio
env:
SCOPE: ${{ matrix.scope }}
loom-multi-thread-alt:
name: loom ALT multi-thread scheduler
# base_ref is null when it's not a pull request
if: github.repository_owner == 'tokio-rs' && (contains(github.event.pull_request.labels.*.name, 'R-loom-multi-thread-alt') || (github.base_ref == null))
runs-on: ubuntu-latest
strategy:
matrix:
include:
- scope: loom_multi_thread_alt::group_a
- scope: loom_multi_thread_alt::group_b
- scope: loom_multi_thread_alt::group_c
- scope: loom_multi_thread_alt::group_d
steps:
- uses: actions/checkout@v4
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: loom ${{ matrix.scope }}
run: cargo test --lib --release --features full -- $SCOPE
working-directory: tokio
env:
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings -C debug-assertions
LOOM_MAX_PREEMPTIONS: ${{ matrix.max_preemptions }}
LOOM_MAX_BRANCHES: 10000
SCOPE: ${{ matrix.scope }}
+5 -1
View File
@@ -8,6 +8,10 @@ on:
paths:
- '**/Cargo.toml'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
permissions:
contents: read
@@ -16,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install cargo-audit
run: cargo install cargo-audit
+7 -3
View File
@@ -5,6 +5,10 @@ on:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
@@ -23,19 +27,19 @@ jobs:
stress-test:
- simple_echo_tcp
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Rust ${{ env.rust_stable }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_stable }}
- uses: Swatinem/rust-cache@v2
- name: Install Valgrind
uses: taiki-e/install-action@valgrind
- uses: Swatinem/rust-cache@v2
# Compiles each of the stress test examples.
- name: Compile stress test examples
run: cargo build -p stress-test --release --example ${{ matrix.stress-test }}
# Runs each of the examples using Valgrind. Detects leaks and displays them.
- name: Run valgrind
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/release/examples/${{ matrix.stress-test }}
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all --fair-sched=yes ./target/release/examples/${{ matrix.stress-test }}
+57 -12
View File
@@ -149,7 +149,7 @@ When updating this, also update:
-->
```
cargo +1.65.0 clippy --all --tests --all-features
cargo +1.76 clippy --all --tests --all-features
```
When building documentation normally, the markers that list the features
@@ -173,10 +173,10 @@ command below instead:
```
# Mac or Linux
rustfmt --check --edition 2018 $(git ls-files '*.rs')
rustfmt --check --edition 2021 $(git ls-files '*.rs')
# Powershell
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
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.
@@ -184,9 +184,11 @@ it, `rustfmt` will update your files locally instead.
You can run loom tests with
```
cd tokio # tokio crate in workspace
LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \
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
```
@@ -194,6 +196,24 @@ MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" \
cargo +nightly miri test --features full --lib
```
### 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
@@ -230,7 +250,7 @@ integration tests in the crate and follow the style.
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
To get started with fuzz testing you'll need to install
[cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz).
`cargo install cargo-fuzz`
@@ -327,6 +347,29 @@ example would explicitly use `Timeout::new`. For example:
/// # }
```
### 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
@@ -350,14 +393,16 @@ A good commit message should describe what changed and why.
and no more than 72 characters)
* be entirely in lowercase with the exception of proper nouns, acronyms, and
the words that refer to code, like function/variable names
* be prefixed with the name of the sub crate being changed (without the `tokio-`
prefix) and start with an imperative verb. If modifying `tokio` proper,
omit the crate prefix.
* 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:
* timer: introduce `Timeout` and deprecate `Deadline`
* export `Encoder`, `Decoder`, `Framed*` from tokio_codec
* 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).
@@ -374,7 +419,7 @@ A good commit message should describe what changed and why.
Sample complete commit message:
```txt
subcrate: explain the commit in one line
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
@@ -678,4 +723,4 @@ When releasing a new version of a crate, follow these steps:
[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
[conditional-compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html
+5 -1
View File
@@ -1,5 +1,5 @@
[workspace]
resolver = "2"
members = [
"tokio",
"tokio-macros",
@@ -14,3 +14,7 @@ members = [
"tests-build",
"tests-integration",
]
[workspace.metadata.spellcheck]
config = "spellcheck.toml"
+21 -14
View File
@@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.28.2", features = ["full"] }
tokio = { version = "1.37.0", features = ["full"] }
```
Then, on your main.rs:
@@ -132,6 +132,8 @@ project.
In addition to the crates in this repository, the Tokio project also maintains
several other libraries, including:
* [`axum`]: A web application framework that focuses on ergonomics and modularity.
* [`hyper`]: A fast and correct HTTP/1.1 and HTTP/2 implementation for Rust.
* [`tonic`]: A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
@@ -142,21 +144,18 @@ several other libraries, including:
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers
`tokio`.
* [`mio`]: A low-level, cross-platform abstraction over OS I/O APIs that powers `tokio`.
* [`bytes`]: Utilities for working with bytes, including efficient byte buffers.
* [`loom`]: A testing tool for concurrent Rust code
* [`loom`]: A testing tool for concurrent Rust code.
[`axum`]: https://github.com/tokio-rs/axum
[`warp`]: https://github.com/seanmonstar/warp
[`hyper`]: https://github.com/hyperium/hyper
[`tonic`]: https://github.com/hyperium/tonic
[`tower`]: https://github.com/tower-rs/tower
[`loom`]: https://github.com/tokio-rs/loom
[`rdbc`]: https://github.com/tokio-rs/rdbc
[`tracing`]: https://github.com/tokio-rs/tracing
[`mio`]: https://github.com/tokio-rs/mio
[`bytes`]: https://github.com/tokio-rs/bytes
@@ -187,12 +186,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.56.0.
released at least six months ago. The current MSRV is 1.63.
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.27 to now - Rust 1.56
* 1.30 to now - Rust 1.63
* 1.27 to 1.29 - Rust 1.56
* 1.17 to 1.26 - Rust 1.49
* 1.15 to 1.16 - Rust 1.46
* 1.0 to 1.14 - Rust 1.45
@@ -215,21 +215,28 @@ 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.18.x` - LTS release until June 2023. (MSRV 1.49)
* `1.20.x` - LTS release until September 2023. (MSRV 1.49)
* `1.25.x` - LTS release until March 2024. (MSRV 1.49)
* `1.32.x` - LTS release until September 2024. (MSRV 1.63)
* `1.36.x` - LTS release until March 2025. (MSRV 1.63)
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.18.x` patch release, you
example, to specify that you wish to use the newest `1.32.x` patch release, you
can use the following dependency specification:
```text
tokio = { version = "~1.18", features = [...] }
tokio = { version = "~1.32", features = [...] }
```
### Previous LTS releases
* `1.8.x` - LTS release until February 2022.
* `1.14.x` - LTS release until June 2022.
* `1.18.x` - LTS release until June 2023.
* `1.20.x` - LTS release until September 2023.
* `1.25.x` - LTS release until March 2024.
## License
This project is licensed under the [MIT license].
+8 -2
View File
@@ -2,16 +2,17 @@
name = "benches"
version = "0.0.0"
publish = false
edition = "2018"
edition = "2021"
[features]
test-util = ["tokio/test-util"]
[dependencies]
tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
bencher = "0.1.5"
criterion = "0.5.1"
rand = "0.8"
rand_chacha = "0.3"
num_cpus = "1.16.0"
[dev-dependencies]
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
@@ -25,6 +26,11 @@ name = "spawn"
path = "spawn.rs"
harness = false
[[bench]]
name = "sync_broadcast"
path = "sync_broadcast.rs"
harness = false
[[bench]]
name = "sync_mpsc"
path = "sync_mpsc.rs"
+54 -42
View File
@@ -1,4 +1,4 @@
use bencher::{benchmark_group, benchmark_main, Bencher};
use criterion::{criterion_group, criterion_main, Criterion};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
@@ -29,7 +29,7 @@ const WRITE_SERVICE_PERIOD: Duration = Duration::from_millis(20);
// because another writer claimed the buffer space
const PROBABILITY_FLUSH_WAIT: f64 = 0.1;
/// A slow writer that aims to simulate HDD behaviour under heavy load.
/// A slow writer that aims to simulate HDD behavior under heavy load.
///
/// There is a limited buffer, which is fully drained on the next write after
/// a time limit is reached. Flush waits for the time limit to be reached
@@ -64,7 +64,7 @@ impl SlowHddWriter {
) -> std::task::Poll<Result<(), std::io::Error>> {
// If we hit a service interval, the buffer can be cleared
let res = self.service_intervals.poll_tick(cx).map(|_| Ok(()));
if let Poll::Ready(_) = res {
if res.is_ready() {
self.buffer_used = 0;
}
res
@@ -123,7 +123,7 @@ impl AsyncWrite for SlowHddWriter {
cx: &mut std::task::Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> std::task::Poll<Result<usize, std::io::Error>> {
let writeable = bufs.into_iter().fold(0, |acc, buf| acc + buf.len());
let writeable = bufs.iter().fold(0, |acc, buf| acc + buf.len());
self.write_bytes(cx, writeable)
}
@@ -174,65 +174,77 @@ fn rt() -> tokio::runtime::Runtime {
.unwrap()
}
fn copy_mem_to_mem(b: &mut Bencher) {
fn copy_mem_to_mem(c: &mut Criterion) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut source = repeat(0).take(SOURCE_SIZE);
let mut dest = Vec::new();
copy(&mut source, &mut dest).await.unwrap();
};
c.bench_function("copy_mem_to_mem", |b| {
b.iter(|| {
let task = || async {
let mut source = repeat(0).take(SOURCE_SIZE);
let mut dest = Vec::new();
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
rt.block_on(task());
})
});
}
fn copy_mem_to_slow_hdd(b: &mut Bencher) {
fn copy_mem_to_slow_hdd(c: &mut Criterion) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut source = repeat(0).take(SOURCE_SIZE);
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
copy(&mut source, &mut dest).await.unwrap();
};
c.bench_function("copy_mem_to_slow_hdd", |b| {
b.iter(|| {
let task = || async {
let mut source = repeat(0).take(SOURCE_SIZE);
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
rt.block_on(task());
})
});
}
fn copy_chunk_to_mem(b: &mut Bencher) {
fn copy_chunk_to_mem(c: &mut Criterion) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
let mut dest = Vec::new();
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
c.bench_function("copy_chunk_to_mem", |b| {
b.iter(|| {
let task = || async {
let mut source =
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
let mut dest = Vec::new();
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
});
}
fn copy_chunk_to_slow_hdd(b: &mut Bencher) {
fn copy_chunk_to_slow_hdd(c: &mut Criterion) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut source = ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
c.bench_function("copy_chunk_to_slow_hdd", |b| {
b.iter(|| {
let task = || async {
let mut source =
ChunkReader::new(CHUNK_SIZE, READ_SERVICE_PERIOD).take(SOURCE_SIZE);
let mut dest = SlowHddWriter::new(WRITE_SERVICE_PERIOD, WRITE_BUFFER);
copy(&mut source, &mut dest).await.unwrap();
};
rt.block_on(task());
})
});
}
benchmark_group!(
criterion_group!(
copy_bench,
copy_mem_to_mem,
copy_mem_to_slow_hdd,
copy_chunk_to_mem,
copy_chunk_to_slow_hdd,
);
benchmark_main!(copy_bench);
criterion_main!(copy_bench);
+66 -57
View File
@@ -6,7 +6,7 @@ use tokio::fs::File;
use tokio::io::AsyncReadExt;
use tokio_util::codec::{BytesCodec, FramedRead /*FramedWrite*/};
use bencher::{benchmark_group, benchmark_main, Bencher};
use criterion::{criterion_group, criterion_main, Criterion};
use std::fs::File as StdFile;
use std::io::Read as StdRead;
@@ -23,81 +23,90 @@ const BLOCK_COUNT: usize = 1_000;
const BUFFER_SIZE: usize = 4096;
const DEV_ZERO: &str = "/dev/zero";
fn async_read_codec(b: &mut Bencher) {
fn async_read_codec(c: &mut Criterion) {
let rt = rt();
b.iter(|| {
let task = || async {
let file = File::open(DEV_ZERO).await.unwrap();
let mut input_stream = FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
c.bench_function("async_read_codec", |b| {
b.iter(|| {
let task = || async {
let file = File::open(DEV_ZERO).await.unwrap();
let mut input_stream =
FramedRead::with_capacity(file, BytesCodec::new(), BUFFER_SIZE);
for _i in 0..BLOCK_COUNT {
let _bytes = input_stream.next().await.unwrap();
}
};
rt.block_on(task());
});
}
fn async_read_buf(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let task = || async {
let mut file = File::open(DEV_ZERO).await.unwrap();
let mut buffer = [0u8; BUFFER_SIZE];
for _i in 0..BLOCK_COUNT {
let count = file.read(&mut buffer).await.unwrap();
if count == 0 {
break;
for _i in 0..BLOCK_COUNT {
let _bytes = input_stream.next().await.unwrap();
}
}
};
};
rt.block_on(task());
rt.block_on(task());
})
});
}
fn async_read_std_file(b: &mut Bencher) {
fn async_read_buf(c: &mut Criterion) {
let rt = rt();
let task = || async {
let mut file = tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
c.bench_function("async_read_buf", |b| {
b.iter(|| {
let task = || async {
let mut file = File::open(DEV_ZERO).await.unwrap();
let mut buffer = [0u8; BUFFER_SIZE];
for _i in 0..BLOCK_COUNT {
for _i in 0..BLOCK_COUNT {
let count = file.read(&mut buffer).await.unwrap();
if count == 0 {
break;
}
}
};
rt.block_on(task());
});
});
}
fn async_read_std_file(c: &mut Criterion) {
let rt = rt();
c.bench_function("async_read_std_file", |b| {
b.iter(|| {
let task = || async {
let mut file =
tokio::task::block_in_place(|| Box::pin(StdFile::open(DEV_ZERO).unwrap()));
for _i in 0..BLOCK_COUNT {
let mut buffer = [0u8; BUFFER_SIZE];
let mut file_ref = file.as_mut();
tokio::task::block_in_place(move || {
file_ref.read_exact(&mut buffer).unwrap();
});
}
};
rt.block_on(task());
});
});
}
fn sync_read(c: &mut Criterion) {
c.bench_function("sync_read", |b| {
b.iter(|| {
let mut file = StdFile::open(DEV_ZERO).unwrap();
let mut buffer = [0u8; BUFFER_SIZE];
let mut file_ref = file.as_mut();
tokio::task::block_in_place(move || {
file_ref.read_exact(&mut buffer).unwrap();
});
}
};
b.iter(|| {
rt.block_on(task());
for _i in 0..BLOCK_COUNT {
file.read_exact(&mut buffer).unwrap();
}
})
});
}
fn sync_read(b: &mut Bencher) {
b.iter(|| {
let mut file = StdFile::open(DEV_ZERO).unwrap();
let mut buffer = [0u8; BUFFER_SIZE];
for _i in 0..BLOCK_COUNT {
file.read_exact(&mut buffer).unwrap();
}
});
}
benchmark_group!(
criterion_group!(
file,
async_read_std_file,
async_read_buf,
async_read_codec,
sync_read
);
benchmark_main!(file);
criterion_main!(file);
+43 -37
View File
@@ -4,46 +4,50 @@
use tokio::runtime::{self, Runtime};
use bencher::{benchmark_group, benchmark_main, Bencher};
use criterion::{criterion_group, criterion_main, Criterion};
const NUM_SPAWN: usize = 1_000;
fn spawn_many_local(b: &mut Bencher) {
fn rt_curr_spawn_many_local(c: &mut Criterion) {
let rt = rt();
let mut handles = Vec::with_capacity(NUM_SPAWN);
b.iter(|| {
rt.block_on(async {
for _ in 0..NUM_SPAWN {
handles.push(tokio::spawn(async move {}));
}
c.bench_function("spawn_many_local", |b| {
b.iter(|| {
rt.block_on(async {
for _ in 0..NUM_SPAWN {
handles.push(tokio::spawn(async move {}));
}
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
}
fn spawn_many_remote_idle(b: &mut Bencher) {
fn rt_curr_spawn_many_remote_idle(c: &mut Criterion) {
let rt = rt();
let rt_handle = rt.handle();
let mut handles = Vec::with_capacity(NUM_SPAWN);
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
c.bench_function("spawn_many_remote_idle", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
});
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
}
fn spawn_many_remote_busy(b: &mut Bencher) {
fn rt_curr_spawn_many_remote_busy(c: &mut Criterion) {
let rt = rt();
let rt_handle = rt.handle();
let mut handles = Vec::with_capacity(NUM_SPAWN);
@@ -56,16 +60,18 @@ fn spawn_many_remote_busy(b: &mut Bencher) {
iter()
});
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
c.bench_function("spawn_many_remote_busy", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
});
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
}
@@ -73,11 +79,11 @@ fn rt() -> Runtime {
runtime::Builder::new_current_thread().build().unwrap()
}
benchmark_group!(
scheduler,
spawn_many_local,
spawn_many_remote_idle,
spawn_many_remote_busy
criterion_group!(
rt_curr_scheduler,
rt_curr_spawn_many_local,
rt_curr_spawn_many_remote_idle,
rt_curr_spawn_many_remote_busy
);
benchmark_main!(scheduler);
criterion_main!(rt_curr_scheduler);
+125 -112
View File
@@ -5,63 +5,68 @@
use tokio::runtime::{self, Runtime};
use tokio::sync::oneshot;
use bencher::{benchmark_group, benchmark_main, Bencher};
use std::sync::atomic::Ordering::Relaxed;
use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::{mpsc, Arc};
use std::time::{Duration, Instant};
use criterion::{criterion_group, criterion_main, Criterion};
const NUM_WORKERS: usize = 4;
const NUM_SPAWN: usize = 10_000;
const STALL_DUR: Duration = Duration::from_micros(10);
fn spawn_many_local(b: &mut Bencher) {
fn rt_multi_spawn_many_local(c: &mut Criterion) {
let rt = rt();
let (tx, rx) = mpsc::sync_channel(1000);
let rem = Arc::new(AtomicUsize::new(0));
b.iter(|| {
rem.store(NUM_SPAWN, Relaxed);
c.bench_function("spawn_many_local", |b| {
b.iter(|| {
rem.store(NUM_SPAWN, Relaxed);
rt.block_on(async {
for _ in 0..NUM_SPAWN {
let tx = tx.clone();
let rem = rem.clone();
rt.block_on(async {
for _ in 0..NUM_SPAWN {
let tx = tx.clone();
let rem = rem.clone();
tokio::spawn(async move {
if 1 == rem.fetch_sub(1, Relaxed) {
tx.send(()).unwrap();
}
});
}
tokio::spawn(async move {
if 1 == rem.fetch_sub(1, Relaxed) {
tx.send(()).unwrap();
}
});
}
let _ = rx.recv().unwrap();
});
rx.recv().unwrap();
});
})
});
}
fn spawn_many_remote_idle(b: &mut Bencher) {
fn rt_multi_spawn_many_remote_idle(c: &mut Criterion) {
let rt = rt();
let mut handles = Vec::with_capacity(NUM_SPAWN);
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
c.bench_function("spawn_many_remote_idle", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt.spawn(async {}));
}
});
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
}
// The runtime is busy with tasks that consume CPU time and yield. Yielding is a
// lower notification priority than spawning / regular notification.
fn spawn_many_remote_busy1(b: &mut Bencher) {
fn rt_multi_spawn_many_remote_busy1(c: &mut Criterion) {
let rt = rt();
let rt_handle = rt.handle();
let mut handles = Vec::with_capacity(NUM_SPAWN);
@@ -78,16 +83,18 @@ fn spawn_many_remote_busy1(b: &mut Bencher) {
});
}
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
c.bench_function("spawn_many_remote_busy1", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
});
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
flag.store(false, Relaxed);
@@ -95,7 +102,7 @@ fn spawn_many_remote_busy1(b: &mut Bencher) {
// The runtime is busy with tasks that consume CPU time and spawn new high-CPU
// tasks. Spawning goes via a higher notification priority than yielding.
fn spawn_many_remote_busy2(b: &mut Bencher) {
fn rt_multi_spawn_many_remote_busy2(c: &mut Criterion) {
const NUM_SPAWN: usize = 1_000;
let rt = rt();
@@ -119,49 +126,52 @@ fn spawn_many_remote_busy2(b: &mut Bencher) {
});
}
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
c.bench_function("spawn_many_remote_busy2", |b| {
b.iter(|| {
for _ in 0..NUM_SPAWN {
handles.push(rt_handle.spawn(async {}));
}
});
rt.block_on(async {
for handle in handles.drain(..) {
handle.await.unwrap();
}
});
})
});
flag.store(false, Relaxed);
}
fn yield_many(b: &mut Bencher) {
fn rt_multi_yield_many(c: &mut Criterion) {
const NUM_YIELD: usize = 1_000;
const TASKS: usize = 200;
let rt = rt();
c.bench_function("yield_many", |b| {
let rt = rt();
let (tx, rx) = mpsc::sync_channel(TASKS);
let (tx, rx) = mpsc::sync_channel(TASKS);
b.iter(move || {
for _ in 0..TASKS {
let tx = tx.clone();
b.iter(move || {
for _ in 0..TASKS {
let tx = tx.clone();
rt.spawn(async move {
for _ in 0..NUM_YIELD {
tokio::task::yield_now().await;
}
rt.spawn(async move {
for _ in 0..NUM_YIELD {
tokio::task::yield_now().await;
}
tx.send(()).unwrap();
});
}
tx.send(()).unwrap();
});
}
for _ in 0..TASKS {
let _ = rx.recv().unwrap();
}
for _ in 0..TASKS {
rx.recv().unwrap();
}
})
});
}
fn ping_pong(b: &mut Bencher) {
fn rt_multi_ping_pong(c: &mut Criterion) {
const NUM_PINGS: usize = 1_000;
let rt = rt();
@@ -169,46 +179,46 @@ fn ping_pong(b: &mut Bencher) {
let (done_tx, done_rx) = mpsc::sync_channel(1000);
let rem = Arc::new(AtomicUsize::new(0));
b.iter(|| {
let done_tx = done_tx.clone();
let rem = rem.clone();
rem.store(NUM_PINGS, Relaxed);
c.bench_function("ping_pong", |b| {
b.iter(|| {
let done_tx = done_tx.clone();
let rem = rem.clone();
rem.store(NUM_PINGS, Relaxed);
rt.block_on(async {
tokio::spawn(async move {
for _ in 0..NUM_PINGS {
let rem = rem.clone();
let done_tx = done_tx.clone();
tokio::spawn(async move {
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
rt.block_on(async {
tokio::spawn(async move {
for _ in 0..NUM_PINGS {
let rem = rem.clone();
let done_tx = done_tx.clone();
tokio::spawn(async move {
rx1.await.unwrap();
tx2.send(()).unwrap();
let (tx1, rx1) = oneshot::channel();
let (tx2, rx2) = oneshot::channel();
tokio::spawn(async move {
rx1.await.unwrap();
tx2.send(()).unwrap();
});
tx1.send(()).unwrap();
rx2.await.unwrap();
if 1 == rem.fetch_sub(1, Relaxed) {
done_tx.send(()).unwrap();
}
});
}
});
tx1.send(()).unwrap();
rx2.await.unwrap();
if 1 == rem.fetch_sub(1, Relaxed) {
done_tx.send(()).unwrap();
}
});
}
done_rx.recv().unwrap();
});
done_rx.recv().unwrap();
});
})
});
}
fn chained_spawn(b: &mut Bencher) {
fn rt_multi_chained_spawn(c: &mut Criterion) {
const ITER: usize = 1_000;
let rt = rt();
fn iter(done_tx: mpsc::SyncSender<()>, n: usize) {
if n == 0 {
done_tx.send(()).unwrap();
@@ -219,18 +229,21 @@ fn chained_spawn(b: &mut Bencher) {
}
}
let (done_tx, done_rx) = mpsc::sync_channel(1000);
c.bench_function("chained_spawn", |b| {
let rt = rt();
let (done_tx, done_rx) = mpsc::sync_channel(1000);
b.iter(move || {
let done_tx = done_tx.clone();
b.iter(move || {
let done_tx = done_tx.clone();
rt.block_on(async {
tokio::spawn(async move {
iter(done_tx, ITER);
rt.block_on(async {
tokio::spawn(async move {
iter(done_tx, ITER);
});
done_rx.recv().unwrap();
});
done_rx.recv().unwrap();
});
})
});
}
@@ -249,15 +262,15 @@ fn stall() {
}
}
benchmark_group!(
scheduler,
spawn_many_local,
spawn_many_remote_idle,
spawn_many_remote_busy1,
spawn_many_remote_busy2,
ping_pong,
yield_many,
chained_spawn,
criterion_group!(
rt_multi_scheduler,
rt_multi_spawn_many_local,
rt_multi_spawn_many_remote_idle,
rt_multi_spawn_many_remote_busy1,
rt_multi_spawn_many_remote_busy2,
rt_multi_ping_pong,
rt_multi_yield_many,
rt_multi_chained_spawn,
);
benchmark_main!(scheduler);
criterion_main!(rt_multi_scheduler);
+17 -15
View File
@@ -1,7 +1,7 @@
//! Benchmark the delay in propagating OS signals to any listeners.
#![cfg(unix)]
use bencher::{benchmark_group, benchmark_main, Bencher};
use criterion::{criterion_group, criterion_main, Criterion};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -41,7 +41,7 @@ pub fn send_signal(signal: libc::c_int) {
}
}
fn many_signals(bench: &mut Bencher) {
fn many_signals(c: &mut Criterion) {
let num_signals = 10;
let (tx, mut rx) = mpsc::channel(num_signals);
@@ -75,21 +75,23 @@ fn many_signals(bench: &mut Bencher) {
// tasks have been polled at least once
rt.block_on(Spinner::new());
bench.iter(|| {
rt.block_on(async {
send_signal(libc::SIGCHLD);
for _ in 0..num_signals {
rx.recv().await.expect("channel closed");
}
c.bench_function("many_signals", |b| {
b.iter(|| {
rt.block_on(async {
send_signal(libc::SIGCHLD);
for _ in 0..num_signals {
rx.recv().await.expect("channel closed");
}
send_signal(libc::SIGIO);
for _ in 0..num_signals {
rx.recv().await.expect("channel closed");
}
});
send_signal(libc::SIGIO);
for _ in 0..num_signals {
rx.recv().await.expect("channel closed");
}
});
})
});
}
benchmark_group!(signal_group, many_signals,);
criterion_group!(signal_group, many_signals);
benchmark_main!(signal_group);
criterion_main!(signal_group);
+47 -40
View File
@@ -2,10 +2,7 @@
//! This essentially measure the time to enqueue a task in the local and remote
//! case.
#[macro_use]
extern crate bencher;
use bencher::{black_box, Bencher};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
async fn work() -> usize {
let val = 1 + 1;
@@ -13,67 +10,77 @@ async fn work() -> usize {
black_box(val)
}
fn basic_scheduler_spawn(bench: &mut Bencher) {
fn basic_scheduler_spawn(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
bench.iter(|| {
runtime.block_on(async {
let h = tokio::spawn(work());
assert_eq!(h.await.unwrap(), 2);
});
c.bench_function("basic_scheduler_spawn", |b| {
b.iter(|| {
runtime.block_on(async {
let h = tokio::spawn(work());
assert_eq!(h.await.unwrap(), 2);
});
})
});
}
fn basic_scheduler_spawn10(bench: &mut Bencher) {
fn basic_scheduler_spawn10(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
bench.iter(|| {
runtime.block_on(async {
let mut handles = Vec::with_capacity(10);
for _ in 0..10 {
handles.push(tokio::spawn(work()));
}
for handle in handles {
assert_eq!(handle.await.unwrap(), 2);
}
});
c.bench_function("basic_scheduler_spawn10", |b| {
b.iter(|| {
runtime.block_on(async {
let mut handles = Vec::with_capacity(10);
for _ in 0..10 {
handles.push(tokio::spawn(work()));
}
for handle in handles {
assert_eq!(handle.await.unwrap(), 2);
}
});
})
});
}
fn threaded_scheduler_spawn(bench: &mut Bencher) {
fn threaded_scheduler_spawn(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.build()
.unwrap();
bench.iter(|| {
runtime.block_on(async {
let h = tokio::spawn(work());
assert_eq!(h.await.unwrap(), 2);
});
c.bench_function("threaded_scheduler_spawn", |b| {
b.iter(|| {
runtime.block_on(async {
let h = tokio::spawn(work());
assert_eq!(h.await.unwrap(), 2);
});
})
});
}
fn threaded_scheduler_spawn10(bench: &mut Bencher) {
fn threaded_scheduler_spawn10(c: &mut Criterion) {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.build()
.unwrap();
bench.iter(|| {
runtime.block_on(async {
let mut handles = Vec::with_capacity(10);
for _ in 0..10 {
handles.push(tokio::spawn(work()));
}
for handle in handles {
assert_eq!(handle.await.unwrap(), 2);
}
});
c.bench_function("threaded_scheduler_spawn10", |b| {
b.iter(|| {
runtime.block_on(async {
let mut handles = Vec::with_capacity(10);
for _ in 0..10 {
handles.push(tokio::spawn(work()));
}
for handle in handles {
assert_eq!(handle.await.unwrap(), 2);
}
});
})
});
}
bencher::benchmark_group!(
criterion_group!(
spawn,
basic_scheduler_spawn,
basic_scheduler_spawn10,
@@ -81,4 +88,4 @@ bencher::benchmark_group!(
threaded_scheduler_spawn10,
);
bencher::benchmark_main!(spawn);
criterion_main!(spawn);
+82
View File
@@ -0,0 +1,82 @@
use rand::{Rng, RngCore, SeedableRng};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::{broadcast, Notify};
use criterion::measurement::WallTime;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap()
}
fn do_work(rng: &mut impl RngCore) -> u32 {
use std::fmt::Write;
let mut message = String::new();
for i in 1..=10 {
let _ = write!(&mut message, " {i}={}", rng.gen::<f64>());
}
message
.as_bytes()
.iter()
.map(|&c| c as u32)
.fold(0, u32::wrapping_add)
}
fn contention_impl<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
let (tx, _rx) = broadcast::channel::<usize>(1000);
let wg = Arc::new((AtomicUsize::new(0), Notify::new()));
for n in 0..N_TASKS {
let wg = wg.clone();
let mut rx = tx.subscribe();
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
rt.spawn(async move {
while (rx.recv().await).is_ok() {
let r = do_work(&mut rng);
let _ = black_box(r);
if wg.0.fetch_sub(1, Ordering::Relaxed) == 1 {
wg.1.notify_one();
}
}
});
}
const N_ITERS: usize = 100;
g.bench_function(N_TASKS.to_string(), |b| {
b.iter(|| {
rt.block_on({
let wg = wg.clone();
let tx = tx.clone();
async move {
for i in 0..N_ITERS {
assert_eq!(wg.0.fetch_add(N_TASKS, Ordering::Relaxed), 0);
tx.send(i).unwrap();
while wg.0.load(Ordering::Relaxed) > 0 {
wg.1.notified().await;
}
}
}
})
})
});
}
fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("contention");
contention_impl::<10>(&mut group);
contention_impl::<100>(&mut group);
contention_impl::<500>(&mut group);
contention_impl::<1000>(&mut group);
group.finish();
}
criterion_group!(contention, bench_contention);
criterion_main!(contention);
+282 -130
View File
@@ -1,8 +1,23 @@
use bencher::{black_box, Bencher};
use tokio::sync::mpsc;
type Medium = [usize; 64];
type Large = [Medium; 64];
use criterion::measurement::WallTime;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
#[derive(Debug, Copy, Clone)]
struct Medium(#[allow(dead_code)] [usize; 64]);
impl Default for Medium {
fn default() -> Self {
Medium([0; 64])
}
}
#[derive(Debug, Copy, Clone)]
struct Large(#[allow(dead_code)] [Medium; 64]);
impl Default for Large {
fn default() -> Self {
Large([Medium::default(); 64])
}
}
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
@@ -11,169 +26,306 @@ fn rt() -> tokio::runtime::Runtime {
.unwrap()
}
fn create_1_medium(b: &mut Bencher) {
b.iter(|| {
black_box(&mpsc::channel::<Medium>(1));
});
}
fn create_100_medium(b: &mut Bencher) {
b.iter(|| {
black_box(&mpsc::channel::<Medium>(100));
});
}
fn create_100_000_medium(b: &mut Bencher) {
b.iter(|| {
black_box(&mpsc::channel::<Medium>(100_000));
});
}
fn send_medium(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let (tx, mut rx) = mpsc::channel::<Medium>(1000);
let _ = rt.block_on(tx.send([0; 64]));
rt.block_on(rx.recv()).unwrap();
});
}
fn send_large(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
let (tx, mut rx) = mpsc::channel::<Large>(1000);
let _ = rt.block_on(tx.send([[0; 64]; 64]));
rt.block_on(rx.recv()).unwrap();
});
}
fn contention_bounded(b: &mut Bencher) {
let rt = rt();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
fn create_medium<const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>) {
g.bench_function(SIZE.to_string(), |b| {
b.iter(|| {
black_box(&mpsc::channel::<Medium>(SIZE));
})
});
}
fn contention_bounded_full(b: &mut Bencher) {
fn send_data<T: Default, const SIZE: usize>(g: &mut BenchmarkGroup<WallTime>, prefix: &str) {
let rt = rt();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(100);
g.bench_function(format!("{}_{}", prefix, SIZE), |b| {
b.iter(|| {
let (tx, mut rx) = mpsc::channel::<T>(SIZE);
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
let _ = rt.block_on(tx.send(T::default()));
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
rt.block_on(rx.recv()).unwrap();
})
});
}
fn contention_unbounded(b: &mut Bencher) {
fn contention_bounded(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
g.bench_function("bounded", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).unwrap();
}
});
}
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
})
})
});
}
fn uncontented_bounded(b: &mut Bencher) {
fn contention_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
g.bench_function("bounded_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for i in 0..5000 {
tx.send(i).await.unwrap();
}
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
for _ in 0..5_000 {
let _ = rx.recv().await;
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn uncontented_unbounded(b: &mut Bencher) {
fn contention_bounded_full(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
g.bench_function("bounded_full", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(100);
for i in 0..5000 {
tx.send(i).unwrap();
}
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
for _ in 0..5_000 {
let _ = rx.recv().await;
}
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
})
})
});
}
bencher::benchmark_group!(
create,
create_1_medium,
create_100_medium,
create_100_000_medium
);
fn contention_bounded_full_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
bencher::benchmark_group!(send, send_medium, send_large);
g.bench_function("bounded_full_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(100);
bencher::benchmark_group!(
contention,
contention_bounded,
contention_bounded_full,
contention_unbounded,
uncontented_bounded,
uncontented_unbounded
);
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).await.unwrap();
}
});
}
bencher::benchmark_main!(create, send, contention);
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn contention_unbounded(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("unbounded", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).unwrap();
}
});
}
for _ in 0..1_000 * 5 {
let _ = rx.recv().await;
}
})
})
});
}
fn contention_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("unbounded_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
for _ in 0..5 {
let tx = tx.clone();
tokio::spawn(async move {
for i in 0..1000 {
tx.send(i).unwrap();
}
});
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn uncontented_bounded(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("bounded", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for i in 0..5000 {
tx.send(i).await.unwrap();
}
for _ in 0..5_000 {
let _ = rx.recv().await;
}
})
})
});
}
fn uncontented_bounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("bounded_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<usize>(1_000_000);
for i in 0..5000 {
tx.send(i).await.unwrap();
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn uncontented_unbounded(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("unbounded", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
for i in 0..5000 {
tx.send(i).unwrap();
}
for _ in 0..5_000 {
let _ = rx.recv().await;
}
})
})
});
}
fn uncontented_unbounded_recv_many(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
g.bench_function("unbounded_recv_many", |b| {
b.iter(|| {
rt.block_on(async move {
let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
for i in 0..5000 {
tx.send(i).unwrap();
}
let mut buffer = Vec::<usize>::with_capacity(5_000);
let mut total = 0;
while total < 1_000 * 5 {
total += rx.recv_many(&mut buffer, 5_000).await;
}
})
})
});
}
fn bench_create_medium(c: &mut Criterion) {
let mut group = c.benchmark_group("create_medium");
create_medium::<1>(&mut group);
create_medium::<100>(&mut group);
create_medium::<100_000>(&mut group);
group.finish();
}
fn bench_send(c: &mut Criterion) {
let mut group = c.benchmark_group("send");
send_data::<Medium, 1000>(&mut group, "medium");
send_data::<Large, 1000>(&mut group, "large");
group.finish();
}
fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("contention");
contention_bounded(&mut group);
contention_bounded_recv_many(&mut group);
contention_bounded_full(&mut group);
contention_bounded_full_recv_many(&mut group);
contention_unbounded(&mut group);
contention_unbounded_recv_many(&mut group);
group.finish();
}
fn bench_uncontented(c: &mut Criterion) {
let mut group = c.benchmark_group("uncontented");
uncontented_bounded(&mut group);
uncontented_bounded_recv_many(&mut group);
uncontented_unbounded(&mut group);
uncontented_unbounded_recv_many(&mut group);
group.finish();
}
criterion_group!(create, bench_create_medium);
criterion_group!(send, bench_send);
criterion_group!(contention, bench_contention);
criterion_group!(uncontented, bench_uncontented);
criterion_main!(create, send, contention, uncontented);
+19 -16
View File
@@ -1,27 +1,28 @@
use bencher::{benchmark_group, benchmark_main, Bencher};
use tokio::{
runtime::Runtime,
sync::{mpsc, oneshot},
};
fn request_reply_current_thread(b: &mut Bencher) {
use criterion::{criterion_group, criterion_main, Criterion};
fn request_reply_current_thread(c: &mut Criterion) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
request_reply(b, rt);
request_reply(c, rt);
}
fn request_reply_multi_threaded(b: &mut Bencher) {
fn request_reply_multi_threaded(c: &mut Criterion) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.build()
.unwrap();
request_reply(b, rt);
request_reply(c, rt);
}
fn request_reply(b: &mut Bencher, rt: Runtime) {
fn request_reply(b: &mut Criterion, rt: Runtime) {
let tx = rt.block_on(async move {
let (tx, mut rx) = mpsc::channel::<oneshot::Sender<()>>(10);
tokio::spawn(async move {
@@ -32,22 +33,24 @@ fn request_reply(b: &mut Bencher, rt: Runtime) {
tx
});
b.iter(|| {
let task_tx = tx.clone();
rt.block_on(async move {
for _ in 0..1_000 {
let (o_tx, o_rx) = oneshot::channel();
task_tx.send(o_tx).await.unwrap();
let _ = o_rx.await;
}
b.bench_function("request_reply", |b| {
b.iter(|| {
let task_tx = tx.clone();
rt.block_on(async move {
for _ in 0..1_000 {
let (o_tx, o_rx) = oneshot::channel();
task_tx.send(o_tx).await.unwrap();
let _ = o_rx.await;
}
})
})
});
}
benchmark_group!(
criterion_group!(
sync_mpsc_oneshot_group,
request_reply_current_thread,
request_reply_multi_threaded,
);
benchmark_main!(sync_mpsc_oneshot_group);
criterion_main!(sync_mpsc_oneshot_group);
+47 -33
View File
@@ -1,9 +1,11 @@
use bencher::Bencher;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::Notify;
use criterion::measurement::WallTime;
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
@@ -11,7 +13,7 @@ fn rt() -> tokio::runtime::Runtime {
.unwrap()
}
fn notify_waiters<const N_WAITERS: usize>(b: &mut Bencher) {
fn notify_waiters<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
let notify = Arc::new(Notify::new());
let counter = Arc::new(AtomicUsize::new(0));
@@ -29,18 +31,20 @@ fn notify_waiters<const N_WAITERS: usize>(b: &mut Bencher) {
}
const N_ITERS: usize = 500;
b.iter(|| {
counter.store(0, Ordering::Relaxed);
loop {
notify.notify_waiters();
if counter.load(Ordering::Relaxed) >= N_ITERS {
break;
g.bench_function(N_WAITERS.to_string(), |b| {
b.iter(|| {
counter.store(0, Ordering::Relaxed);
loop {
notify.notify_waiters();
if counter.load(Ordering::Relaxed) >= N_ITERS {
break;
}
}
}
})
});
}
fn notify_one<const N_WAITERS: usize>(b: &mut Bencher) {
fn notify_one<const N_WAITERS: usize>(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
let notify = Arc::new(Notify::new());
let counter = Arc::new(AtomicUsize::new(0));
@@ -58,33 +62,43 @@ fn notify_one<const N_WAITERS: usize>(b: &mut Bencher) {
}
const N_ITERS: usize = 500;
b.iter(|| {
counter.store(0, Ordering::Relaxed);
loop {
notify.notify_one();
if counter.load(Ordering::Relaxed) >= N_ITERS {
break;
g.bench_function(N_WAITERS.to_string(), |b| {
b.iter(|| {
counter.store(0, Ordering::Relaxed);
loop {
notify.notify_one();
if counter.load(Ordering::Relaxed) >= N_ITERS {
break;
}
}
}
})
});
}
bencher::benchmark_group!(
fn bench_notify_one(c: &mut Criterion) {
let mut group = c.benchmark_group("notify_one");
notify_one::<10>(&mut group);
notify_one::<50>(&mut group);
notify_one::<100>(&mut group);
notify_one::<200>(&mut group);
notify_one::<500>(&mut group);
group.finish();
}
fn bench_notify_waiters(c: &mut Criterion) {
let mut group = c.benchmark_group("notify_waiters");
notify_waiters::<10>(&mut group);
notify_waiters::<50>(&mut group);
notify_waiters::<100>(&mut group);
notify_waiters::<200>(&mut group);
notify_waiters::<500>(&mut group);
group.finish();
}
criterion_group!(
notify_waiters_simple,
notify_waiters::<10>,
notify_waiters::<50>,
notify_waiters::<100>,
notify_waiters::<200>,
notify_waiters::<500>
bench_notify_one,
bench_notify_waiters
);
bencher::benchmark_group!(
notify_one_simple,
notify_one::<10>,
notify_one::<50>,
notify_one::<100>,
notify_one::<200>,
notify_one::<500>
);
bencher::benchmark_main!(notify_waiters_simple, notify_one_simple);
criterion_main!(notify_waiters_simple);
+91 -70
View File
@@ -1,26 +1,30 @@
use bencher::{black_box, Bencher};
use std::sync::Arc;
use tokio::{sync::RwLock, task};
fn read_uncontended(b: &mut Bencher) {
use criterion::measurement::WallTime;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn read_uncontended(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap();
let lock = Arc::new(RwLock::new(()));
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
for _ in 0..6 {
let read = lock.read().await;
let _read = black_box(read);
}
g.bench_function("read", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
for _ in 0..6 {
let read = lock.read().await;
let _read = black_box(read);
}
})
})
});
}
fn read_concurrent_uncontended_multi(b: &mut Bencher) {
fn read_concurrent_uncontended_multi(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
@@ -32,23 +36,25 @@ fn read_concurrent_uncontended_multi(b: &mut Bencher) {
}
let lock = Arc::new(RwLock::new(()));
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone()))
};
j.unwrap();
g.bench_function("read_concurrent_multi", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone()))
};
j.unwrap();
})
})
});
}
fn read_concurrent_uncontended(b: &mut Bencher) {
fn read_concurrent_uncontended(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
@@ -59,22 +65,24 @@ fn read_concurrent_uncontended(b: &mut Bencher) {
}
let lock = Arc::new(RwLock::new(()));
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
tokio::join! {
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone())
};
g.bench_function("read_concurrent", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
tokio::join! {
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone())
};
})
})
});
}
fn read_concurrent_contended_multi(b: &mut Bencher) {
fn read_concurrent_contended_multi(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
@@ -86,24 +94,26 @@ fn read_concurrent_contended_multi(b: &mut Bencher) {
}
let lock = Arc::new(RwLock::new(()));
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let write = lock.write().await;
let j = tokio::try_join! {
async move { drop(write); Ok(()) },
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
};
j.unwrap();
g.bench_function("read_concurrent_multi", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let write = lock.write().await;
let j = tokio::try_join! {
async move { drop(write); Ok(()) },
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
task::spawn(task(lock.clone())),
};
j.unwrap();
})
})
});
}
fn read_concurrent_contended(b: &mut Bencher) {
fn read_concurrent_contended(g: &mut BenchmarkGroup<WallTime>) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
@@ -114,29 +124,40 @@ fn read_concurrent_contended(b: &mut Bencher) {
}
let lock = Arc::new(RwLock::new(()));
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let write = lock.write().await;
tokio::join! {
async move { drop(write) },
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
};
g.bench_function("read_concurrent", |b| {
b.iter(|| {
let lock = lock.clone();
rt.block_on(async move {
let write = lock.write().await;
tokio::join! {
async move { drop(write) },
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
task(lock.clone()),
};
})
})
});
}
bencher::benchmark_group!(
sync_rwlock,
read_uncontended,
read_concurrent_uncontended,
read_concurrent_uncontended_multi,
read_concurrent_contended,
read_concurrent_contended_multi
);
fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("contention");
read_concurrent_contended(&mut group);
read_concurrent_contended_multi(&mut group);
group.finish();
}
bencher::benchmark_main!(sync_rwlock);
fn bench_uncontented(c: &mut Criterion) {
let mut group = c.benchmark_group("uncontented");
read_uncontended(&mut group);
read_concurrent_uncontended(&mut group);
read_concurrent_uncontended_multi(&mut group);
group.finish();
}
criterion_group!(contention, bench_contention);
criterion_group!(uncontented, bench_uncontented);
criterion_main!(contention, uncontented);
+106 -84
View File
@@ -1,21 +1,36 @@
use bencher::Bencher;
use std::sync::Arc;
use tokio::runtime::Runtime;
use tokio::{sync::Semaphore, task};
fn uncontended(b: &mut Bencher) {
let rt = tokio::runtime::Builder::new_multi_thread()
use criterion::measurement::WallTime;
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn single_rt() -> Runtime {
tokio::runtime::Builder::new_current_thread()
.build()
.unwrap()
}
fn multi_rt() -> Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap();
.unwrap()
}
fn uncontended(g: &mut BenchmarkGroup<WallTime>) {
let rt = multi_rt();
let s = Arc::new(Semaphore::new(10));
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
for _ in 0..6 {
let permit = s.acquire().await;
drop(permit);
}
g.bench_function("multi", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
for _ in 0..6 {
let permit = s.acquire().await;
drop(permit);
}
})
})
});
}
@@ -25,101 +40,108 @@ async fn task(s: Arc<Semaphore>) {
drop(permit);
}
fn uncontended_concurrent_multi(b: &mut Bencher) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap();
fn uncontended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
let rt = multi_rt();
let s = Arc::new(Semaphore::new(10));
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone()))
};
j.unwrap();
g.bench_function("concurrent_multi", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone()))
};
j.unwrap();
})
})
});
}
fn uncontended_concurrent_single(b: &mut Bencher) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
fn uncontended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
let rt = single_rt();
let s = Arc::new(Semaphore::new(10));
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
tokio::join! {
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone())
};
g.bench_function("concurrent_single", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
tokio::join! {
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone())
};
})
})
});
}
fn contended_concurrent_multi(b: &mut Bencher) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
.build()
.unwrap();
fn contended_concurrent_multi(g: &mut BenchmarkGroup<WallTime>) {
let rt = multi_rt();
let s = Arc::new(Semaphore::new(5));
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone()))
};
j.unwrap();
g.bench_function("concurrent_multi", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
let j = tokio::try_join! {
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone())),
task::spawn(task(s.clone()))
};
j.unwrap();
})
})
});
}
fn contended_concurrent_single(b: &mut Bencher) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
fn contended_concurrent_single(g: &mut BenchmarkGroup<WallTime>) {
let rt = single_rt();
let s = Arc::new(Semaphore::new(5));
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
tokio::join! {
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone())
};
g.bench_function("concurrent_single", |b| {
b.iter(|| {
let s = s.clone();
rt.block_on(async move {
tokio::join! {
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone()),
task(s.clone())
};
})
})
});
}
bencher::benchmark_group!(
sync_semaphore,
uncontended,
uncontended_concurrent_multi,
uncontended_concurrent_single,
contended_concurrent_multi,
contended_concurrent_single
);
fn bench_contention(c: &mut Criterion) {
let mut group = c.benchmark_group("contention");
contended_concurrent_multi(&mut group);
contended_concurrent_single(&mut group);
group.finish();
}
bencher::benchmark_main!(sync_semaphore);
fn bench_uncontented(c: &mut Criterion) {
let mut group = c.benchmark_group("uncontented");
uncontended(&mut group);
uncontended_concurrent_multi(&mut group);
uncontended_concurrent_single(&mut group);
group.finish();
}
criterion_group!(contention, bench_contention);
criterion_group!(uncontented, bench_uncontented);
criterion_main!(contention, uncontented);
+40 -19
View File
@@ -1,9 +1,11 @@
use bencher::{black_box, Bencher};
use rand::prelude::*;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tokio::sync::{watch, Notify};
use criterion::measurement::WallTime;
use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion};
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(6)
@@ -24,16 +26,15 @@ fn do_work(rng: &mut impl RngCore) -> u32 {
.fold(0, u32::wrapping_add)
}
fn contention_resubscribe(b: &mut Bencher) {
const NTASK: u64 = 1000;
fn contention_resubscribe<const N_TASKS: usize>(g: &mut BenchmarkGroup<WallTime>) {
let rt = rt();
let (snd, rcv) = watch::channel(0i32);
let (snd, _) = watch::channel(0i32);
let snd = Arc::new(snd);
let wg = Arc::new((AtomicU64::new(0), Notify::new()));
for n in 0..NTASK {
let mut rcv = rcv.clone();
for n in 0..N_TASKS {
let mut rcv = snd.subscribe();
let wg = wg.clone();
let mut rng = rand::rngs::StdRng::seed_from_u64(n);
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
rt.spawn(async move {
while rcv.changed().await.is_ok() {
let _ = *rcv.borrow(); // contend on rwlock
@@ -46,19 +47,39 @@ fn contention_resubscribe(b: &mut Bencher) {
});
}
b.iter(|| {
rt.block_on(async {
for _ in 0..100 {
assert_eq!(wg.0.fetch_add(NTASK, Ordering::Relaxed), 0);
let _ = snd.send(black_box(42));
while wg.0.load(Ordering::Acquire) > 0 {
wg.1.notified().await;
const N_ITERS: usize = 100;
g.bench_function(N_TASKS.to_string(), |b| {
b.iter(|| {
rt.block_on({
let snd = snd.clone();
let wg = wg.clone();
async move {
tokio::spawn(async move {
for _ in 0..N_ITERS {
assert_eq!(wg.0.fetch_add(N_TASKS as u64, Ordering::Relaxed), 0);
let _ = snd.send(black_box(42));
while wg.0.load(Ordering::Acquire) > 0 {
wg.1.notified().await;
}
}
})
.await
.unwrap();
}
}
});
});
})
});
}
bencher::benchmark_group!(contention, contention_resubscribe);
fn bench_contention_resubscribe(c: &mut Criterion) {
let mut group = c.benchmark_group("contention_resubscribe");
contention_resubscribe::<10>(&mut group);
contention_resubscribe::<100>(&mut group);
contention_resubscribe::<500>(&mut group);
contention_resubscribe::<1000>(&mut group);
group.finish();
}
bencher::benchmark_main!(contention);
criterion_group!(contention, bench_contention_resubscribe);
criterion_main!(contention);
+10 -11
View File
@@ -2,24 +2,23 @@
//! This essentially measure the time to enqueue a task in the local and remote
//! case.
#[macro_use]
extern crate bencher;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use bencher::{black_box, Bencher};
fn time_now_current_thread(bench: &mut Bencher) {
fn time_now_current_thread(c: &mut Criterion) {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap();
bench.iter(|| {
rt.block_on(async {
black_box(tokio::time::Instant::now());
c.bench_function("time_now_current_thread", |b| {
b.iter(|| {
rt.block_on(async {
black_box(tokio::time::Instant::now());
})
})
})
});
}
bencher::benchmark_group!(time_now, time_now_current_thread,);
criterion_group!(time_now, time_now_current_thread);
bencher::benchmark_main!(time_now);
criterion_main!(time_now);
-121
View File
@@ -1,121 +0,0 @@
#!/usr/bin/env bash
set -e
USAGE="Publish a new release of a tokio crate
USAGE:
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
OPTIONS:
-v, --verbose Use verbose Cargo output
-d, --dry-run Perform a dry run (do not publish or tag the release)
-h, --help Show this help text and exit"
DRY_RUN=""
VERBOSE=""
err() {
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
}
status() {
WIDTH=12
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
}
verify() {
status "Verifying" "if $CRATE v$VERSION can be released"
ACTUAL=$(cargo pkgid | sed -n 's/.*#\(.*\)/\1/p')
if [ "$ACTUAL" != "$VERSION" ]; then
err "expected to release version $VERSION, but Cargo.toml contained $ACTUAL"
exit 1
fi
if git tag -l | grep -Fxq "$TAG" ; then
err "git tag \`$TAG\` already exists"
exit 1
fi
PATH_DEPS=$(grep -F "path = \"" Cargo.toml | sed -e 's/^/ /')
if [ -n "$PATH_DEPS" ]; then
err "crate \`$CRATE\` contained path dependencies:\n$PATH_DEPS"
echo "path dependencies must be removed prior to release"
exit 1
fi
}
release() {
status "Releasing" "$CRATE v$VERSION"
cargo package $VERBOSE
cargo publish $VERBOSE $DRY_RUN
status "Tagging" "$TAG"
if [ -n "$DRY_RUN" ]; then
echo "# git tag $TAG && git push --tags"
else
git tag "$TAG" && git push --tags
fi
}
while [[ $# -gt 0 ]]
do
case "$1" in
-h|--help)
echo "$USAGE"
exit 0
;;
-v|--verbose)
VERBOSE="--verbose"
set +x
shift
;;
-d|--dry-run)
DRY_RUN="--dry-run"
shift
;;
-*)
err "unknown flag \"$1\""
echo "$USAGE"
exit 1
;;
*) # crate or version
if [ -z "$CRATE" ]; then
CRATE="$1"
elif [ -z "$VERSION" ]; then
VERSION="$1"
else
err "unknown positional argument \"$1\""
echo "$USAGE"
exit 1
fi
shift
;;
esac
done
# set -- "${POSITIONAL[@]}"
if [ -z "$VERSION" ]; then
err "no version specified!"
HELP=1
fi
if [ -n "$CRATE" ]; then
TAG="$CRATE-$VERSION"
else
err "no crate specified!"
HELP=1
fi
if [ -n "$HELP" ]; then
echo "$USAGE"
exit 1
fi
if [ -d "$CRATE" ]; then
(cd "$CRATE" && verify && release )
else
err "no such crate \"$CRATE\""
exit 1
fi
-118
View File
@@ -1,118 +0,0 @@
#!/usr/bin/env bash
set -e
USAGE="Update links to docs.rs in a tokio crate
USAGE:
$(basename "$0") [OPTIONS] [CRATE] [VERSION]
OPTIONS:
-d, --dry-run Perform a dry run (do not modify any file)
-h, --help Show this help text and exit"
err() {
echo -e "\e[31m\e[1merror:\e[0m $@" 1>&2;
}
status() {
WIDTH=12
printf "\e[32m\e[1m%${WIDTH}s\e[0m %s\n" "$1" "$2"
}
c1grep() { grep "$@" || test $? = 1; }
update_versions_in_doc() {
# Print what is being/would be done
if [ -n "$DRY_RUN" ]; then
local MSG="Would change:"
else
local MSG="Updating:"
fi
git grep -lr "docs.rs/$CRATE/" \
| xargs sed --quiet \
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|gp" \
| sed -e "s/^/$MSG /"
# Apply changes if not in dry run
if [ -z "$DRY_RUN" ]; then
git grep -lr "docs.rs/$CRATE/" \
| xargs sed -i \
-E "s|docs.rs/$CRATE/[0-9.]+|docs.rs/$CRATE/$VERSION|g"
fi
}
update() {
update_versions_in_doc
}
show_outdated() {
OUTDATED=$(git grep -rn "docs.rs/$CRATE/" \
| c1grep -v "$VERSION" \
| sed -e 's/^/ - /')
if [[ -n "$OUTDATED" ]]; then
echo "Found the following links to docs.rs with an outdated version:"
echo "$OUTDATED"
echo
else
echo "Nothing to do."
exit 1
fi
}
while [[ $# -gt 0 ]]
do
case "$1" in
-h|--help)
echo "$USAGE"
exit 0
;;
-d|--dry-run)
DRY_RUN="--dry-run"
shift
;;
-*)
err "unknown flag \"$1\""
echo "$USAGE"
exit 1
;;
*) # crate or version
if [ -z "$CRATE" ]; then
CRATE="$1"
elif [ -z "$VERSION" ]; then
VERSION="$1"
else
err "unknown positional argument \"$1\""
echo "$USAGE"
exit 1
fi
shift
;;
esac
done
# set -- "${POSITIONAL[@]}"
if [ -z "$VERSION" ]; then
err "no version specified!"
HELP=1
fi
if [ -n "$CRATE" ]; then
TAG="$CRATE-$VERSION"
else
err "no crate specified!"
HELP=1
fi
if [ -n "$HELP" ]; then
echo "$USAGE"
exit 1
fi
if [ -d "$CRATE" ]; then
# Does not cd in order to update everywhere
show_outdated && update
else
err "no such crate \"$CRATE\""
exit 1
fi
+1 -1
View File
@@ -2,7 +2,7 @@
name = "examples"
version = "0.0.0"
publish = false
edition = "2018"
edition = "2021"
# If you copy one of the examples into a new project, you should be using
# [dependencies] instead, and delete the **path**.
+1 -1
View File
@@ -23,7 +23,7 @@ fn main() {
// Without the `HandleExt.wrap()` there would be a panic because there is
// no timer running, since it would be referencing runtime r1.
let _ = rt1.block_on(rt2.wrap(async move {
rt1.block_on(rt2.wrap(async move {
let listener = TcpListener::bind("0.0.0.0:0").await.unwrap();
println!("addr: {:?}", listener.local_addr());
tx.send(()).unwrap();
+61 -20
View File
@@ -1,4 +1,6 @@
//! This example demonstrates tokio's experimental taskdumping functionality.
//! This example demonstrates tokio's experimental task dumping functionality.
//! This application deadlocks. Input CTRL+C to display traces of each task, or
//! input CTRL+C twice within 1 second to quit.
#[cfg(all(
tokio_unstable,
@@ -6,37 +8,76 @@
target_os = "linux",
any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")
))]
#[tokio::main(flavor = "current_thread")]
async fn main() {
use std::hint::black_box;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::sync::Arc;
use tokio::sync::Barrier;
#[inline(never)]
async fn a() {
black_box(b()).await
async fn a(barrier: Arc<Barrier>) {
b(barrier).await
}
#[inline(never)]
async fn b() {
black_box(c()).await
async fn b(barrier: Arc<Barrier>) {
c(barrier).await
}
#[inline(never)]
async fn c() {
black_box(tokio::task::yield_now()).await
async fn c(barrier: Arc<Barrier>) {
barrier.wait().await;
}
tokio::spawn(a());
tokio::spawn(b());
tokio::spawn(c());
// Prints a task dump upon receipt of CTRL+C, or returns if CTRL+C is
// inputted twice within a second.
async fn dump_or_quit() {
use tokio::time::{timeout, Duration, Instant};
let handle = tokio::runtime::Handle::current();
let mut last_signal: Option<Instant> = None;
// wait for CTRL+C
while let Ok(_) = tokio::signal::ctrl_c().await {
// exit if a CTRL+C is inputted twice within 1 second
if let Some(time_since_last_signal) = last_signal.map(|i| i.elapsed()) {
if time_since_last_signal < Duration::from_secs(1) {
return;
}
}
last_signal = Some(Instant::now());
let handle = tokio::runtime::Handle::current();
let dump = handle.dump();
for (i, task) in dump.tasks().iter().enumerate() {
let trace = task.trace();
println!("task {i} trace:");
println!("{trace}");
// capture a dump, and print each trace
println!("{:-<80}", "");
if let Ok(dump) = timeout(Duration::from_secs(2), handle.dump()).await {
for task in dump.tasks().iter() {
let id = task.id();
let trace = task.trace();
println!("TASK {id}:");
println!("{trace}\n");
}
} else {
println!("Task dumping timed out. Use a native debugger (like gdb) to debug the deadlock.");
}
println!("{:-<80}", "");
println!("Input CTRL+C twice within 1 second to exit.");
}
}
println!("This program has a deadlock.");
println!("Input CTRL+C to print a task dump.");
println!("Input CTRL+C twice within 1 second to exit.");
// oops! this barrier waits for one more task than will ever come.
let barrier = Arc::new(Barrier::new(3));
let task_1 = tokio::spawn(a(barrier.clone()));
let task_2 = tokio::spawn(a(barrier));
tokio::select!(
_ = dump_or_quit() => {},
_ = task_1 => {},
_ = task_2 => {},
);
Ok(())
}
#[cfg(not(all(
+12 -30
View File
@@ -22,8 +22,7 @@
#![warn(rust_2018_idioms)]
use tokio::io;
use tokio::io::AsyncWriteExt;
use tokio::io::copy_bidirectional;
use tokio::net::{TcpListener, TcpStream};
use futures::FutureExt;
@@ -44,36 +43,19 @@ async fn main() -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind(listen_addr).await?;
while let Ok((inbound, _)) = listener.accept().await {
let transfer = transfer(inbound, server_addr.clone()).map(|r| {
if let Err(e) = r {
println!("Failed to transfer; error={}", e);
}
});
while let Ok((mut inbound, _)) = listener.accept().await {
let mut outbound = TcpStream::connect(server_addr.clone()).await?;
tokio::spawn(transfer);
tokio::spawn(async move {
copy_bidirectional(&mut inbound, &mut outbound)
.map(|r| {
if let Err(e) = r {
println!("Failed to transfer; error={}", e);
}
})
.await
});
}
Ok(())
}
async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<dyn Error>> {
let mut outbound = TcpStream::connect(proxy_addr).await?;
let (mut ri, mut wi) = inbound.split();
let (mut ro, mut wo) = outbound.split();
let client_to_server = async {
io::copy(&mut ri, &mut wo).await?;
wo.shutdown().await
};
let server_to_client = async {
io::copy(&mut ro, &mut wi).await?;
wi.shutdown().await
};
tokio::try_join!(client_to_server, server_to_client)?;
Ok(())
}
+1 -1
View File
@@ -18,7 +18,7 @@ use futures::SinkExt;
use http::{header::HeaderValue, Request, Response, StatusCode};
#[macro_use]
extern crate serde_derive;
use std::{convert::TryFrom, env, error::Error, fmt, io};
use std::{env, error::Error, fmt, io};
use tokio::net::{TcpListener, TcpStream};
use tokio_stream::StreamExt;
use tokio_util::codec::{Decoder, Encoder, Framed};
+2 -1
View File
@@ -8,8 +8,9 @@
RUSTDOCFLAGS="""
--cfg docsrs \
--cfg tokio_unstable \
--cfg tokio_taskdump \
"""
RUSTFLAGS="--cfg tokio_unstable --cfg docsrs"
RUSTFLAGS="--cfg tokio_unstable --cfg tokio_taskdump --cfg docsrs"
[[redirects]]
from = "/"
+283
View File
@@ -0,0 +1,283 @@
283
&
+
<
=
>
\
~
0o777
0s
0xA
0xD
100ms
10ms
~12
±1m
±1ms
1ms
1s
250ms
2x
~4
443
450ms
50ms
8MB
adaptor
adaptors
Adaptors
AIO
ambiant
amongst
api
APIs
async
awaitable
backend
backpressure
backtrace
backtraces
backtracing
binded
bitfield
bitfields
Blockingly
boolean
broadcasted
cancelled
cancelling
Cancelling
CLI
cloneable
codebase
codec
codecs
combinator
combinators
config
Config
connectionless
coroutines
cpu
cpus
Customizable
datagram
Datagram
datagrams
deallocate
deallocated
Deallocates
decrementing
dequeued
deregister
deregistered
deregistering
deregisters
Deregisters
deregistration
descriptor's
destructor
destructors
destructure
Destructures
Dev
dns
DNS
DoS
dwOpenMode
endian
enqueue
enqueued
EntryInner
enum
eof
Erlang's
errored
EWMA
expirations
fcntl
fd's
FIFOs
filename
filesystem
filesystems
fn
fns
FreeBSD
frontend
fs
functionalities
getters
GID
goroutines
Growable
gzip
hashmaps
HashMaps
hashsets
ie
Illumos
impl
implementers
implementor
implementors
incrementing
interoperate
invariants
Invariants
io
IOCP
iOS
IOs
IP
IPv4
IPv6
iteratively
Kotlin's
latencies
Lauck
libc
lifecycle
lifo
lookups
macOS
MacOS
Marsaglia's
metadata
mio
Mio
mio's
misconfigured
mock's
mpmc
mpsc
multi
Multi
multicast
Multithreaded
mut
mutex
Mutex
Nagle
nonblocking
nondecreasing
noop
ntasks
ok
oneshot
ORed
os
overweighing
parker
parsers
peekable
PGID
PID
plaintext
poller
POSIX
proxied
qos
RAII
reallocations
recv's
refactors
refcount
refcounting
repo
repr
representable
reqwest
reregistering
resize
resized
RMW
runtime
runtime's
runtimes
rwlock
rx
scalability
scheduler's
semver
setpgid
sharded
signalled
signalling
SmallCrush
Solaris
spawner
Splitter
spmc
spsc
src
stabilised
startup
stateful
stderr
stdin
stdout
stealable
stealer
stealers
struct
structs
subfield
suboptimal
subprocess
superset
symlink
symlinks
sys
syscall
syscalls
tcp
TCP
TestU01
threadpool
timestamp
timestamps
TLS
TOCTOU
TODO
tokio
Tokio
tokio's
Tokio's
tuple
Tuple
tx
udp
UDP
UID
unhandled
unix
unlink
unpark
Unpark
unparked
unparking
unparks
Unparks
unreceived
unsafety
Unsets
unsynchronized
untrusted
usecases
Valgrind
Varghese
vec
versa
versioned
versioning
vtable
waker
wakers
Wakers
wakeup
wakeups
workstealing
+13
View File
@@ -0,0 +1,13 @@
dev_comments = false
skip_readme = false
[Hunspell]
lang = "en_US"
search_dirs = ["."]
extra_dictionaries = ["spellcheck.dic"]
skip_os_lookups = true
use_builtin = true
[Hunspell.quirks]
allow_concatenation = true
+1 -1
View File
@@ -2,7 +2,7 @@
name = "stress-test"
version = "0.1.0"
authors = ["Tokio Contributors <[email protected]>"]
edition = "2018"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+1 -1
View File
@@ -42,7 +42,7 @@ fn main() {
.write_all(one_mega_random_bytes.as_slice())
.await
.unwrap();
stream.read(&mut buff).await.unwrap();
let _ = stream.read(&mut buff).await.unwrap();
}
tx.send(()).unwrap();
});
+1 -1
View File
@@ -2,7 +2,7 @@
name = "tests-build"
version = "0.1.0"
authors = ["Tokio Contributors <[email protected]>"]
edition = "2018"
edition = "2021"
publish = false
[features]
@@ -1,24 +1,36 @@
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:5:5
|
4 | async fn missing_semicolon_or_return_type() {
| - help: a return type might be missing here: `-> _`
5 | Ok(())
| ^^^^^^ expected `()`, found `Result<(), _>`
|
= note: expected unit type `()`
found enum `Result<(), _>`
help: a return type might be missing here
|
4 | async fn missing_semicolon_or_return_type() -> _ {
| ++++
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
5 | Ok(()).expect("REASON")
| +++++++++++++++++
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:10:5
|
9 | async fn missing_return_type() {
| - help: a return type might be missing here: `-> _`
10 | return Ok(());
| ^^^^^^^^^^^^^^ expected `()`, found `Result<(), _>`
|
= note: expected unit type `()`
found enum `Result<(), _>`
help: a return type might be missing here
|
9 | async fn missing_return_type() -> _ {
| ++++
help: consider using `Result::expect` to unwrap the `Result<(), _>` value, panicking if the value is a `Result::Err`
|
10 | return Ok(());.expect("REASON")
| +++++++++++++++++
error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:23:5
@@ -41,7 +53,7 @@ error[E0308]: mismatched types
--> tests/fail/macros_type_mismatch.rs:32:5
|
30 | async fn issue_4635() {
| - help: try adding a return type: `-> i32`
| - help: try adding a return type: `-> i32`
31 | return 1;
32 | ;
| ^ expected `()`, found integer
+1 -1
View File
@@ -2,7 +2,7 @@
name = "tests-integration"
version = "0.1.0"
authors = ["Tokio Contributors <[email protected]>"]
edition = "2018"
edition = "2021"
publish = false
[[bin]]
-1
View File
@@ -7,7 +7,6 @@ use tokio::process::{Child, Command};
use tokio_test::assert_ok;
use futures::future::{self, FutureExt};
use std::convert::TryInto;
use std::env;
use std::io;
use std::process::{ExitStatus, Stdio};
+8
View File
@@ -1,3 +1,11 @@
# 2.2.0 (November 19th, 2023)
### Changed
- use `::core` qualified imports instead of `::std` inside `tokio::test` macro ([#5973])
[#5973]: https://github.com/tokio-rs/tokio/pull/5973
# 2.1.0 (April 25th, 2023)
- macros: fix typo in `#[tokio::test]` docs ([#5636])
+4 -4
View File
@@ -4,9 +4,9 @@ name = "tokio-macros"
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-macros-1.x.y" git tag.
version = "2.1.0"
edition = "2018"
rust-version = "1.56"
version = "2.2.0"
edition = "2021"
rust-version = "1.63"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
@@ -22,7 +22,7 @@ proc-macro = true
[features]
[dependencies]
proc-macro2 = "1.0.7"
proc-macro2 = "1.0.60"
quote = "1"
syn = { version = "2.0", features = ["full"] }
+11 -11
View File
@@ -126,22 +126,22 @@ impl Configuration {
}
fn build(&self) -> Result<FinalConfig, syn::Error> {
let flavor = self.flavor.unwrap_or(self.default_flavor);
use RuntimeFlavor::*;
use RuntimeFlavor as F;
let flavor = self.flavor.unwrap_or(self.default_flavor);
let worker_threads = match (flavor, self.worker_threads) {
(CurrentThread, Some((_, worker_threads_span))) => {
(F::CurrentThread, 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));
}
(CurrentThread, None) => None,
(Threaded, worker_threads) if self.rt_multi_thread_available => {
(F::CurrentThread, None) => None,
(F::Threaded, worker_threads) if self.rt_multi_thread_available => {
worker_threads.map(|(val, _span)| val)
}
(Threaded, _) => {
(F::Threaded, _) => {
let msg = if self.flavor.is_none() {
"The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled."
} else {
@@ -152,14 +152,14 @@ impl Configuration {
};
let start_paused = match (flavor, self.start_paused) {
(Threaded, Some((_, start_paused_span))) => {
(F::Threaded, Some((_, start_paused_span))) => {
let msg = format!(
"The `start_paused` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
self.macro_name(),
);
return Err(syn::Error::new(start_paused_span, msg));
}
(CurrentThread, Some((start_paused, _))) => Some(start_paused),
(F::CurrentThread, Some((start_paused, _))) => Some(start_paused),
(_, None) => None,
};
@@ -402,7 +402,7 @@ fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenSt
quote! {
let body = async #body;
#crate_path::pin!(body);
let body: ::std::pin::Pin<&mut dyn ::std::future::Future<Output = #output_type>> = body;
let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
}
} else {
quote! {
@@ -507,7 +507,7 @@ impl ItemFn {
// Inner attributes require extra care, since they're not supported on
// blocks (which is what we're expanded into) we instead lift them
// outside of the function. This matches the behaviour of `syn`.
// outside of the function. This matches the behavior of `syn`.
for mut attr in self.inner_attrs {
attr.style = syn::AttrStyle::Outer;
attr.to_tokens(&mut tokens);
@@ -586,6 +586,6 @@ impl ToTokens for Body<'_> {
for stmt in self.stmts {
stmt.to_tokens(tokens);
}
})
});
}
}
+5 -5
View File
@@ -73,27 +73,27 @@ fn clean_pattern(pat: &mut syn::Pat) {
}
}
syn::Pat::Or(or) => {
for case in or.cases.iter_mut() {
for case in &mut or.cases {
clean_pattern(case);
}
}
syn::Pat::Slice(slice) => {
for elem in slice.elems.iter_mut() {
for elem in &mut slice.elems {
clean_pattern(elem);
}
}
syn::Pat::Struct(struct_pat) => {
for field in struct_pat.fields.iter_mut() {
for field in &mut struct_pat.fields {
clean_pattern(&mut field.pat);
}
}
syn::Pat::Tuple(tuple) => {
for elem in tuple.elems.iter_mut() {
for elem in &mut tuple.elems {
clean_pattern(elem);
}
}
syn::Pat::TupleStruct(tuple) => {
for elem in tuple.elems.iter_mut() {
for elem in &mut tuple.elems {
clean_pattern(elem);
}
}
+16
View File
@@ -1,3 +1,19 @@
# 0.1.15 (March 14th, 2024)
This release bumps the MSRV of tokio-stream to 1.63.
- docs: fix typo in argument name ([#6389])
- docs: fix typo in peekable docs ([#6130])
- docs: link to latest version of tokio-util docs ([#5694])
- docs: typographic improvements ([#6262])
- stream: add `StreamExt::peekable` ([#6095])
[#5694]: https://github.com/tokio-rs/tokio/pull/5694
[#6095]: https://github.com/tokio-rs/tokio/pull/6095
[#6130]: https://github.com/tokio-rs/tokio/pull/6130
[#6262]: https://github.com/tokio-rs/tokio/pull/6262
[#6389]: https://github.com/tokio-rs/tokio/pull/6389
# 0.1.14 (April 26th, 2023)
This bugfix release bumps the minimum version of Tokio to 1.15, which is
+3 -3
View File
@@ -4,9 +4,9 @@ name = "tokio-stream"
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-stream-0.1.x" git tag.
version = "0.1.14"
version = "0.1.15"
edition = "2021"
rust-version = "1.56"
rust-version = "1.63"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
@@ -37,7 +37,7 @@ signal = ["tokio/signal"]
[dependencies]
futures-core = { version = "0.3.0" }
pin-project-lite = "0.2.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 }
+1 -1
View File
@@ -2,7 +2,7 @@
name = "tokio-stream-fuzz"
version = "0.0.0"
publish = false
edition = "2018"
edition = "2021"
[package.metadata]
cargo-fuzz = true
+4 -1
View File
@@ -54,7 +54,7 @@
//!
//! [async-stream]: https://docs.rs/async-stream
//!
//! # Conversion to and from AsyncRead/AsyncWrite
//! # Conversion to and from `AsyncRead`/`AsyncWrite`
//!
//! It is often desirable to convert a `Stream` into an [`AsyncRead`],
//! especially when dealing with plaintext formats streamed over the network.
@@ -73,6 +73,9 @@
#[macro_use]
mod macros;
mod poll_fn;
pub(crate) use poll_fn::poll_fn;
pub mod wrappers;
mod stream_ext;
+1 -1
View File
@@ -35,7 +35,7 @@ impl<I> Unpin for Once<I> {}
/// ```
pub fn once<T>(value: T) -> Once<T> {
Once {
iter: crate::iter(Some(value).into_iter()),
iter: crate::iter(Some(value)),
}
}
+35
View File
@@ -0,0 +1,35 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
pub(crate) struct PollFn<F> {
f: F,
}
pub(crate) fn poll_fn<T, F>(f: F) -> PollFn<F>
where
F: FnMut(&mut Context<'_>) -> Poll<T>,
{
PollFn { f }
}
impl<T, F> Future for PollFn<F>
where
F: FnMut(&mut Context<'_>) -> Poll<T>,
{
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
// Safety: We never construct a `Pin<&mut F>` anywhere, so accessing `f`
// mutably in an unpinned way is sound.
//
// This use of unsafe cannot be replaced with the pin-project macro
// because:
// * If we put `#[pin]` on the field, then it gives us a `Pin<&mut F>`,
// which we can't use to call the closure.
// * If we don't put `#[pin]` on the field, then it makes `PollFn` be
// unconditionally `Unpin`, which we also don't want.
let me = unsafe { Pin::into_inner_unchecked(self) };
(me.f)(cx)
}
}
+34 -8
View File
@@ -55,6 +55,9 @@ use then::Then;
mod try_next;
use try_next::TryNext;
mod peekable;
use peekable::Peekable;
cfg_time! {
pub(crate) mod timeout;
pub(crate) mod timeout_repeating;
@@ -848,8 +851,7 @@ pub trait StreamExt: Stream {
///
/// `collect` streams all values, awaiting as needed. Values are pushed into
/// a collection. A number of different target collection types are
/// supported, including [`Vec`](std::vec::Vec),
/// [`String`](std::string::String), and [`Bytes`].
/// supported, including [`Vec`], [`String`], and [`Bytes`].
///
/// [`Bytes`]: https://docs.rs/bytes/0.6.0/bytes/struct.Bytes.html
///
@@ -994,7 +996,7 @@ pub trait StreamExt: Stream {
/// assert!(timeout_stream.try_next().await.is_ok(), "expected no more timeouts");
/// # }
/// ```
#[cfg(all(feature = "time"))]
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
fn timeout(self, duration: Duration) -> Timeout<Self>
where
@@ -1005,9 +1007,8 @@ pub trait StreamExt: Stream {
/// Applies a per-item timeout to the passed stream.
///
/// `timeout_repeating()` takes an [`Interval`](tokio::time::Interval) that
/// controls the time each element of the stream has to complete before
/// timing out.
/// `timeout_repeating()` takes an [`Interval`] that controls the time each
/// element of the stream has to complete before timing out.
///
/// If the wrapped stream yields a value before the deadline is reached, the
/// value is returned. Otherwise, an error is returned. The caller may decide
@@ -1083,7 +1084,7 @@ pub trait StreamExt: Stream {
/// assert!(timeout_stream.try_next().await.is_ok(), "expected non-timeout");
/// # }
/// ```
#[cfg(all(feature = "time"))]
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
fn timeout_repeating(self, interval: Interval) -> TimeoutRepeating<Self>
where
@@ -1113,7 +1114,7 @@ pub trait StreamExt: Stream {
/// }
/// # }
/// ```
#[cfg(all(feature = "time"))]
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
fn throttle(self, duration: Duration) -> Throttle<Self>
where
@@ -1178,6 +1179,31 @@ pub trait StreamExt: Stream {
assert!(max_size > 0, "`max_size` must be non-zero.");
ChunksTimeout::new(self, max_size, duration)
}
/// Turns the stream into a peekable stream, whose next element can be peeked at without being
/// consumed.
/// ```rust
/// use tokio_stream::{self as stream, StreamExt};
///
/// #[tokio::main]
/// # async fn _unused() {}
/// # #[tokio::main(flavor = "current_thread", start_paused = true)]
/// async fn main() {
/// let iter = vec![1, 2, 3, 4].into_iter();
/// let mut stream = stream::iter(iter).peekable();
///
/// assert_eq!(*stream.peek().await.unwrap(), 1);
/// assert_eq!(*stream.peek().await.unwrap(), 1);
/// assert_eq!(stream.next().await.unwrap(), 1);
/// assert_eq!(*stream.peek().await.unwrap(), 2);
/// }
/// ```
fn peekable(self) -> Peekable<Self>
where
Self: Sized,
{
Peekable::new(self)
}
}
impl<St: ?Sized> StreamExt for St where St: Stream {}
+1 -1
View File
@@ -26,7 +26,7 @@ pin_project! {
}
}
/// Convert from a [`Stream`](crate::Stream).
/// Convert from a [`Stream`].
///
/// This trait is not intended to be used directly. Instead, call
/// [`StreamExt::collect()`](super::StreamExt::collect).
+8 -10
View File
@@ -66,25 +66,23 @@ where
T: Stream,
U: Stream<Item = T::Item>,
{
use Poll::*;
let mut done = true;
match first.poll_next(cx) {
Ready(Some(val)) => return Ready(Some(val)),
Ready(None) => {}
Pending => done = false,
Poll::Ready(Some(val)) => return Poll::Ready(Some(val)),
Poll::Ready(None) => {}
Poll::Pending => done = false,
}
match second.poll_next(cx) {
Ready(Some(val)) => return Ready(Some(val)),
Ready(None) => {}
Pending => done = false,
Poll::Ready(Some(val)) => return Poll::Ready(Some(val)),
Poll::Ready(None) => {}
Poll::Pending => done = false,
}
if done {
Ready(None)
Poll::Ready(None)
} else {
Pending
Poll::Pending
}
}
+50
View File
@@ -0,0 +1,50 @@
use std::pin::Pin;
use std::task::{Context, Poll};
use futures_core::Stream;
use pin_project_lite::pin_project;
use crate::stream_ext::Fuse;
use crate::StreamExt;
pin_project! {
/// Stream returned by the [`chain`](super::StreamExt::peekable) method.
pub struct Peekable<T: Stream> {
peek: Option<T::Item>,
#[pin]
stream: Fuse<T>,
}
}
impl<T: Stream> Peekable<T> {
pub(crate) fn new(stream: T) -> Self {
let stream = stream.fuse();
Self { peek: None, stream }
}
/// Peek at the next item in the stream.
pub async fn peek(&mut self) -> Option<&T::Item>
where
T: Unpin,
{
if let Some(ref it) = self.peek {
Some(it)
} else {
self.peek = self.next().await;
self.peek.as_ref()
}
}
}
impl<T: Stream> Stream for Peekable<T> {
type Item = T::Item;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
if let Some(it) = this.peek.take() {
Poll::Ready(Some(it))
} else {
this.stream.poll_next(cx)
}
}
}
+3 -3
View File
@@ -64,11 +64,11 @@ where
let (lower, upper) = self.stream.size_hint();
let lower = cmp::min(lower, self.remaining as usize);
let lower = cmp::min(lower, self.remaining);
let upper = match upper {
Some(x) if x < self.remaining as usize => Some(x),
_ => Some(self.remaining as usize),
Some(x) if x < self.remaining => Some(x),
_ => Some(self.remaining),
};
(lower, upper)
+117 -15
View File
@@ -1,4 +1,4 @@
use crate::Stream;
use crate::{poll_fn, Stream};
use std::borrow::Borrow;
use std::hash::Hash;
@@ -209,7 +209,7 @@ pub struct StreamMap<K, V> {
impl<K, V> StreamMap<K, V> {
/// An iterator visiting all key-value pairs in arbitrary order.
///
/// The iterator element type is &'a (K, V).
/// The iterator element type is `&'a (K, V)`.
///
/// # Examples
///
@@ -232,7 +232,7 @@ impl<K, V> StreamMap<K, V> {
/// An iterator visiting all key-value pairs mutably in arbitrary order.
///
/// The iterator element type is &'a mut (K, V).
/// The iterator element type is `&'a mut (K, V)`.
///
/// # Examples
///
@@ -289,7 +289,7 @@ impl<K, V> StreamMap<K, V> {
/// Returns an iterator visiting all keys in arbitrary order.
///
/// The iterator element type is &'a K.
/// The iterator element type is `&'a K`.
///
/// # Examples
///
@@ -312,7 +312,7 @@ impl<K, V> StreamMap<K, V> {
/// An iterator visiting all values in arbitrary order.
///
/// The iterator element type is &'a V.
/// The iterator element type is `&'a V`.
///
/// # Examples
///
@@ -335,7 +335,7 @@ impl<K, V> StreamMap<K, V> {
/// An iterator visiting all values mutably in arbitrary order.
///
/// The iterator element type is &'a mut V.
/// The iterator element type is `&'a mut V`.
///
/// # Examples
///
@@ -518,8 +518,6 @@ where
{
/// Polls the next value, includes the vec entry index
fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<Option<(usize, V::Item)>> {
use Poll::*;
let start = self::rand::thread_rng_n(self.entries.len() as u32) as usize;
let mut idx = start;
@@ -527,8 +525,8 @@ where
let (_, stream) = &mut self.entries[idx];
match Pin::new(stream).poll_next(cx) {
Ready(Some(val)) => return Ready(Some((idx, val))),
Ready(None) => {
Poll::Ready(Some(val)) => return Poll::Ready(Some((idx, val))),
Poll::Ready(None) => {
// Remove the entry
self.entries.swap_remove(idx);
@@ -542,7 +540,7 @@ where
idx = idx.wrapping_add(1) % self.entries.len();
}
}
Pending => {
Poll::Pending => {
idx = idx.wrapping_add(1) % self.entries.len();
}
}
@@ -550,9 +548,9 @@ where
// If the map is empty, then the stream is complete.
if self.entries.is_empty() {
Ready(None)
Poll::Ready(None)
} else {
Pending
Poll::Pending
}
}
}
@@ -563,6 +561,110 @@ impl<K, V> Default for StreamMap<K, V> {
}
}
impl<K, V> StreamMap<K, V>
where
K: Clone + Unpin,
V: Stream + Unpin,
{
/// Receives multiple items on this [`StreamMap`], extending the provided `buffer`.
///
/// This method returns the number of items that is appended to the `buffer`.
///
/// Note that this method does not guarantee that exactly `limit` items
/// are received. Rather, if at least one item is available, it returns
/// as many items as it can up to the given limit. This method returns
/// zero only if the `StreamMap` is empty (or if `limit` is zero).
///
/// # 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.
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
}
/// Polls to receive multiple items on this `StreamMap`, extending the provided `buffer`.
///
/// This method returns:
/// * `Poll::Pending` if no items are available but the `StreamMap` is not empty.
/// * `Poll::Ready(count)` where `count` is the number of items successfully received and
/// stored in `buffer`. This can be less than, or equal to, `limit`.
/// * `Poll::Ready(0)` if `limit` is set to zero or when the `StreamMap` is empty.
///
/// Note that this method does not guarantee that exactly `limit` items
/// are received. Rather, if at least one item is available, it returns
/// as many items as it can up to the given limit. This method returns
/// zero only if the `StreamMap` is empty (or if `limit` is zero).
pub fn poll_next_many(
&mut self,
cx: &mut Context<'_>,
buffer: &mut Vec<(K, V::Item)>,
limit: usize,
) -> Poll<usize> {
if limit == 0 || self.entries.is_empty() {
return Poll::Ready(0);
}
let mut added = 0;
let start = self::rand::thread_rng_n(self.entries.len() as u32) as usize;
let mut idx = start;
while added < limit {
// Indicates whether at least one stream returned a value when polled or not
let mut should_loop = false;
for _ in 0..self.entries.len() {
let (_, stream) = &mut self.entries[idx];
match Pin::new(stream).poll_next(cx) {
Poll::Ready(Some(val)) => {
added += 1;
let key = self.entries[idx].0.clone();
buffer.push((key, val));
should_loop = true;
idx = idx.wrapping_add(1) % self.entries.len();
}
Poll::Ready(None) => {
// Remove the entry
self.entries.swap_remove(idx);
// Check if this was the last entry, if so the cursor needs
// to wrap
if idx == self.entries.len() {
idx = 0;
} else if idx < start && start <= self.entries.len() {
// The stream being swapped into the current index has
// already been polled, so skip it.
idx = idx.wrapping_add(1) % self.entries.len();
}
}
Poll::Pending => {
idx = idx.wrapping_add(1) % self.entries.len();
}
}
}
if !should_loop {
break;
}
}
if added > 0 {
Poll::Ready(added)
} else if self.entries.is_empty() {
Poll::Ready(0)
} else {
Poll::Pending
}
}
}
impl<K, V> Stream for StreamMap<K, V>
where
K: Clone + Unpin,
@@ -660,9 +762,9 @@ mod rand {
/// Fast random number generate
///
/// Implement xorshift64+: 2 32-bit xorshift sequences added together.
/// Implement `xorshift64+`: 2 32-bit `xorshift` sequences added together.
/// Shift triplet `[17,7,16]` was calculated as indicated in Marsaglia's
/// Xorshift paper: <https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf>
/// `Xorshift` paper: <https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf>
/// This generator passes the SmallCrush suite, part of TestU01 framework:
/// <http://simul.iro.umontreal.ca/testu01/tu01.html>
#[derive(Debug)]
-1
View File
@@ -30,7 +30,6 @@ impl<R> LinesStream<R> {
}
/// Obtain a pinned reference to the inner `Lines<R>`.
#[allow(clippy::wrong_self_convention)] // https://github.com/rust-lang/rust-clippy/issues/4546
pub fn as_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Lines<R>> {
self.project().inner
}
+1 -1
View File
@@ -34,7 +34,7 @@ impl<T> ReceiverStream<T> {
///
/// [`Permit`]: struct@tokio::sync::mpsc::Permit
pub fn close(&mut self) {
self.inner.close()
self.inner.close();
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ impl<T> UnboundedReceiverStream<T> {
/// This prevents any further messages from being sent on the channel while
/// still enabling the receiver to drain messages that are buffered.
pub fn close(&mut self) {
self.inner.close()
self.inner.close();
}
}
+2 -2
View File
@@ -15,8 +15,8 @@ pub struct SignalStream {
impl SignalStream {
/// Create a new `SignalStream`.
pub fn new(interval: Signal) -> Self {
Self { inner: interval }
pub fn new(signal: Signal) -> Self {
Self { inner: signal }
}
/// Get back the inner `Signal`.
-1
View File
@@ -30,7 +30,6 @@ impl<R> SplitStream<R> {
}
/// Obtain a pinned reference to the inner `Split<R>`.
#[allow(clippy::wrong_self_convention)] // https://github.com/rust-lang/rust-clippy/issues/4546
pub fn as_pin_mut(self: Pin<&mut Self>) -> Pin<&mut Split<R>> {
self.project().inner
}
+1 -1
View File
@@ -10,7 +10,7 @@ use tokio::sync::watch::error::RecvError;
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
///
/// This stream will start by yielding the current value when the WatchStream is polled,
/// This stream will start by yielding the current value when the `WatchStream` is polled,
/// regardless of whether it was the initial value or sent afterwards,
/// unless you use [`WatchStream<T>::from_changes`].
///
+1
View File
@@ -1,5 +1,6 @@
#![warn(rust_2018_idioms)]
#![cfg(all(feature = "time", not(target_os = "wasi")))] // Wasi does not support panic recovery
#![cfg(panic = "unwind")]
use parking_lot::{const_mutex, Mutex};
use std::error::Error;
+235 -2
View File
@@ -1,14 +1,17 @@
use futures::stream::iter;
use tokio_stream::{self as stream, pending, Stream, StreamExt, StreamMap};
use tokio_test::{assert_ok, assert_pending, assert_ready, task};
use std::future::{poll_fn, Future};
use std::pin::{pin, Pin};
use std::task::Poll;
mod support {
pub(crate) mod mpsc;
}
use support::mpsc;
use std::pin::Pin;
macro_rules! assert_ready_some {
($($t:tt)*) => {
match assert_ready!($($t)*) {
@@ -328,3 +331,233 @@ fn one_ready_many_none() {
fn pin_box<T: Stream<Item = U> + 'static, U>(s: T) -> Pin<Box<dyn Stream<Item = U>>> {
Box::pin(s)
}
type UsizeStream = Pin<Box<dyn Stream<Item = usize> + Send>>;
#[tokio::test]
async fn poll_next_many_zero() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut vec![], 0)).await;
assert_eq!(n, 0);
}
#[tokio::test]
async fn poll_next_many_empty() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut vec![], 1)).await;
assert_eq!(n, 0);
}
#[tokio::test]
async fn poll_next_many_pending() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
let mut is_pending = false;
poll_fn(|cx| {
let poll = stream_map.poll_next_many(cx, &mut vec![], 1);
is_pending = poll.is_pending();
Poll::Ready(())
})
.await;
assert!(is_pending);
}
#[tokio::test]
async fn poll_next_many_not_enough() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 3)).await;
assert_eq!(n, 2);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn poll_next_many_enough() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 2)).await;
assert_eq!(n, 2);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn poll_next_many_correctly_loops_around() {
for _ in 0..10 {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([0usize, 1].into_iter())) as UsizeStream);
stream_map.insert(2, Box::pin(iter([0usize, 1, 2].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 3)).await;
assert_eq!(n, 3);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![0, 0, 0]
);
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 2)).await;
assert_eq!(n, 2);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![1, 1]
);
let n = poll_fn(|cx| stream_map.poll_next_many(cx, &mut buffer, 1)).await;
assert_eq!(n, 1);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![2]
);
}
}
#[tokio::test]
async fn next_many_zero() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut vec![], 0)).poll(cx)).await;
assert_eq!(n, 0);
}
#[tokio::test]
async fn next_many_empty() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
let n = stream_map.next_many(&mut vec![], 1).await;
assert_eq!(n, 0);
}
#[tokio::test]
async fn next_many_pending() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(pending()) as UsizeStream);
let mut is_pending = false;
poll_fn(|cx| {
let poll = pin!(stream_map.next_many(&mut vec![], 1)).poll(cx);
is_pending = poll.is_pending();
Poll::Ready(())
})
.await;
assert!(is_pending);
}
#[tokio::test]
async fn next_many_not_enough() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 3)).poll(cx)).await;
assert_eq!(n, 2);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn next_many_enough() {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([1usize].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 2)).poll(cx)).await;
assert_eq!(n, 2);
assert_eq!(buffer.len(), 2);
assert!(buffer.contains(&(0, 0)));
assert!(buffer.contains(&(1, 1)));
}
#[tokio::test]
async fn next_many_correctly_loops_around() {
for _ in 0..10 {
let mut stream_map: StreamMap<usize, UsizeStream> = StreamMap::new();
stream_map.insert(0, Box::pin(iter([0usize].into_iter())) as UsizeStream);
stream_map.insert(1, Box::pin(iter([0usize, 1].into_iter())) as UsizeStream);
stream_map.insert(2, Box::pin(iter([0usize, 1, 2].into_iter())) as UsizeStream);
let mut buffer = vec![];
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 3)).poll(cx)).await;
assert_eq!(n, 3);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![0, 0, 0]
);
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 2)).poll(cx)).await;
assert_eq!(n, 2);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![1, 1]
);
let n = poll_fn(|cx| pin!(stream_map.next_many(&mut buffer, 1)).poll(cx)).await;
assert_eq!(n, 1);
assert_eq!(
std::mem::take(&mut buffer)
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<_>>(),
vec![2]
);
}
}
+24
View File
@@ -1,3 +1,27 @@
# 0.4.4 (March 14, 2024)
- task: mark `Spawn` as `#[must_use]` ([#6371])
- test: increase MSRV to 1.63 ([#6126])
- test: update category slug ([#5953])
[#5953]: https://github.com/tokio-rs/tokio/pull/5953
[#6126]: https://github.com/tokio-rs/tokio/pull/6126
[#6371]: https://github.com/tokio-rs/tokio/pull/6371
# 0.4.3 (August 23, 2023)
- deps: fix minimum required version of `async-stream` ([#5347])
- deps: fix minimum required version of `tokio-stream` ([#4376])
- docs: improve `tokio_test::task` docs ([#5132])
- io: fetch actions from mock handle before write ([#5814])
- io: fix wait operation on mock ([#5554])
[#4376]: https://github.com/tokio-rs/tokio/pull/4376
[#5132]: https://github.com/tokio-rs/tokio/pull/5132
[#5347]: https://github.com/tokio-rs/tokio/pull/5347
[#5554]: https://github.com/tokio-rs/tokio/pull/5554
[#5814]: https://github.com/tokio-rs/tokio/pull/5814
# 0.4.2 (May 14, 2021)
- test: add `assert_elapsed!` macro ([#3728])
+3 -3
View File
@@ -4,9 +4,9 @@ name = "tokio-test"
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-test-0.4.x" git tag.
version = "0.4.2"
version = "0.4.4"
edition = "2021"
rust-version = "1.56"
rust-version = "1.63"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
@@ -14,7 +14,7 @@ homepage = "https://tokio.rs"
description = """
Testing utilities for Tokio- and futures-based code
"""
categories = ["asynchronous", "testing"]
categories = ["asynchronous", "development-tools::testing"]
[dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
+16 -2
View File
@@ -74,7 +74,7 @@ struct Inner {
}
impl Builder {
/// Return a new, empty `Builder.
/// Return a new, empty `Builder`.
pub fn new() -> Self {
Self::default()
}
@@ -409,6 +409,20 @@ impl AsyncWrite for Mock {
// If a sleep is set, it has already fired
self.inner.sleep = None;
if self.inner.actions.is_empty() {
match self.inner.poll_action(cx) {
Poll::Pending => {
// do not propagate pending
}
Poll::Ready(Some(action)) => {
self.inner.actions.push_back(action);
}
Poll::Ready(None) => {
panic!("unexpected write");
}
}
}
match self.inner.write(buf) {
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
if let Some(rem) = self.inner.remaining_wait() {
@@ -464,7 +478,7 @@ impl Drop for Mock {
Action::Read(data) => assert!(data.is_empty(), "There is still data left to read."),
Action::Write(data) => assert!(data.is_empty(), "There is still data left to write."),
_ => (),
})
});
}
}
/*
+1
View File
@@ -12,6 +12,7 @@
//! Tokio and Futures based testing utilities
pub mod io;
pub mod stream_mock;
mod macros;
pub mod task;
+12 -12
View File
@@ -22,17 +22,17 @@
#[macro_export]
macro_rules! assert_ready {
($e:expr) => {{
use core::task::Poll::*;
use core::task::Poll;
match $e {
Ready(v) => v,
Pending => panic!("pending"),
Poll::Ready(v) => v,
Poll::Pending => panic!("pending"),
}
}};
($e:expr, $($msg:tt)+) => {{
use core::task::Poll::*;
use core::task::Poll;
match $e {
Ready(v) => v,
Pending => {
Poll::Ready(v) => v,
Poll::Pending => {
panic!("pending; {}", format_args!($($msg)+))
}
}
@@ -127,17 +127,17 @@ macro_rules! assert_ready_err {
#[macro_export]
macro_rules! assert_pending {
($e:expr) => {{
use core::task::Poll::*;
use core::task::Poll;
match $e {
Pending => {}
Ready(v) => panic!("ready; value = {:?}", v),
Poll::Pending => {}
Poll::Ready(v) => panic!("ready; value = {:?}", v),
}
}};
($e:expr, $($msg:tt)+) => {{
use core::task::Poll::*;
use core::task::Poll;
match $e {
Pending => {}
Ready(v) => {
Poll::Pending => {}
Poll::Ready(v) => {
panic!("ready; value = {:?}; {}", v, format_args!($($msg)+))
}
}
+168
View File
@@ -0,0 +1,168 @@
#![cfg(not(loom))]
//! A mock stream implementing [`Stream`].
//!
//! # Overview
//! This crate provides a `StreamMock` that can be used to test code that interacts with streams.
//! It allows you to mock the behavior of a stream and control the items it yields and the waiting
//! intervals between items.
//!
//! # Usage
//! To use the `StreamMock`, you need to create a builder using [`StreamMockBuilder`]. The builder
//! allows you to enqueue actions such as returning items or waiting for a certain duration.
//!
//! # Example
//! ```rust
//!
//! use futures_util::StreamExt;
//! use std::time::Duration;
//! use tokio_test::stream_mock::StreamMockBuilder;
//!
//! async fn test_stream_mock_wait() {
//! let mut stream_mock = StreamMockBuilder::new()
//! .next(1)
//! .wait(Duration::from_millis(300))
//! .next(2)
//! .build();
//!
//! assert_eq!(stream_mock.next().await, Some(1));
//! let start = std::time::Instant::now();
//! assert_eq!(stream_mock.next().await, Some(2));
//! let elapsed = start.elapsed();
//! assert!(elapsed >= Duration::from_millis(300));
//! assert_eq!(stream_mock.next().await, None);
//! }
//! ```
use std::collections::VecDeque;
use std::pin::Pin;
use std::task::Poll;
use std::time::Duration;
use futures_core::{ready, Stream};
use std::future::Future;
use tokio::time::{sleep_until, Instant, Sleep};
#[derive(Debug, Clone)]
enum Action<T: Unpin> {
Next(T),
Wait(Duration),
}
/// A builder for [`StreamMock`]
#[derive(Debug, Clone)]
pub struct StreamMockBuilder<T: Unpin> {
actions: VecDeque<Action<T>>,
}
impl<T: Unpin> StreamMockBuilder<T> {
/// Create a new empty [`StreamMockBuilder`]
pub fn new() -> Self {
StreamMockBuilder::default()
}
/// Queue an item to be returned by the stream
pub fn next(mut self, value: T) -> Self {
self.actions.push_back(Action::Next(value));
self
}
// Queue an item to be consumed by the sink,
// commented out until Sink is implemented.
//
// pub fn consume(mut self, value: T) -> Self {
// self.actions.push_back(Action::Consume(value));
// self
// }
/// Queue the stream to wait for a duration
pub fn wait(mut self, duration: Duration) -> Self {
self.actions.push_back(Action::Wait(duration));
self
}
/// Build the [`StreamMock`]
pub fn build(self) -> StreamMock<T> {
StreamMock {
actions: self.actions,
sleep: None,
}
}
}
impl<T: Unpin> Default for StreamMockBuilder<T> {
fn default() -> Self {
StreamMockBuilder {
actions: VecDeque::new(),
}
}
}
/// A mock stream implementing [`Stream`]
///
/// See [`StreamMockBuilder`] for more information.
#[derive(Debug)]
pub struct StreamMock<T: Unpin> {
actions: VecDeque<Action<T>>,
sleep: Option<Pin<Box<Sleep>>>,
}
impl<T: Unpin> StreamMock<T> {
fn next_action(&mut self) -> Option<Action<T>> {
self.actions.pop_front()
}
}
impl<T: Unpin> Stream for StreamMock<T> {
type Item = T;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
// Try polling the sleep future first
if let Some(ref mut sleep) = self.sleep {
ready!(Pin::new(sleep).poll(cx));
// Since we're ready, discard the sleep future
self.sleep.take();
}
match self.next_action() {
Some(action) => match action {
Action::Next(item) => Poll::Ready(Some(item)),
Action::Wait(duration) => {
// Set up a sleep future and schedule this future to be polled again for it.
self.sleep = Some(Box::pin(sleep_until(Instant::now() + duration)));
cx.waker().wake_by_ref();
Poll::Pending
}
},
None => Poll::Ready(None),
}
}
}
impl<T: Unpin> Drop for StreamMock<T> {
fn drop(&mut self) {
// Avoid double panicking to make debugging easier.
if std::thread::panicking() {
return;
}
let undropped_count = self
.actions
.iter()
.filter(|action| match action {
Action::Next(_) => true,
Action::Wait(_) => false,
})
.count();
assert!(
undropped_count == 0,
"StreamMock was dropped before all actions were consumed, {} actions were not consumed",
undropped_count
);
}
}
+2 -3
View File
@@ -25,8 +25,6 @@
//! assert!(task.poll().is_ready(), "Task was not ready!");
//! ```
#![allow(clippy::mutex_atomic)]
use std::future::Future;
use std::mem;
use std::ops;
@@ -51,6 +49,7 @@ pub fn spawn<T>(task: T) -> Spawn<T> {
/// Future spawned on a mock task that can be used to poll the future or stream
/// without needing pinning or context types.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Spawn<T> {
task: MockTask,
future: Pin<Box<T>>,
@@ -127,7 +126,7 @@ impl<T: Future> Spawn<T> {
}
impl<T: Stream> Spawn<T> {
/// If `T` is a [`Stream`] then poll_next it. This will handle pinning and the context
/// If `T` is a [`Stream`] then `poll_next` it. This will handle pinning and the context
/// type for the stream.
pub fn poll_next(&mut self) -> Poll<Option<T::Item>> {
let stream = self.future.as_mut();
+23
View File
@@ -51,6 +51,29 @@ async fn write() {
mock.write_all(b"world!").await.expect("write 2");
}
#[tokio::test]
async fn write_with_handle() {
let (mut mock, mut handle) = Builder::new().build_with_handle();
handle.write(b"hello ");
handle.write(b"world!");
mock.write_all(b"hello ").await.expect("write 1");
mock.write_all(b"world!").await.expect("write 2");
}
#[tokio::test]
async fn read_with_handle() {
let (mut mock, mut handle) = Builder::new().build_with_handle();
handle.read(b"hello ");
handle.read(b"world!");
let mut buf = vec![0; 6];
mock.read_exact(&mut buf).await.expect("read 1");
assert_eq!(&buf[..], b"hello ");
mock.read_exact(&mut buf).await.expect("read 2");
assert_eq!(&buf[..], b"world!");
}
#[tokio::test]
async fn write_error() {
let error = io::Error::new(io::ErrorKind::Other, "cruel");
+50
View File
@@ -0,0 +1,50 @@
use futures_util::StreamExt;
use std::time::Duration;
use tokio_test::stream_mock::StreamMockBuilder;
#[tokio::test]
async fn test_stream_mock_empty() {
let mut stream_mock = StreamMockBuilder::<u32>::new().build();
assert_eq!(stream_mock.next().await, None);
assert_eq!(stream_mock.next().await, None);
}
#[tokio::test]
async fn test_stream_mock_items() {
let mut stream_mock = StreamMockBuilder::new().next(1).next(2).build();
assert_eq!(stream_mock.next().await, Some(1));
assert_eq!(stream_mock.next().await, Some(2));
assert_eq!(stream_mock.next().await, None);
}
#[tokio::test]
async fn test_stream_mock_wait() {
let mut stream_mock = StreamMockBuilder::new()
.next(1)
.wait(Duration::from_millis(300))
.next(2)
.build();
assert_eq!(stream_mock.next().await, Some(1));
let start = std::time::Instant::now();
assert_eq!(stream_mock.next().await, Some(2));
let elapsed = start.elapsed();
assert!(elapsed >= Duration::from_millis(300));
assert_eq!(stream_mock.next().await, None);
}
#[tokio::test]
#[should_panic(expected = "StreamMock was dropped before all actions were consumed")]
async fn test_stream_mock_drop_without_consuming_all() {
let stream_mock = StreamMockBuilder::new().next(1).next(2).build();
drop(stream_mock);
}
#[tokio::test]
#[should_panic(expected = "test panic was not masked")]
async fn test_stream_mock_drop_during_panic_doesnt_mask_panic() {
let _stream_mock = StreamMockBuilder::new().next(1).next(2).build();
panic!("test panic was not masked");
}
+45
View File
@@ -1,3 +1,48 @@
# 0.7.10 (October 24th, 2023)
### Added
- task: add `TaskTracker` ([#6033])
- task: add `JoinMap::keys` ([#6046])
- io: implement `Seek` for `SyncIoBridge` ([#6058])
### Changed
- deps: update hashbrown to 0.14 ([#6102])
[#6033]: https://github.com/tokio-rs/tokio/pull/6033
[#6046]: https://github.com/tokio-rs/tokio/pull/6046
[#6058]: https://github.com/tokio-rs/tokio/pull/6058
[#6102]: https://github.com/tokio-rs/tokio/pull/6102
# 0.7.9 (September 20th, 2023)
### Added
- io: add passthrough `AsyncRead`/`AsyncWrite` to `InspectWriter`/`InspectReader` ([#5739])
- task: add spawn blocking methods to `JoinMap` ([#5797])
- io: pass through traits for `StreamReader` and `SinkWriter` ([#5941])
- io: add `SyncIoBridge::into_inner` ([#5971])
### Fixed
- sync: handle possibly dangling reference safely ([#5812])
- util: fix broken intra-doc link ([#5849])
- compat: fix clippy warnings ([#5891])
### Documented
- codec: Specify the line ending of `LinesCodec` ([#5982])
[#5739]: https://github.com/tokio-rs/tokio/pull/5739
[#5797]: https://github.com/tokio-rs/tokio/pull/5797
[#5941]: https://github.com/tokio-rs/tokio/pull/5941
[#5971]: https://github.com/tokio-rs/tokio/pull/5971
[#5812]: https://github.com/tokio-rs/tokio/pull/5812
[#5849]: https://github.com/tokio-rs/tokio/pull/5849
[#5891]: https://github.com/tokio-rs/tokio/pull/5891
[#5982]: https://github.com/tokio-rs/tokio/pull/5982
# 0.7.8 (April 25th, 2023)
This release bumps the MSRV of tokio-util to 1.56.
+6 -5
View File
@@ -4,9 +4,9 @@ name = "tokio-util"
# - Remove path dependencies
# - Update CHANGELOG.md.
# - Create "tokio-util-0.7.x" git tag.
version = "0.7.8"
version = "0.7.10"
edition = "2021"
rust-version = "1.56"
rust-version = "1.63"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
@@ -34,18 +34,18 @@ rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
__docs_rs = ["futures-util"]
[dependencies]
tokio = { version = "1.22.0", path = "../tokio", features = ["sync"] }
tokio = { version = "1.28.0", path = "../tokio", features = ["sync"] }
bytes = "1.0.0"
futures-core = "0.3.0"
futures-sink = "0.3.0"
futures-io = { version = "0.3.0", optional = true }
futures-util = { version = "0.3.0", optional = true }
pin-project-lite = "0.2.0"
pin-project-lite = "0.2.11"
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true }
[target.'cfg(tokio_unstable)'.dependencies]
hashbrown = { version = "0.12.0", optional = true }
hashbrown = { version = "0.14.0", optional = true }
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
@@ -56,6 +56,7 @@ async-stream = "0.3.0"
futures = "0.3.0"
futures-test = "0.3.5"
parking_lot = "0.12.0"
tempfile = "3.1.0"
[package.metadata.docs.rs]
all-features = true
+12 -1
View File
@@ -20,10 +20,21 @@ pin_project! {
/// You can create a `Framed` instance by using the [`Decoder::framed`] adapter, or
/// by using the `new` function seen below.
///
/// # Cancellation safety
///
/// * [`futures_util::sink::SinkExt::send`]: if send is used as the event in a
/// `tokio::select!` statement and some other branch completes first, then it is
/// guaranteed that the message was not sent, but the message itself is lost.
/// * [`tokio_stream::StreamExt::next`]: This method is cancel safe. The returned
/// future only holds onto a reference to the underlying stream, so dropping it will
/// never lose a value.
///
/// [`Stream`]: futures_core::Stream
/// [`Sink`]: futures_sink::Sink
/// [`AsyncRead`]: tokio::io::AsyncRead
/// [`Decoder::framed`]: crate::codec::Decoder::framed()
/// [`futures_util::sink::SinkExt::send`]: futures_util::sink::SinkExt::send
/// [`tokio_stream::StreamExt::next`]: https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.next
pub struct Framed<T, U> {
#[pin]
inner: FramedImpl<T, U, RWFrames>
@@ -130,7 +141,7 @@ impl<T, U> Framed<T, U> {
/// things like gzip or TLS, which require both read and write access to the
/// underlying object.
///
/// This objects takes a stream and a readbuffer and a writebuffer. These field
/// This objects takes a stream and a `readbuffer` and a `writebuffer`. These field
/// can be obtained from an existing `Framed` with the [`into_parts`] method.
///
/// If you want to work more directly with the streams and sink, consider
+1
View File
@@ -218,6 +218,7 @@ where
// Make sure we've got room for at least one byte to read to ensure
// that we don't get a spurious 0 that looks like EOF.
state.buffer.reserve(1);
#[allow(clippy::blocks_in_conditions)]
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer).map_err(
|err| {
trace!("Got an error, going to errored state");
+10
View File
@@ -14,8 +14,18 @@ use std::task::{Context, Poll};
pin_project! {
/// A [`Stream`] of messages decoded from an [`AsyncRead`].
///
/// For examples of how to use `FramedRead` with a codec, see the
/// examples on the [`codec`] module.
///
/// # Cancellation safety
/// * [`tokio_stream::StreamExt::next`]: This method is cancel safe. The returned
/// future only holds onto a reference to the underlying stream, so dropping it will
/// never lose a value.
///
/// [`Stream`]: futures_core::Stream
/// [`AsyncRead`]: tokio::io::AsyncRead
/// [`codec`]: crate::codec
/// [`tokio_stream::StreamExt::next`]: https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.next
pub struct FramedRead<T, D> {
#[pin]
inner: FramedImpl<T, D, ReadFrame>,
+11
View File
@@ -15,7 +15,18 @@ use std::task::{Context, Poll};
pin_project! {
/// A [`Sink`] of frames encoded to an `AsyncWrite`.
///
/// For examples of how to use `FramedWrite` with a codec, see the
/// examples on the [`codec`] module.
///
/// # Cancellation safety
///
/// * [`futures_util::sink::SinkExt::send`]: if send is used as the event in a
/// `tokio::select!` statement and some other branch completes first, then it is
/// guaranteed that the message was not sent, but the message itself is lost.
///
/// [`Sink`]: futures_sink::Sink
/// [`codec`]: crate::codec
/// [`futures_util::sink::SinkExt::send`]: futures_util::sink::SinkExt::send
pub struct FramedWrite<T, E> {
#[pin]
inner: FramedImpl<T, E, WriteFrame>,
+38 -1
View File
@@ -386,6 +386,10 @@ use std::{cmp, fmt, mem};
/// `Builder` enables constructing configured length delimited codecs. Note
/// that not all configuration settings apply to both encoding and decoding. See
/// the documentation for specific methods for more detail.
///
/// Note that the if the value of [`Builder::max_frame_length`] becomes larger than
/// what can actually fit in [`Builder::length_field_length`], it will be clipped to
/// the maximum value that can fit.
#[derive(Debug, Clone, Copy)]
pub struct Builder {
// Maximum frame length
@@ -935,8 +939,12 @@ impl Builder {
/// # }
/// ```
pub fn new_codec(&self) -> LengthDelimitedCodec {
let mut builder = *self;
builder.adjust_max_frame_len();
LengthDelimitedCodec {
builder: *self,
builder,
state: DecodeState::Head,
}
}
@@ -1018,6 +1026,35 @@ impl Builder {
self.num_skip
.unwrap_or(self.length_field_offset + self.length_field_len)
}
fn adjust_max_frame_len(&mut self) {
// This function is basically `std::u64::saturating_add_signed`. Since it
// requires MSRV 1.66, its implementation is copied here.
//
// TODO: use the method from std when MSRV becomes >= 1.66
fn saturating_add_signed(num: u64, rhs: i64) -> u64 {
let (res, overflow) = num.overflowing_add(rhs as u64);
if overflow == (rhs < 0) {
res
} else if overflow {
u64::MAX
} else {
0
}
}
// Calculate the maximum number that can be represented using `length_field_len` bytes.
let max_number = match 1u64.checked_shl((8 * self.length_field_len) as u32) {
Some(shl) => shl - 1,
None => u64::MAX,
};
let max_allowed_len = saturating_add_signed(max_number, self.length_adjustment as i64);
if self.max_frame_len as u64 > max_allowed_len {
self.max_frame_len = usize::try_from(max_allowed_len).unwrap_or(usize::MAX);
}
}
}
impl Default for Builder {
+2
View File
@@ -6,6 +6,8 @@ use std::{cmp, fmt, io, str, usize};
/// A simple [`Decoder`] and [`Encoder`] implementation that splits up data into lines.
///
/// This uses the `\n` character as the line ending on all platforms.
///
/// [`Decoder`]: crate::codec::Decoder
/// [`Encoder`]: crate::codec::Encoder
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+67 -1
View File
@@ -1,4 +1,4 @@
//! Adaptors from AsyncRead/AsyncWrite to Stream/Sink
//! Adaptors from `AsyncRead`/`AsyncWrite` to Stream/Sink
//!
//! Raw I/O objects work with byte sequences, but higher-level code usually
//! wants to batch these into meaningful chunks, called "frames".
@@ -7,6 +7,71 @@
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
//! Framed streams are also known as transports.
//!
//! # Example encoding using `LinesCodec`
//!
//! The following example demonstrates how to use a codec such as [`LinesCodec`] to
//! write framed data. [`FramedWrite`] can be used to achieve this. Data sent to
//! [`FramedWrite`] are first framed according to a specific codec, and then sent to
//! an implementor of [`AsyncWrite`].
//!
//! ```
//! use futures::sink::SinkExt;
//! 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();
//!
//! // 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();
//!
//! let buffer = writer.get_ref();
//!
//! assert_eq!(buffer.as_slice(), "Hello\nWorld\n".as_bytes());
//! }
//!```
//!
//! # Example decoding using `LinesCodec`
//! The following example demonstrates how to use a codec such as [`LinesCodec`] to
//! read a stream of framed data. [`FramedRead`] can be used to achieve this. [`FramedRead`]
//! will keep reading from an [`AsyncRead`] implementor until a whole frame, according to a codec,
//! can be parsed.
//!
//!```
//! use tokio_stream::StreamExt;
//! 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();
//!
//! // 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();
//!
//! assert!(reader.next().await.is_none());
//! assert_eq!(frame1, "Hello");
//! assert_eq!(frame2, "World");
//! }
//! ```
//!
//! # The Decoder trait
//!
//! A [`Decoder`] is used together with [`FramedRead`] or [`Framed`] to turn an
@@ -248,6 +313,7 @@
//! [`AsyncWrite`]: tokio::io::AsyncWrite
//! [`Stream`]: futures_core::Stream
//! [`Sink`]: futures_sink::Sink
//! [`SinkExt`]: futures::sink::SinkExt
//! [`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
+4 -2
View File
@@ -227,12 +227,14 @@ impl<T: tokio::io::AsyncSeek> futures_io::AsyncSeek for Compat<T> {
pos: io::SeekFrom,
) -> Poll<io::Result<u64>> {
if self.seek_pos != Some(pos) {
// Ensure previous seeks have finished before starting a new one
ready!(self.as_mut().project().inner.poll_complete(cx))?;
self.as_mut().project().inner.start_seek(pos)?;
*self.as_mut().project().seek_pos = Some(pos);
}
let res = ready!(self.as_mut().project().inner.poll_complete(cx));
*self.as_mut().project().seek_pos = None;
Poll::Ready(res.map(|p| p as u64))
Poll::Ready(res)
}
}
@@ -255,7 +257,7 @@ impl<T: futures_io::AsyncSeek> tokio::io::AsyncSeek for Compat<T> {
};
let res = ready!(self.as_mut().project().inner.poll_seek(cx, pos));
*self.as_mut().project().seek_pos = None;
Poll::Ready(res.map(|p| p as u64))
Poll::Ready(res)
}
}
+34 -1
View File
@@ -116,7 +116,7 @@ where
}
fn consume(self: Pin<&mut Self>, amt: usize) {
delegate_call!(self.consume(amt))
delegate_call!(self.consume(amt));
}
}
@@ -164,6 +164,39 @@ where
}
}
impl<L, R, Item, Error> futures_sink::Sink<Item> for Either<L, R>
where
L: futures_sink::Sink<Item, Error = Error>,
R: futures_sink::Sink<Item, Error = Error>,
{
type Error = Error;
fn poll_ready(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
delegate_call!(self.poll_ready(cx))
}
fn start_send(self: Pin<&mut Self>, item: Item) -> std::result::Result<(), Self::Error> {
delegate_call!(self.start_send(item))
}
fn poll_flush(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
delegate_call!(self.poll_flush(cx))
}
fn poll_close(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
delegate_call!(self.poll_close(cx))
}
}
#[cfg(test)]
mod tests {
use super::*;
+8
View File
@@ -1,4 +1,5 @@
use bytes::Bytes;
use futures_core::stream::Stream;
use futures_sink::Sink;
use pin_project_lite::pin_project;
use std::pin::Pin;
@@ -66,3 +67,10 @@ where
self.project().inner.poll_close(cx)
}
}
impl<S: Stream> Stream for CopyToBytes<S> {
type Item = S::Item;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.project().inner.poll_next(cx)
}
}
+2 -2
View File
@@ -18,7 +18,7 @@ pin_project! {
}
impl<R, F> InspectReader<R, F> {
/// Create a new InspectReader, wrapping `reader` and calling `f` for the
/// Create a new `InspectReader`, wrapping `reader` and calling `f` for the
/// new data supplied by each read call.
///
/// The closure will only be called with an empty slice if the inner reader
@@ -100,7 +100,7 @@ pin_project! {
}
impl<W, F> InspectWriter<W, F> {
/// Create a new InspectWriter, wrapping `write` and calling `f` for the
/// Create a new `InspectWriter`, wrapping `write` and calling `f` for the
/// data successfully written by each write call.
///
/// The closure `f` will never be called with an empty slice. A vectored
+20 -2
View File
@@ -1,11 +1,12 @@
use futures_core::ready;
use futures_sink::Sink;
use futures_core::stream::Stream;
use pin_project_lite::pin_project;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::AsyncWrite;
use tokio::io::{AsyncRead, AsyncWrite};
pin_project! {
/// Convert a [`Sink`] of byte chunks into an [`AsyncWrite`].
@@ -59,7 +60,7 @@ pin_project! {
/// [`CopyToBytes`]: crate::io::CopyToBytes
/// [`Encoder`]: crate::codec::Encoder
/// [`Sink`]: futures_sink::Sink
/// [`codec`]: tokio_util::codec
/// [`codec`]: crate::codec
#[derive(Debug)]
pub struct SinkWriter<S> {
#[pin]
@@ -115,3 +116,20 @@ where
self.project().inner.poll_close(cx).map_err(Into::into)
}
}
impl<S: Stream> Stream for SinkWriter<S> {
type Item = S::Item;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.project().inner.poll_next(cx)
}
}
impl<S: AsyncRead> AsyncRead for SinkWriter<S> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<io::Result<()>> {
self.project().inner.poll_read(cx, buf)
}
}
+22 -2
View File
@@ -1,5 +1,6 @@
use bytes::Buf;
use futures_core::stream::Stream;
use futures_sink::Sink;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -165,7 +166,7 @@ where
B: Buf,
E: Into<std::io::Error>,
{
/// Convert a stream of byte chunks into an [`AsyncRead`](tokio::io::AsyncRead).
/// Convert a stream of byte chunks into an [`AsyncRead`].
///
/// The item should be a [`Result`] with the ok variant being something that
/// implements the [`Buf`] trait (e.g. `Vec<u8>` or `Bytes`). The error
@@ -301,7 +302,7 @@ where
}
// The code below is a manual expansion of the code that pin-project-lite would
// generate. This is done because pin-project-lite fails by hitting the recusion
// generate. This is done because pin-project-lite fails by hitting the recursion
// limit on this struct. (Every line of documentation is handled recursively by
// the macro.)
@@ -324,3 +325,22 @@ impl<S, B> StreamReader<S, B> {
}
}
}
impl<S: Sink<T, Error = E>, E, T> Sink<T> for StreamReader<S, E> {
type Error = E;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().inner.poll_ready(cx)
}
fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
self.project().inner.start_send(item)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().inner.poll_flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.project().inner.poll_close(cx)
}
}
+15 -2
View File
@@ -1,6 +1,7 @@
use std::io::{BufRead, Read, Write};
use std::io::{BufRead, Read, Seek, Write};
use tokio::io::{
AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt,
AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite,
AsyncWriteExt,
};
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
@@ -79,6 +80,13 @@ impl<T: AsyncWrite + Unpin> Write for SyncIoBridge<T> {
}
}
impl<T: AsyncSeek + Unpin> Seek for SyncIoBridge<T> {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
let src = &mut self.src;
self.rt.block_on(AsyncSeekExt::seek(src, pos))
}
}
// Because https://doc.rust-lang.org/std/io/trait.Write.html#method.is_write_vectored is at the time
// of this writing still unstable, we expose this as part of a standalone method.
impl<T: AsyncWrite> SyncIoBridge<T> {
@@ -140,4 +148,9 @@ impl<T: Unpin> SyncIoBridge<T> {
pub fn new_with_handle(src: T, rt: tokio::runtime::Handle) -> Self {
Self { src, rt }
}
/// Consume this bridge, returning the underlying stream.
pub fn into_inner(self) -> T {
self.src
}
}
+1 -148
View File
@@ -57,151 +57,4 @@ pub mod either;
pub use bytes;
#[cfg(any(feature = "io", feature = "codec"))]
mod util {
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use bytes::{Buf, BufMut};
use futures_core::ready;
use std::io::{self, IoSlice};
use std::mem::MaybeUninit;
use std::pin::Pin;
use std::task::{Context, Poll};
/// Try to read data from an `AsyncRead` into an implementer of the [`BufMut`] trait.
///
/// [`BufMut`]: bytes::Buf
///
/// # Example
///
/// ```
/// use bytes::{Bytes, BytesMut};
/// use tokio_stream as stream;
/// use tokio::io::Result;
/// use tokio_util::io::{StreamReader, poll_read_buf};
/// use futures::future::poll_fn;
/// use std::pin::Pin;
/// # #[tokio::main]
/// # async fn main() -> std::io::Result<()> {
///
/// // Create a reader from an iterator. This particular reader will always be
/// // ready.
/// let mut read = StreamReader::new(stream::iter(vec![Result::Ok(Bytes::from_static(&[0, 1, 2, 3]))]));
///
/// let mut buf = BytesMut::new();
/// let mut reads = 0;
///
/// loop {
/// reads += 1;
/// let n = poll_fn(|cx| poll_read_buf(Pin::new(&mut read), cx, &mut buf)).await?;
///
/// if n == 0 {
/// break;
/// }
/// }
///
/// // one or more reads might be necessary.
/// assert!(reads >= 1);
/// assert_eq!(&buf[..], &[0, 1, 2, 3]);
/// # Ok(())
/// # }
/// ```
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
pub fn poll_read_buf<T: AsyncRead, B: BufMut>(
io: Pin<&mut T>,
cx: &mut Context<'_>,
buf: &mut B,
) -> Poll<io::Result<usize>> {
if !buf.has_remaining_mut() {
return Poll::Ready(Ok(0));
}
let n = {
let dst = buf.chunk_mut();
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
// transparent wrapper around `[MaybeUninit<u8>]`.
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
let mut buf = ReadBuf::uninit(dst);
let ptr = buf.filled().as_ptr();
ready!(io.poll_read(cx, &mut buf)?);
// Ensure the pointer does not change from under us
assert_eq!(ptr, buf.filled().as_ptr());
buf.filled().len()
};
// Safety: This is guaranteed to be the number of initialized (and read)
// bytes due to the invariants provided by `ReadBuf::filled`.
unsafe {
buf.advance_mut(n);
}
Poll::Ready(Ok(n))
}
/// Try to write data from an implementer of the [`Buf`] trait to an
/// [`AsyncWrite`], advancing the buffer's internal cursor.
///
/// This function will use [vectored writes] when the [`AsyncWrite`] supports
/// vectored writes.
///
/// # Examples
///
/// [`File`] implements [`AsyncWrite`] and [`Cursor<&[u8]>`] implements
/// [`Buf`]:
///
/// ```no_run
/// use tokio_util::io::poll_write_buf;
/// use tokio::io;
/// use tokio::fs::File;
///
/// use bytes::Buf;
/// use std::io::Cursor;
/// use std::pin::Pin;
/// use futures::future::poll_fn;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut file = File::create("foo.txt").await?;
/// let mut buf = Cursor::new(b"data to write");
///
/// // Loop until the entire contents of the buffer are written to
/// // the file.
/// while buf.has_remaining() {
/// poll_fn(|cx| poll_write_buf(Pin::new(&mut file), cx, &mut buf)).await?;
/// }
///
/// Ok(())
/// }
/// ```
///
/// [`Buf`]: bytes::Buf
/// [`AsyncWrite`]: tokio::io::AsyncWrite
/// [`File`]: tokio::fs::File
/// [vectored writes]: tokio::io::AsyncWrite::poll_write_vectored
#[cfg_attr(not(feature = "io"), allow(unreachable_pub))]
pub fn poll_write_buf<T: AsyncWrite, B: Buf>(
io: Pin<&mut T>,
cx: &mut Context<'_>,
buf: &mut B,
) -> Poll<io::Result<usize>> {
const MAX_BUFS: usize = 64;
if !buf.has_remaining() {
return Poll::Ready(Ok(0));
}
let n = if io.is_write_vectored() {
let mut slices = [IoSlice::new(&[]); MAX_BUFS];
let cnt = buf.chunks_vectored(&mut slices);
ready!(io.poll_write_vectored(cx, &slices[..cnt]))?
} else {
ready!(io.poll_write(cx, buf.chunk()))?
};
buf.advance(n);
Poll::Ready(Ok(n))
}
}
mod util;
+22 -8
View File
@@ -4,6 +4,7 @@ pub(crate) mod guard;
mod tree_node;
use crate::loom::sync::Arc;
use crate::util::MaybeDangling;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
@@ -77,11 +78,23 @@ pin_project! {
/// [`CancellationToken`] by value instead of using a reference.
#[must_use = "futures do nothing unless polled"]
pub struct WaitForCancellationFutureOwned {
// Since `future` is the first field, it is dropped before the
// cancellation_token field. This ensures that the reference inside the
// `Notified` remains valid.
// This field internally has a reference to the cancellation token, but camouflages
// the relationship with `'static`. To avoid Undefined Behavior, we must ensure
// that the reference is only used while the cancellation token is still alive. To
// do that, we ensure that the future is the first field, so that it is dropped
// before the cancellation token.
//
// We use `MaybeDanglingFuture` here because without it, the compiler could assert
// the reference inside `future` to be valid even after the destructor of that
// field runs. (Specifically, when the `WaitForCancellationFutureOwned` is passed
// as an argument to a function, the reference can be asserted to be valid for the
// rest of that function.) To avoid that, we use `MaybeDangling` which tells the
// compiler that the reference stored inside it might not be valid.
//
// See <https://users.rust-lang.org/t/unsafe-code-review-semi-owning-weak-rwlock-t-guard/95706>
// for more info.
#[pin]
future: tokio::sync::futures::Notified<'static>,
future: MaybeDangling<tokio::sync::futures::Notified<'static>>,
cancellation_token: CancellationToken,
}
}
@@ -120,7 +133,7 @@ impl Default for CancellationToken {
}
impl CancellationToken {
/// Creates a new CancellationToken in the non-cancelled state.
/// Creates a new `CancellationToken` in the non-cancelled state.
pub fn new() -> CancellationToken {
CancellationToken {
inner: Arc::new(tree_node::TreeNode::new()),
@@ -279,7 +292,7 @@ impl WaitForCancellationFutureOwned {
// # Safety
//
// cancellation_token is dropped after future due to the field ordering.
future: unsafe { Self::new_future(&cancellation_token) },
future: MaybeDangling::new(unsafe { Self::new_future(&cancellation_token) }),
cancellation_token,
}
}
@@ -320,8 +333,9 @@ impl Future for WaitForCancellationFutureOwned {
// # Safety
//
// cancellation_token is dropped after future due to the field ordering.
this.future
.set(unsafe { Self::new_future(this.cancellation_token) });
this.future.set(MaybeDangling::new(unsafe {
Self::new_future(this.cancellation_token)
}));
}
}
}
@@ -1,16 +1,16 @@
//! This mod provides the logic for the inner tree structure of the CancellationToken.
//! This mod provides the logic for the inner tree structure of the `CancellationToken`.
//!
//! CancellationTokens are only light handles with references to TreeNode.
//! All the logic is actually implemented in the TreeNode.
//! `CancellationTokens` are only light handles with references to [`TreeNode`].
//! All the logic is actually implemented in the [`TreeNode`].
//!
//! A TreeNode is part of the cancellation tree and may have one parent and an arbitrary number of
//! A [`TreeNode`] is part of the cancellation tree and may have one parent and an arbitrary number of
//! children.
//!
//! A TreeNode can receive the request to perform a cancellation through a CancellationToken.
//! A [`TreeNode`] can receive the request to perform a cancellation through a `CancellationToken`.
//! This cancellation request will cancel the node and all of its descendants.
//!
//! As soon as a node cannot get cancelled any more (because it was already cancelled or it has no
//! more CancellationTokens pointing to it any more), it gets removed from the tree, to keep the
//! more `CancellationTokens` pointing to it any more), it gets removed from the tree, to keep the
//! tree as small as possible.
//!
//! # Invariants
@@ -66,7 +66,7 @@ impl TreeNode {
}
}
/// The data contained inside a TreeNode.
/// The data contained inside a `TreeNode`.
///
/// This struct exists so that the data of the node can be wrapped
/// in a Mutex.
@@ -178,6 +178,8 @@ where
locked_node = node.inner.lock().unwrap();
locked_parent
}
// https://github.com/tokio-rs/tokio/pull/6273#discussion_r1443752911
#[allow(clippy::unnecessary_literal_unwrap)]
Err(TryLockError::Poisoned(err)) => Err(err).unwrap(),
};
@@ -196,7 +198,7 @@ where
/// `parent` MUST have been a parent of the node when they both got locked,
/// otherwise there is a potential for a deadlock as invariant #2 would be violated.
///
/// To acquire the locks for node and parent, use [with_locked_node_and_parent].
/// To acquire the locks for node and parent, use [`with_locked_node_and_parent`].
fn move_children_to_parent(node: &mut Inner, parent: &mut Inner) {
// Pre-allocate in the parent, for performance
parent.children.reserve(node.children.len());
@@ -214,7 +216,7 @@ fn move_children_to_parent(node: &mut Inner, parent: &mut Inner) {
/// Removes a child from the parent.
///
/// `parent` MUST be the parent of `node`.
/// To acquire the locks for node and parent, use [with_locked_node_and_parent].
/// To acquire the locks for node and parent, use [`with_locked_node_and_parent`].
fn remove_child(parent: &mut Inner, mut node: MutexGuard<'_, Inner>) {
// Query the position from where to remove a node
let pos = node.parent_idx;
+1 -1
View File
@@ -303,7 +303,7 @@ impl<T> Clone for PollSender<T> {
}
}
impl<T: Send + 'static> Sink<T> for PollSender<T> {
impl<T: Send> Sink<T> for PollSender<T> {
type Error = PollSendError<T>;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+1 -1
View File
@@ -29,7 +29,7 @@ impl PollSemaphore {
/// Closes the semaphore.
pub fn close(&self) {
self.semaphore.close()
self.semaphore.close();
}
/// Obtain a clone of the inner semaphore.
+2 -16
View File
@@ -1,7 +1,6 @@
use std::alloc::Layout;
use std::fmt;
use std::future::Future;
use std::marker::PhantomData;
use std::future::{self, Future};
use std::mem::{self, ManuallyDrop};
use std::pin::Pin;
use std::ptr;
@@ -61,7 +60,7 @@ impl<'a, T> ReusableBoxFuture<'a, T> {
F: Future + Send + 'a,
{
// future::Pending<T> is a ZST so this never allocates.
let boxed = mem::replace(&mut this.boxed, Box::pin(Pending(PhantomData)));
let boxed = mem::replace(&mut this.boxed, Box::pin(future::pending()));
reuse_pin_box(boxed, future, |boxed| this.boxed = Pin::from(boxed))
}
@@ -156,16 +155,3 @@ impl<O, F: FnOnce() -> O> Drop for CallOnDrop<O, F> {
f();
}
}
/// The same as `std::future::Pending<T>`; we can't use that type directly because on rustc
/// versions <1.60 it didn't unconditionally implement `Send`.
// FIXME: use `std::future::Pending<T>` once the MSRV is >=1.60
struct Pending<T>(PhantomData<fn() -> T>);
impl<T> Future for Pending<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Pending
}
}

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