Compare commits

...
243 Commits
Author SHA1 Message Date
Alice Ryhl 91b9850505 chore: prepare Tokio v1.16.1 release (#4438) 2022-01-28 10:30:23 +01:00
Alice Ryhl 3c467056e9 io: fix take pointer check (#4437) 2022-01-28 10:04:13 +01:00
Carl Lerche afd2189eec chore: prepare Tokio v1.16 release (#4431) 2022-01-27 15:16:08 -08:00
Carl Lerche 986b88b3f1 chore: update year in LICENSE files (#4429) 2022-01-27 13:36:21 -08:00
Mark Drobnak 257053e40b util: add spawn_pinned (#3370) 2022-01-27 15:26:09 +01:00
Daniel Henry-Mantilla 5af9e0db2b sync: add blocking lock methods to RwLock (#4425) 2022-01-27 15:15:18 +01:00
Cecile Tonglet 8f77ee8609 net: add generic trait to combine UnixListener and TcpListener (#4385) 2022-01-27 15:13:37 +01:00
Ivan Petkov 2747043f6f tests: enable running wasm32-unknown-unknown tests (#4421)
* Several of tokio's features (e.g. the channel implementation) do not
  need a runtime to work, and can be compiled and used for
  wasm32-unknown-unknown targets
* This change enables running tests for the `sync` and `macros` features
  so that we can note any regressions there
2022-01-27 15:07:52 +01:00
Luiz Carlos 2a5071fc2d feat: implement Framed::map_codec (#4427) 2022-01-27 12:37:30 +01:00
Alice Ryhl 621790e165 io: fix take when using evil reader (#4428) 2022-01-27 11:45:04 +01:00
Braulio Valdivielso Martínez 7aad428994 fs: guarantee that File::write will attempt the write even if the runtime shuts down (#4316) 2022-01-25 19:46:06 +01:00
Jamie 9e38ebcaa9 task: mark JoinHandle as UnwindSafe (#4418) 2022-01-24 11:07:37 +01:00
Carl Lerche 9a57a6a7c4 chore: setup ARM CI with CircleCI (#4417) 2022-01-22 13:49:07 -08:00
Carl Lerche 24f4ee31f0 runtime: expand on runtime metrics (#4373)
This patch adds more runtime metrics. The API is still unstable.
2022-01-21 22:17:38 -08:00
Carl Lerche 4eed411519 rt: reduce no-op wakeups in the multi-threaded scheduler (#4383)
This patch reduces the number of times worker threads wake up without having
work to do in the multi-threaded scheduler. Unnecessary wake-ups are expensive
and slow down the scheduler. I have observed this change reduce no-op wakes
by up to 50%.

The multi-threaded scheduler is work-stealing. When a worker has tasks to process,
and other workers are idle (parked), these idle workers must be unparked so that
they can steal work from the busy worker. However, unparking threads is expensive,
so there is an optimization that avoids unparking a worker if there already exists
workers in a "searching" state (the worker is unparked and looking for work). This
works pretty well, but transitioning from 1 "searching" worker to 0 searching workers
introduces a race condition where a thread unpark can be lost:

* thread 1: last searching worker about to exit searching state
* thread 2: needs to unpark a thread, but skip because there is a searching worker.
* thread 1: exits searching state w/o seeing thread 2's work.

Because this should be a rare condition, Tokio solves this by always unparking a
new worker when the current worker:

* is the last searching worker
* is transitioning out of searching
* has work to process.

When the newly unparked worker wakes, if the race condition described above
happened, "thread 2"'s work will be found. Otherwise, it will just go back to sleep.

Now we come to the issue at hand. A bug incorrectly set a worker to "searching"
when the I/O driver unparked the thread. In a situation where the scheduler was
only partially under load and is able to operate with 1 active worker, the I/O driver
would unpark the thread when new I/O events are received, incorrectly transition
it to "searching", find new work generated by inbound I/O events, incorrectly
transition itself from the last searcher -> no searchers, and unpark a new thread.
This new thread would wake, find no work and go back to sleep.

Note that, when the scheduler is fully saturated, this change will make no impact
as most workers are always unparked and the optimization to avoid unparking
threads described at the top apply.
2022-01-13 15:18:32 -08:00
Carl Lerche 16a8404967 chore: fix ci to track Rust 1.58 (#4401) 2022-01-13 20:49:13 +01:00
Matthew Pomes 089eeae24b runtime: add better error message when spawning blocking threads (#4398) 2022-01-12 19:49:57 +01:00
Taiki Endo e255a265d3 ci: upgrade to new nightly (#4396) 2022-01-13 00:27:05 +09:00
Carl Lerche e951d55720 rt: refactor current-thread scheduler (take 2) (#4395)
Re-applies #4377 and fixes the bug resulting in Hyper's double panic.

Revert: #4394

Original PR:

This PR does some refactoring to the current-thread scheduler bringing it closer to the structure of the
multi-threaded scheduler. More specifically, the core scheduler data is stored in a Core struct and that
struct is passed around as a "token" indicating permission to do work. The Core structure is also stored
in the thread-local context.

This refactor is intended to support #4373, making it easier to track counters in more locations in the
current-thread scheduler.

I tried to keep commits small, but the "set Core in thread-local context" is both the biggest commit and
the key one.
2022-01-11 18:39:56 -08:00
Taiki Endo 1d698b5a90 chore: test hyper on CI (#4393) 2022-01-11 13:38:18 -08:00
Carl Lerche 867f137dc9 Revert "rt: refactor current-thread scheduler (#4377)" (#4394)
This reverts commit cc8ad367a0.
2022-01-11 11:57:14 -08:00
Carl Lerche aea26b322c Revert "Update mio to 0.8 (#4270)" and dependent changes (#4392)
This reverts commits:
 * ee0e811a36
 * 49a9dc6743
 * 0190831ec1
 * 43cdb2cb50
 * 96370ba4ce
 * a9d9bde068
2022-01-11 10:53:45 -08:00
0xd34d10cc bcb968af84 sync: add blocking_recv to oneshot::Receiver (#4334) 2022-01-10 14:42:16 +01:00
Matt Schulte cec1bc151e watch: document recursive borrow deadlock (#4360)
Under the hood, the watch channel uses a RwLock to implement reading
(borrow) and writing (send). This may cause a deadlock if a user has
concurrent borrows on the same thread. This is most likely to occur  due
to a recursive borrow.

This PR adds documentation to describe the deadlock so that future users
of the watch channel will be aware.
2022-01-10 11:41:01 +01:00
Alice Ryhl 1601de1196 process: drop pipe after child exits in wait_with_output (#4315) 2022-01-10 11:40:28 +01:00
b-naber c800deaacc util: add shrink_to_fit and compact methods to DelayQueue (#4170) 2022-01-09 12:41:30 +01:00
Jamie ac2343d984 net: add UnwindSafe impl to PollEvented (#4384) 2022-01-08 13:58:26 +01:00
Trey Smith 553cc3b194 net: document that port 0 picks a random port (#4386) 2022-01-08 13:21:11 +01:00
Eliza Weisman cb9a68eb1a examples: update tracing-subscriber to 0.3 (#4227) 2022-01-08 13:13:28 +09:00
Carl Lerche cc8ad367a0 rt: refactor current-thread scheduler (#4377)
This patch does some refactoring to the current-thread scheduler bringing it closer to the
structure of the multi-threaded scheduler. More specifically, the core scheduler data is stored
in a Core struct and that struct is passed around as a "token" indicating permission to do
work. The Core structure is also stored in the thread-local context.

This refactor is intended to support #4373, making it easier to track counters in more locations
in the current-thread scheduler.
2022-01-06 17:19:26 -08:00
Rob Ede 25e5141c36 test: fix version requirement of tokio-stream (#4376) 2022-01-04 22:01:12 +01:00
Tom Dohrmann 4a12163d7c util: add mutable reference getters for codecs to pinned Framed (#4372) 2022-01-03 22:21:43 +01:00
Elichai Turkel 12dd06336d sync: add a has_changed method to watch::Receiver (#4342) 2021-12-31 16:23:29 +01:00
Alice Ryhl c301f6d83a sync: don't inherit Send from parking_lot::*Guard (#4359) 2021-12-31 15:57:56 +01:00
Braulio Valdivielso Martínez fb35c83944 tokio-stream: add StreamExt::map_while (#4351)
Fixes #4337

Rust 1.57 stabilized the `Iterator::map_while` API. This PR adds the
same functionality to the `StreamExt` trait, to keep parity.
2021-12-31 22:53:09 +09:00
Taiki Endo 43cdb2cb50 net: add tos and set_tos methods to TCP and UDP sockets (#4366) 2021-12-31 21:19:14 +09:00
Taiki Endo 49a9dc6743 net: add buffer size methods to UdpSocket (#4363)
This adds the following methods:

- UdpSocket::set_send_buffer_size
- UdpSocket::send_buffer_size
- UdpSocket::set_recv_buffer_size
- UdpSocket::recv_buffer_size
2021-12-31 20:47:34 +09:00
Taiki Endo 96370ba4ce net: add TcpSocket::take_error (#4364) 2021-12-31 11:25:50 +01:00
Taiki Endo a9d9bde068 net: add UdpSocket::peer_addr (#4362) 2021-12-31 11:23:04 +01:00
Taiki Endo 0190831ec1 net: fix build error on master (#4361) 2021-12-31 11:21:23 +01:00
Taiki Endo ee0e811a36 Update mio to 0.8 (#4270) 2021-12-31 12:28:14 +09:00
Alice Ryhl 47feaa7a89 io: fix clippy lint in write_all (#4358) 2021-12-30 15:31:11 +01:00
Alice Ryhl dda8da75d0 stream: add StreamExt::then (#4355) 2021-12-30 15:28:13 +01:00
David Kleingeld dc1894105b codec: improve Builder::max_frame_length docs (#4352) 2021-12-28 15:08:37 +01:00
Eliza Weisman 78e0f0b42a docs: improve RustDoc for unstable features (#4331)
Currently, the docs.rs documentation for tokio is built without
--cfg tokio_unstable set. This means that unstable features are not shown in
the API docs, making them difficutl to discover. Clearly, we do want to
document the existence of unstable APIs, given that there's a section in
the lib.rs documentation listing them, so it would be better if it was
also possible to determine what APIs an unstable feature enables when
reading the RustDoc documentation.

This branch changes the docs.rs metadata to also pass --cfg tokio_unstable
when building the documentation. It turns out that it's
necessary to separately pass the cfg flag to both RustDoc and rustc,
or else the tracing dependency, which is only enabled in
target.cfg(tokio_unstable).dependencies, will be missing and the build
will fail.

In addition, I made some minor improvements to the docs for unstable
features. Some links in the task::Builder docs were broken, and the
required tokio_unstable cfg was missing from the doc(cfg(...))
attributes. Furthermore, I added a note in the top-level docs for
unstable APIs, stating that they are unstable and linking back to the
section in the crate-level docs that explains how to enable unstable
features.

Fixes #4328
2021-12-21 11:11:48 -08:00
Jinhua Tan e55f3d4398 examples: make the introduction in examples/Cargo.toml more clear (#4333) 2021-12-21 14:02:18 +01:00
Alice Ryhl 8582363b4e stats: mark stats feature unstable in lib.rs (#4327) 2021-12-18 13:40:24 +01:00
Cyborus04 c3fbaba1f9 io: replace use of transmute with pointer manipulations (#4307) 2021-12-17 20:00:24 +01:00
Fabien GaudandFabien Gaud 22e6aef6e7 net: allow to set linger on TcpSocket (#4324)
For now, this is only allowed on TcpStream. This is a problem when one
want to disable lingering (i.e. set it to Duration(0, 0)). Without being
able to set it prior to the connect call, if the connect future is
dropped it would leave sockets in a TIME_WAIT state.

Co-authored-by: Fabien Gaud <[email protected]>
2021-12-16 20:34:00 +01:00
Carl Lerche f64673580d chore: prepare Tokio v1.15.0 release (#4320)
Includes `tokio-macros` v1.7.0
2021-12-15 10:36:09 -08:00
Braulio Valdivielso Martínez 54e6693dff time: make timeout robust against budget-depleting tasks (#4314) 2021-12-15 11:59:21 +01:00
Zahari Dichev 4e3268d222 tracing: instrument more resources (#4302)
This PR adds instrumentation to more resources from the sync package. The new
instrumentation requires the `tokio_unstable` feature flag to enable.
2021-12-14 14:04:19 -08:00
Toby Lawrence 4b6bb1d9a7 chore(util): start v0.7 release cycle (#4313)
* chore(util): start v0.7 release cycle

Signed-off-by: Toby Lawrence <[email protected]>
2021-12-10 13:16:17 -05:00
Braulio Valdivielso Martínez eb1af7f29c io: make tokio::io::empty cooperative (#4300)
Reads and buffered reads from a `tokio::io::empty` were always marked
as ready. That makes sense, given that there is nothing to wait for.
However, doing repeated reads on the `empty` could stall the event
loop and prevent other tasks from making progress.

This change uses tokio's coop system to yield control back to the
executor when appropriate.

Note that the issue that originally triggered this PR is not fixed
yet, because the `timeout` function will not poll the timer after
empty::read runs out of budget. A different change will be needed to
address that.

Refs: #4291
2021-12-10 11:08:49 +01:00
Toby Lawrence 0bc9160e25 chore: update labeler.yml to drop filepath prefixes (#4308)
- update labeler.yml to drop filepath prefixes
- make sure labeler enforces labels over lifetime of PR
2021-12-09 10:40:27 -05:00
Shin Seunghun 4c571b55b1 runtime: fix typo in the task modules (#4306) 2021-12-08 13:30:52 +01:00
Naruto210 60ba634d60 time: fix typo in tokio-time document (#4304) 2021-12-07 15:49:45 +01:00
Shin Seunghun f73ed1fdba runtime: fix typo (#4303) 2021-12-07 11:07:00 +01:00
Ivan Petkov ee4b2ede83 process: add as_std() method to Command (#4295) 2021-12-03 08:51:27 +01:00
Shin Seunghun 64da914d17 time: add doc links in entry doc (#4293) 2021-12-02 15:27:46 +01:00
kenmasu d764ba5816 io: call tcp.set_nonblocking(true) in AsyncFd example. (#4292) 2021-12-02 11:01:23 +01:00
Alice Ryhl 65fb0210d5 tokio: add 1.14.x to LTS releases (#4273) 2021-11-24 09:18:50 +01:00
Axel Forsman a77b2fbab2 io: extend AsyncFdReadyGuard method lifetimes (#4267)
The implicit elided lifetimes of the `AsyncFd` references in return
types of methods on `AsyncFdReadyGuard` resolved to that of `&self`.
However that lifetime is smaller than `'a` since `self` contains an `&'a
AsyncFd` reference. This will not change so the change also does not
lessen future proofing.
2021-11-23 13:56:31 +01:00
oblique 347c0cdaba time: add Interval::reset method (#4248) 2021-11-23 13:51:07 +01:00
Shin Seunghun 2c0e5c9704 time: document missing timer panics (#4247) 2021-11-23 12:14:08 +01:00
omjadas 2a614fba0d docs: document that parking_lot is enabled by full (#4269) 2021-11-23 12:11:42 +01:00
David Pedersen 3b339024f0 stream: impl Extend for StreamMap (#4272)
## Motivation

This allows `StreamMap` to be used with [`futures::stream::StreamExt::collect`][collect].

My use case is something like this:

```rust
let stream_map: StreamMap<_, _> = things
    .into_iter()
    .map(|thing| make_stream(thing)) // iterator of futures
    .collect::<FuturesUnordered<_>>() // stream of streams
    .collect::<StreamMap<_, _>>() // combine all the inner streams into one
    .await;

async fn make_stream(thing: Thing) -> impl Stream { ... }
```

[collect]: https://docs.rs/futures/0.3.17/futures/stream/trait.StreamExt.html#method.collect

## Solution

Add `Extend` impl that delegates to the inner `Vec`.
2021-11-23 11:54:06 +01:00
Taiki Endo 1a423b3322 chore: remove doc URL from Cargo.toml (#4251)
https://doc.rust-lang.org/cargo/reference/manifest.html#the-documentation-field

> If no URL is specified in the manifest file, crates.io will
> automatically link your crate to the corresponding docs.rs page.
2021-11-23 11:53:32 +01:00
Taiki Endo a8b662f643 ci: upgrade to new nightly (#4268) 2021-11-23 19:29:57 +09:00
Taiki Endo cf3206842c chore: bump MSRV to 1.46 (#4254) 2021-11-23 12:09:24 +09:00
Taiki Endo 8943e8aeef macros: address remainging clippy::semicolon_if_nothing_returned warning (#4252) 2021-11-22 18:41:17 +09:00
Taiki Endo fe770dc509 chore: fix newly added warnings (#4253) 2021-11-22 18:40:57 +09:00
Taiki Endo 8b6542fc3e chore: update nix to 0.23 (#4255) 2021-11-22 06:13:14 +09:00
Alice Ryhl b1afd95994 sync: elaborate on cross-runtime message passing (#4240) 2021-11-17 16:02:07 +01:00
Alice Ryhl 095b5dcf93 net: add connect_std -> TcpSocket doc alias (#4219) 2021-11-16 22:22:47 +01:00
Alice Ryhl 623c09c52c chore: prepare tokio-macros 1.6.0 (#4239) 2021-11-16 09:54:07 +01:00
Eliza Weisman 884a9a4b18 chore: prepare Tokio v1.14.0 (#4234)
# 1.14.0 (November 15, 2021)

### Fixed

- macros: fix compiler errors when using `mut` patterns in `select!`
  ([#4211])
- sync: fix a data race between `oneshot::Sender::send` and awaiting a
  `oneshot::Receiver` when the oneshot has been closed ([#4226])
- sync: make `AtomicWaker` panic safe ([#3689])
- runtime: fix basic scheduler dropping tasks outside a runtime context
  ([#4213])

### Added

- stats: add `RuntimeStats::busy_duration_total` ([#4179], [#4223])

### Changed

- io: updated `copy` buffer size to match `std::io::copy` ([#4209])

### Documented

- io: rename buffer to file in doc-test ([#4230])
- sync: fix Notify example ([#4212])

[#4211]: https://github.com/tokio-rs/tokio/pull/4211
[#4226]: https://github.com/tokio-rs/tokio/pull/4226
[#3689]: https://github.com/tokio-rs/tokio/pull/3689
[#4213]: https://github.com/tokio-rs/tokio/pull/4213
[#4179]: https://github.com/tokio-rs/tokio/pull/4179
[#4223]: https://github.com/tokio-rs/tokio/pull/4223
[#4209]: https://github.com/tokio-rs/tokio/pull/4209
[#4230]: https://github.com/tokio-rs/tokio/pull/4230
[#4212]: https://github.com/tokio-rs/tokio/pull/4212
2021-11-15 16:39:09 -08:00
Eliza Weisman e0be45e49b Merge v1.13.1 (#4236)
Refs: #4235
2021-11-15 16:09:33 -08:00
Eliza Weisman 26f0938bf3 oneshot: document UnsafeCell invariants (#4229)
Depends on #4226

## Motivation

Currently, the safety invariants and synchronization strategy used in
`tokio::sync::oneshot` are not particularly obvious, especially to a new
reader. It would be nice to better document this code to make these
invariants clearer.

## Solution

This branch adds `SAFETY:` comments to the `oneshot` channel
implementation. In particular, I've focused on documenting the
invariants around when the inner `UnsafeCell` that stores the value can
be accessed by the sender and receiver sides of the channel.

I still want to take a closer look at when the waker cells can be set,
and I'd like to add more documentation there in a follow-up branch.

Signed-off-by: Eliza Weisman <[email protected]>
2021-11-15 12:46:12 -08:00
Eliza Weisman 4b78ed4d68 sync: fix racy UnsafeCell access on a closed oneshot (#4226) 2021-11-14 18:03:41 +01:00
Shin Seunghun 79d7a625d0 io: rename buffer to file in doc-test (#4230) 2021-11-13 11:31:42 +01:00
Alice Ryhl ccf855ec24 stats: only expose busy_duration_total (#4223) 2021-11-11 21:53:29 +01:00
Alice Ryhl 579f61106d ci: check for minimal versions with tokio_unstable (#4224) 2021-11-11 21:53:15 +01:00
Alice Ryhl f45320a9c0 stats: add busy_duration stats (#4179) 2021-11-08 14:59:36 -05:00
Alice Ryhl 1f8105588c runtime: drop basic scheduler tasks inside context (#4213) 2021-11-02 23:25:34 +01:00
Alice Ryhl 94ee305741 macros: fix mut patterns in select! macro (#4211) 2021-11-02 18:05:24 +01:00
Alice Ryhl 669bc4476e io: update copy buffer size (#4209) 2021-11-02 16:22:45 +01:00
Alice Ryhl 1265d0c5dc sync: fix Notify example (#4212) 2021-11-02 16:22:30 +01:00
John-John Tedro 09b770c5db sync: make AtomicWaker panic safe (#3689) 2021-11-02 13:41:36 +01:00
Alice Ryhl d1a400912e chore: prepare tokio-stream 0.1.8 (#4198) 2021-10-29 18:34:43 +02:00
Alice Ryhl aa03622cf3 chore: prepare tokio-util 0.6.9 (#4199) 2021-10-29 18:34:23 +02:00
Alice Ryhl ac89d8926d chore: prepare Tokio v1.13.0 (#4196) 2021-10-29 18:34:13 +02:00
Alice Ryhl e184205421 chore: prepare tokio-macros 1.6.0 (#4197) 2021-10-29 18:33:40 +02:00
sander2 44a1aad8df task: allocate callback on heap immediately in debug mode (#4203) 2021-10-29 17:34:25 +02:00
Alice Ryhl 75c07770bf sync: make watch::send_replace infallible (#4195) 2021-10-27 14:05:25 +02:00
Colin Walters 268ed5e73e task: add more tips + links to spawn_blocking docs (#4150) 2021-10-26 20:57:57 +02:00
Bhargav 0c68b89452 codec: update stream impl for Framed to return None after Err (#4166) 2021-10-26 16:56:15 +02:00
Alice Ryhl 827694a9e3 ci: fix nightly version for cirrus ci (#4200) 2021-10-26 16:53:05 +02:00
Alice Ryhl d15e5fad16 ci: split FreeBSD into two jobs (#4194) 2021-10-26 15:44:00 +02:00
Alice Ryhl 9cb495cdb8 tokio: upgrade to new nightly for CI (#4193) 2021-10-26 14:08:11 +02:00
Ibraheem Ahmed e7d3e0c93c macros: use qualified syntax when polling in select! (#4192) 2021-10-25 23:54:34 +09:00
Alice Ryhl e04b5be1f5 time: update deadline on removal in DelayQueue (#4178) 2021-10-22 20:34:36 +02:00
Colin Walters 2734fa9a85 util/io: add SyncIoBridge (#4146) 2021-10-22 18:46:29 +02:00
Alice Ryhl 83aeae8610 chore: fix output of macro after new rustc release (#4189) 2021-10-22 09:36:12 +02:00
Taiki Endo cf550b2183 ci: ignore RUSTSEC-2020-0159 in audit (#4186) 2021-10-21 10:27:26 +02:00
Colin Walters e1c8b5a159 tests: add #[cfg(sync)] to watch test (#4183) 2021-10-21 09:11:14 +02:00
Jared Stanbrough eb7a615c96 tokio: add riscv32 to non atomic64 architectures (#4185) 2021-10-21 04:25:00 +09:00
John-John Tedro 44e9013f64 tokio: assert platform-minimum requirements at build time (#3797) 2021-10-19 15:16:45 +02:00
antogilbertandAntonello Palazzi 03969cdae7 doc: conversion of doc comments to indicative mood (#4174)
Co-authored-by: Antonello Palazzi <[email protected]>
2021-10-19 10:54:16 +02:00
Taiki Endo 095012b03b macros: fix type resolution error in #[tokio::main] (#4176) 2021-10-19 12:49:57 +09:00
Jonathan b5c1fb4012 signal: add example with background listener (#4171) 2021-10-17 20:37:15 +02:00
Philip Dubé d4848a9e2e examples: replace time crate with httpdate (#4169) 2021-10-15 15:21:08 +02:00
Alice Ryhl bb6a292d0a chore: prepare tokio-macros v1.5.0 (#4167) 2021-10-13 15:51:09 +02:00
Suika eb2106f87e runtime: reset woken of outer future after polled (#4157) 2021-10-12 13:35:14 +02:00
Lukas Wirth fadd0190da macros: make tokio-macros attributes more IDE friendly (#4162) 2021-10-11 22:04:47 +02:00
Alice Ryhl f0cb360d70 sync: add more oneshot examples (#4153) 2021-10-08 08:52:50 +02:00
Alice Ryhl d047584e86 time: document Interval::tick cancel safety (#4152) 2021-10-05 15:55:15 +02:00
Alice Ryhl c5d37204dc chore: fix unused field warnings (#4151) 2021-10-05 13:21:38 +02:00
我就像屎的倒影 7f26ad85c2 fmt: ignore the target dir when formatting (#4145)
Signed-off-by: hi-rustin <[email protected]>
2021-10-04 08:36:17 +02:00
Alice Ryhl d39c9ed9dc chore: prepare tokio-macros 1.4.1 (#4142)
This reverts commit 33f0a1fd2e.
2021-09-30 10:38:52 +02:00
Noah Kennedy 44cfe10ee5 chore: prepare tokio-macros 1.4.0 (#4139) 2021-09-29 23:30:47 +02:00
Frank Steffahn 1073f6e8be sync: expand Debug for Mutex<T> impl to unsized T (#4134) 2021-09-26 12:29:08 +02:00
Kai Jewson dee3236c97 sync: add watch::Sender::send_replace (#3962) 2021-09-25 22:06:40 +02:00
Noah Kennedy d9ca1517c6 net: add try_*, readable, writable, ready, and peer_addr methods to split halves (#4120) 2021-09-25 20:19:29 +02:00
你好-肚财 6c1a1d9b07 docs: add returning on the first error example for try_join! (#4133) 2021-09-25 16:04:38 +02:00
Arthur Gautier d32acd97eb net: add poll_{recv,send}_ready methods to udp and uds_datagram (#4131) 2021-09-24 20:28:18 +02:00
João Marcos Bezerra 7875f26586 docs: fixing broken links in tokio/src/lib.rs (#4132) 2021-09-24 11:16:30 +02:00
我就像屎的倒影 7ce8f05cff sync: add blocking_lock to Mutex (#4130) 2021-09-23 11:46:14 +02:00
Sean McArthur ea19606bc4 sync: fix Notify to clone the waker before locking its waiter list (#4129)
Since a waker can trigger arbitrary code, such as with a custom waker,
and even more so now that it can emit a tracing event that could do
respond, we must be careful about the internal state when that code is
triggered. The clone method of a waker is one of those instances.

This changes the internals of `Notify` so that the waker is cloned
*before* locking the waiter list. While this does mean that in some
contended cases, we'll have made an optimistic clone, it makes `Notify`
more robust and correct.

Note that the included test case is built from an instance that did
happen naturally in another project, see
https://github.com/tokio-rs/console/issues/133.
2021-09-23 08:33:14 +02:00
Zahari Dichev b9b59e4f15 tracing: use structured location fields for spawned tasks (#4128)
This change enables `tokio-console` to parse the location information
for a spawned task into a structured object rather than simply displaying
this info as a field on the task.

Signed-off-by: Zahari Dichev <[email protected]>
2021-09-22 23:17:41 +03:00
Suika cdc46a9ded io: add assert in copy_bidirectional that poll_write is sensible (#4125) 2021-09-22 10:06:51 +02:00
Zahari Dichev b9834f6d8b tracing: instrument time::Sleep (#4072)
This branch instruments the `Sleep` resource to allow the tokio-console
to consume data about resources usage. The corresponding console branch
is here: https://github.com/tokio-rs/console/pull/77

Signed-off-by: Zahari Dichev <[email protected]>
2021-09-22 08:34:41 +03:00
Alice Ryhl 1ed89aa5cf chore: prepare Tokio v1.12.0 (#4123) 2021-09-21 17:15:22 +02:00
Andrew Hlynskyi d9b2dc81ca task: improve JoinHandle::abort cancellation doc (#4121) 2021-09-21 10:20:03 +02:00
Alice Ryhl 8e54145c8b ci: make loom tests optional (#4112) 2021-09-20 21:52:35 +02:00
Alice Ryhl 279e8b001a sync: document spurious failures on poll_recv (#4117) 2021-09-19 09:52:40 +02:00
Alan Briolat e9f6faee67 mpsc: ensure try_reserve error is consistent with try_send (#4119) 2021-09-19 09:52:28 +02:00
Alice Ryhl f1b89675eb mpsc: use spin_loop_hint instead of yield_now (#4115) 2021-09-18 09:27:25 +02:00
Alice Ryhl ddd33f2b05 sync: implement try_recv for mpsc channels (#4113) 2021-09-18 09:27:16 +02:00
Alan Somers 8e92f05795 io: update the mio-aio dev-dependency (#4116) 2021-09-18 09:25:52 +02:00
Simon Farnsworth 957ed3eac0 runtime: callback when a worker parks and unparks (#4070) 2021-09-16 11:44:30 +02:00
Alice Ryhl ab34805849 time: more docs on advance (#4103) 2021-09-15 19:22:24 +02:00
Alan Somers 8b298d9ed4 io: add POSIX AIO on FreeBSD (#4054) 2021-09-15 18:55:50 +02:00
Andrew Lamb 57563e218b docs: clarify CPU-bound tasks on Tokio (#4105) 2021-09-15 14:07:52 +02:00
Ben Noordhuis 33f0a1fd2e macros: run runtime inside LocalSet when using macro (#4027) 2021-09-15 11:25:02 +02:00
Alice Ryhl 4c9b469562 sync: PollSender impls Sink (#4110) 2021-09-14 22:39:52 +02:00
Thibeau Vercruyssen 7af0f32751 io: add convenience method AsyncSeekExt::rewind. (#4107) 2021-09-14 16:05:01 +02:00
Milo 3fe1662e4b ci: caching for CI (#4108) 2021-09-14 09:40:04 +02:00
Nylonicious a73428252b util: update README (#4099) 2021-09-10 08:55:03 +02:00
Alice Ryhl b99eedc2ea sync: make SendError field public (#4097) 2021-09-09 19:13:26 +02:00
Sean McArthur 6ebd0575e4 runtime: add tracing span for block_on futures (#4094) 2021-09-09 08:58:21 -07:00
Alice Ryhl 7e51b44a20 task: document non-guarantees of yield_now (#4091) 2021-09-08 12:02:25 +02:00
Alice Ryhl bd1e4aaea6 chore: add Debug to NotDefinedHere (#4092) 2021-09-08 12:02:07 +02:00
Alice Ryhl fd22164f5d ci: disable benchmarks in CI (#4090) 2021-09-08 10:52:40 +02:00
Pablo Sichert 98e78a6f7b codec: implement Clone for LengthDelimitedCodec (#4089) 2021-09-07 13:51:35 +02:00
Toby Lawrence 01a6feb0dc chore: prepare tokio-util v0.6.8 (#4087)
Signed-off-by: Toby Lawrence <[email protected]>
2021-09-03 10:57:27 -04:00
Jorge Leitao e31e06c5c2 compat: added AsyncSeek compat (#4078) 2021-09-03 08:54:40 +02:00
ttys3 d0dd74a058 io: add with_capacity for ReaderStream (#4086) 2021-09-02 09:53:23 +02:00
Alice Ryhl 6778a7def6 time: document paused time details better (#4061) 2021-09-01 18:49:50 +02:00
Alice Ryhl 9a97eb36bc chore: prepare Tokio v1.11.0 (#4083) 2021-08-31 23:26:42 +02:00
Alice Ryhl 1409041525 io: fix fill_buf by not calling poll_fill_buf twice (#4084) 2021-08-31 23:25:52 +02:00
Toby Lawrence 23b0aee5dd tokio-util: expose key used in DelayQueue's Expired (#4081)
Signed-off-by: Toby Lawrence <[email protected]>
2021-08-31 17:09:10 -04:00
Francis Murillo 909d3ec0ff stream: add From<Receiver<T>> impl for receiver streams (#4080) 2021-08-29 16:30:13 +02:00
Alan Somers b67d46403f process: skip the process_kill_on_drop test if bash is not installed. (#4079) 2021-08-29 10:59:58 +02:00
Alice Ryhl 98578a6f4a stats: initial work on runtime stats (#4043) 2021-08-27 11:40:41 +02:00
Alice Ryhl 8a097d27b5 util: add safety comment to assume_init (#4075) 2021-08-26 23:06:58 +02:00
Eliza Weisman 1e2e38b7cd sync: use WakeList in Notify and batch_semaphore (#4071)
## Motivation

PR #4055 added a new `WakeList` type, to manage a potentially
uninitialized array when waking batches of wakers. This has the
advantage of not initializing a bunch of empty `Option`s when only a
small number of tasks are being woken, potentially improving performance
in these cases.

Currently, `WakeList` is used only in the IO driver. However,
`tokio::sync` contains some code that's almost identical to the code in
the IO driver that was replaced with `WakeList`, so we can apply the
same optimizations there.

## Solution

This branch changes `tokio::sync::Notify` and
`tokio::sync::batch_semaphore::Semaphore` to use `WakeList` when waking
batches of wakers. This was a pretty straightforward drop-in
replacement.

Signed-off-by: Eliza Weisman <[email protected]>
2021-08-26 11:33:22 -07:00
Fedorenko Dmitrij 80bda3bf5f macros: fix wrong error messages (#4067) 2021-08-25 19:59:41 +02:00
Gleb Pomykalov 51f4f0594c io: speed-up waking by using uninitialized array (#4055) 2021-08-25 16:18:19 +02:00
Alice Ryhl 897fed1609 ci: fail if valgrind complains (#4066) 2021-08-24 23:35:37 +02:00
Alice Ryhl fd52f9f66b Merge branch 'merge-1.10.1' into master 2021-08-24 20:50:43 +02:00
Alice Ryhl dd060b16f5 chore: prepare Tokio v1.10.1 2021-08-24 17:48:25 +02:00
Alice Ryhl 4152918a39 runtime: fix leak in UnownedTask 2021-08-24 17:48:25 +02:00
Alice Ryhl 7e474640dd chore: fix chores (#4060) 2021-08-24 14:57:23 +02:00
Nylonicious 84f6845bf2 stream: impl FromIterator for StreamMap (#4052) 2021-08-24 13:21:02 +02:00
Alice Ryhl 2bc9a42d2b process: add from_std to ChildStd* (#4045) 2021-08-19 09:46:05 +02:00
Christoph HerzogandAlice Ryhl 8aa2bfe23e watch: make watch::Sender::subscribe public (#3800)
Co-authored-by: Alice Ryhl <[email protected]>
2021-08-19 09:43:59 +02:00
Alice Ryhl 5ac32934b4 time: don't panic when Instant is not monotonic (#4044) 2021-08-19 09:41:35 +02:00
Eliza Weisman d0305d57e5 tracing: change span naming to new console convention (#4042)
Currently, the per-task spans generated by Tokio's `tracing` feature
have the span name "task" and the target "tokio::task". This is because
the console subscriber identified tasks by looking specifically for the
"tokio::task" target.

In tokio-rs/console#41, it was proposed that the console change to a
more generic system for identifying the spans that correspond to tasks,
to allow recording tasks belonging to multiple runtime crates (e.g. an
application that uses Tokio for async tasks and Rayon for CPU-bound
tasks). PR tokio-rs/console#68 changed the console to track any spans
"runtime.spawn", regardless of target (so that the target can be used to
identify the runtime a task came from), with "tokio::task/task" tracked
for backwards-compatibility with the current release version of tokio.

This branch changes Tokio's span naming to the new convention. I also
rearranged a couple fields so that the task's kind field always comes
before the name and spawn location, since it's likely to be the
shortest, and renamed the `function` field on blocking tasks to `fn`,
for brevity's sake.

Signed-off-by: Eliza Weisman <[email protected]>
2021-08-16 12:43:34 -07:00
Alice Ryhl 5c19b5a162 time: make Sleep examples easier to find (#4040) 2021-08-15 19:24:04 +02:00
Alice Ryhl f478647a8e ci: add readme check to CI (#4039) 2021-08-13 15:05:09 +02:00
Alice Ryhl c0974bad94 chore: prepare Tokio v1.10.0 (#4038) 2021-08-12 21:55:48 +02:00
Alice Ryhl b67534a20b tests: fix flaky test (#4024) 2021-08-12 20:15:06 +02:00
Alice Ryhl c4e56232ff sync: use spin_loop_hint instead of yield_now in mpsc (#4037) 2021-08-12 16:03:10 +02:00
Blas Rodriguez Irizar 84c4a6d89f task: quickly send task to heap on debug mode (#4009) 2021-08-12 15:37:26 +02:00
Alice Ryhl b501f25202 runtime: give Notified a safe API (#4005) 2021-08-12 10:06:05 +02:00
Alan Somers 032c55e77f tokio: the test-util feature depends on rt, sync, and time (#4036)
Fixes #4035
2021-08-12 10:04:09 +02:00
Kateřina Churanová 1e95d6994a chore: explicitly relaxed clippy lint for runtime entry macro (#4030) 2021-08-11 18:16:00 +09:00
LinkTed 362df5a317 io: add test for write_f(32|64)[_le] (#4026) 2021-08-04 18:58:38 +02:00
LinkTed 106bb94896 io: add (read|write)_f(32|64)[_le] methods (#4022) 2021-08-04 13:45:26 +02:00
LinkTed 8198ef3881 chore: fix clippy warnings (#4017) 2021-08-03 10:50:40 +02:00
Alice Ryhl e66217575b sync: document when watch::send fails (#4021) 2021-08-03 09:38:58 +02:00
Alice Ryhl 175d84e2b1 chore: fix doc failure in CI on master (#4020) 2021-08-03 09:07:38 +02:00
Alice Ryhl 69a6585429 signal: make windows docs for signal module show up on unix builds (#3770) 2021-08-02 20:55:17 +02:00
Alan Somers cf02b3f32d fs: reorganize tokio::fs::file's mock tests (#3988) 2021-07-31 09:55:32 +02:00
Alice Ryhl d01bda86a4 tests: simplify loom tests (#3995) 2021-07-30 13:03:38 +02:00
Alice Ryhl e60d7a474b chore: fix CI on master (#4008) 2021-07-30 12:28:46 +02:00
Alice Ryhl 0d9430b99c tests: reduce sleep durations (#3994) 2021-07-30 11:27:04 +02:00
quininer f51676891f io: fix copy buffered write (#4001) 2021-07-30 11:20:16 +02:00
Alice Ryhl 3340ae6aa9 io: add fill_buf and consume (#3991) 2021-07-30 11:17:40 +02:00
Félix Saparelli f957f7f9a7 process: add Child::raw_handle() on windows (#3998)
Fixes #3987
2021-07-28 15:42:03 +00:00
Alice Ryhl 8b447649bb io: document cancellation safety of AsyncBufReadExt (#3997) 2021-07-27 17:59:38 +02:00
Erik Tews 4d8cc28b76 io: add missing Option to doc (#3999) 2021-07-27 16:06:47 +02:00
Alice Ryhl f2a06bff1b runtime: add owner id for tasks in OwnedTasks (#3979) 2021-07-27 10:41:35 +02:00
Alice Ryhl 0de05422ce Merge branch 'master' and 'merge-1.8.3' 2021-07-26 21:07:52 +02:00
Alice Ryhl 2b731c0e01 task: elaborate on queue behavior of spawn_blocking (#3981) 2021-07-26 15:37:17 +02:00
Alice Ryhl 8f27c04a9e codec: remove unnecessary doc(cfg(...)) (#3989) 2021-07-26 12:32:24 +02:00
LinkTed c85a0e524e process: add arg0 method to Command (#3984) 2021-07-26 11:43:55 +02:00
Alice Ryhl df10b68d47 readme: add release schedule and bugfix policy (#3948) 2021-07-22 13:53:43 +02:00
Alice Ryhl b280c6dcd7 chore: prepare Tokio v1.9.0 (#3961) 2021-07-22 12:05:39 +02:00
Alice Ryhl 998dc5a2eb task: fix leak in LocalSet (#3978) 2021-07-22 12:05:02 +02:00
Alice Ryhl ced7992f65 runtime: add large test and fix leak it found (#3967) 2021-07-22 09:06:38 +02:00
Alice Ryhl 0cefa85bfa runtime: make scheduler non-optional (#3980) 2021-07-21 20:05:21 +02:00
Alice Ryhl 2087f3e0eb runtime: rework binding of new tasks (#3955) 2021-07-20 16:43:34 +02:00
Blas Rodriguez Irizar 5ae2855eaf io: fix docs referring to clear_{read,write}_ready (#3957) 2021-07-20 15:35:18 +02:00
Ian Jackson 252004811f sync: add getter for the mutex from a guard (#3928) 2021-07-20 15:28:31 +02:00
Alice Ryhl 549e89e9cd chore: use the loom mutex wrapper everywhere (#3958) 2021-07-20 12:12:30 +02:00
Carl Lerche c8fc492748 Merge remote-tracking branch 'origin/tokio-1.8.x' into merge-1.8 2021-07-19 12:13:21 -07:00
Diggory Blake 7c4183a45d runtime: drop future when polling cancelled future (#3965) 2021-07-18 22:46:35 +02:00
Alice Ryhl aef2d64b0a task: remove mutex in JoinError (#3959) 2021-07-16 09:50:03 +02:00
Alice Ryhl 8f10d81613 net: documentation updates (#3944) 2021-07-13 14:23:25 +02:00
Alice Ryhl 7a11cfd77a chore: update async_send_sync test (#3943) 2021-07-12 07:38:19 -07:00
Alice Ryhl 6610ba9bd6 runtime: use OwnedTasks in LocalSet (#3950) 2021-07-12 14:42:00 +02:00
Alan Somers 3fd88dac64 net: fix the uds_datagram tests with the latest nightly stdlib (#3952) 2021-07-12 11:36:50 +02:00
Alice Ryhl ccd495647d chore: update nightly version (#3953) 2021-07-12 11:21:10 +02:00
Alice Ryhl 80d8d40a34 sync: clean up OnceCell (#3945) 2021-07-12 11:19:14 +02:00
Alice Ryhl 3b38ebd7f5 runtime: move inject queue to tokio::runtime::task (#3939) 2021-07-12 10:31:36 +02:00
Alan Somers 127983e5b4 tests: update Nix to 0.22.0 (#3951) 2021-07-11 09:53:04 +02:00
ty c306bf853a net: allow customized I/O operations for TcpStream (#3888) 2021-07-08 13:02:40 +02:00
Moritz Gunz c6fbb9aeb1 task: expose nameable future for TaskLocal::scope (#3273) 2021-07-08 12:54:31 +02:00
Alice Ryhl 5d61c997e9 chore: prepare tokio-stream 0.1.7 (#3923) 2021-07-07 10:58:51 +02:00
Alice Ryhl c505a2f81a chore: prepare tokio-macros 1.3.0 (#3931) 2021-07-07 10:58:02 +02:00
Alice Ryhl e2589a0e40 runtime: add OwnedTasks (#3909) 2021-07-07 10:53:57 +02:00
Alice Ryhl 51fad066e2 bench: update spawn benchmarks (#3927) 2021-07-07 10:52:53 +02:00
Alice Ryhl ae233c1e9f Merge branch 'upstream/merge-1.8.1' into 'master' 2021-07-07 10:22:13 +02:00
Alice Ryhl be26ca7625 sync: wrap state in helper struct (#3922) 2021-07-07 10:17:47 +02:00
Carl Lerche 2d41f0303e Merge branch 'tokio-1.8.x' into merge-1.8.1 2021-07-06 16:20:57 -07:00
Blas Rodriguez Irizar 4818c2ed05 fs: document performance considerations (#3920) 2021-07-06 16:25:13 +02:00
Blas Rodriguez Irizar 80f0801e19 tokio-macros: compat with clippy::unwrap_used (#3926) 2021-07-06 15:01:13 +02:00
David CARLIER 8148b2107c net: make ucred return pid field for OpenBSD (#3919) 2021-07-06 11:15:15 +02:00
oiovoyo 2e7de1ae1d stream: modify HashMap to StreamMap in example. (#3925) 2021-07-05 16:10:55 +02:00
Alice Ryhl 37e60fc7f9 chore: fix new clippy complaint (#3915) 2021-07-05 09:58:49 +02:00
Alice Ryhl 8170e2787c sync: fix watch wrapper (#3914) 2021-07-02 23:24:02 +02:00
326 changed files with 16675 additions and 4309 deletions
+8
View File
@@ -0,0 +1,8 @@
# 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",
]
+22
View File
@@ -0,0 +1,22 @@
version: 2.1
jobs:
test-arm:
machine:
image: ubuntu-2004:202101-01
resource_class: arm.medium
steps:
- checkout
- run:
name: Install Rust
command: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o rustup.sh
chmod +x rustup.sh
./rustup.sh -y
source "$HOME"/.cargo/env
# Only run Tokio tests
- run: cargo test --all-features -p tokio
workflows:
ci:
jobs:
- test-arm
+34 -9
View File
@@ -1,15 +1,45 @@
freebsd_instance:
image: freebsd-12-2-release-amd64
env:
RUSTFLAGS: -D warnings
# Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the
# same VM. The binary will be built in 32-bit mode, but will execute on a
# 64-bit kernel and in a 64-bit environment. Our tests don't execute any of
# the system's binaries, so the environment shouldn't matter.
task:
name: FreeBSD
name: FreeBSD 64-bit
setup_script:
- pkg install -y bash curl
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain stable
- . $HOME/.cargo/env
- |
echo "~~~~ rustc --version ~~~~"
rustc --version
test_script:
- . $HOME/.cargo/env
- cargo test --all --all-features
task:
name: FreeBSD docs
env:
LOOM_MAX_PREEMPTIONS: 2
RUSTFLAGS: -Dwarnings
RUSTFLAGS: --cfg docsrs
RUSTDOCFLAGS: --cfg docsrs -Dwarnings
setup_script:
- pkg install -y bash curl
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain nightly-2022-01-12
- . $HOME/.cargo/env
- |
echo "~~~~ rustc --version ~~~~"
rustc --version
test_script:
- . $HOME/.cargo/env
- cargo doc --lib --no-deps --all-features --document-private-items
task:
name: FreeBSD 32-bit
setup_script:
- pkg install -y bash curl
- curl https://sh.rustup.rs -sSf --output rustup.sh
@@ -21,9 +51,4 @@ task:
rustc --version
test_script:
- . $HOME/.cargo/env
- cargo test --all --all-features
- cargo doc --all --no-deps
i686_test_script:
- . $HOME/.cargo/env
- |
cargo test --all --all-features --target i686-unknown-freebsd
- cargo test --all --all-features --target i686-unknown-freebsd
+1 -1
View File
@@ -1 +1 @@
msrv = "1.45"
msrv = "1.46"
+8
View File
@@ -0,0 +1,8 @@
R-loom:
- tokio/src/sync/*
- tokio/src/sync/**/*
- tokio-util/src/sync/*
- tokio-util/src/sync/**/*
- tokio/src/runtime/*
- tokio/src/runtime/**/*
-55
View File
@@ -1,55 +0,0 @@
name: Benchmark
on:
push:
branches:
- master
jobs:
benchmark:
name: Benchmark
runs-on: ubuntu-latest
strategy:
matrix:
bench:
- rt_multi_threaded
- sync_mpsc
- sync_rwlock
- sync_semaphore
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
# Run benchmark with `go test -bench` and stores the output to a file
- name: Run benchmark
run: cargo bench --bench ${{ matrix.bench }} | tee ../output.txt
working-directory: benches
# Download previous benchmark result from cache (if exists)
- name: Download previous benchmark data
uses: actions/cache@v1
with:
path: ./cache
key: ${{ runner.os }}-benchmark
# Run `github-action-benchmark` action
- name: Store benchmark result
uses: rhysd/github-action-benchmark@v1
with:
name: ${{ matrix.bench }}
# What benchmark tool the output.txt came from
tool: 'cargo'
# Where the output from the benchmark tool is stored
output-file-path: output.txt
# # Where the previous data file is stored
# external-data-json-path: ./cache/benchmark-data.json
# Workflow will fail when an alert happens
fail-on-alert: true
# GitHub API token to make a commit comment
github-token: ${{ secrets.GITHUB_TOKEN }}
# Enable alert commit comment
comment-on-alert: true
alert-comment-cc-users: '@tokio-rs/maintainers'
auto-push: true
# Upload the updated cache file for the next job by actions/cache
+122 -28
View File
@@ -9,8 +9,12 @@ name: CI
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
nightly: nightly-2021-04-25
minrust: 1.45.2
nightly: nightly-2022-01-12
minrust: 1.46
defaults:
run:
shell: bash
jobs:
# Depends on all action sthat are required for a "successful" CI run.
@@ -20,6 +24,7 @@ jobs:
needs:
- test
- test-unstable
- test-parking_lot
- miri
- cross
- features
@@ -27,8 +32,11 @@ jobs:
- fmt
- clippy
- docs
- loom
- valgrind
- loom-compile
- check-readme
- test-hyper
- wasm32-unknown-unknown
steps:
- run: exit 0
@@ -45,6 +53,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
- name: Install cargo-hack
run: cargo install cargo-hack
@@ -75,6 +84,27 @@ jobs:
# bench.yml workflow runs benchmarks only on linux.
if: startsWith(matrix.os, 'ubuntu')
test-parking_lot:
# The parking_lot crate has a feature called send_guard which changes when
# some of its types are Send. Tokio has some measures in place to prevent
# this from affecting when Tokio types are Send, and this test exists to
# ensure that those measures are working.
#
# This relies on the potentially affected Tokio type being listed in
# `tokio/tokio/tests/async_send_sync.rs`.
name: compile tests with parking lot send guards
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
- name: Enable parking_lot send_guard feature
# Inserts the line "plsend = ["parking_lot/send_guard"]" right after [features]
run: sed -i '/\[features\]/a plsend = ["parking_lot/send_guard"]' tokio/Cargo.toml
- name: Compile tests with all features enabled
run: cargo build --workspace --all-features --tests
valgrind:
name: valgrind
runs-on: ubuntu-latest
@@ -82,6 +112,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
- name: Install Valgrind
run: |
@@ -95,7 +126,7 @@ jobs:
# Run with valgrind
- name: Run valgrind test-mem
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/test-mem
# Compile tests
- name: cargo build test-process-signal
@@ -104,7 +135,7 @@ jobs:
# Run with valgrind
- name: Run valgrind test-process-signal
run: valgrind --leak-check=full --show-leak-kinds=all ./target/debug/test-process-signal
run: valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./target/debug/test-process-signal
test-unstable:
name: test tokio full --unstable
@@ -119,6 +150,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
# Run `tokio` with "unstable" cfg flag.
- name: test tokio full --cfg unstable
@@ -136,6 +168,7 @@ jobs:
with:
toolchain: ${{ env.nightly }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Install Miri
run: |
set -e
@@ -155,6 +188,7 @@ jobs:
with:
toolchain: ${{ env.nightly }}
override: true
- uses: Swatinem/rust-cache@v1
- name: asan
run: cargo test --all-features --target x86_64-unknown-linux-gnu --lib -- --test-threads 1
working-directory: tokio
@@ -180,6 +214,7 @@ jobs:
toolchain: stable
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/cargo@v1
with:
use-cross: true
@@ -195,6 +230,7 @@ jobs:
with:
toolchain: ${{ env.nightly }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Install cargo-hack
run: cargo install cargo-hack
@@ -216,7 +252,7 @@ jobs:
with:
toolchain: ${{ env.minrust }}
override: true
- uses: Swatinem/rust-cache@v1
- name: "test --workspace --all-features"
run: cargo check --workspace --all-features
@@ -229,6 +265,7 @@ jobs:
with:
toolchain: ${{ env.nightly }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Install cargo-hack
run: cargo install cargo-hack
- name: "check --all-features -Z minimal-versions"
@@ -239,6 +276,16 @@ jobs:
# Update Cargo.lock to minimal version dependencies.
cargo update -Z minimal-versions
cargo hack check --all-features --ignore-private
- name: "check --all-features --unstable -Z minimal-versions"
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
run: |
# Remove dev-dependencies from Cargo.toml to prevent the next `cargo update`
# from determining minimal versions based on dev-dependencies.
cargo hack --remove-dev-deps --workspace
# Update Cargo.lock to minimal version dependencies.
cargo update -Z minimal-versions
cargo hack check --all-features --ignore-private
fmt:
name: fmt
@@ -247,15 +294,14 @@ jobs:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- name: Install rustfmt
run: rustup component add rustfmt
- uses: Swatinem/rust-cache@v1
# Check fmt
- name: "rustfmt --check"
# Workaround for rust-lang/cargo#7732
run: |
if ! rustfmt --check --edition 2018 $(find . -name '*.rs' -print); then
printf "Please run \`rustfmt --edition 2018 \$(find . -name '*.rs' -print)\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
if ! rustfmt --check --edition 2018 $(git ls-files '*.rs'); then
printf "Please run \`rustfmt --edition 2018 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2
exit 1
fi
@@ -265,7 +311,8 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update 1.52.1 && rustup default 1.52.1
run: rustup update 1.57 && rustup default 1.57
- uses: Swatinem/rust-cache@v1
- name: Install clippy
run: rustup component add clippy
@@ -282,33 +329,80 @@ jobs:
with:
toolchain: ${{ env.nightly }}
override: true
- uses: Swatinem/rust-cache@v1
- name: "doc --lib --all-features"
run: cargo doc --lib --no-deps --all-features --document-private-items
env:
RUSTFLAGS: --cfg docsrs
RUSTDOCFLAGS: --cfg docsrs -Dwarnings
loom:
name: loom
loom-compile:
name: build loom tests
runs-on: ubuntu-latest
strategy:
matrix:
scope:
- --skip loom_pool
- loom_pool::group_a
- loom_pool::group_b
- loom_pool::group_c
- loom_pool::group_d
- time::driver
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- name: loom ${{ matrix.scope }}
run: cargo test --lib --release --features full -- --nocapture $SCOPE
- uses: Swatinem/rust-cache@v1
- name: build --cfg loom
run: cargo test --no-run --lib --features full
working-directory: tokio
env:
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
LOOM_MAX_PREEMPTIONS: 2
SCOPE: ${{ matrix.scope }}
check-readme:
name: Check README
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Verify that both READMEs are identical
run: diff README.md tokio/README.md
- name: Verify that Tokio version is up to date in README
working-directory: tokio
run: grep -q "$(sed '/^version = /!d' Cargo.toml | head -n1)" README.md
test-hyper:
name: Test hyper
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- windows-latest
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
- name: Test hyper
run: |
set -x
git clone https://github.com/hyperium/hyper.git
cd hyper
# checkout the latest release because HEAD maybe contains breakage.
tag=$(git describe --abbrev=0 --tags)
git checkout "${tag}"
echo '[workspace]' >>Cargo.toml
echo '[patch.crates-io]' >>Cargo.toml
echo 'tokio = { path = "../tokio" }' >>Cargo.toml
echo 'tokio-util = { path = "../tokio-util" }' >>Cargo.toml
echo 'tokio-stream = { path = "../tokio-stream" }' >>Cargo.toml
echo 'tokio-test = { path = "../tokio-test" }' >>Cargo.toml
git diff
cargo test --features full
wasm32-unknown-unknown:
name: test tokio for wasm32-unknown-unknown
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: test tokio
run: wasm-pack test --node -- --features "macros sync"
working-directory: tokio
+14
View File
@@ -0,0 +1,14 @@
name: "Pull Request Labeler"
on:
- pull_request_target
# See .github/labeler.yml file
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v3
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
+40
View File
@@ -0,0 +1,40 @@
on:
push:
branches: ["master", "tokio-*.x"]
pull_request:
types: [labeled, opened, synchronize, reopened]
branches: ["master", "tokio-*.x"]
name: Loom
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
jobs:
loom:
name: loom
# base_ref is null when it's not a pull request
if: contains(github.event.pull_request.labels.*.name, 'R-loom') || (github.base_ref == null)
runs-on: ubuntu-latest
strategy:
matrix:
scope:
- --skip loom_pool
- loom_pool::group_a
- loom_pool::group_b
- loom_pool::group_c
- loom_pool::group_d
- time::driver
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
- name: loom ${{ matrix.scope }}
run: cargo test --lib --release --features full -- --nocapture $SCOPE
working-directory: tokio
env:
RUSTFLAGS: --cfg loom --cfg tokio_unstable -Dwarnings
LOOM_MAX_PREEMPTIONS: 2
SCOPE: ${{ matrix.scope }}
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
- name: Install Valgrind
run: |
sudo apt-get update -y
+9 -1
View File
@@ -139,6 +139,14 @@ correctly, use this command:
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
```
To build documentation including Tokio's unstable features, it is necessary to
pass `--cfg tokio_unstable` to both RustDoc *and* rustc. To build the
documentation for unstable features, use this command:
```
RUSTDOCFLAGS="--cfg docsrs --cfg tokio_unstable" RUSTFLAGS="--cfg tokio_unstable" cargo +nightly doc --all-features
```
There is currently a [bug in cargo] that means documentation cannot be built
from the root of the workspace. If you `cd` into the `tokio` subdirectory the
command shown above will work.
@@ -150,7 +158,7 @@ command below instead:
```
# Mac or Linux
rustfmt --check --edition 2018 $(find . -name '*.rs' -print)
rustfmt --check --edition 2018 $(git ls-files '*.rs')
# Powershell
Get-ChildItem . -Filter "*.rs" -Recurse | foreach { rustfmt --check --edition 2018 $_.FullName }
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2021 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+32 -6
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.8.0", features = ["full"] }
tokio = { version = "1.16.1", features = ["full"] }
```
Then, on your main.rs:
@@ -140,8 +140,7 @@ several other libraries, including:
* [`tower`]: A library of modular and reusable components for building robust networking clients and servers.
* [`tracing`] (formerly `tokio-trace`): A framework for application-level
tracing and async-aware diagnostics.
* [`tracing`] (formerly `tokio-trace`): A framework for application-level tracing and async-aware diagnostics.
* [`rdbc`]: A Rust database connectivity library for MySQL, Postgres and SQLite.
@@ -164,9 +163,36 @@ several other libraries, including:
## Supported Rust Versions
Tokio is built against the latest stable release. The minimum supported version is 1.45.
The current Tokio version is not guaranteed to build on Rust versions earlier than the
minimum supported version.
Tokio is built against the latest stable release. The minimum supported version
is 1.46. The current Tokio version is not guaranteed to build on Rust versions
earlier than the minimum supported version.
## Release schedule
Tokio doesn't follow a fixed release schedule, but we typically make one to two
new minor releases each month. We make patch releases for bugfixes as necessary.
## Bug patching policy
For the purposes of making patch releases with bugfixes, we have designated
certain minor releases as LTS (long term support) releases. Whenever a bug
warrants a patch release with a fix for the bug, it will be backported and
released as a new patch release for each LTS minor version. Our current LTS
releases are:
* `1.8.x` - LTS release until February 2022.
* `1.14.x` - LTS release until June 2022.
Each LTS release will continue to receive backported fixes for at least half 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.8.x` patch release, you
can use the following dependency specification:
```text
tokio = { version = "~1.8", features = [...] }
```
## License
+1 -1
View File
@@ -9,7 +9,7 @@ tokio = { version = "1.5.0", path = "../tokio", features = ["full"] }
bencher = "0.1.5"
[dev-dependencies]
tokio-util = { version = "0.6.6", path = "../tokio-util", features = ["full"] }
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
tokio-stream = { path = "../tokio-stream" }
[target.'cfg(unix)'.dependencies]
+1 -1
View File
@@ -21,7 +21,7 @@ fn rt() -> tokio::runtime::Runtime {
const BLOCK_COUNT: usize = 1_000;
const BUFFER_SIZE: usize = 4096;
const DEV_ZERO: &'static str = "/dev/zero";
const DEV_ZERO: &str = "/dev/zero";
fn async_read_codec(b: &mut Bencher) {
let rt = rt();
+57 -37
View File
@@ -2,63 +2,83 @@
//! 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};
async fn work() -> usize {
let val = 1 + 1;
tokio::task::yield_now().await;
black_box(val)
}
fn basic_scheduler_local_spawn(bench: &mut Bencher) {
fn basic_scheduler_spawn(bench: &mut Bencher) {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
runtime.block_on(async {
bench.iter(|| {
let h = tokio::spawn(work());
black_box(h);
})
});
}
fn threaded_scheduler_local_spawn(bench: &mut Bencher) {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
runtime.block_on(async {
bench.iter(|| {
let h = tokio::spawn(work());
black_box(h);
})
});
}
fn basic_scheduler_remote_spawn(bench: &mut Bencher) {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
bench.iter(|| {
let h = runtime.spawn(work());
black_box(h);
runtime.block_on(async {
let h = tokio::spawn(work());
assert_eq!(h.await.unwrap(), 2);
});
});
}
fn threaded_scheduler_remote_spawn(bench: &mut Bencher) {
let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap();
fn basic_scheduler_spawn10(bench: &mut Bencher) {
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
bench.iter(|| {
let h = runtime.spawn(work());
black_box(h);
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) {
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);
});
});
}
fn threaded_scheduler_spawn10(bench: &mut Bencher) {
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);
}
});
});
}
bencher::benchmark_group!(
spawn,
basic_scheduler_local_spawn,
threaded_scheduler_local_spawn,
basic_scheduler_remote_spawn,
threaded_scheduler_remote_spawn
basic_scheduler_spawn,
basic_scheduler_spawn10,
threaded_scheduler_spawn,
threaded_scheduler_spawn10,
);
bencher::benchmark_main!(spawn);
+5 -5
View File
@@ -5,14 +5,14 @@ publish = false
edition = "2018"
# If you copy one of the examples into a new project, you should be using
# [dependencies] instead.
# [dependencies] instead, and delete the **path**.
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio",features = ["full", "tracing"] }
tokio-util = { version = "0.6.3", path = "../tokio-util",features = ["full"] }
tokio = { version = "1.0.0", path = "../tokio", features = ["full", "tracing"] }
tokio-util = { version = "0.7.0", path = "../tokio-util", features = ["full"] }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
tracing = "0.1"
tracing-subscriber = { version = "0.2.7", default-features = false, features = ["fmt", "ansi", "env-filter", "chrono", "tracing-log"] }
tracing-subscriber = { version = "0.3.1", default-features = false, features = ["fmt", "ansi", "env-filter", "tracing-log"] }
bytes = "1.0.0"
futures = { version = "0.3.0", features = ["thread-pool"]}
http = "0.2"
@@ -20,7 +20,7 @@ serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
httparse = "1.0"
time = "0.1"
httpdate = "1.0"
once_cell = "1.5.2"
rand = "0.8.3"
+1 -1
View File
@@ -149,7 +149,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
}
fn handle_request(line: &str, db: &Arc<Database>) -> Response {
let request = match Request::parse(&line) {
let request = match Request::parse(line) {
Ok(req) => req,
Err(e) => return Response::Error { msg: e },
};
+14 -10
View File
@@ -221,8 +221,9 @@ mod date {
use std::cell::RefCell;
use std::fmt::{self, Write};
use std::str;
use std::time::SystemTime;
use time::{self, Duration};
use httpdate::HttpDate;
pub struct Now(());
@@ -252,22 +253,26 @@ mod date {
struct LastRenderedNow {
bytes: [u8; 128],
amt: usize,
next_update: time::Timespec,
unix_date: u64,
}
thread_local!(static LAST: RefCell<LastRenderedNow> = RefCell::new(LastRenderedNow {
bytes: [0; 128],
amt: 0,
next_update: time::Timespec::new(0, 0),
unix_date: 0,
}));
impl fmt::Display for Now {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
LAST.with(|cache| {
let mut cache = cache.borrow_mut();
let now = time::get_time();
if now >= cache.next_update {
cache.update(now);
let now = SystemTime::now();
let now_unix = now
.duration_since(SystemTime::UNIX_EPOCH)
.map(|since_epoch| since_epoch.as_secs())
.unwrap_or(0);
if cache.unix_date != now_unix {
cache.update(now, now_unix);
}
f.write_str(cache.buffer())
})
@@ -279,11 +284,10 @@ mod date {
str::from_utf8(&self.bytes[..self.amt]).unwrap()
}
fn update(&mut self, now: time::Timespec) {
fn update(&mut self, now: SystemTime, now_unix: u64) {
self.amt = 0;
write!(LocalBuffer(self), "{}", time::at(now).rfc822()).unwrap();
self.next_update = now + Duration::seconds(1);
self.next_update.nsec = 0;
self.unix_date = now_unix;
write!(LocalBuffer(self), "{}", HttpDate::from(now)).unwrap();
}
}
@@ -4,4 +4,4 @@ error: The default runtime flavor is `multi_thread`, but the `rt-multi-thread` f
3 | #[tokio::main]
| ^^^^^^^^^^^^^^
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -7,12 +7,6 @@ async fn missing_semicolon_or_return_type() {
#[tokio::main]
async fn missing_return_type() {
/* TODO(taiki-e): one of help messages still wrong
help: consider using a semicolon here
|
16 | return Ok(());;
|
*/
return Ok(());
}
@@ -21,9 +15,9 @@ async fn extra_semicolon() -> Result<(), ()> {
/* TODO(taiki-e): help message still wrong
help: try using a variant of the expected enum
|
29 | Ok(Ok(());)
23 | Ok(Ok(());)
|
29 | Err(Ok(());)
23 | Err(Ok(());)
|
*/
Ok(());
@@ -1,5 +1,5 @@
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:5:5
--> tests/fail/macros_type_mismatch.rs:5:5
|
5 | Ok(())
| ^^^^^^ expected `()`, found enum `Result`
@@ -9,43 +9,36 @@ error[E0308]: mismatched types
help: consider using a semicolon here
|
5 | Ok(());
| ^
| +
help: try adding a return type
|
4 | async fn missing_semicolon_or_return_type() -> Result<(), _> {
| ^^^^^^^^^^^^^^^^
| ++++++++++++++++
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:16:5
--> tests/fail/macros_type_mismatch.rs:10:5
|
16 | return Ok(());
9 | async fn missing_return_type() {
| - help: try adding a return type: `-> Result<(), _>`
10 | return Ok(());
| ^^^^^^^^^^^^^^ expected `()`, found enum `Result`
|
= note: expected unit type `()`
found enum `Result<(), _>`
help: consider using a semicolon here
|
16 | return Ok(());;
| ^
help: try adding a return type
|
9 | async fn missing_return_type() -> Result<(), _> {
| ^^^^^^^^^^^^^^^^
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:29:5
--> tests/fail/macros_type_mismatch.rs:23:5
|
20 | async fn extra_semicolon() -> Result<(), ()> {
14 | async fn extra_semicolon() -> Result<(), ()> {
| -------------- expected `Result<(), ()>` because of return type
...
29 | Ok(());
23 | Ok(());
| ^^^^^^^ expected enum `Result`, found `()`
|
= note: expected enum `Result<(), ()>`
found unit type `()`
help: try using a variant of the expected enum
help: try adding an expression at the end of the block
|
29 | Ok(Ok(());)
|
29 | Err(Ok(());)
23 ~ Ok(());;
24 + Ok(())
|
+6
View File
@@ -5,6 +5,12 @@ fn compile_fail_full() {
#[cfg(feature = "full")]
t.pass("tests/pass/forward_args_and_output.rs");
#[cfg(feature = "full")]
t.pass("tests/pass/macros_main_return.rs");
#[cfg(feature = "full")]
t.pass("tests/pass/macros_main_loop.rs");
#[cfg(feature = "full")]
t.compile_fail("tests/fail/macros_invalid_input.rs");
+7
View File
@@ -0,0 +1,7 @@
#[cfg(feature = "full")]
#[tokio::test]
async fn test_with_semicolon_without_return_type() {
#![deny(clippy::semicolon_if_nothing_returned)]
dbg!(0);
}
@@ -0,0 +1,14 @@
use tests_build::tokio;
#[tokio::main]
async fn main() -> Result<(), ()> {
loop {
if !never() {
return Ok(());
}
}
}
fn never() -> bool {
std::time::Instant::now() > std::time::Instant::now()
}
@@ -0,0 +1,6 @@
use tests_build::tokio;
#[tokio::main]
async fn main() -> Result<(), ()> {
return Ok(());
}
+51
View File
@@ -1,3 +1,54 @@
# 1.7.0 (December 15th, 2021)
- macros: address remainging clippy::semicolon_if_nothing_returned warning ([#4252])
[#4252]: https://github.com/tokio-rs/tokio/pull/4252
# 1.6.0 (November 16th, 2021)
- macros: fix mut patterns in `select!` macro ([#4211])
[#4211]: https://github.com/tokio-rs/tokio/pull/4211
# 1.5.1 (October 29th, 2021)
- macros: fix type resolution error in `#[tokio::main]` ([#4176])
[#4176]: https://github.com/tokio-rs/tokio/pull/4176
# 1.5.0 (October 13th, 2021)
- macros: make tokio-macros attributes more IDE friendly ([#4162])
[#4162]: https://github.com/tokio-rs/tokio/pull/4162
# 1.4.1 (September 30th, 2021)
Reverted: run `current_thread` inside `LocalSet` ([#4027])
# 1.4.0 (September 29th, 2021)
(yanked)
### Changed
- macros: run `current_thread` inside `LocalSet` ([#4027])
- macros: explicitly relaxed clippy lint for `.expect()` in runtime entry macro ([#4030])
### Fixed
- macros: fix invalid error messages in functions wrapped with `#[main]` or `#[test]` ([#4067])
[#4027]: https://github.com/tokio-rs/tokio/pull/4027
[#4030]: https://github.com/tokio-rs/tokio/pull/4030
[#4067]: https://github.com/tokio-rs/tokio/pull/4067
# 1.3.0 (July 7, 2021)
- macros: don't trigger `clippy::unwrap_used` ([#3926])
[#3926]: https://github.com/tokio-rs/tokio/pull/3926
# 1.2.0 (May 14, 2021)
- macros: forward input arguments in `#[tokio::test]` ([#3691])
+2 -4
View File
@@ -2,17 +2,15 @@
name = "tokio-macros"
# When releasing to crates.io:
# - Remove path dependencies
# - Update doc url
# - Cargo.toml
# - Update CHANGELOG.md.
# - Create "tokio-macros-1.0.x" git tag.
version = "1.2.0"
version = "1.7.0"
edition = "2018"
rust-version = "1.46"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
documentation = "https://docs.rs/tokio-macros/1.2.0/tokio_macros"
description = """
Tokio's proc macros.
"""
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2021 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+97 -42
View File
@@ -1,6 +1,10 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, quote_spanned, ToTokens};
use syn::parse::Parser;
// syn::AttributeArgs does not implement syn::Parse
type AttributeArgs = syn::punctuated::Punctuated<syn::NestedMeta, syn::Token![,]>;
#[derive(Clone, Copy, PartialEq)]
enum RuntimeFlavor {
@@ -27,6 +31,13 @@ struct FinalConfig {
start_paused: Option<bool>,
}
/// Config used in case of the attribute not being able to build a valid config
const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
flavor: RuntimeFlavor::CurrentThread,
worker_threads: None,
start_paused: None,
};
struct Configuration {
rt_multi_thread_available: bool,
default_flavor: RuntimeFlavor,
@@ -184,13 +195,13 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
}
}
fn parse_knobs(
mut input: syn::ItemFn,
args: syn::AttributeArgs,
fn build_config(
input: syn::ItemFn,
args: AttributeArgs,
is_test: bool,
rt_multi_thread: bool,
) -> Result<TokenStream, syn::Error> {
if input.sig.asyncness.take().is_none() {
) -> Result<FinalConfig, syn::Error> {
if input.sig.asyncness.is_none() {
let msg = "the `async` keyword is missing from the function declaration";
return Err(syn::Error::new_spanned(input.sig.fn_token, msg));
}
@@ -201,12 +212,15 @@ fn parse_knobs(
for arg in args {
match arg {
syn::NestedMeta::Meta(syn::Meta::NameValue(namevalue)) => {
let ident = namevalue.path.get_ident();
if ident.is_none() {
let msg = "Must have specified ident";
return Err(syn::Error::new_spanned(namevalue, msg));
}
match ident.unwrap().to_string().to_lowercase().as_str() {
let ident = namevalue
.path
.get_ident()
.ok_or_else(|| {
syn::Error::new_spanned(&namevalue, "Must have specified ident")
})?
.to_string()
.to_lowercase();
match ident.as_str() {
"worker_threads" => {
config.set_worker_threads(
namevalue.lit.clone(),
@@ -239,12 +253,11 @@ fn parse_knobs(
}
}
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
let ident = path.get_ident();
if ident.is_none() {
let msg = "Must have specified ident";
return Err(syn::Error::new_spanned(path, msg));
}
let name = ident.unwrap().to_string().to_lowercase();
let name = path
.get_ident()
.ok_or_else(|| syn::Error::new_spanned(&path, "Must have specified ident"))?
.to_string()
.to_lowercase();
let msg = match name.as_str() {
"threaded_scheduler" | "multi_thread" => {
format!(
@@ -276,7 +289,11 @@ fn parse_knobs(
}
}
let config = config.build()?;
config.build()
}
fn parse_knobs(mut input: syn::ItemFn, is_test: bool, config: FinalConfig) -> TokenStream {
input.sig.asyncness = None;
// If type mismatch occurs, the current rustc points to the last statement.
let (last_stmt_start_span, last_stmt_end_span) = {
@@ -321,16 +338,32 @@ fn parse_knobs(
let body = &input.block;
let brace_token = input.block.brace_token;
let (tail_return, tail_semicolon) = match body.stmts.last() {
Some(syn::Stmt::Semi(syn::Expr::Return(_), _)) => (quote! { return }, quote! { ; }),
Some(syn::Stmt::Semi(..)) | Some(syn::Stmt::Local(..)) | None => {
match &input.sig.output {
syn::ReturnType::Type(_, ty) if matches!(&**ty, syn::Type::Tuple(ty) if ty.elems.is_empty()) =>
{
(quote! {}, quote! { ; }) // unit
}
syn::ReturnType::Default => (quote! {}, quote! { ; }), // unit
syn::ReturnType::Type(..) => (quote! {}, quote! {}), // ! or another
}
}
_ => (quote! {}, quote! {}),
};
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
{
#rt
let body = async #body;
#[allow(clippy::expect_used)]
#tail_return #rt
.enable_all()
.build()
.unwrap()
.block_on(async #body)
.expect("Failed building the Runtime")
.block_on(body)#tail_semicolon
}
})
.unwrap();
.expect("Parsing failure");
input.block.brace_token = brace_token;
let result = quote! {
@@ -338,36 +371,58 @@ fn parse_knobs(
#input
};
Ok(result.into())
result.into()
}
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
tokens.extend(TokenStream::from(error.into_compile_error()));
tokens
}
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
// If any of the steps for this macro fail, we still want to expand to an item that is as close
// to the expected output as possible. This helps out IDEs such that completions and other
// related features keep working.
let input: syn::ItemFn = match syn::parse(item.clone()) {
Ok(it) => it,
Err(e) => return token_stream_with_error(item, e),
};
if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
let config = if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
let msg = "the main function cannot accept arguments";
return syn::Error::new_spanned(&input.sig.ident, msg)
.to_compile_error()
.into();
}
Err(syn::Error::new_spanned(&input.sig.ident, msg))
} else {
AttributeArgs::parse_terminated
.parse(args)
.and_then(|args| build_config(input.clone(), args, false, rt_multi_thread))
};
parse_knobs(input, args, false, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
match config {
Ok(config) => parse_knobs(input, false, config),
Err(e) => token_stream_with_error(parse_knobs(input, false, DEFAULT_ERROR_CONFIG), e),
}
}
pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
// If any of the steps for this macro fail, we still want to expand to an item that is as close
// to the expected output as possible. This helps out IDEs such that completions and other
// related features keep working.
let input: syn::ItemFn = match syn::parse(item.clone()) {
Ok(it) => it,
Err(e) => return token_stream_with_error(item, e),
};
let config = if let Some(attr) = input.attrs.iter().find(|attr| attr.path.is_ident("test")) {
let msg = "second test attribute is supplied";
Err(syn::Error::new_spanned(&attr, msg))
} else {
AttributeArgs::parse_terminated
.parse(args)
.and_then(|args| build_config(input.clone(), args, true, rt_multi_thread))
};
for attr in &input.attrs {
if attr.path.is_ident("test") {
let msg = "second test attribute is supplied";
return syn::Error::new_spanned(&attr, msg)
.to_compile_error()
.into();
}
match config {
Ok(config) => parse_knobs(input, true, config),
Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e),
}
parse_knobs(input, args, true, rt_multi_thread).unwrap_or_else(|e| e.to_compile_error().into())
}
+8 -1
View File
@@ -5,7 +5,6 @@
rust_2018_idioms,
unreachable_pub
)]
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
#![doc(test(
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
@@ -329,3 +328,11 @@ pub fn test_fail(_args: TokenStream, _item: TokenStream) -> TokenStream {
pub fn select_priv_declare_output_enum(input: TokenStream) -> TokenStream {
select::declare_output_enum(input)
}
/// Implementation detail of the `select!` macro. This macro is **not** intended
/// to be used as part of the public API and is permitted to change.
#[proc_macro]
#[doc(hidden)]
pub fn select_priv_clean_pattern(input: TokenStream) -> TokenStream {
select::clean_pattern_macro(input)
}
+67
View File
@@ -41,3 +41,70 @@ pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream {
pub(super) type Mask = #mask;
})
}
pub(crate) fn clean_pattern_macro(input: TokenStream) -> TokenStream {
// If this isn't a pattern, we return the token stream as-is. The select!
// macro is using it in a location requiring a pattern, so an error will be
// emitted there.
let mut input: syn::Pat = match syn::parse(input.clone()) {
Ok(it) => it,
Err(_) => return input,
};
clean_pattern(&mut input);
quote::ToTokens::into_token_stream(input).into()
}
// Removes any occurrences of ref or mut in the provided pattern.
fn clean_pattern(pat: &mut syn::Pat) {
match pat {
syn::Pat::Box(_box) => {}
syn::Pat::Lit(_literal) => {}
syn::Pat::Macro(_macro) => {}
syn::Pat::Path(_path) => {}
syn::Pat::Range(_range) => {}
syn::Pat::Rest(_rest) => {}
syn::Pat::Verbatim(_tokens) => {}
syn::Pat::Wild(_underscore) => {}
syn::Pat::Ident(ident) => {
ident.by_ref = None;
ident.mutability = None;
if let Some((_at, pat)) = &mut ident.subpat {
clean_pattern(&mut *pat);
}
}
syn::Pat::Or(or) => {
for case in or.cases.iter_mut() {
clean_pattern(case);
}
}
syn::Pat::Slice(slice) => {
for elem in slice.elems.iter_mut() {
clean_pattern(elem);
}
}
syn::Pat::Struct(struct_pat) => {
for field in struct_pat.fields.iter_mut() {
clean_pattern(&mut field.pat);
}
}
syn::Pat::Tuple(tuple) => {
for elem in tuple.elems.iter_mut() {
clean_pattern(elem);
}
}
syn::Pat::TupleStruct(tuple) => {
for elem in tuple.pat.elems.iter_mut() {
clean_pattern(elem);
}
}
syn::Pat::Reference(reference) => {
reference.mutability = None;
clean_pattern(&mut *reference.pat);
}
syn::Pat::Type(type_pat) => {
clean_pattern(&mut *type_pat.pat);
}
_ => {}
}
}
+20
View File
@@ -1,3 +1,23 @@
# 0.1.8 (October 29, 2021)
- stream: add `From<Receiver<T>>` impl for receiver streams ([#4080])
- stream: impl `FromIterator` for `StreamMap` ([#4052])
- signal: make windows docs for signal module show up on unix builds ([#3770])
[#3770]: https://github.com/tokio-rs/tokio/pull/3770
[#4052]: https://github.com/tokio-rs/tokio/pull/4052
[#4080]: https://github.com/tokio-rs/tokio/pull/4080
# 0.1.7 (July 7, 2021)
### Fixed
- sync: fix watch wrapper ([#3914])
- time: fix `Timeout::size_hint` ([#3902])
[#3902]: https://github.com/tokio-rs/tokio/pull/3902
[#3914]: https://github.com/tokio-rs/tokio/pull/3914
# 0.1.6 (May 14, 2021)
### Added
+9 -6
View File
@@ -2,17 +2,15 @@
name = "tokio-stream"
# When releasing to crates.io:
# - Remove path dependencies
# - Update doc url
# - Cargo.toml
# - Update CHANGELOG.md.
# - Create "tokio-stream-0.1.x" git tag.
version = "0.1.6"
version = "0.1.8"
edition = "2018"
rust-version = "1.46"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
documentation = "https://docs.rs/tokio-stream/0.1.6/tokio_stream"
description = """
Utilities to work with `Stream` and `tokio`.
"""
@@ -30,8 +28,8 @@ signal = ["tokio/signal"]
[dependencies]
futures-core = { version = "0.3.0" }
pin-project-lite = "0.2.0"
tokio = { version = "1.2.0", path = "../tokio", features = ["sync"] }
tokio-util = { version = "0.6.3", path = "../tokio-util", optional = true }
tokio = { version = "1.8.0", path = "../tokio", features = ["sync"] }
tokio-util = { version = "0.7.0", path = "../tokio-util", optional = true }
[dev-dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["full", "test-util"] }
@@ -44,3 +42,8 @@ proptest = "1"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
# Issue #3770
#
# This should allow `docsrs` to be read across projects, so that `tokio-stream`
# can pick up stubbed types exported by `tokio`.
rustc-args = ["--cfg", "docsrs"]
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2021 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
-1
View File
@@ -10,7 +10,6 @@
unreachable_pub
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
#![doc(test(
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
+97 -3
View File
@@ -1,3 +1,4 @@
use core::future::Future;
use futures_core::Stream;
mod all;
@@ -27,6 +28,9 @@ use fuse::Fuse;
mod map;
use map::Map;
mod map_while;
use map_while::MapWhile;
mod merge;
use merge::Merge;
@@ -39,15 +43,18 @@ use skip::Skip;
mod skip_while;
use skip_while::SkipWhile;
mod try_next;
use try_next::TryNext;
mod take;
use take::Take;
mod take_while;
use take_while::TakeWhile;
mod then;
use then::Then;
mod try_next;
use try_next::TryNext;
cfg_time! {
mod timeout;
use timeout::Timeout;
@@ -197,6 +204,93 @@ pub trait StreamExt: Stream {
Map::new(self, f)
}
/// Map this stream's items to a different type for as long as determined by
/// the provided closure. A stream of the target type will be returned,
/// which will yield elements until the closure returns `None`.
///
/// The provided closure is executed over all elements of this stream as
/// they are made available, until it returns `None`. It is executed inline
/// with calls to [`poll_next`](Stream::poll_next). Once `None` is returned,
/// the underlying stream will not be polled again.
///
/// Note that this function consumes the stream passed into it and returns a
/// wrapped version of it, similar to the [`Iterator::map_while`] method in the
/// standard library.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
/// let stream = stream::iter(1..=10);
/// let mut stream = stream.map_while(|x| {
/// if x < 4 {
/// Some(x + 3)
/// } else {
/// None
/// }
/// });
/// assert_eq!(stream.next().await, Some(4));
/// assert_eq!(stream.next().await, Some(5));
/// assert_eq!(stream.next().await, Some(6));
/// assert_eq!(stream.next().await, None);
/// # }
/// ```
fn map_while<T, F>(self, f: F) -> MapWhile<Self, F>
where
F: FnMut(Self::Item) -> Option<T>,
Self: Sized,
{
MapWhile::new(self, f)
}
/// Maps this stream's items asynchronously to a different type, returning a
/// new stream of the resulting type.
///
/// The provided closure is executed over all elements of this stream as
/// they are made available, and the returned future is executed. Only one
/// future is executed at the time.
///
/// Note that this function consumes the stream passed into it and returns a
/// wrapped version of it, similar to the existing `then` methods in the
/// standard library.
///
/// Be aware that if the future is not `Unpin`, then neither is the `Stream`
/// returned by this method. To handle this, you can use `tokio::pin!` as in
/// the example below or put the stream in a `Box` with `Box::pin(stream)`.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio_stream::{self as stream, StreamExt};
///
/// async fn do_async_work(value: i32) -> i32 {
/// value + 3
/// }
///
/// let stream = stream::iter(1..=3);
/// let stream = stream.then(do_async_work);
///
/// tokio::pin!(stream);
///
/// assert_eq!(stream.next().await, Some(4));
/// assert_eq!(stream.next().await, Some(5));
/// assert_eq!(stream.next().await, Some(6));
/// # }
/// ```
fn then<F, Fut>(self, f: F) -> Then<Self, Fut, F>
where
F: FnMut(Self::Item) -> Fut,
Fut: Future,
Self: Sized,
{
Then::new(self, f)
}
/// Combine two streams into one by interleaving the output of both as it
/// is produced.
///
+6 -6
View File
@@ -66,17 +66,17 @@ where
use Poll::Ready;
loop {
let mut me = self.as_mut().project();
let me = self.as_mut().project();
let item = match ready!(me.stream.poll_next(cx)) {
Some(item) => item,
None => {
return Ready(U::finalize(sealed::Internal, &mut me.collection));
return Ready(U::finalize(sealed::Internal, me.collection));
}
};
if !U::extend(sealed::Internal, &mut me.collection, item) {
return Ready(U::finalize(sealed::Internal, &mut me.collection));
if !U::extend(sealed::Internal, me.collection, item) {
return Ready(U::finalize(sealed::Internal, me.collection));
}
}
}
@@ -113,7 +113,7 @@ impl<T: AsRef<str>> sealed::FromStreamPriv<T> for String {
}
fn finalize(_: sealed::Internal, collection: &mut String) -> String {
mem::replace(collection, String::new())
mem::take(collection)
}
}
@@ -132,7 +132,7 @@ impl<T> sealed::FromStreamPriv<T> for Vec<T> {
}
fn finalize(_: sealed::Internal, collection: &mut Vec<T>) -> Vec<T> {
mem::replace(collection, vec![])
mem::take(collection)
}
}
+52
View File
@@ -0,0 +1,52 @@
use crate::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{Context, Poll};
use pin_project_lite::pin_project;
pin_project! {
/// Stream for the [`map_while`](super::StreamExt::map_while) method.
#[must_use = "streams do nothing unless polled"]
pub struct MapWhile<St, F> {
#[pin]
stream: St,
f: F,
}
}
impl<St, F> fmt::Debug for MapWhile<St, F>
where
St: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapWhile")
.field("stream", &self.stream)
.finish()
}
}
impl<St, F> MapWhile<St, F> {
pub(super) fn new(stream: St, f: F) -> Self {
MapWhile { stream, f }
}
}
impl<St, F, T> Stream for MapWhile<St, F>
where
St: Stream,
F: FnMut(St::Item) -> Option<T>,
{
type Item = T;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
let me = self.project();
let f = me.f;
me.stream.poll_next(cx).map(|opt| opt.and_then(f))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (_, upper) = self.stream.size_hint();
(0, upper)
}
}
+83
View File
@@ -0,0 +1,83 @@
use crate::Stream;
use core::fmt;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use pin_project_lite::pin_project;
pin_project! {
/// Stream for the [`then`](super::StreamExt::then) method.
#[must_use = "streams do nothing unless polled"]
pub struct Then<St, Fut, F> {
#[pin]
stream: St,
#[pin]
future: Option<Fut>,
f: F,
}
}
impl<St, Fut, F> fmt::Debug for Then<St, Fut, F>
where
St: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Then")
.field("stream", &self.stream)
.finish()
}
}
impl<St, Fut, F> Then<St, Fut, F> {
pub(super) fn new(stream: St, f: F) -> Self {
Then {
stream,
future: None,
f,
}
}
}
impl<St, F, Fut> Stream for Then<St, Fut, F>
where
St: Stream,
Fut: Future,
F: FnMut(St::Item) -> Fut,
{
type Item = Fut::Output;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Fut::Output>> {
let mut me = self.project();
loop {
if let Some(future) = me.future.as_mut().as_pin_mut() {
match future.poll(cx) {
Poll::Ready(item) => {
me.future.set(None);
return Poll::Ready(Some(item));
}
Poll::Pending => return Poll::Pending,
}
}
match me.stream.as_mut().poll_next(cx) {
Poll::Ready(Some(item)) => {
me.future.set(Some((me.f)(item)));
}
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending => return Poll::Pending,
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let future_len = if self.future.is_some() { 1 } else { 0 };
let (lower, upper) = self.stream.size_hint();
let lower = lower.saturating_add(future_len);
let upper = upper.and_then(|upper| upper.checked_add(future_len));
(lower, upper)
}
}
+29 -3
View File
@@ -364,11 +364,11 @@ impl<K, V> StreamMap<K, V> {
/// # Examples
///
/// ```
/// use std::collections::HashMap;
/// use tokio_stream::{StreamMap, pending};
///
/// let mut a = HashMap::new();
/// let mut a = StreamMap::new();
/// assert!(a.is_empty());
/// a.insert(1, "a");
/// a.insert(1, pending::<i32>());
/// assert!(!a.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
@@ -568,6 +568,32 @@ where
}
}
impl<K, V> std::iter::FromIterator<(K, V)> for StreamMap<K, V>
where
K: Hash + Eq,
{
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
let iterator = iter.into_iter();
let (lower_bound, _) = iterator.size_hint();
let mut stream_map = Self::with_capacity(lower_bound);
for (key, value) in iterator {
stream_map.insert(key, value);
}
stream_map
}
}
impl<K, V> Extend<(K, V)> for StreamMap<K, V> {
fn extend<T>(&mut self, iter: T)
where
T: IntoIterator<Item = (K, V)>,
{
self.entries.extend(iter);
}
}
mod rand {
use std::cell::Cell;
+2 -11
View File
@@ -1,13 +1,4 @@
//! Wrappers for Tokio types that implement `Stream`.
//!
#![cfg_attr(
unix,
doc = "You are viewing documentation built under unix. To view windows-specific wrappers, change to the `x86_64-pc-windows-msvc` platform."
)]
#![cfg_attr(
windows,
doc = "You are viewing documentation built under windows. To view unix-specific wrappers, change to the `x86_64-unknown-linux-gnu` platform."
)]
/// Error types for the wrappers.
pub mod errors {
@@ -36,9 +27,9 @@ cfg_signal! {
#[cfg(unix)]
pub use signal_unix::SignalStream;
#[cfg(windows)]
#[cfg(any(windows, docsrs))]
mod signal_windows;
#[cfg(windows)]
#[cfg(any(windows, docsrs))]
pub use signal_windows::{CtrlCStream, CtrlBreakStream};
}
+6
View File
@@ -71,3 +71,9 @@ impl<T> fmt::Debug for BroadcastStream<T> {
f.debug_struct("BroadcastStream").finish()
}
}
impl<T: 'static + Clone + Send> From<Receiver<T>> for BroadcastStream<T> {
fn from(recv: Receiver<T>) -> Self {
Self::new(recv)
}
}
@@ -57,3 +57,9 @@ impl<T> AsMut<Receiver<T>> for ReceiverStream<T> {
&mut self.inner
}
}
impl<T> From<Receiver<T>> for ReceiverStream<T> {
fn from(recv: Receiver<T>) -> Self {
Self::new(recv)
}
}
@@ -51,3 +51,9 @@ impl<T> AsMut<UnboundedReceiver<T>> for UnboundedReceiverStream<T> {
&mut self.inner
}
}
impl<T> From<UnboundedReceiver<T>> for UnboundedReceiverStream<T> {
fn from(recv: UnboundedReceiver<T>) -> Self {
Self::new(recv)
}
}
+9 -3
View File
@@ -59,7 +59,7 @@ async fn make_future<T: Clone + Send + Sync>(
(result, rx)
}
impl<T: 'static + Clone + Unpin + Send + Sync> WatchStream<T> {
impl<T: 'static + Clone + Send + Sync> WatchStream<T> {
/// Create a new `WatchStream`.
pub fn new(rx: Receiver<T>) -> Self {
Self {
@@ -72,10 +72,10 @@ impl<T: Clone + 'static + Send + Sync> Stream for WatchStream<T> {
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let (result, rx) = ready!(self.inner.poll(cx));
let (result, mut rx) = ready!(self.inner.poll(cx));
match result {
Ok(_) => {
let received = (*rx.borrow()).clone();
let received = (*rx.borrow_and_update()).clone();
self.inner.set(make_future(rx));
Poll::Ready(Some(received))
}
@@ -94,3 +94,9 @@ impl<T> fmt::Debug for WatchStream<T> {
f.debug_struct("WatchStream").finish()
}
}
impl<T: 'static + Clone + Send + Sync> From<Receiver<T>> for WatchStream<T> {
fn from(recv: Receiver<T>) -> Self {
Self::new(recv)
}
}
+29
View File
@@ -0,0 +1,29 @@
#![cfg(feature = "sync")]
use tokio::sync::watch;
use tokio_stream::wrappers::WatchStream;
use tokio_stream::StreamExt;
#[tokio::test]
async fn message_not_twice() {
let (tx, rx) = watch::channel("hello");
let mut counter = 0;
let mut stream = WatchStream::new(rx).map(move |payload| {
println!("{}", payload);
if payload == "goodbye" {
counter += 1;
}
if counter >= 2 {
panic!("too many goodbyes");
}
});
let task = tokio::spawn(async move { while stream.next().await.is_some() {} });
// Send goodbye just once
tx.send("goodbye").unwrap();
drop(tx);
task.await.unwrap();
}
+2 -4
View File
@@ -2,17 +2,15 @@
name = "tokio-test"
# When releasing to crates.io:
# - Remove path dependencies
# - Update doc url
# - Cargo.toml
# - Update CHANGELOG.md.
# - Create "tokio-test-0.4.x" git tag.
version = "0.4.2"
edition = "2018"
rust-version = "1.46"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
documentation = "https://docs.rs/tokio-test/0.4.2/tokio_test"
description = """
Testing utilities for Tokio- and futures-based code
"""
@@ -20,7 +18,7 @@ categories = ["asynchronous", "testing"]
[dependencies]
tokio = { version = "1.2.0", path = "../tokio", features = ["rt", "sync", "time", "test-util"] }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
tokio-stream = { version = "0.1.1", path = "../tokio-stream" }
async-stream = "0.3"
bytes = "1.0.0"
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2021 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
-1
View File
@@ -4,7 +4,6 @@
rust_2018_idioms,
unreachable_pub
)]
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
#![doc(test(
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
+36
View File
@@ -1,3 +1,39 @@
# 0.6.9 (October 29, 2021)
### Added
- codec: implement `Clone` for `LengthDelimitedCodec` ([#4089])
- io: add `SyncIoBridge` ([#4146])
### Fixed
- time: update deadline on removal in `DelayQueue` ([#4178])
- codec: Update stream impl for Framed to return None after Err ([#4166])
[#4089]: https://github.com/tokio-rs/tokio/pull/4089
[#4146]: https://github.com/tokio-rs/tokio/pull/4146
[#4166]: https://github.com/tokio-rs/tokio/pull/4166
[#4178]: https://github.com/tokio-rs/tokio/pull/4178
# 0.6.8 (September 3, 2021)
### Added
- sync: add drop guard for `CancellationToken` ([#3839])
- compact: added `AsyncSeek` compat ([#4078])
- time: expose `Key` used in `DelayQueue`'s `Expired` ([#4081])
- io: add `with_capacity` to `ReaderStream` ([#4086])
### Fixed
- codec: remove unnecessary `doc(cfg(...))` ([#3989])
[#3839]: https://github.com/tokio-rs/tokio/pull/3839
[#4078]: https://github.com/tokio-rs/tokio/pull/4078
[#4081]: https://github.com/tokio-rs/tokio/pull/4081
[#4086]: https://github.com/tokio-rs/tokio/pull/4086
[#3989]: https://github.com/tokio-rs/tokio/pull/3989
# 0.6.7 (May 14, 2021)
### Added
+8 -8
View File
@@ -2,35 +2,35 @@
name = "tokio-util"
# When releasing to crates.io:
# - Remove path dependencies
# - Update doc url
# - Cargo.toml
# - Update CHANGELOG.md.
# - Create "tokio-util-0.6.x" git tag.
version = "0.6.7"
# - Create "tokio-util-0.7.x" git tag.
version = "0.7.0"
edition = "2018"
rust-version = "1.46"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
documentation = "https://docs.rs/tokio-util/0.6.7/tokio_util"
description = """
Additional utilities for working with Tokio.
"""
categories = ["asynchronous"]
publish = false
[features]
# No features on by default
default = []
# Shorthand for enabling everything
full = ["codec", "compat", "io", "time", "net", "rt"]
full = ["codec", "compat", "io-util", "time", "net", "rt"]
net = ["tokio/net"]
compat = ["futures-io",]
codec = []
time = ["tokio/time","slab"]
io = []
rt = ["tokio/rt"]
io-util = ["io", "tokio/rt", "tokio/io-util"]
rt = ["tokio/rt", "tokio/sync", "futures-util"]
__docs_rs = ["futures-util"]
@@ -44,7 +44,7 @@ futures-io = { version = "0.3.0", optional = true }
futures-util = { version = "0.3.0", optional = true }
log = "0.4"
pin-project-lite = "0.2.0"
slab = { version = "0.4.1", optional = true } # Backs `DelayQueue`
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2021 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+1 -1
View File
@@ -1,6 +1,6 @@
# tokio-util
Utilities for encoding and decoding frames.
Utilities for working with Tokio.
## License
+12
View File
@@ -38,6 +38,18 @@ macro_rules! cfg_io {
}
}
cfg_io! {
macro_rules! cfg_io_util {
($($item:item)*) => {
$(
#[cfg(feature = "io-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "io-util")))]
$item
)*
}
}
}
macro_rules! cfg_rt {
($($item:item)*) => {
$(
+30
View File
@@ -106,6 +106,7 @@ where
eof: false,
is_readable: false,
buffer: BytesMut::with_capacity(capacity),
has_errored: false,
},
write: WriteFrame::default(),
},
@@ -203,6 +204,35 @@ impl<T, U> Framed<T, U> {
&mut self.inner.codec
}
/// Maps the codec `U` to `C`, preserving the read and write buffers
/// wrapped by `Framed`.
///
/// Note that care should be taken to not tamper with the underlying codec
/// as it may corrupt the stream of frames otherwise being worked with.
pub fn map_codec<C, F>(self, map: F) -> Framed<T, C>
where
F: FnOnce(U) -> C,
{
// This could be potentially simplified once rust-lang/rust#86555 hits stable
let parts = self.into_parts();
Framed::from_parts(FramedParts {
io: parts.io,
codec: map(parts.codec),
read_buf: parts.read_buf,
write_buf: parts.write_buf,
_priv: (),
})
}
/// Returns a mutable reference to the underlying codec wrapped by
/// `Framed`.
///
/// Note that care should be taken to not tamper with the underlying codec
/// as it may corrupt the stream of frames otherwise being worked with.
pub fn codec_pin_mut(self: Pin<&mut Self>) -> &mut U {
self.project().inner.project().codec
}
/// Returns a reference to the read buffer.
pub fn read_buffer(&self) -> &BytesMut {
&self.inner.state.read.buffer
+56 -26
View File
@@ -27,10 +27,12 @@ pin_project! {
const INITIAL_CAPACITY: usize = 8 * 1024;
const BACKPRESSURE_BOUNDARY: usize = INITIAL_CAPACITY;
#[derive(Debug)]
pub(crate) struct ReadFrame {
pub(crate) eof: bool,
pub(crate) is_readable: bool,
pub(crate) buffer: BytesMut,
pub(crate) has_errored: bool,
}
pub(crate) struct WriteFrame {
@@ -49,6 +51,7 @@ impl Default for ReadFrame {
eof: false,
is_readable: false,
buffer: BytesMut::with_capacity(INITIAL_CAPACITY),
has_errored: false,
}
}
}
@@ -72,6 +75,7 @@ impl From<BytesMut> for ReadFrame {
buffer,
is_readable: size > 0,
eof: false,
has_errored: false,
}
}
}
@@ -126,30 +130,42 @@ where
//
// The initial state is `reading`.
//
// | state | eof | is_readable |
// |---------|-------|-------------|
// | reading | false | false |
// | framing | false | true |
// | pausing | true | true |
// | paused | true | false |
//
// `decode_eof`
// returns `Some` read 0 bytes
// │ │
//
// ┌───────┐ `decode_eof` ┌──────┐
// ┌──read 0 bytes──▶│pausing│─returns `None`─▶│paused│──┐
// │ └───────┘ └──────┘
// pending read┐────── │ ▲ │
// │ │ │ │ │
// │ ▼ │ `decode` returns `Some`│ pending read
// │ ╔═══════╗ ┌───────┐◀─┘
// └──║reading║─read n>0 bytes─▶│framing│
// ╚═══════╝ └───────┘◀──────read n>0 bytes┘
//
//
// └─`decode` returns `None`─
// | state | eof | is_readable | has_errored |
// |---------|-------|-------------|-------------|
// | reading | false | false | false |
// | framing | false | true | false |
// | pausing | true | true | false |
// | paused | true | false | false |
// | errored | <any> | <any> | true |
// `decode_eof` returns Err
// ┌────────────────────────────────────────────────────────┐
// `decode_eof` returns │
// `Ok(Some)` │
// ─────┐ `decode_eof` returns After returning │
// Read 0 bytes ├─────▼──┴┐ `Ok(None)` ┌────────┐ ◄───┐ `None` ┌───▼─────┐
// ┌────────────────►│ Pausing ├───────────────────────►│ Paused ├─┐ └───────────┤ Errored
// ─────────┘ └─┬──▲───┘ │ └───▲───▲─┘
// Pending read │ │ │ │
// ┌──────┐ │ `decode` returns `Some` │ └─────┘ │ │
// │ │ │ ┌────── │ Pending │
// │ ┌────▼──┴─┐ Read n>0 bytes ┌┴──────▼─┐ read n>0 bytes │ read
// └─┤ Reading ├───────────────►│ Framing │◄────────────────────────┘ │ │
// └──┬─▲────┘ └─────┬──┬┘ │
// │ │ │ │ `decode` returns Err │
// └───decode` returns `None`──┘ └───────────────────────────────────────────────────────┘ │
// │ read returns Err │
// └────────────────────────────────────────────────────────────────────────────────────────────┘
loop {
// Return `None` if we have encountered an error from the underlying decoder
// See: https://github.com/tokio-rs/tokio/issues/3976
if state.has_errored {
// preparing has_errored -> paused
trace!("Returning None and setting paused");
state.is_readable = false;
state.has_errored = false;
return Poll::Ready(None);
}
// Repeatedly call `decode` or `decode_eof` while the buffer is "readable",
// i.e. it _might_ contain data consumable as a frame or closing frame.
// Both signal that there is no such data by returning `None`.
@@ -165,7 +181,11 @@ where
// pausing or framing
if state.eof {
// pausing
let frame = pinned.codec.decode_eof(&mut state.buffer)?;
let frame = pinned.codec.decode_eof(&mut state.buffer).map_err(|err| {
trace!("Got an error, going to errored state");
state.has_errored = true;
err
})?;
if frame.is_none() {
state.is_readable = false; // prepare pausing -> paused
}
@@ -176,7 +196,11 @@ where
// framing
trace!("attempting to decode a frame");
if let Some(frame) = pinned.codec.decode(&mut state.buffer)? {
if let Some(frame) = pinned.codec.decode(&mut state.buffer).map_err(|op| {
trace!("Got an error, going to errored state");
state.has_errored = true;
op
})? {
trace!("frame decoded from buffer");
// implicit framing -> framing
return Poll::Ready(Some(Ok(frame)));
@@ -190,7 +214,13 @@ 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);
let bytect = match poll_read_buf(pinned.inner.as_mut(), cx, &mut state.buffer)? {
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");
state.has_errored = true;
err
},
)? {
Poll::Ready(ct) => ct,
// implicit reading -> reading or implicit paused -> paused
Poll::Pending => return Poll::Pending,
+27
View File
@@ -51,6 +51,7 @@ where
eof: false,
is_readable: false,
buffer: BytesMut::with_capacity(capacity),
has_errored: false,
},
},
}
@@ -107,6 +108,32 @@ impl<T, D> FramedRead<T, D> {
&mut self.inner.codec
}
/// Maps the decoder `D` to `C`, preserving the read buffer
/// wrapped by `Framed`.
pub fn map_decoder<C, F>(self, map: F) -> FramedRead<T, C>
where
F: FnOnce(D) -> C,
{
// This could be potentially simplified once rust-lang/rust#86555 hits stable
let FramedImpl {
inner,
state,
codec,
} = self.inner;
FramedRead {
inner: FramedImpl {
inner,
state,
codec: map(codec),
},
}
}
/// Returns a mutable reference to the underlying decoder.
pub fn decoder_pin_mut(self: Pin<&mut Self>) -> &mut D {
self.project().inner.project().codec
}
/// Returns a reference to the read buffer.
pub fn read_buffer(&self) -> &BytesMut {
&self.inner.state.buffer
+26
View File
@@ -88,6 +88,32 @@ impl<T, E> FramedWrite<T, E> {
&mut self.inner.codec
}
/// Maps the encoder `E` to `C`, preserving the write buffer
/// wrapped by `Framed`.
pub fn map_encoder<C, F>(self, map: F) -> FramedWrite<T, C>
where
F: FnOnce(E) -> C,
{
// This could be potentially simplified once rust-lang/rust#86555 hits stable
let FramedImpl {
inner,
state,
codec,
} = self.inner;
FramedWrite {
inner: FramedImpl {
inner,
state,
codec: map(codec),
},
}
}
/// Returns a mutable reference to the underlying encoder.
pub fn encoder_pin_mut(self: Pin<&mut Self>) -> &mut E {
self.project().inner.project().codec
}
/// Returns a reference to the write buffer.
pub fn write_buffer(&self) -> &BytesMut {
&self.inner.state.buffer
+3 -3
View File
@@ -421,7 +421,7 @@ pub struct LengthDelimitedCodecError {
/// See [module level] documentation for more detail.
///
/// [module level]: index.html
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct LengthDelimitedCodec {
// Configuration values
builder: Builder,
@@ -746,7 +746,7 @@ impl Builder {
}
}
/// Sets the max frame length
/// Sets the max frame length in bytes
///
/// This configuration option applies to both encoding and decoding. The
/// default value is 8MB.
@@ -767,7 +767,7 @@ impl Builder {
///
/// # fn bind_read<T: AsyncRead>(io: T) {
/// LengthDelimitedCodec::builder()
/// .max_frame_length(8 * 1024)
/// .max_frame_length(8 * 1024 * 1024)
/// .new_read(io);
/// # }
/// # pub fn main() {}
+44 -1
View File
@@ -13,6 +13,7 @@ pin_project! {
pub struct Compat<T> {
#[pin]
inner: T,
seek_pos: Option<io::SeekFrom>,
}
}
@@ -80,7 +81,10 @@ impl<T: tokio::io::AsyncWrite> TokioAsyncWriteCompatExt for T {}
impl<T> Compat<T> {
fn new(inner: T) -> Self {
Self { inner }
Self {
inner,
seek_pos: None,
}
}
/// Get a reference to the `Future`, `Stream`, `AsyncRead`, or `AsyncWrite` object
@@ -216,6 +220,45 @@ where
}
}
impl<T: tokio::io::AsyncSeek> futures_io::AsyncSeek for Compat<T> {
fn poll_seek(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
pos: io::SeekFrom,
) -> Poll<io::Result<u64>> {
if self.seek_pos != Some(pos) {
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))
}
}
impl<T: futures_io::AsyncSeek> tokio::io::AsyncSeek for Compat<T> {
fn start_seek(mut self: Pin<&mut Self>, pos: io::SeekFrom) -> io::Result<()> {
*self.as_mut().project().seek_pos = Some(pos);
Ok(())
}
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
let pos = match self.seek_pos {
None => {
// tokio 1.x AsyncSeek recommends calling poll_complete before start_seek.
// We don't have to guarantee that the value returned by
// poll_complete called without start_seek is correct,
// so we'll return 0.
return Poll::Ready(Ok(0));
}
Some(pos) => pos,
};
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))
}
}
#[cfg(unix)]
impl<T: std::os::unix::io::AsRawFd> std::os::unix::io::AsRawFd for Compat<T> {
fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
+9 -1
View File
@@ -1,14 +1,22 @@
//! Helpers for IO related tasks.
//!
//! These types are often used in combination with hyper or reqwest, as they
//! The stream types are often used in combination with hyper or reqwest, as they
//! allow converting between a hyper [`Body`] and [`AsyncRead`].
//!
//! The [`SyncIoBridge`] type converts from the world of async I/O
//! to synchronous I/O; this may often come up when using synchronous APIs
//! inside [`tokio::task::spawn_blocking`].
//!
//! [`Body`]: https://docs.rs/hyper/0.13/hyper/struct.Body.html
//! [`AsyncRead`]: tokio::io::AsyncRead
mod read_buf;
mod reader_stream;
mod stream_reader;
cfg_io_util! {
mod sync_bridge;
pub use self::sync_bridge::SyncIoBridge;
}
pub use self::read_buf::read_buf;
pub use self::reader_stream::ReaderStream;
+18 -2
View File
@@ -5,7 +5,7 @@ use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::AsyncRead;
const CAPACITY: usize = 4096;
const DEFAULT_CAPACITY: usize = 4096;
pin_project! {
/// Convert an [`AsyncRead`] into a [`Stream`] of byte chunks.
@@ -50,6 +50,7 @@ pin_project! {
reader: Option<R>,
// Working buffer, used to optimize allocations.
buf: BytesMut,
capacity: usize,
}
}
@@ -63,6 +64,21 @@ impl<R: AsyncRead> ReaderStream<R> {
ReaderStream {
reader: Some(reader),
buf: BytesMut::new(),
capacity: DEFAULT_CAPACITY,
}
}
/// Convert an [`AsyncRead`] into a [`Stream`] with item type
/// `Result<Bytes, std::io::Error>`,
/// with a specific read buffer initial capacity.
///
/// [`AsyncRead`]: tokio::io::AsyncRead
/// [`Stream`]: futures_core::Stream
pub fn with_capacity(reader: R, capacity: usize) -> Self {
ReaderStream {
reader: Some(reader),
buf: BytesMut::with_capacity(capacity),
capacity,
}
}
}
@@ -80,7 +96,7 @@ impl<R: AsyncRead> Stream for ReaderStream<R> {
};
if this.buf.capacity() == 0 {
this.buf.reserve(CAPACITY);
this.buf.reserve(*this.capacity);
}
match poll_read_buf(reader, cx, &mut this.buf) {
+103
View File
@@ -0,0 +1,103 @@
use std::io::{Read, Write};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
#[derive(Debug)]
pub struct SyncIoBridge<T> {
src: T,
rt: tokio::runtime::Handle,
}
impl<T: AsyncRead + Unpin> Read for SyncIoBridge<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let src = &mut self.src;
self.rt.block_on(AsyncReadExt::read(src, buf))
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
let src = &mut self.src;
self.rt.block_on(src.read_to_end(buf))
}
fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
let src = &mut self.src;
self.rt.block_on(src.read_to_string(buf))
}
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
let src = &mut self.src;
// The AsyncRead trait returns the count, synchronous doesn't.
let _n = self.rt.block_on(src.read_exact(buf))?;
Ok(())
}
}
impl<T: AsyncWrite + Unpin> Write for SyncIoBridge<T> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let src = &mut self.src;
self.rt.block_on(src.write(buf))
}
fn flush(&mut self) -> std::io::Result<()> {
let src = &mut self.src;
self.rt.block_on(src.flush())
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
let src = &mut self.src;
self.rt.block_on(src.write_all(buf))
}
fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
let src = &mut self.src;
self.rt.block_on(src.write_vectored(bufs))
}
}
// 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> {
/// Determines if the underlying [`tokio::io::AsyncWrite`] target supports efficient vectored writes.
///
/// See [`tokio::io::AsyncWrite::is_write_vectored`].
pub fn is_write_vectored(&self) -> bool {
self.src.is_write_vectored()
}
}
impl<T: Unpin> SyncIoBridge<T> {
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
///
/// When this struct is created, it captures a handle to the current thread's runtime with [`tokio::runtime::Handle::current`].
/// It is hence OK to move this struct into a separate thread outside the runtime, as created
/// by e.g. [`tokio::task::spawn_blocking`].
///
/// Stated even more strongly: to make use of this bridge, you *must* move
/// it into a separate thread outside the runtime. The synchronous I/O will use the
/// underlying handle to block on the backing asynchronous source, via
/// [`tokio::runtime::Handle::block_on`]. As noted in the documentation for that
/// function, an attempt to `block_on` from an asynchronous execution context
/// will panic.
///
/// # Wrapping `!Unpin` types
///
/// Use e.g. `SyncIoBridge::new(Box::pin(src))`.
///
/// # Panic
///
/// This will panic if called outside the context of a Tokio runtime.
pub fn new(src: T) -> Self {
Self::new_with_handle(src, tokio::runtime::Handle::current())
}
/// Use a [`tokio::io::AsyncRead`] synchronously as a [`std::io::Read`] or
/// a [`tokio::io::AsyncWrite`] as a [`std::io::Write`].
///
/// This is the same as [`SyncIoBridge::new`], but allows passing an arbitrary handle and hence may
/// be initially invoked outside of an asynchronous context.
pub fn new_with_handle(src: T, rt: tokio::runtime::Handle) -> Self {
Self { src, rt }
}
}
+2 -1
View File
@@ -5,7 +5,6 @@
rust_2018_idioms,
unreachable_pub
)]
#![cfg_attr(docsrs, deny(broken_intra_doc_links))]
#![doc(test(
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
@@ -31,6 +30,7 @@ cfg_codec! {
cfg_net! {
pub mod udp;
pub mod net;
}
cfg_compat! {
@@ -43,6 +43,7 @@ cfg_io! {
cfg_rt! {
pub mod context;
pub mod task;
}
cfg_time! {
+97
View File
@@ -0,0 +1,97 @@
//! TCP/UDP/Unix helpers for tokio.
use crate::either::Either;
use std::future::Future;
use std::io::Result;
use std::pin::Pin;
use std::task::{Context, Poll};
#[cfg(unix)]
pub mod unix;
/// A trait for a listener: `TcpListener` and `UnixListener`.
pub trait Listener {
/// The stream's type of this listener.
type Io: tokio::io::AsyncRead + tokio::io::AsyncWrite;
/// The socket address type of this listener.
type Addr;
/// Polls to accept a new incoming connection to this listener.
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>>;
/// Accepts a new incoming connection from this listener.
fn accept(&mut self) -> ListenerAcceptFut<'_, Self>
where
Self: Sized,
{
ListenerAcceptFut { listener: self }
}
/// Returns the local address that this listener is bound to.
fn local_addr(&self) -> Result<Self::Addr>;
}
impl Listener for tokio::net::TcpListener {
type Io = tokio::net::TcpStream;
type Addr = std::net::SocketAddr;
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>> {
Self::poll_accept(self, cx)
}
fn local_addr(&self) -> Result<Self::Addr> {
self.local_addr().map(Into::into)
}
}
/// Future for accepting a new connection from a listener.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct ListenerAcceptFut<'a, L> {
listener: &'a mut L,
}
impl<'a, L> Future for ListenerAcceptFut<'a, L>
where
L: Listener,
{
type Output = Result<(L::Io, L::Addr)>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.listener.poll_accept(cx)
}
}
impl<L, R> Either<L, R>
where
L: Listener,
R: Listener,
{
/// Accepts a new incoming connection from this listener.
pub async fn accept(&mut self) -> Result<Either<(L::Io, L::Addr), (R::Io, R::Addr)>> {
match self {
Either::Left(listener) => {
let (stream, addr) = listener.accept().await?;
Ok(Either::Left((stream, addr)))
}
Either::Right(listener) => {
let (stream, addr) = listener.accept().await?;
Ok(Either::Right((stream, addr)))
}
}
}
/// Returns the local address that this listener is bound to.
pub fn local_addr(&self) -> Result<Either<L::Addr, R::Addr>> {
match self {
Either::Left(listener) => {
let addr = listener.local_addr()?;
Ok(Either::Left(addr))
}
Either::Right(listener) => {
let addr = listener.local_addr()?;
Ok(Either::Right(addr))
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
//! Unix domain socket helpers.
use super::Listener;
use std::io::Result;
use std::task::{Context, Poll};
impl Listener for tokio::net::UnixListener {
type Io = tokio::net::UnixStream;
type Addr = tokio::net::unix::SocketAddr;
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<(Self::Io, Self::Addr)>> {
Self::poll_accept(self, cx)
}
fn local_addr(&self) -> Result<Self::Addr> {
self.local_addr().map(Into::into)
}
}
+4
View File
@@ -0,0 +1,4 @@
//! Extra utilities for spawning tasks
mod spawn_pinned;
pub use spawn_pinned::LocalPoolHandle;
+307
View File
@@ -0,0 +1,307 @@
use futures_util::future::{AbortHandle, Abortable};
use std::fmt;
use std::fmt::{Debug, Formatter};
use std::future::Future;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::runtime::Builder;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot;
use tokio::task::{spawn_local, JoinHandle, LocalSet};
/// A handle to a local pool, used for spawning `!Send` tasks.
#[derive(Clone)]
pub struct LocalPoolHandle {
pool: Arc<LocalPool>,
}
impl LocalPoolHandle {
/// Create a new pool of threads to handle `!Send` tasks. Spawn tasks onto this
/// pool via [`LocalPoolHandle::spawn_pinned`].
///
/// # Panics
/// Panics if the pool size is less than one.
pub fn new(pool_size: usize) -> LocalPoolHandle {
assert!(pool_size > 0);
let workers = (0..pool_size)
.map(|_| LocalWorkerHandle::new_worker())
.collect();
let pool = Arc::new(LocalPool { workers });
LocalPoolHandle { pool }
}
/// Spawn a task onto a worker thread and pin it there so it can't be moved
/// off of the thread. Note that the future is not [`Send`], but the
/// [`FnOnce`] which creates it is.
///
/// # Examples
/// ```
/// use std::rc::Rc;
/// use tokio_util::task::LocalPoolHandle;
///
/// #[tokio::main]
/// async fn main() {
/// // Create the local pool
/// let pool = LocalPoolHandle::new(1);
///
/// // Spawn a !Send future onto the pool and await it
/// let output = pool
/// .spawn_pinned(|| {
/// // Rc is !Send + !Sync
/// let local_data = Rc::new("test");
///
/// // This future holds an Rc, so it is !Send
/// async move { local_data.to_string() }
/// })
/// .await
/// .unwrap();
///
/// assert_eq!(output, "test");
/// }
/// ```
pub fn spawn_pinned<F, Fut>(&self, create_task: F) -> JoinHandle<Fut::Output>
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
self.pool.spawn_pinned(create_task)
}
}
impl Debug for LocalPoolHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("LocalPoolHandle")
}
}
struct LocalPool {
workers: Vec<LocalWorkerHandle>,
}
impl LocalPool {
/// Spawn a `?Send` future onto a worker
fn spawn_pinned<F, Fut>(&self, create_task: F) -> JoinHandle<Fut::Output>
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
let (sender, receiver) = oneshot::channel();
let (worker, job_guard) = self.find_and_incr_least_burdened_worker();
let worker_spawner = worker.spawner.clone();
// Spawn a future onto the worker's runtime so we can immediately return
// a join handle.
worker.runtime_handle.spawn(async move {
// Move the job guard into the task
let _job_guard = job_guard;
// Propagate aborts via Abortable/AbortHandle
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let _abort_guard = AbortGuard(abort_handle);
// Inside the future we can't run spawn_local yet because we're not
// in the context of a LocalSet. We need to send create_task to the
// LocalSet task for spawning.
let spawn_task = Box::new(move || {
// Once we're in the LocalSet context we can call spawn_local
let join_handle =
spawn_local(
async move { Abortable::new(create_task(), abort_registration).await },
);
// Send the join handle back to the spawner. If sending fails,
// we assume the parent task was canceled, so cancel this task
// as well.
if let Err(join_handle) = sender.send(join_handle) {
join_handle.abort()
}
});
// Send the callback to the LocalSet task
if let Err(e) = worker_spawner.send(spawn_task) {
// Propagate the error as a panic in the join handle.
panic!("Failed to send job to worker: {}", e);
}
// Wait for the task's join handle
let join_handle = match receiver.await {
Ok(handle) => handle,
Err(e) => {
// We sent the task successfully, but failed to get its
// join handle... We assume something happened to the worker
// and the task was not spawned. Propagate the error as a
// panic in the join handle.
panic!("Worker failed to send join handle: {}", e);
}
};
// Wait for the task to complete
let join_result = join_handle.await;
match join_result {
Ok(Ok(output)) => output,
Ok(Err(_)) => {
// Pinned task was aborted. But that only happens if this
// task is aborted. So this is an impossible branch.
unreachable!(
"Reaching this branch means this task was previously \
aborted but it continued running anyways"
)
}
Err(e) => {
if e.is_panic() {
std::panic::resume_unwind(e.into_panic());
} else if e.is_cancelled() {
// No one else should have the join handle, so this is
// unexpected. Forward this error as a panic in the join
// handle.
panic!("spawn_pinned task was canceled: {}", e);
} else {
// Something unknown happened (not a panic or
// cancellation). Forward this error as a panic in the
// join handle.
panic!("spawn_pinned task failed: {}", e);
}
}
}
})
}
/// Find the worker with the least number of tasks, increment its task
/// count, and return its handle. Make sure to actually spawn a task on
/// the worker so the task count is kept consistent with load.
///
/// A job count guard is also returned to ensure the task count gets
/// decremented when the job is done.
fn find_and_incr_least_burdened_worker(&self) -> (&LocalWorkerHandle, JobCountGuard) {
loop {
let (worker, task_count) = self
.workers
.iter()
.map(|worker| (worker, worker.task_count.load(Ordering::SeqCst)))
.min_by_key(|&(_, count)| count)
.expect("There must be more than one worker");
// Make sure the task count hasn't changed since when we choose this
// worker. Otherwise, restart the search.
if worker
.task_count
.compare_exchange(
task_count,
task_count + 1,
Ordering::SeqCst,
Ordering::Relaxed,
)
.is_ok()
{
return (worker, JobCountGuard(Arc::clone(&worker.task_count)));
}
}
}
}
/// Automatically decrements a worker's job count when a job finishes (when
/// this gets dropped).
struct JobCountGuard(Arc<AtomicUsize>);
impl Drop for JobCountGuard {
fn drop(&mut self) {
// Decrement the job count
let previous_value = self.0.fetch_sub(1, Ordering::SeqCst);
debug_assert!(previous_value >= 1);
}
}
/// Calls abort on the handle when dropped.
struct AbortGuard(AbortHandle);
impl Drop for AbortGuard {
fn drop(&mut self) {
self.0.abort();
}
}
type PinnedFutureSpawner = Box<dyn FnOnce() + Send + 'static>;
struct LocalWorkerHandle {
runtime_handle: tokio::runtime::Handle,
spawner: UnboundedSender<PinnedFutureSpawner>,
task_count: Arc<AtomicUsize>,
}
impl LocalWorkerHandle {
/// Create a new worker for executing pinned tasks
fn new_worker() -> LocalWorkerHandle {
let (sender, receiver) = unbounded_channel();
let runtime = Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to start a pinned worker thread runtime");
let runtime_handle = runtime.handle().clone();
let task_count = Arc::new(AtomicUsize::new(0));
let task_count_clone = Arc::clone(&task_count);
std::thread::spawn(|| Self::run(runtime, receiver, task_count_clone));
LocalWorkerHandle {
runtime_handle,
spawner: sender,
task_count,
}
}
fn run(
runtime: tokio::runtime::Runtime,
mut task_receiver: UnboundedReceiver<PinnedFutureSpawner>,
task_count: Arc<AtomicUsize>,
) {
let local_set = LocalSet::new();
local_set.block_on(&runtime, async {
while let Some(spawn_task) = task_receiver.recv().await {
// Calls spawn_local(future)
(spawn_task)();
}
});
// If there are any tasks on the runtime associated with a LocalSet task
// that has already completed, but whose output has not yet been
// reported, let that task complete.
//
// Since the task_count is decremented when the runtime task exits,
// reading that counter lets us know if any such tasks completed during
// the call to `block_on`.
//
// Tasks on the LocalSet can't complete during this loop since they're
// stored on the LocalSet and we aren't accessing it.
let mut previous_task_count = task_count.load(Ordering::SeqCst);
loop {
// This call will also run tasks spawned on the runtime.
runtime.block_on(tokio::task::yield_now());
let new_task_count = task_count.load(Ordering::SeqCst);
if new_task_count == previous_task_count {
break;
} else {
previous_task_count = new_task_count;
}
}
// It's now no longer possible for a task on the runtime to be
// associated with a LocalSet task that has completed. Drop both the
// LocalSet and runtime to let tasks on the runtime be cancelled if and
// only if they are still on the LocalSet.
//
// Drop the LocalSet task first so that anyone awaiting the runtime
// JoinHandle will see the cancelled error after the LocalSet task
// destructor has completed.
drop(local_set);
drop(runtime);
}
}
+341 -37
View File
@@ -9,8 +9,13 @@ use crate::time::wheel::{self, Wheel};
use futures_core::ready;
use tokio::time::{error::Error, sleep_until, Duration, Instant, Sleep};
use core::ops::{Index, IndexMut};
use slab::Slab;
use std::cmp;
use std::collections::HashMap;
use std::convert::From;
use std::fmt;
use std::fmt::Debug;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
@@ -128,7 +133,7 @@ use std::task::{self, Poll, Waker};
#[derive(Debug)]
pub struct DelayQueue<T> {
/// Stores data associated with entries
slab: Slab<Data<T>>,
slab: SlabStorage<T>,
/// Lookup structure tracking all delays in the queue
wheel: Wheel<Stack<T>>,
@@ -152,6 +157,216 @@ pub struct DelayQueue<T> {
waker: Option<Waker>,
}
#[derive(Default)]
struct SlabStorage<T> {
inner: Slab<Data<T>>,
// A `compact` call requires a re-mapping of the `Key`s that were changed
// during the `compact` call of the `slab`. Since the keys that were given out
// cannot be changed retroactively we need to keep track of these re-mappings.
// The keys of `key_map` correspond to the old keys that were given out and
// the values to the `Key`s that were re-mapped by the `compact` call.
key_map: HashMap<Key, KeyInternal>,
// Index used to create new keys to hand out.
next_key_index: usize,
// Whether `compact` has been called, necessary in order to decide whether
// to include keys in `key_map`.
compact_called: bool,
}
impl<T> SlabStorage<T> {
pub(crate) fn with_capacity(capacity: usize) -> SlabStorage<T> {
SlabStorage {
inner: Slab::with_capacity(capacity),
key_map: HashMap::new(),
next_key_index: 0,
compact_called: false,
}
}
// Inserts data into the inner slab and re-maps keys if necessary
pub(crate) fn insert(&mut self, val: Data<T>) -> Key {
let mut key = KeyInternal::new(self.inner.insert(val));
let key_contained = self.key_map.contains_key(&key.into());
if key_contained {
// It's possible that a `compact` call creates capacitiy in `self.inner` in
// such a way that a `self.inner.insert` call creates a `key` which was
// previously given out during an `insert` call prior to the `compact` call.
// If `key` is contained in `self.key_map`, we have encountered this exact situation,
// We need to create a new key `key_to_give_out` and include the relation
// `key_to_give_out` -> `key` in `self.key_map`.
let key_to_give_out = self.create_new_key();
assert!(!self.key_map.contains_key(&key_to_give_out.into()));
self.key_map.insert(key_to_give_out.into(), key);
key = key_to_give_out;
} else if self.compact_called {
// Include an identity mapping in `self.key_map` in order to allow us to
// panic if a key that was handed out is removed more than once.
self.key_map.insert(key.into(), key);
}
key.into()
}
// Re-map the key in case compact was previously called.
// Note: Since we include identity mappings in key_map after compact was called,
// we have information about all keys that were handed out. In the case in which
// compact was called and we try to remove a Key that was previously removed
// we can detect invalid keys if no key is found in `key_map`. This is necessary
// in order to prevent situations in which a previously removed key
// corresponds to a re-mapped key internally and which would then be incorrectly
// removed from the slab.
//
// Example to illuminate this problem:
//
// Let's assume our `key_map` is {1 -> 2, 2 -> 1} and we call remove(1). If we
// were to remove 1 again, we would not find it inside `key_map` anymore.
// If we were to imply from this that no re-mapping was necessary, we would
// incorrectly remove 1 from `self.slab.inner`, which corresponds to the
// handed-out key 2.
pub(crate) fn remove(&mut self, key: &Key) -> Data<T> {
let remapped_key = if self.compact_called {
match self.key_map.remove(key) {
Some(key_internal) => key_internal,
None => panic!("invalid key"),
}
} else {
(*key).into()
};
self.inner.remove(remapped_key.index)
}
pub(crate) fn shrink_to_fit(&mut self) {
self.inner.shrink_to_fit();
self.key_map.shrink_to_fit();
}
pub(crate) fn compact(&mut self) {
if !self.compact_called {
for (key, _) in self.inner.iter() {
self.key_map.insert(Key::new(key), KeyInternal::new(key));
}
}
let mut remapping = HashMap::new();
self.inner.compact(|_, from, to| {
remapping.insert(from, to);
true
});
// At this point `key_map` contains a mapping for every element.
for internal_key in self.key_map.values_mut() {
if let Some(new_internal_key) = remapping.get(&internal_key.index) {
*internal_key = KeyInternal::new(*new_internal_key);
}
}
if self.key_map.capacity() > 2 * self.key_map.len() {
self.key_map.shrink_to_fit();
}
self.compact_called = true;
}
// Tries to re-map a `Key` that was given out to the user to its
// corresponding internal key.
fn remap_key(&self, key: &Key) -> Option<KeyInternal> {
let key_map = &self.key_map;
if self.compact_called {
key_map.get(&*key).copied()
} else {
Some((*key).into())
}
}
fn create_new_key(&mut self) -> KeyInternal {
while self.key_map.contains_key(&Key::new(self.next_key_index)) {
self.next_key_index = self.next_key_index.wrapping_add(1);
}
KeyInternal::new(self.next_key_index)
}
pub(crate) fn len(&self) -> usize {
self.inner.len()
}
pub(crate) fn capacity(&self) -> usize {
self.inner.capacity()
}
pub(crate) fn clear(&mut self) {
self.inner.clear();
self.key_map.clear();
self.compact_called = false;
}
pub(crate) fn reserve(&mut self, additional: usize) {
self.inner.reserve(additional);
if self.compact_called {
self.key_map.reserve(additional);
}
}
pub(crate) fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub(crate) fn contains(&self, key: &Key) -> bool {
let remapped_key = self.remap_key(key);
match remapped_key {
Some(internal_key) => self.inner.contains(internal_key.index),
None => false,
}
}
}
impl<T> fmt::Debug for SlabStorage<T>
where
T: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
if fmt.alternate() {
fmt.debug_map().entries(self.inner.iter()).finish()
} else {
fmt.debug_struct("Slab")
.field("len", &self.len())
.field("cap", &self.capacity())
.finish()
}
}
}
impl<T> Index<Key> for SlabStorage<T> {
type Output = Data<T>;
fn index(&self, key: Key) -> &Self::Output {
let remapped_key = self.remap_key(&key);
match remapped_key {
Some(internal_key) => &self.inner[internal_key.index],
None => panic!("Invalid index {}", key.index),
}
}
}
impl<T> IndexMut<Key> for SlabStorage<T> {
fn index_mut(&mut self, key: Key) -> &mut Data<T> {
let remapped_key = self.remap_key(&key);
match remapped_key {
Some(internal_key) => &mut self.inner[internal_key.index],
None => panic!("Invalid index {}", key.index),
}
}
}
/// An entry in `DelayQueue` that has expired and been removed.
///
/// Values are returned by [`DelayQueue::poll_expired`].
@@ -176,15 +391,23 @@ pub struct Expired<T> {
///
/// [`DelayQueue`]: struct@DelayQueue
/// [`DelayQueue::insert`]: method@DelayQueue::insert
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Key {
index: usize,
}
// Whereas `Key` is given out to users that use `DelayQueue`, internally we use
// `KeyInternal` as the key type in order to make the logic of mapping between keys
// as a result of `compact` calls clearer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct KeyInternal {
index: usize,
}
#[derive(Debug)]
struct Stack<T> {
/// Head of the stack
head: Option<usize>,
head: Option<Key>,
_p: PhantomData<fn() -> T>,
}
@@ -201,10 +424,10 @@ struct Data<T> {
expired: bool,
/// Next entry in the stack
next: Option<usize>,
next: Option<Key>,
/// Previous entry in the stack
prev: Option<usize>,
prev: Option<Key>,
}
/// Maximum number of entries the queue can handle
@@ -253,7 +476,7 @@ impl<T> DelayQueue<T> {
pub fn with_capacity(capacity: usize) -> DelayQueue<T> {
DelayQueue {
wheel: Wheel::new(),
slab: Slab::with_capacity(capacity),
slab: SlabStorage::with_capacity(capacity),
expired: Stack::default(),
delay: None,
wheel_now: 0,
@@ -348,7 +571,7 @@ impl<T> DelayQueue<T> {
}
}
Key::new(key)
key
}
/// Attempts to pull out the next value of the delay queue, registering the
@@ -369,13 +592,13 @@ impl<T> DelayQueue<T> {
let item = ready!(self.poll_idx(cx));
Poll::Ready(item.map(|result| {
result.map(|idx| {
let data = self.slab.remove(idx);
result.map(|key| {
let data = self.slab.remove(&key);
debug_assert!(data.next.is_none());
debug_assert!(data.prev.is_none());
Expired {
key: Key::new(idx),
key,
data: data.inner,
deadline: self.start + Duration::from_millis(data.when),
}
@@ -437,7 +660,7 @@ impl<T> DelayQueue<T> {
self.insert_at(value, Instant::now() + timeout)
}
fn insert_idx(&mut self, when: u64, key: usize) {
fn insert_idx(&mut self, when: u64, key: Key) {
use self::wheel::{InsertError, Stack};
// Register the deadline with the timer wheel
@@ -462,10 +685,10 @@ impl<T> DelayQueue<T> {
use crate::time::wheel::Stack;
// Special case the `expired` queue
if self.slab[key.index].expired {
self.expired.remove(&key.index, &mut self.slab);
if self.slab[*key].expired {
self.expired.remove(key, &mut self.slab);
} else {
self.wheel.remove(&key.index, &mut self.slab);
self.wheel.remove(key, &mut self.slab);
}
}
@@ -498,8 +721,19 @@ impl<T> DelayQueue<T> {
/// # }
/// ```
pub fn remove(&mut self, key: &Key) -> Expired<T> {
let prev_deadline = self.next_deadline();
self.remove_key(key);
let data = self.slab.remove(key.index);
let data = self.slab.remove(key);
let next_deadline = self.next_deadline();
if prev_deadline != next_deadline {
match (next_deadline, &mut self.delay) {
(None, _) => self.delay = None,
(Some(deadline), Some(delay)) => delay.as_mut().reset(deadline),
(Some(deadline), None) => self.delay = Some(Box::pin(sleep_until(deadline))),
}
}
Expired {
key: Key::new(key.index),
@@ -548,10 +782,10 @@ impl<T> DelayQueue<T> {
// Normalize the deadline. Values cannot be set to expire in the past.
let when = self.normalize_deadline(when);
self.slab[key.index].when = when;
self.slab[key.index].expired = false;
self.slab[*key].when = when;
self.slab[*key].expired = false;
self.insert_idx(when, key.index);
self.insert_idx(when, *key);
let next_deadline = self.next_deadline();
if let (Some(ref mut delay), Some(deadline)) = (&mut self.delay, next_deadline) {
@@ -560,6 +794,50 @@ impl<T> DelayQueue<T> {
}
}
/// Shrink the capacity of the slab, which `DelayQueue` uses internally for storage allocation.
/// This function is not guaranteed to, and in most cases, won't decrease the capacity of the slab
/// to the number of elements still contained in it, because elements cannot be moved to a different
/// index. To decrease the capacity to the size of the slab use [`compact`].
///
/// This function can take O(n) time even when the capacity cannot be reduced or the allocation is
/// shrunk in place. Repeated calls run in O(1) though.
///
/// [`compact`]: method@Self::compact
pub fn shrink_to_fit(&mut self) {
self.slab.shrink_to_fit();
}
/// Shrink the capacity of the slab, which `DelayQueue` uses internally for storage allocation,
/// to the number of elements that are contained in it.
///
/// This methods runs in O(n).
///
/// # Examples
///
/// Basic usage
///
/// ```rust
/// use tokio_util::time::DelayQueue;
/// use std::time::Duration;
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut delay_queue = DelayQueue::with_capacity(10);
///
/// let key1 = delay_queue.insert(5, Duration::from_secs(5));
/// let key2 = delay_queue.insert(10, Duration::from_secs(10));
/// let key3 = delay_queue.insert(15, Duration::from_secs(15));
///
/// delay_queue.remove(&key2);
///
/// delay_queue.compact();
/// assert_eq!(delay_queue.capacity(), 2);
/// # }
/// ```
pub fn compact(&mut self) {
self.slab.compact();
}
/// Returns the next time to poll as determined by the wheel
fn next_deadline(&mut self) -> Option<Instant> {
self.wheel
@@ -739,7 +1017,7 @@ impl<T> DelayQueue<T> {
/// should be returned.
///
/// A slot should be returned when the associated deadline has been reached.
fn poll_idx(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Result<usize, Error>>> {
fn poll_idx(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<Result<Key, Error>>> {
use self::wheel::Stack;
let expired = self.expired.pop(&mut self.slab);
@@ -805,9 +1083,9 @@ impl<T> futures_core::Stream for DelayQueue<T> {
}
impl<T> wheel::Stack for Stack<T> {
type Owned = usize;
type Borrowed = usize;
type Store = Slab<Data<T>>;
type Owned = Key;
type Borrowed = Key;
type Store = SlabStorage<T>;
fn is_empty(&self) -> bool {
self.head.is_none()
@@ -826,28 +1104,29 @@ impl<T> wheel::Stack for Stack<T> {
}
store[item].next = old;
self.head = Some(item)
self.head = Some(item);
}
fn pop(&mut self, store: &mut Self::Store) -> Option<Self::Owned> {
if let Some(idx) = self.head {
self.head = store[idx].next;
if let Some(key) = self.head {
self.head = store[key].next;
if let Some(idx) = self.head {
store[idx].prev = None;
}
store[idx].next = None;
debug_assert!(store[idx].prev.is_none());
store[key].next = None;
debug_assert!(store[key].prev.is_none());
Some(idx)
Some(key)
} else {
None
}
}
fn remove(&mut self, item: &Self::Borrowed, store: &mut Self::Store) {
assert!(store.contains(*item));
let key = *item;
assert!(store.contains(item));
// Ensure that the entry is in fact contained by the stack
debug_assert!({
@@ -856,29 +1135,31 @@ impl<T> wheel::Stack for Stack<T> {
let mut contains = false;
while let Some(idx) = next {
let data = &store[idx];
if idx == *item {
debug_assert!(!contains);
contains = true;
}
next = store[idx].next;
next = data.next;
}
contains
});
if let Some(next) = store[*item].next {
store[next].prev = store[*item].prev;
if let Some(next) = store[key].next {
store[next].prev = store[key].prev;
}
if let Some(prev) = store[*item].prev {
store[prev].next = store[*item].next;
if let Some(prev) = store[key].prev {
store[prev].next = store[key].next;
} else {
self.head = store[*item].next;
self.head = store[key].next;
}
store[*item].next = None;
store[*item].prev = None;
store[key].next = None;
store[key].prev = None;
}
fn when(item: &Self::Borrowed, store: &Self::Store) -> u64 {
@@ -901,6 +1182,24 @@ impl Key {
}
}
impl KeyInternal {
pub(crate) fn new(index: usize) -> KeyInternal {
KeyInternal { index }
}
}
impl From<Key> for KeyInternal {
fn from(item: Key) -> Self {
KeyInternal::new(item.index)
}
}
impl From<KeyInternal> for Key {
fn from(item: KeyInternal) -> Self {
Key::new(item.index)
}
}
impl<T> Expired<T> {
/// Returns a reference to the inner value.
pub fn get_ref(&self) -> &T {
@@ -921,4 +1220,9 @@ impl<T> Expired<T> {
pub fn deadline(&self) -> Instant {
self.deadline
}
/// Returns the key that the expiration is indexed by.
pub fn key(&self) -> Key {
self.key
}
}
+1
View File
@@ -6,6 +6,7 @@ mod stack;
pub(crate) use self::stack::Stack;
use std::borrow::Borrow;
use std::fmt::Debug;
use std::usize;
/// Timing wheel implementation.
+3 -1
View File
@@ -1,4 +1,6 @@
use std::borrow::Borrow;
use std::cmp::Eq;
use std::hash::Hash;
/// Abstracts the stack operations needed to track timeouts.
pub(crate) trait Stack: Default {
@@ -6,7 +8,7 @@ pub(crate) trait Stack: Default {
type Owned: Borrow<Self::Borrowed>;
/// Borrowed item
type Borrowed;
type Borrowed: Eq + Hash;
/// Item storage, this allows a slab to be used instead of just the heap
type Store;
+1 -2
View File
@@ -35,7 +35,6 @@ use std::{io, mem::MaybeUninit};
/// [`Sink`]: futures_sink::Sink
/// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
#[must_use = "sinks do nothing unless polled"]
#[cfg_attr(docsrs, doc(cfg(all(feature = "codec", feature = "udp"))))]
#[derive(Debug)]
pub struct UdpFramed<C, T = UdpSocket> {
socket: T,
@@ -144,7 +143,7 @@ where
..
} = *self;
let n = ready!(socket.borrow().poll_send_to(cx, &wr, *out_addr))?;
let n = ready!(socket.borrow().poll_send_to(cx, wr, *out_addr))?;
let wrote_all = n == self.wr.len();
self.wr.clear();
+61 -4
View File
@@ -12,7 +12,10 @@ use std::task::{Context, Poll};
const INITIAL_CAPACITY: usize = 8 * 1024;
/// Encode and decode u32 values.
struct U32Codec;
#[derive(Default)]
struct U32Codec {
read_bytes: usize,
}
impl Decoder for U32Codec {
type Item = u32;
@@ -24,6 +27,7 @@ impl Decoder for U32Codec {
}
let n = buf.split_to(4).get_u32();
self.read_bytes += 4;
Ok(Some(n))
}
}
@@ -39,6 +43,38 @@ impl Encoder<u32> for U32Codec {
}
}
/// Encode and decode u64 values.
#[derive(Default)]
struct U64Codec {
read_bytes: usize,
}
impl Decoder for U64Codec {
type Item = u64;
type Error = io::Error;
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u64>> {
if buf.len() < 8 {
return Ok(None);
}
let n = buf.split_to(8).get_u64();
self.read_bytes += 8;
Ok(Some(n))
}
}
impl Encoder<u64> for U64Codec {
type Error = io::Error;
fn encode(&mut self, item: u64, dst: &mut BytesMut) -> io::Result<()> {
// Reserve space
dst.reserve(8);
dst.put_u64(item);
Ok(())
}
}
/// This value should never be used
struct DontReadIntoThis;
@@ -63,18 +99,39 @@ impl tokio::io::AsyncRead for DontReadIntoThis {
#[tokio::test]
async fn can_read_from_existing_buf() {
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec::default());
parts.read_buf = BytesMut::from(&[0, 0, 0, 42][..]);
let mut framed = Framed::from_parts(parts);
let num = assert_ok!(framed.next().await.unwrap());
assert_eq!(num, 42);
assert_eq!(framed.codec().read_bytes, 4);
}
#[tokio::test]
async fn can_read_from_existing_buf_after_codec_changed() {
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec::default());
parts.read_buf = BytesMut::from(&[0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 84][..]);
let mut framed = Framed::from_parts(parts);
let num = assert_ok!(framed.next().await.unwrap());
assert_eq!(num, 42);
assert_eq!(framed.codec().read_bytes, 4);
let mut framed = framed.map_codec(|codec| U64Codec {
read_bytes: codec.read_bytes,
});
let num = assert_ok!(framed.next().await.unwrap());
assert_eq!(num, 84);
assert_eq!(framed.codec().read_bytes, 12);
}
#[test]
fn external_buf_grows_to_init() {
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec::default());
parts.read_buf = BytesMut::from(&[0, 0, 0, 42][..]);
let framed = Framed::from_parts(parts);
@@ -85,7 +142,7 @@ fn external_buf_grows_to_init() {
#[test]
fn external_buf_does_not_shrink() {
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec);
let mut parts = FramedParts::new(DontReadIntoThis, U32Codec::default());
parts.read_buf = BytesMut::from(&vec![0; INITIAL_CAPACITY * 2][..]);
let framed = Framed::from_parts(parts);
+34
View File
@@ -50,6 +50,22 @@ impl Decoder for U32Decoder {
}
}
struct U64Decoder;
impl Decoder for U64Decoder {
type Item = u64;
type Error = io::Error;
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<u64>> {
if buf.len() < 8 {
return Ok(None);
}
let n = buf.split_to(8).get_u64();
Ok(Some(n))
}
}
#[test]
fn read_multi_frame_in_packet() {
let mut task = task::spawn(());
@@ -84,6 +100,24 @@ fn read_multi_frame_across_packets() {
});
}
#[test]
fn read_multi_frame_in_packet_after_codec_changed() {
let mut task = task::spawn(());
let mock = mock! {
Ok(b"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x08".to_vec()),
};
let mut framed = FramedRead::new(mock, U32Decoder);
task.enter(|cx, _| {
assert_read!(pin!(framed).poll_next(cx), 0x04);
let mut framed = framed.map_decoder(|_| U64Decoder);
assert_read!(pin!(framed).poll_next(cx), 0x08);
assert!(assert_ready!(pin!(framed).poll_next(cx)).is_none());
});
}
#[test]
fn read_not_ready() {
let mut task = task::spawn(());
+38
View File
@@ -0,0 +1,38 @@
use futures_core::stream::Stream;
use std::{io, pin::Pin};
use tokio_test::{assert_ready, io::Builder, task};
use tokio_util::codec::{BytesCodec, FramedRead};
macro_rules! pin {
($id:ident) => {
Pin::new(&mut $id)
};
}
macro_rules! assert_read {
($e:expr, $n:expr) => {{
let val = assert_ready!($e);
assert_eq!(val.unwrap().unwrap(), $n);
}};
}
#[tokio::test]
async fn return_none_after_error() {
let mut io = FramedRead::new(
Builder::new()
.read(b"abcdef")
.read_error(io::Error::new(io::ErrorKind::Other, "Resource errored out"))
.read(b"more data")
.build(),
BytesCodec::new(),
);
let mut task = task::spawn(());
task.enter(|cx, _| {
assert_read!(pin!(io).poll_next(cx), b"abcdef".to_vec());
assert!(assert_ready!(pin!(io).poll_next(cx)).unwrap().is_err());
assert!(assert_ready!(pin!(io).poll_next(cx)).is_none());
assert_read!(pin!(io).poll_next(cx), b"more data".to_vec());
})
}
+39
View File
@@ -39,6 +39,19 @@ impl Encoder<u32> for U32Encoder {
}
}
struct U64Encoder;
impl Encoder<u64> for U64Encoder {
type Error = io::Error;
fn encode(&mut self, item: u64, dst: &mut BytesMut) -> io::Result<()> {
// Reserve space
dst.reserve(8);
dst.put_u64(item);
Ok(())
}
}
#[test]
fn write_multi_frame_in_packet() {
let mut task = task::spawn(());
@@ -65,6 +78,32 @@ fn write_multi_frame_in_packet() {
});
}
#[test]
fn write_multi_frame_after_codec_changed() {
let mut task = task::spawn(());
let mock = mock! {
Ok(b"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x08".to_vec()),
};
let mut framed = FramedWrite::new(mock, U32Encoder);
task.enter(|cx, _| {
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
assert!(pin!(framed).start_send(0x04).is_ok());
let mut framed = framed.map_encoder(|_| U64Encoder);
assert!(assert_ready!(pin!(framed).poll_ready(cx)).is_ok());
assert!(pin!(framed).start_send(0x08).is_ok());
// Nothing written yet
assert_eq!(1, framed.get_ref().calls.len());
// Flush the writes
assert!(assert_ready!(pin!(framed).poll_flush(cx)).is_ok());
assert_eq!(0, framed.get_ref().calls.len());
});
}
#[test]
fn write_hits_backpressure() {
const ITER: usize = 2 * 1024;
+43
View File
@@ -0,0 +1,43 @@
#![cfg(feature = "io-util")]
use std::error::Error;
use std::io::{Cursor, Read, Result as IoResult};
use tokio::io::AsyncRead;
use tokio_util::io::SyncIoBridge;
async fn test_reader_len(
r: impl AsyncRead + Unpin + Send + 'static,
expected_len: usize,
) -> IoResult<()> {
let mut r = SyncIoBridge::new(r);
let res = tokio::task::spawn_blocking(move || {
let mut buf = Vec::new();
r.read_to_end(&mut buf)?;
Ok::<_, std::io::Error>(buf)
})
.await?;
assert_eq!(res?.len(), expected_len);
Ok(())
}
#[tokio::test]
async fn test_async_read_to_sync() -> Result<(), Box<dyn Error>> {
test_reader_len(tokio::io::empty(), 0).await?;
let buf = b"hello world";
test_reader_len(Cursor::new(buf), buf.len()).await?;
Ok(())
}
#[tokio::test]
async fn test_async_write_to_sync() -> Result<(), Box<dyn Error>> {
let mut dest = Vec::new();
let src = b"hello world";
let dest = tokio::task::spawn_blocking(move || -> Result<_, String> {
let mut w = SyncIoBridge::new(Cursor::new(&mut dest));
std::io::copy(&mut Cursor::new(src), &mut w).map_err(|e| e.to_string())?;
Ok(dest)
})
.await??;
assert_eq!(dest.as_slice(), src);
Ok(())
}
+193
View File
@@ -0,0 +1,193 @@
#![warn(rust_2018_idioms)]
use std::rc::Rc;
use std::sync::Arc;
use tokio_util::task;
/// Simple test of running a !Send future via spawn_pinned
#[tokio::test]
async fn can_spawn_not_send_future() {
let pool = task::LocalPoolHandle::new(1);
let output = pool
.spawn_pinned(|| {
// Rc is !Send + !Sync
let local_data = Rc::new("test");
// This future holds an Rc, so it is !Send
async move { local_data.to_string() }
})
.await
.unwrap();
assert_eq!(output, "test");
}
/// Dropping the join handle still lets the task execute
#[test]
fn can_drop_future_and_still_get_output() {
let pool = task::LocalPoolHandle::new(1);
let (sender, receiver) = std::sync::mpsc::channel();
let _ = pool.spawn_pinned(move || {
// Rc is !Send + !Sync
let local_data = Rc::new("test");
// This future holds an Rc, so it is !Send
async move {
let _ = sender.send(local_data.to_string());
}
});
assert_eq!(receiver.recv(), Ok("test".to_string()));
}
#[test]
#[should_panic(expected = "assertion failed: pool_size > 0")]
fn cannot_create_zero_sized_pool() {
let _pool = task::LocalPoolHandle::new(0);
}
/// We should be able to spawn multiple futures onto the pool at the same time.
#[tokio::test]
async fn can_spawn_multiple_futures() {
let pool = task::LocalPoolHandle::new(2);
let join_handle1 = pool.spawn_pinned(|| {
let local_data = Rc::new("test1");
async move { local_data.to_string() }
});
let join_handle2 = pool.spawn_pinned(|| {
let local_data = Rc::new("test2");
async move { local_data.to_string() }
});
assert_eq!(join_handle1.await.unwrap(), "test1");
assert_eq!(join_handle2.await.unwrap(), "test2");
}
/// A panic in the spawned task causes the join handle to return an error.
/// But, you can continue to spawn tasks.
#[tokio::test]
async fn task_panic_propagates() {
let pool = task::LocalPoolHandle::new(1);
let join_handle = pool.spawn_pinned(|| async {
panic!("Test panic");
});
let result = join_handle.await;
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.is_panic());
let panic_str: &str = *error.into_panic().downcast().unwrap();
assert_eq!(panic_str, "Test panic");
// Trying again with a "safe" task still works
let join_handle = pool.spawn_pinned(|| async { "test" });
let result = join_handle.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "test");
}
/// A panic during task creation causes the join handle to return an error.
/// But, you can continue to spawn tasks.
#[tokio::test]
async fn callback_panic_does_not_kill_worker() {
let pool = task::LocalPoolHandle::new(1);
let join_handle = pool.spawn_pinned(|| {
panic!("Test panic");
#[allow(unreachable_code)]
async {}
});
let result = join_handle.await;
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.is_panic());
let panic_str: &str = *error.into_panic().downcast().unwrap();
assert_eq!(panic_str, "Test panic");
// Trying again with a "safe" callback works
let join_handle = pool.spawn_pinned(|| async { "test" });
let result = join_handle.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "test");
}
/// Canceling the task via the returned join handle cancels the spawned task
/// (which has a different, internal join handle).
#[tokio::test]
async fn task_cancellation_propagates() {
let pool = task::LocalPoolHandle::new(1);
let notify_dropped = Arc::new(());
let weak_notify_dropped = Arc::downgrade(&notify_dropped);
let (start_sender, start_receiver) = tokio::sync::oneshot::channel();
let (drop_sender, drop_receiver) = tokio::sync::oneshot::channel::<()>();
let join_handle = pool.spawn_pinned(|| async move {
let _drop_sender = drop_sender;
// Move the Arc into the task
let _notify_dropped = notify_dropped;
let _ = start_sender.send(());
// Keep the task running until it gets aborted
futures::future::pending::<()>().await;
});
// Wait for the task to start
let _ = start_receiver.await;
join_handle.abort();
// Wait for the inner task to abort, dropping the sender.
// The top level join handle aborts quicker than the inner task (the abort
// needs to propagate and get processed on the worker thread), so we can't
// just await the top level join handle.
let _ = drop_receiver.await;
// Check that the Arc has been dropped. This verifies that the inner task
// was canceled as well.
assert!(weak_notify_dropped.upgrade().is_none());
}
/// Tasks should be given to the least burdened worker. When spawning two tasks
/// on a pool with two empty workers the tasks should be spawned on separate
/// workers.
#[tokio::test]
async fn tasks_are_balanced() {
let pool = task::LocalPoolHandle::new(2);
// Spawn a task so one thread has a task count of 1
let (start_sender1, start_receiver1) = tokio::sync::oneshot::channel();
let (end_sender1, end_receiver1) = tokio::sync::oneshot::channel();
let join_handle1 = pool.spawn_pinned(|| async move {
let _ = start_sender1.send(());
let _ = end_receiver1.await;
std::thread::current().id()
});
// Wait for the first task to start up
let _ = start_receiver1.await;
// This task should be spawned on the other thread
let (start_sender2, start_receiver2) = tokio::sync::oneshot::channel();
let join_handle2 = pool.spawn_pinned(|| async move {
let _ = start_sender2.send(());
std::thread::current().id()
});
// Wait for the second task to start up
let _ = start_receiver2.await;
// Allow the first task to end
let _ = end_sender1.send(());
let thread_id1 = join_handle1.await.unwrap();
let thread_id2 = join_handle2.await.unwrap();
// Since the first task was active when the second task spawned, they should
// be on separate workers/threads.
assert_ne!(thread_id1, thread_id2);
}
+8 -8
View File
@@ -11,7 +11,7 @@ use futures_test::task::new_count_waker;
fn cancel_token() {
let (waker, wake_counter) = new_count_waker();
let token = CancellationToken::new();
assert_eq!(false, token.is_cancelled());
assert!(!token.is_cancelled());
let wait_fut = token.cancelled();
pin!(wait_fut);
@@ -27,7 +27,7 @@ fn cancel_token() {
token.cancel();
assert_eq!(wake_counter, 1);
assert_eq!(true, token.is_cancelled());
assert!(token.is_cancelled());
assert_eq!(
Poll::Ready(()),
@@ -64,8 +64,8 @@ fn cancel_child_token_through_parent() {
token.cancel();
assert_eq!(wake_counter, 2);
assert_eq!(true, token.is_cancelled());
assert_eq!(true, child_token.is_cancelled());
assert!(token.is_cancelled());
assert!(child_token.is_cancelled());
assert_eq!(
Poll::Ready(()),
@@ -101,8 +101,8 @@ fn cancel_child_token_without_parent() {
child_token_1.cancel();
assert_eq!(wake_counter, 1);
assert_eq!(false, token.is_cancelled());
assert_eq!(true, child_token_1.is_cancelled());
assert!(!token.is_cancelled());
assert!(child_token_1.is_cancelled());
assert_eq!(
Poll::Ready(()),
@@ -128,8 +128,8 @@ fn cancel_child_token_without_parent() {
token.cancel();
assert_eq!(wake_counter, 3);
assert_eq!(true, token.is_cancelled());
assert_eq!(true, child_token_2.is_cancelled());
assert!(token.is_cancelled());
assert!(child_token_2.is_cancelled());
assert_eq!(
Poll::Ready(()),
+183
View File
@@ -109,6 +109,7 @@ async fn multi_delay_at_start() {
let start = Instant::now();
for elapsed in 0..1200 {
println!("elapsed: {:?}", elapsed);
let elapsed = elapsed + 1;
tokio::time::sleep_until(start + ms(elapsed)).await;
@@ -128,10 +129,12 @@ async fn multi_delay_at_start() {
assert_pending!(poll!(queue));
}
}
println!("finished multi_delay_start");
}
#[tokio::test]
async fn insert_in_past_fires_immediately() {
println!("running insert_in_past_fires_immediately");
time::pause();
let mut queue = task::spawn(DelayQueue::new());
@@ -142,6 +145,7 @@ async fn insert_in_past_fires_immediately() {
queue.insert_at("foo", now);
assert_ready!(poll!(queue));
println!("finished insert_in_past_fires_immediately");
}
#[tokio::test]
@@ -630,6 +634,185 @@ async fn insert_in_past_after_poll_fires_immediately() {
assert_eq!(entry, "bar");
}
#[tokio::test]
async fn delay_queue_poll_expired_when_empty() {
let mut delay_queue = task::spawn(DelayQueue::new());
let key = delay_queue.insert(0, std::time::Duration::from_secs(10));
assert_pending!(poll!(delay_queue));
delay_queue.remove(&key);
assert!(assert_ready!(poll!(delay_queue)).is_none());
}
#[tokio::test(start_paused = true)]
async fn compact_expire_empty() {
let mut queue = task::spawn(DelayQueue::new());
let now = Instant::now();
queue.insert_at("foo1", now + ms(10));
queue.insert_at("foo2", now + ms(10));
sleep(ms(10)).await;
let mut res = vec![];
while res.len() < 2 {
let entry = assert_ready_ok!(poll!(queue));
res.push(entry.into_inner());
}
queue.compact();
assert_eq!(queue.len(), 0);
assert_eq!(queue.capacity(), 0);
}
#[tokio::test(start_paused = true)]
async fn compact_remove_empty() {
let mut queue = task::spawn(DelayQueue::new());
let now = Instant::now();
let key1 = queue.insert_at("foo1", now + ms(10));
let key2 = queue.insert_at("foo2", now + ms(10));
queue.remove(&key1);
queue.remove(&key2);
queue.compact();
assert_eq!(queue.len(), 0);
assert_eq!(queue.capacity(), 0);
}
#[tokio::test(start_paused = true)]
// Trigger a re-mapping of keys in the slab due to a `compact` call and
// test removal of re-mapped keys
async fn compact_remove_remapped_keys() {
let mut queue = task::spawn(DelayQueue::new());
let now = Instant::now();
queue.insert_at("foo1", now + ms(10));
queue.insert_at("foo2", now + ms(10));
// should be assigned indices 3 and 4
let key3 = queue.insert_at("foo3", now + ms(20));
let key4 = queue.insert_at("foo4", now + ms(20));
sleep(ms(10)).await;
let mut res = vec![];
while res.len() < 2 {
let entry = assert_ready_ok!(poll!(queue));
res.push(entry.into_inner());
}
// items corresponding to `foo3` and `foo4` will be assigned
// new indices here
queue.compact();
queue.insert_at("foo5", now + ms(10));
// test removal of re-mapped keys
let expired3 = queue.remove(&key3);
let expired4 = queue.remove(&key4);
assert_eq!(expired3.into_inner(), "foo3");
assert_eq!(expired4.into_inner(), "foo4");
queue.compact();
assert_eq!(queue.len(), 1);
assert_eq!(queue.capacity(), 1);
}
#[tokio::test(start_paused = true)]
async fn compact_change_deadline() {
let mut queue = task::spawn(DelayQueue::new());
let mut now = Instant::now();
queue.insert_at("foo1", now + ms(10));
queue.insert_at("foo2", now + ms(10));
// should be assigned indices 3 and 4
queue.insert_at("foo3", now + ms(20));
let key4 = queue.insert_at("foo4", now + ms(20));
sleep(ms(10)).await;
let mut res = vec![];
while res.len() < 2 {
let entry = assert_ready_ok!(poll!(queue));
res.push(entry.into_inner());
}
// items corresponding to `foo3` and `foo4` should be assigned
// new indices
queue.compact();
now = Instant::now();
queue.insert_at("foo5", now + ms(10));
let key6 = queue.insert_at("foo6", now + ms(10));
queue.reset_at(&key4, now + ms(20));
queue.reset_at(&key6, now + ms(20));
// foo3 and foo5 will expire
sleep(ms(10)).await;
while res.len() < 4 {
let entry = assert_ready_ok!(poll!(queue));
res.push(entry.into_inner());
}
sleep(ms(10)).await;
while res.len() < 6 {
let entry = assert_ready_ok!(poll!(queue));
res.push(entry.into_inner());
}
let entry = assert_ready!(poll!(queue));
assert!(entry.is_none());
}
#[tokio::test(start_paused = true)]
async fn remove_after_compact() {
let now = Instant::now();
let mut queue = DelayQueue::new();
let foo_key = queue.insert_at("foo", now + ms(10));
queue.insert_at("bar", now + ms(20));
queue.remove(&foo_key);
queue.compact();
let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
queue.remove(&foo_key);
}));
assert!(panic.is_err());
}
#[tokio::test(start_paused = true)]
async fn remove_after_compact_poll() {
let now = Instant::now();
let mut queue = task::spawn(DelayQueue::new());
let foo_key = queue.insert_at("foo", now + ms(10));
queue.insert_at("bar", now + ms(20));
sleep(ms(10)).await;
assert_eq!(assert_ready_ok!(poll!(queue)).key(), foo_key);
queue.compact();
let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
queue.remove(&foo_key);
}));
assert!(panic.is_err());
}
fn ms(n: u64) -> Duration {
Duration::from_millis(n)
}
+351
View File
@@ -1,3 +1,354 @@
# 1.16.1 (January 28, 2022)
This release fixes a bug in [#4428] with the change [#4437].
[#4428]: https://github.com/tokio-rs/tokio/pull/4428
[#4437]: https://github.com/tokio-rs/tokio/pull/4437
# 1.16.0 (January 27, 2022)
Fixes a soundness bug in `io::Take` ([#4428]). The unsoundness is exposed when
leaking memory in the given `AsyncRead` implementation and then overwriting the
supplied buffer:
```rust
impl AsyncRead for Buggy {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>
) -> Poll<Result<()>> {
let new_buf = vec![0; 5].leak();
*buf = ReadBuf::new(new_buf);
buf.put_slice(b"hello");
Poll::Ready(Ok(()))
}
}
```
Also, this release includes improvements to the multi-threaded scheduler that
can increase throughput by up to 20% in some cases ([#4383]).
### Fixed
- io: **soundness** don't expose uninitialized memory when using `io::Take` in edge case ([#4428])
- fs: ensure `File::write` results in a `write` syscall when the runtime shuts down ([#4316])
- process: drop pipe after child exits in `wait_with_output` ([#4315])
- rt: improve error message when spawning a thread fails ([#4398])
- rt: reduce false-positive thread wakups in the multi-threaded scheduler ([#4383])
- sync: don't inherit `Send` from `parking_lot::*Guard` ([#4359])
### Added
- net: `TcpSocket::linger()` and `set_linger()` ([#4324])
- net: impl `UnwindSafe` for socket types ([#4384])
- rt: impl `UnwindSafe` for `JoinHandle` ([#4418])
- sync: `watch::Receiver::has_changed()` ([#4342])
- sync: `oneshot::Receiver::blocking_recv()` ([#4334])
- sync: `RwLock` blocking operations ([#4425])
### Unstable
The following changes only apply when building with `--cfg tokio_unstable`
- rt: **breaking change** overhaul runtime metrics API ([#4373])
[#4428]: https://github.com/tokio-rs/tokio/pull/4428
[#4316]: https://github.com/tokio-rs/tokio/pull/4316
[#4315]: https://github.com/tokio-rs/tokio/pull/4315
[#4398]: https://github.com/tokio-rs/tokio/pull/4398
[#4383]: https://github.com/tokio-rs/tokio/pull/4383
[#4359]: https://github.com/tokio-rs/tokio/pull/4359
[#4324]: https://github.com/tokio-rs/tokio/pull/4324
[#4384]: https://github.com/tokio-rs/tokio/pull/4384
[#4418]: https://github.com/tokio-rs/tokio/pull/4418
[#4342]: https://github.com/tokio-rs/tokio/pull/4342
[#4334]: https://github.com/tokio-rs/tokio/pull/4334
[#4425]: https://github.com/tokio-rs/tokio/pull/4425
[#4373]: https://github.com/tokio-rs/tokio/pull/4373
# 1.15.0 (December 15, 2021)
### Fixed
- io: add cooperative yielding support to `io::empty()` ([#4300])
- time: make timeout robust against budget-depleting tasks ([#4314])
### Changed
- update minimum supported Rust version to 1.46.
### Added
- time: add `Interval::reset()` ([#4248])
- io: add explicit lifetimes to `AsyncFdReadyGuard` ([#4267])
- process: add `Command::as_std()` ([#4295])
### Added (unstable)
- tracing: instrument `tokio::sync` types ([#4302])
[#4302]: https://github.com/tokio-rs/tokio/pull/4302
[#4300]: https://github.com/tokio-rs/tokio/pull/4300
[#4295]: https://github.com/tokio-rs/tokio/pull/4295
[#4267]: https://github.com/tokio-rs/tokio/pull/4267
[#4248]: https://github.com/tokio-rs/tokio/pull/4248
[#4314]: https://github.com/tokio-rs/tokio/pull/4314
# 1.14.0 (November 15, 2021)
### Fixed
- macros: fix compiler errors when using `mut` patterns in `select!` ([#4211])
- sync: fix a data race between `oneshot::Sender::send` and awaiting a
`oneshot::Receiver` when the oneshot has been closed ([#4226])
- sync: make `AtomicWaker` panic safe ([#3689])
- runtime: fix basic scheduler dropping tasks outside a runtime context
([#4213])
### Added
- stats: add `RuntimeStats::busy_duration_total` ([#4179], [#4223])
### Changed
- io: updated `copy` buffer size to match `std::io::copy` ([#4209])
### Documented
- io: rename buffer to file in doc-test ([#4230])
- sync: fix Notify example ([#4212])
[#4211]: https://github.com/tokio-rs/tokio/pull/4211
[#4226]: https://github.com/tokio-rs/tokio/pull/4226
[#3689]: https://github.com/tokio-rs/tokio/pull/3689
[#4213]: https://github.com/tokio-rs/tokio/pull/4213
[#4179]: https://github.com/tokio-rs/tokio/pull/4179
[#4223]: https://github.com/tokio-rs/tokio/pull/4223
[#4209]: https://github.com/tokio-rs/tokio/pull/4209
[#4230]: https://github.com/tokio-rs/tokio/pull/4230
[#4212]: https://github.com/tokio-rs/tokio/pull/4212
# 1.13.1 (November 15, 2021)
### Fixed
- sync: fix a data race between `oneshot::Sender::send` and awaiting a
`oneshot::Receiver` when the oneshot has been closed ([#4226])
[#4226]: https://github.com/tokio-rs/tokio/pull/4226
# 1.13.0 (October 29, 2021)
### Fixed
- sync: fix `Notify` to clone the waker before locking its waiter list ([#4129])
- tokio: add riscv32 to non atomic64 architectures ([#4185])
### Added
- net: add `poll_{recv,send}_ready` methods to `udp` and `uds_datagram` ([#4131])
- net: add `try_*`, `readable`, `writable`, `ready`, and `peer_addr` methods to split halves ([#4120])
- sync: add `blocking_lock` to `Mutex` ([#4130])
- sync: add `watch::Sender::send_replace` ([#3962], [#4195])
- sync: expand `Debug` for `Mutex<T>` impl to unsized `T` ([#4134])
- tracing: instrument time::Sleep ([#4072])
- tracing: use structured location fields for spawned tasks ([#4128])
### Changed
- io: add assert in `copy_bidirectional` that `poll_write` is sensible ([#4125])
- macros: use qualified syntax when polling in `select!` ([#4192])
- runtime: handle `block_on` wakeups better ([#4157])
- task: allocate callback on heap immediately in debug mode ([#4203])
- tokio: assert platform-minimum requirements at build time ([#3797])
### Documented
- docs: conversion of doc comments to indicative mood ([#4174])
- docs: add returning on the first error example for `try_join!` ([#4133])
- docs: fixing broken links in `tokio/src/lib.rs` ([#4132])
- signal: add example with background listener ([#4171])
- sync: add more oneshot examples ([#4153])
- time: document `Interval::tick` cancel safety ([#4152])
[#3797]: https://github.com/tokio-rs/tokio/pull/3797
[#3962]: https://github.com/tokio-rs/tokio/pull/3962
[#4072]: https://github.com/tokio-rs/tokio/pull/4072
[#4120]: https://github.com/tokio-rs/tokio/pull/4120
[#4125]: https://github.com/tokio-rs/tokio/pull/4125
[#4128]: https://github.com/tokio-rs/tokio/pull/4128
[#4129]: https://github.com/tokio-rs/tokio/pull/4129
[#4130]: https://github.com/tokio-rs/tokio/pull/4130
[#4131]: https://github.com/tokio-rs/tokio/pull/4131
[#4132]: https://github.com/tokio-rs/tokio/pull/4132
[#4133]: https://github.com/tokio-rs/tokio/pull/4133
[#4134]: https://github.com/tokio-rs/tokio/pull/4134
[#4152]: https://github.com/tokio-rs/tokio/pull/4152
[#4153]: https://github.com/tokio-rs/tokio/pull/4153
[#4157]: https://github.com/tokio-rs/tokio/pull/4157
[#4171]: https://github.com/tokio-rs/tokio/pull/4171
[#4174]: https://github.com/tokio-rs/tokio/pull/4174
[#4185]: https://github.com/tokio-rs/tokio/pull/4185
[#4192]: https://github.com/tokio-rs/tokio/pull/4192
[#4195]: https://github.com/tokio-rs/tokio/pull/4195
[#4203]: https://github.com/tokio-rs/tokio/pull/4203
# 1.12.0 (September 21, 2021)
### Fixed
- mpsc: ensure `try_reserve` error is consistent with `try_send` ([#4119])
- mpsc: use `spin_loop_hint` instead of `yield_now` ([#4115])
- sync: make `SendError` field public ([#4097])
### Added
- io: add POSIX AIO on FreeBSD ([#4054])
- io: add convenience method `AsyncSeekExt::rewind` ([#4107])
- runtime: add tracing span for `block_on` futures ([#4094])
- runtime: callback when a worker parks and unparks ([#4070])
- sync: implement `try_recv` for mpsc channels ([#4113])
### Documented
- docs: clarify CPU-bound tasks on Tokio ([#4105])
- mpsc: document spurious failures on `poll_recv` ([#4117])
- mpsc: document that `PollSender` impls `Sink` ([#4110])
- task: document non-guarantees of `yield_now` ([#4091])
- time: document paused time details better ([#4061], [#4103])
[#4027]: https://github.com/tokio-rs/tokio/pull/4027
[#4054]: https://github.com/tokio-rs/tokio/pull/4054
[#4061]: https://github.com/tokio-rs/tokio/pull/4061
[#4070]: https://github.com/tokio-rs/tokio/pull/4070
[#4091]: https://github.com/tokio-rs/tokio/pull/4091
[#4094]: https://github.com/tokio-rs/tokio/pull/4094
[#4097]: https://github.com/tokio-rs/tokio/pull/4097
[#4103]: https://github.com/tokio-rs/tokio/pull/4103
[#4105]: https://github.com/tokio-rs/tokio/pull/4105
[#4107]: https://github.com/tokio-rs/tokio/pull/4107
[#4110]: https://github.com/tokio-rs/tokio/pull/4110
[#4113]: https://github.com/tokio-rs/tokio/pull/4113
[#4115]: https://github.com/tokio-rs/tokio/pull/4115
[#4117]: https://github.com/tokio-rs/tokio/pull/4117
[#4119]: https://github.com/tokio-rs/tokio/pull/4119
# 1.11.0 (August 31, 2021)
### Fixed
- time: don't panic when Instant is not monotonic ([#4044])
- io: fix panic in `fill_buf` by not calling `poll_fill_buf` twice ([#4084])
### Added
- watch: add `watch::Sender::subscribe` ([#3800])
- process: add `from_std` to `ChildStd*` ([#4045])
- stats: initial work on runtime stats ([#4043])
### Changed
- tracing: change span naming to new console convention ([#4042])
- io: speed-up waking by using uninitialized array ([#4055], [#4071], [#4075])
### Documented
- time: make Sleep examples easier to find ([#4040])
[#3800]: https://github.com/tokio-rs/tokio/pull/3800
[#4040]: https://github.com/tokio-rs/tokio/pull/4040
[#4042]: https://github.com/tokio-rs/tokio/pull/4042
[#4043]: https://github.com/tokio-rs/tokio/pull/4043
[#4044]: https://github.com/tokio-rs/tokio/pull/4044
[#4045]: https://github.com/tokio-rs/tokio/pull/4045
[#4055]: https://github.com/tokio-rs/tokio/pull/4055
[#4071]: https://github.com/tokio-rs/tokio/pull/4071
[#4075]: https://github.com/tokio-rs/tokio/pull/4075
[#4084]: https://github.com/tokio-rs/tokio/pull/4084
# 1.10.1 (August 24, 2021)
### Fixed
- runtime: fix leak in UnownedTask ([#4063])
[#4063]: https://github.com/tokio-rs/tokio/pull/4063
# 1.10.0 (August 12, 2021)
### Added
- io: add `(read|write)_f(32|64)[_le]` methods ([#4022])
- io: add `fill_buf` and `consume` to `AsyncBufReadExt` ([#3991])
- process: add `Child::raw_handle()` on windows ([#3998])
### Fixed
- doc: fix non-doc builds with `--cfg docsrs` ([#4020])
- io: flush eagerly in `io::copy` ([#4001])
- runtime: a debug assert was sometimes triggered during shutdown ([#4005])
- sync: use `spin_loop_hint` instead of `yield_now` in mpsc ([#4037])
- tokio: the test-util feature depends on rt, sync, and time ([#4036])
### Changes
- runtime: reorganize parts of the runtime ([#3979], [#4005])
- signal: make windows docs for signal module show up on unix builds ([#3770])
- task: quickly send task to heap on debug mode ([#4009])
### Documented
- io: document cancellation safety of `AsyncBufReadExt` ([#3997])
- sync: document when `watch::send` fails ([#4021])
[#3770]: https://github.com/tokio-rs/tokio/pull/3770
[#3979]: https://github.com/tokio-rs/tokio/pull/3979
[#3991]: https://github.com/tokio-rs/tokio/pull/3991
[#3997]: https://github.com/tokio-rs/tokio/pull/3997
[#3998]: https://github.com/tokio-rs/tokio/pull/3998
[#4001]: https://github.com/tokio-rs/tokio/pull/4001
[#4005]: https://github.com/tokio-rs/tokio/pull/4005
[#4009]: https://github.com/tokio-rs/tokio/pull/4009
[#4020]: https://github.com/tokio-rs/tokio/pull/4020
[#4021]: https://github.com/tokio-rs/tokio/pull/4021
[#4022]: https://github.com/tokio-rs/tokio/pull/4022
[#4036]: https://github.com/tokio-rs/tokio/pull/4036
[#4037]: https://github.com/tokio-rs/tokio/pull/4037
# 1.9.0 (July 22, 2021)
### Added
- net: allow customized I/O operations for `TcpStream` ([#3888])
- sync: add getter for the mutex from a guard ([#3928])
- task: expose nameable future for `TaskLocal::scope` ([#3273])
### Fixed
- Fix leak if output of future panics on drop ([#3967])
- Fix leak in `LocalSet` ([#3978])
### Changes
- runtime: reorganize parts of the runtime ([#3909], [#3939], [#3950], [#3955], [#3980])
- sync: clean up `OnceCell` ([#3945])
- task: remove mutex in `JoinError` ([#3959])
[#3273]: https://github.com/tokio-rs/tokio/pull/3273
[#3888]: https://github.com/tokio-rs/tokio/pull/3888
[#3909]: https://github.com/tokio-rs/tokio/pull/3909
[#3928]: https://github.com/tokio-rs/tokio/pull/3928
[#3934]: https://github.com/tokio-rs/tokio/pull/3934
[#3939]: https://github.com/tokio-rs/tokio/pull/3939
[#3945]: https://github.com/tokio-rs/tokio/pull/3945
[#3950]: https://github.com/tokio-rs/tokio/pull/3950
[#3955]: https://github.com/tokio-rs/tokio/pull/3955
[#3959]: https://github.com/tokio-rs/tokio/pull/3959
[#3967]: https://github.com/tokio-rs/tokio/pull/3967
[#3978]: https://github.com/tokio-rs/tokio/pull/3978
[#3980]: https://github.com/tokio-rs/tokio/pull/3980
# 1.8.3 (July 26, 2021)
This release backports two fixes from 1.9.0
+27 -13
View File
@@ -3,16 +3,15 @@ name = "tokio"
# When releasing to crates.io:
# - Remove path dependencies
# - Update doc url
# - Cargo.toml
# - README.md
# - Update CHANGELOG.md.
# - Create "v1.0.x" git tag.
version = "1.8.3"
version = "1.16.1"
edition = "2018"
rust-version = "1.46"
authors = ["Tokio Contributors <[email protected]>"]
license = "MIT"
readme = "README.md"
documentation = "https://docs.rs/tokio/1.8.3/tokio/"
repository = "https://github.com/tokio-rs/tokio"
homepage = "https://tokio.rs"
description = """
@@ -82,11 +81,16 @@ signal = [
"winapi/consoleapi",
]
sync = []
test-util = []
test-util = ["rt", "sync", "time"]
time = []
# Technically, removing this is a breaking change even though it only ever did
# anything with the unstable flag on. It is probably safe to get rid of it after
# a few releases.
stats = []
[dependencies]
tokio-macros = { version = "1.1.0", path = "../tokio-macros", optional = true }
tokio-macros = { version = "1.7.0", path = "../tokio-macros", optional = true }
pin-project-lite = "0.2.0"
@@ -101,7 +105,7 @@ parking_lot = { version = "0.11.0", optional = true }
# Currently unstable. The API exposed by these features may be broken at any time.
# Requires `--cfg tokio_unstable` to enable.
[target.'cfg(tokio_unstable)'.dependencies]
tracing = { version = "0.1.21", default-features = false, features = ["std"], optional = true } # Not in full
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true } # Not in full
[target.'cfg(unix)'.dependencies]
libc = { version = "0.2.42", optional = true }
@@ -109,7 +113,7 @@ signal-hook-registry = { version = "1.1.1", optional = true }
[target.'cfg(unix)'.dev-dependencies]
libc = { version = "0.2.42" }
nix = { version = "0.19.0" }
nix = { version = "0.23" }
[target.'cfg(windows)'.dependencies.winapi]
version = "0.3.8"
@@ -123,21 +127,31 @@ version = "0.3.6"
tokio-test = { version = "0.4.0", path = "../tokio-test" }
tokio-stream = { version = "0.1", path = "../tokio-stream" }
futures = { version = "0.3.0", features = ["async-await"] }
proptest = "1"
rand = "0.8.0"
mockall = "0.10.2"
tempfile = "3.1.0"
async-stream = "0.3"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
proptest = "1"
rand = "0.8.0"
socket2 = "0.4"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.0"
[target.'cfg(target_os = "freebsd")'.dev-dependencies]
mio-aio = { version = "0.6.0", features = ["tokio"] }
[target.'cfg(loom)'.dev-dependencies]
loom = { version = "0.5", features = ["futures", "checkpoint"] }
[build-dependencies]
autocfg = "1" # Needed for conditionally enabling `track-caller`
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
# enable unstable features in the documentation
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"]
# it's necessary to _also_ pass `--cfg tokio_unstable` to rustc, or else
# dependencies will not be enabled, and the docs build will fail.
rustc-args = ["--cfg", "tokio_unstable"]
[package.metadata.playground]
features = ["full", "test-util"]
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2021 Tokio Contributors
Copyright (c) 2022 Tokio Contributors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
+41 -6
View File
@@ -50,7 +50,15 @@ an asynchronous application.
## Example
A basic TCP echo server with Tokio:
A basic TCP echo server with Tokio.
Make sure you activated the full features of the tokio crate on Cargo.toml:
```toml
[dependencies]
tokio = { version = "1.16.1", features = ["full"] }
```
Then, on your main.rs:
```rust,no_run
use tokio::net::TcpListener;
@@ -58,7 +66,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut listener = TcpListener::bind("127.0.0.1:8080").await?;
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
@@ -132,7 +140,7 @@ several other libraries, including:
* [`tower`]: A library of modular and reusable components for building robust networking clients and servers.
* [`tracing`]: A framework for application-level tracing and async-aware diagnostics.
* [`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.
@@ -155,9 +163,36 @@ several other libraries, including:
## Supported Rust Versions
Tokio is built against the latest stable release. The minimum supported version is 1.45.
The current Tokio version is not guaranteed to build on Rust versions earlier than the
minimum supported version.
Tokio is built against the latest stable release. The minimum supported version
is 1.46. The current Tokio version is not guaranteed to build on Rust versions
earlier than the minimum supported version.
## Release schedule
Tokio doesn't follow a fixed release schedule, but we typically make one to two
new minor releases each month. We make patch releases for bugfixes as necessary.
## Bug patching policy
For the purposes of making patch releases with bugfixes, we have designated
certain minor releases as LTS (long term support) releases. Whenever a bug
warrants a patch release with a fix for the bug, it will be backported and
released as a new patch release for each LTS minor version. Our current LTS
releases are:
* `1.8.x` - LTS release until February 2022.
* `1.14.x` - LTS release until June 2022.
Each LTS release will continue to receive backported fixes for at least half 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.8.x` patch release, you
can use the following dependency specification:
```text
tokio = { version = "~1.8", features = [...] }
```
## License
-22
View File
@@ -1,22 +0,0 @@
use autocfg::AutoCfg;
fn main() {
match AutoCfg::new() {
Ok(ac) => {
// The #[track_caller] attribute was stabilized in rustc 1.46.0.
if ac.probe_rustc_version(1, 46) {
autocfg::emit("tokio_track_caller")
}
}
Err(e) => {
// If we couldn't detect the compiler version and features, just
// print a warning. This isn't a fatal error: we can still build
// Tokio, we just can't enable cfgs automatically.
println!(
"cargo:warning=tokio: failed to detect compiler features: {}",
e
);
}
}
}
+15
View File
@@ -1,5 +1,11 @@
cfg_rt! {
pub(crate) use crate::runtime::spawn_blocking;
cfg_fs! {
#[allow(unused_imports)]
pub(crate) use crate::runtime::spawn_mandatory_blocking;
}
pub(crate) use crate::task::JoinHandle;
}
@@ -16,7 +22,16 @@ cfg_not_rt! {
{
assert_send_sync::<JoinHandle<std::cell::Cell<()>>>();
panic!("requires the `rt` Tokio feature flag")
}
cfg_fs! {
pub(crate) fn spawn_mandatory_blocking<F, R>(_f: F) -> Option<JoinHandle<R>>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
panic!("requires the `rt` Tokio feature flag")
}
}
pub(crate) struct JoinHandle<R> {
+15 -16
View File
@@ -59,24 +59,20 @@ impl Budget {
const fn unconstrained() -> Budget {
Budget(None)
}
}
cfg_rt_multi_thread! {
impl Budget {
fn has_remaining(self) -> bool {
self.0.map(|budget| budget > 0).unwrap_or(true)
}
fn has_remaining(self) -> bool {
self.0.map(|budget| budget > 0).unwrap_or(true)
}
}
/// Run the given closure with a cooperative task budget. When the function
/// Runs the given closure with a cooperative task budget. When the function
/// returns, the budget is reset to the value prior to calling the function.
#[inline(always)]
pub(crate) fn budget<R>(f: impl FnOnce() -> R) -> R {
with_budget(Budget::initial(), f)
}
/// Run the given closure with an unconstrained task budget. When the function returns, the budget
/// Runs the given closure with an unconstrained task budget. When the function returns, the budget
/// is reset to the value prior to calling the function.
#[inline(always)]
pub(crate) fn with_unconstrained<R>(f: impl FnOnce() -> R) -> R {
@@ -107,20 +103,20 @@ fn with_budget<R>(budget: Budget, f: impl FnOnce() -> R) -> R {
})
}
#[inline(always)]
pub(crate) fn has_budget_remaining() -> bool {
CURRENT.with(|cell| cell.get().has_remaining())
}
cfg_rt_multi_thread! {
/// Set the current task's budget
/// Sets the current task's budget.
pub(crate) fn set(budget: Budget) {
CURRENT.with(|cell| cell.set(budget))
}
#[inline(always)]
pub(crate) fn has_budget_remaining() -> bool {
CURRENT.with(|cell| cell.get().has_remaining())
}
}
cfg_rt! {
/// Forcibly remove the budgeting constraints early.
/// Forcibly removes the budgeting constraints early.
///
/// Returns the remaining budget
pub(crate) fn stop() -> Budget {
@@ -186,7 +182,7 @@ cfg_coop! {
}
impl Budget {
/// Decrement the budget. Returns `true` if successful. Decrementing fails
/// Decrements the budget. Returns `true` if successful. Decrementing fails
/// when there is not enough remaining budget.
fn decrement(&mut self) -> bool {
if let Some(num) = &mut self.0 {
@@ -211,6 +207,9 @@ cfg_coop! {
mod test {
use super::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
fn get() -> Budget {
CURRENT.with(|cell| cell.get())
}
+1
View File
@@ -17,6 +17,7 @@
/// will ever accidentally use it.
///
/// [`never` type]: https://doc.rust-lang.org/std/primitive.never.html
#[derive(Debug)]
pub enum NotDefinedHere {}
pub mod os;
+1 -1
View File
@@ -3,7 +3,7 @@ use crate::fs::asyncify;
use std::io;
use std::path::Path;
/// Creates a new, empty directory at the provided path
/// Creates a new, empty directory at the provided path.
///
/// This is an async version of [`std::fs::create_dir`][std]
///
+1 -1
View File
@@ -14,7 +14,7 @@ pub struct DirBuilder {
/// Indicates whether to create parent directories if they are missing.
recursive: bool,
/// Set the Unix mode for newly created directories.
/// Sets the Unix mode for newly created directories.
#[cfg(unix)]
pub(super) mode: Option<u32>,
}
+40 -19
View File
@@ -3,7 +3,7 @@
//! [`File`]: File
use self::State::*;
use crate::fs::{asyncify, sys};
use crate::fs::asyncify;
use crate::io::blocking::Buf;
use crate::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
use crate::sync::Mutex;
@@ -19,6 +19,19 @@ use std::task::Context;
use std::task::Poll;
use std::task::Poll::*;
#[cfg(test)]
use super::mocks::JoinHandle;
#[cfg(test)]
use super::mocks::MockFile as StdFile;
#[cfg(test)]
use super::mocks::{spawn_blocking, spawn_mandatory_blocking};
#[cfg(not(test))]
use crate::blocking::JoinHandle;
#[cfg(not(test))]
use crate::blocking::{spawn_blocking, spawn_mandatory_blocking};
#[cfg(not(test))]
use std::fs::File as StdFile;
/// A reference to an open file on the filesystem.
///
/// This is a specialized version of [`std::fs::File`][std] for usage from the
@@ -61,7 +74,7 @@ use std::task::Poll::*;
/// # }
/// ```
///
/// Read the contents of a file into a buffer
/// Read the contents of a file into a buffer:
///
/// ```no_run
/// use tokio::fs::File;
@@ -78,7 +91,7 @@ use std::task::Poll::*;
/// # }
/// ```
pub struct File {
std: Arc<sys::File>,
std: Arc<StdFile>,
inner: Mutex<Inner>,
}
@@ -96,7 +109,7 @@ struct Inner {
#[derive(Debug)]
enum State {
Idle(Option<Buf>),
Busy(sys::Blocking<(Operation, Buf)>),
Busy(JoinHandle<(Operation, Buf)>),
}
#[derive(Debug)]
@@ -142,7 +155,7 @@ impl File {
/// [`AsyncReadExt`]: trait@crate::io::AsyncReadExt
pub async fn open(path: impl AsRef<Path>) -> io::Result<File> {
let path = path.as_ref().to_owned();
let std = asyncify(|| sys::File::open(path)).await?;
let std = asyncify(|| StdFile::open(path)).await?;
Ok(File::from_std(std))
}
@@ -182,7 +195,7 @@ impl File {
/// [`AsyncWriteExt`]: trait@crate::io::AsyncWriteExt
pub async fn create(path: impl AsRef<Path>) -> io::Result<File> {
let path = path.as_ref().to_owned();
let std_file = asyncify(move || sys::File::create(path)).await?;
let std_file = asyncify(move || StdFile::create(path)).await?;
Ok(File::from_std(std_file))
}
@@ -199,7 +212,7 @@ impl File {
/// let std_file = std::fs::File::open("foo.txt").unwrap();
/// let file = tokio::fs::File::from_std(std_file);
/// ```
pub fn from_std(std: sys::File) -> File {
pub fn from_std(std: StdFile) -> File {
File {
std: Arc::new(std),
inner: Mutex::new(Inner {
@@ -323,7 +336,7 @@ impl File {
let std = self.std.clone();
inner.state = Busy(sys::run(move || {
inner.state = Busy(spawn_blocking(move || {
let res = if let Some(seek) = seek {
(&*std).seek(seek).and_then(|_| std.set_len(size))
} else {
@@ -370,7 +383,7 @@ impl File {
asyncify(move || std.metadata()).await
}
/// Create a new `File` instance that shares the same underlying file handle
/// Creates a new `File` instance that shares the same underlying file handle
/// as the existing `File` instance. Reads, writes, and seeks will affect both
/// File instances simultaneously.
///
@@ -409,7 +422,7 @@ impl File {
/// # Ok(())
/// # }
/// ```
pub async fn into_std(mut self) -> sys::File {
pub async fn into_std(mut self) -> StdFile {
self.inner.get_mut().complete_inflight().await;
Arc::try_unwrap(self.std).expect("Arc::try_unwrap failed")
}
@@ -434,7 +447,7 @@ impl File {
/// # Ok(())
/// # }
/// ```
pub fn try_into_std(mut self) -> Result<sys::File, Self> {
pub fn try_into_std(mut self) -> Result<StdFile, Self> {
match Arc::try_unwrap(self.std) {
Ok(file) => Ok(file),
Err(std_file_arc) => {
@@ -502,7 +515,7 @@ impl AsyncRead for File {
buf.ensure_capacity_for(dst);
let std = me.std.clone();
inner.state = Busy(sys::run(move || {
inner.state = Busy(spawn_blocking(move || {
let res = buf.read_from(&mut &*std);
(Operation::Read(res), buf)
}));
@@ -569,7 +582,7 @@ impl AsyncSeek for File {
let std = me.std.clone();
inner.state = Busy(sys::run(move || {
inner.state = Busy(spawn_blocking(move || {
let res = (&*std).seek(pos);
(Operation::Seek(res), buf)
}));
@@ -636,7 +649,7 @@ impl AsyncWrite for File {
let n = buf.copy_from(src);
let std = me.std.clone();
inner.state = Busy(sys::run(move || {
let blocking_task_join_handle = spawn_mandatory_blocking(move || {
let res = if let Some(seek) = seek {
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
} else {
@@ -644,7 +657,12 @@ impl AsyncWrite for File {
};
(Operation::Write(res), buf)
}));
})
.ok_or_else(|| {
io::Error::new(io::ErrorKind::Other, "background task failed")
})?;
inner.state = Busy(blocking_task_join_handle);
return Ready(Ok(n));
}
@@ -685,8 +703,8 @@ impl AsyncWrite for File {
}
}
impl From<sys::File> for File {
fn from(std: sys::File) -> Self {
impl From<StdFile> for File {
fn from(std: StdFile) -> Self {
Self::from_std(std)
}
}
@@ -709,7 +727,7 @@ impl std::os::unix::io::AsRawFd for File {
#[cfg(unix)]
impl std::os::unix::io::FromRawFd for File {
unsafe fn from_raw_fd(fd: std::os::unix::io::RawFd) -> Self {
sys::File::from_raw_fd(fd).into()
StdFile::from_raw_fd(fd).into()
}
}
@@ -723,7 +741,7 @@ impl std::os::windows::io::AsRawHandle for File {
#[cfg(windows)]
impl std::os::windows::io::FromRawHandle for File {
unsafe fn from_raw_handle(handle: std::os::windows::io::RawHandle) -> Self {
sys::File::from_raw_handle(handle).into()
StdFile::from_raw_handle(handle).into()
}
}
@@ -756,3 +774,6 @@ impl Inner {
}
}
}
#[cfg(test)]
mod tests;
@@ -1,80 +1,21 @@
#![warn(rust_2018_idioms)]
#![cfg(feature = "full")]
macro_rules! ready {
($e:expr $(,)?) => {
match $e {
std::task::Poll::Ready(t) => t,
std::task::Poll::Pending => return std::task::Poll::Pending,
}
};
}
#[macro_export]
macro_rules! cfg_fs {
($($item:item)*) => { $($item)* }
}
#[macro_export]
macro_rules! cfg_io_std {
($($item:item)*) => { $($item)* }
}
use futures::future;
// Load source
#[allow(warnings)]
#[path = "../src/fs/file.rs"]
mod file;
use file::File;
#[allow(warnings)]
#[path = "../src/io/blocking.rs"]
mod blocking;
// Load mocked types
mod support {
pub(crate) mod mock_file;
pub(crate) mod mock_pool;
}
pub(crate) use support::mock_pool as pool;
// Place them where the source expects them
pub(crate) mod io {
pub(crate) use tokio::io::*;
pub(crate) use crate::blocking;
pub(crate) mod sys {
pub(crate) use crate::support::mock_pool::{run, Blocking};
}
}
pub(crate) mod fs {
pub(crate) mod sys {
pub(crate) use crate::support::mock_file::File;
pub(crate) use crate::support::mock_pool::{run, Blocking};
}
pub(crate) use crate::support::mock_pool::asyncify;
}
pub(crate) mod sync {
pub(crate) use tokio::sync::Mutex;
}
use fs::sys;
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use tokio_test::{assert_pending, assert_ready, assert_ready_err, assert_ready_ok, task};
use std::io::SeekFrom;
use super::*;
use crate::{
fs::mocks::*,
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt},
};
use mockall::{predicate::eq, Sequence};
use tokio_test::{assert_pending, assert_ready_err, assert_ready_ok, task};
const HELLO: &[u8] = b"hello world...";
const FOO: &[u8] = b"foo bar baz...";
#[test]
fn open_read() {
let (mock, file) = sys::File::mock();
mock.read(HELLO);
let mut file = MockFile::default();
file.expect_inner_read().once().returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
let mut file = File::from_std(file);
let mut buf = [0; 1024];
@@ -83,12 +24,10 @@ fn open_read() {
assert_eq!(0, pool::len());
assert_pending!(t.poll());
assert_eq!(1, mock.remaining());
assert_eq!(1, pool::len());
pool::run_one();
assert_eq!(0, mock.remaining());
assert!(t.is_woken());
let n = assert_ready_ok!(t.poll());
@@ -98,9 +37,11 @@ fn open_read() {
#[test]
fn read_twice_before_dispatch() {
let (mock, file) = sys::File::mock();
mock.read(HELLO);
let mut file = MockFile::default();
file.expect_inner_read().once().returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
let mut file = File::from_std(file);
let mut buf = [0; 1024];
@@ -120,8 +61,11 @@ fn read_twice_before_dispatch() {
#[test]
fn read_with_smaller_buf() {
let (mock, file) = sys::File::mock();
mock.read(HELLO);
let mut file = MockFile::default();
file.expect_inner_read().once().returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
let mut file = File::from_std(file);
@@ -153,8 +97,22 @@ fn read_with_smaller_buf() {
#[test]
fn read_with_bigger_buf() {
let (mock, file) = sys::File::mock();
mock.read(&HELLO[..4]).read(&HELLO[4..]);
let mut seq = Sequence::new();
let mut file = MockFile::default();
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..4].copy_from_slice(&HELLO[..4]);
Ok(4)
});
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..HELLO.len() - 4].copy_from_slice(&HELLO[4..]);
Ok(HELLO.len() - 4)
});
let mut file = File::from_std(file);
@@ -194,8 +152,19 @@ fn read_with_bigger_buf() {
#[test]
fn read_err_then_read_success() {
let (mock, file) = sys::File::mock();
mock.read_err().read(&HELLO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|_| Err(io::ErrorKind::Other.into()));
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
let mut file = File::from_std(file);
@@ -225,8 +194,11 @@ fn read_err_then_read_success() {
#[test]
fn open_write() {
let (mock, file) = sys::File::mock();
mock.write(HELLO);
let mut file = MockFile::default();
file.expect_inner_write()
.once()
.with(eq(HELLO))
.returning(|buf| Ok(buf.len()));
let mut file = File::from_std(file);
@@ -235,12 +207,10 @@ fn open_write() {
assert_eq!(0, pool::len());
assert_ready_ok!(t.poll());
assert_eq!(1, mock.remaining());
assert_eq!(1, pool::len());
pool::run_one();
assert_eq!(0, mock.remaining());
assert!(!t.is_woken());
let mut t = task::spawn(file.flush());
@@ -249,7 +219,7 @@ fn open_write() {
#[test]
fn flush_while_idle() {
let (_mock, file) = sys::File::mock();
let file = MockFile::default();
let mut file = File::from_std(file);
@@ -271,13 +241,42 @@ fn read_with_buffer_larger_than_max() {
for i in 0..(chunk_d - 1) {
data.push((i % 151) as u8);
}
let data = Arc::new(data);
let d0 = data.clone();
let d1 = data.clone();
let d2 = data.clone();
let d3 = data.clone();
let (mock, file) = sys::File::mock();
mock.read(&data[0..chunk_a])
.read(&data[chunk_a..chunk_b])
.read(&data[chunk_b..chunk_c])
.read(&data[chunk_c..]);
let mut seq = Sequence::new();
let mut file = MockFile::default();
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(move |buf| {
buf[0..chunk_a].copy_from_slice(&d0[0..chunk_a]);
Ok(chunk_a)
});
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(move |buf| {
buf[..chunk_a].copy_from_slice(&d1[chunk_a..chunk_b]);
Ok(chunk_b - chunk_a)
});
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(move |buf| {
buf[..chunk_a].copy_from_slice(&d2[chunk_b..chunk_c]);
Ok(chunk_c - chunk_b)
});
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(move |buf| {
buf[..chunk_a - 1].copy_from_slice(&d3[chunk_c..]);
Ok(chunk_a - 1)
});
let mut file = File::from_std(file);
let mut actual = vec![0; chunk_d];
@@ -296,8 +295,7 @@ fn read_with_buffer_larger_than_max() {
pos += n;
}
assert_eq!(mock.remaining(), 0);
assert_eq!(data, &actual[..data.len()]);
assert_eq!(&data[..], &actual[..data.len()]);
}
#[test]
@@ -314,12 +312,34 @@ fn write_with_buffer_larger_than_max() {
for i in 0..(chunk_d - 1) {
data.push((i % 151) as u8);
}
let data = Arc::new(data);
let d0 = data.clone();
let d1 = data.clone();
let d2 = data.clone();
let d3 = data.clone();
let (mock, file) = sys::File::mock();
mock.write(&data[0..chunk_a])
.write(&data[chunk_a..chunk_b])
.write(&data[chunk_b..chunk_c])
.write(&data[chunk_c..]);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.withf(move |buf| buf == &d0[0..chunk_a])
.returning(|buf| Ok(buf.len()));
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.withf(move |buf| buf == &d1[chunk_a..chunk_b])
.returning(|buf| Ok(buf.len()));
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.withf(move |buf| buf == &d2[chunk_b..chunk_c])
.returning(|buf| Ok(buf.len()));
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.withf(move |buf| buf == &d3[chunk_c..chunk_d - 1])
.returning(|buf| Ok(buf.len()));
let mut file = File::from_std(file);
@@ -344,14 +364,22 @@ fn write_with_buffer_larger_than_max() {
}
pool::run_one();
assert_eq!(mock.remaining(), 0);
}
#[test]
fn write_twice_before_dispatch() {
let (mock, file) = sys::File::mock();
mock.write(HELLO).write(FOO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(HELLO))
.returning(|buf| Ok(buf.len()));
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(FOO))
.returning(|buf| Ok(buf.len()));
let mut file = File::from_std(file);
@@ -380,10 +408,24 @@ fn write_twice_before_dispatch() {
#[test]
fn incomplete_read_followed_by_write() {
let (mock, file) = sys::File::mock();
mock.read(HELLO)
.seek_current_ok(-(HELLO.len() as i64), 0)
.write(FOO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
file.expect_inner_seek()
.once()
.with(eq(SeekFrom::Current(-(HELLO.len() as i64))))
.in_sequence(&mut seq)
.returning(|_| Ok(0));
file.expect_inner_write()
.once()
.with(eq(FOO))
.returning(|_| Ok(FOO.len()));
let mut file = File::from_std(file);
@@ -406,8 +448,25 @@ fn incomplete_read_followed_by_write() {
#[test]
fn incomplete_partial_read_followed_by_write() {
let (mock, file) = sys::File::mock();
mock.read(HELLO).seek_current_ok(-10, 0).write(FOO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
file.expect_inner_seek()
.once()
.in_sequence(&mut seq)
.with(eq(SeekFrom::Current(-10)))
.returning(|_| Ok(0));
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(FOO))
.returning(|_| Ok(FOO.len()));
let mut file = File::from_std(file);
@@ -433,10 +492,25 @@ fn incomplete_partial_read_followed_by_write() {
#[test]
fn incomplete_read_followed_by_flush() {
let (mock, file) = sys::File::mock();
mock.read(HELLO)
.seek_current_ok(-(HELLO.len() as i64), 0)
.write(FOO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
file.expect_inner_seek()
.once()
.in_sequence(&mut seq)
.with(eq(SeekFrom::Current(-(HELLO.len() as i64))))
.returning(|_| Ok(0));
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(FOO))
.returning(|_| Ok(FOO.len()));
let mut file = File::from_std(file);
@@ -458,8 +532,18 @@ fn incomplete_read_followed_by_flush() {
#[test]
fn incomplete_flush_followed_by_write() {
let (mock, file) = sys::File::mock();
mock.write(HELLO).write(FOO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(HELLO))
.returning(|_| Ok(HELLO.len()));
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(FOO))
.returning(|_| Ok(FOO.len()));
let mut file = File::from_std(file);
@@ -484,8 +568,10 @@ fn incomplete_flush_followed_by_write() {
#[test]
fn read_err() {
let (mock, file) = sys::File::mock();
mock.read_err();
let mut file = MockFile::default();
file.expect_inner_read()
.once()
.returning(|_| Err(io::ErrorKind::Other.into()));
let mut file = File::from_std(file);
@@ -502,8 +588,10 @@ fn read_err() {
#[test]
fn write_write_err() {
let (mock, file) = sys::File::mock();
mock.write_err();
let mut file = MockFile::default();
file.expect_inner_write()
.once()
.returning(|_| Err(io::ErrorKind::Other.into()));
let mut file = File::from_std(file);
@@ -518,8 +606,19 @@ fn write_write_err() {
#[test]
fn write_read_write_err() {
let (mock, file) = sys::File::mock();
mock.write_err().read(HELLO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.returning(|_| Err(io::ErrorKind::Other.into()));
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
let mut file = File::from_std(file);
@@ -541,8 +640,19 @@ fn write_read_write_err() {
#[test]
fn write_read_flush_err() {
let (mock, file) = sys::File::mock();
mock.write_err().read(HELLO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.returning(|_| Err(io::ErrorKind::Other.into()));
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
let mut file = File::from_std(file);
@@ -564,8 +674,17 @@ fn write_read_flush_err() {
#[test]
fn write_seek_write_err() {
let (mock, file) = sys::File::mock();
mock.write_err().seek_start_ok(0);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.returning(|_| Err(io::ErrorKind::Other.into()));
file.expect_inner_seek()
.once()
.with(eq(SeekFrom::Start(0)))
.in_sequence(&mut seq)
.returning(|_| Ok(0));
let mut file = File::from_std(file);
@@ -587,8 +706,17 @@ fn write_seek_write_err() {
#[test]
fn write_seek_flush_err() {
let (mock, file) = sys::File::mock();
mock.write_err().seek_start_ok(0);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.returning(|_| Err(io::ErrorKind::Other.into()));
file.expect_inner_seek()
.once()
.with(eq(SeekFrom::Start(0)))
.in_sequence(&mut seq)
.returning(|_| Ok(0));
let mut file = File::from_std(file);
@@ -610,8 +738,14 @@ fn write_seek_flush_err() {
#[test]
fn sync_all_ordered_after_write() {
let (mock, file) = sys::File::mock();
mock.write(HELLO).sync_all();
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(HELLO))
.returning(|_| Ok(HELLO.len()));
file.expect_sync_all().once().returning(|| Ok(()));
let mut file = File::from_std(file);
let mut t = task::spawn(file.write(HELLO));
@@ -635,8 +769,16 @@ fn sync_all_ordered_after_write() {
#[test]
fn sync_all_err_ordered_after_write() {
let (mock, file) = sys::File::mock();
mock.write(HELLO).sync_all_err();
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(HELLO))
.returning(|_| Ok(HELLO.len()));
file.expect_sync_all()
.once()
.returning(|| Err(io::ErrorKind::Other.into()));
let mut file = File::from_std(file);
let mut t = task::spawn(file.write(HELLO));
@@ -660,8 +802,14 @@ fn sync_all_err_ordered_after_write() {
#[test]
fn sync_data_ordered_after_write() {
let (mock, file) = sys::File::mock();
mock.write(HELLO).sync_data();
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(HELLO))
.returning(|_| Ok(HELLO.len()));
file.expect_sync_data().once().returning(|| Ok(()));
let mut file = File::from_std(file);
let mut t = task::spawn(file.write(HELLO));
@@ -685,8 +833,16 @@ fn sync_data_ordered_after_write() {
#[test]
fn sync_data_err_ordered_after_write() {
let (mock, file) = sys::File::mock();
mock.write(HELLO).sync_data_err();
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_write()
.once()
.in_sequence(&mut seq)
.with(eq(HELLO))
.returning(|_| Ok(HELLO.len()));
file.expect_sync_data()
.once()
.returning(|| Err(io::ErrorKind::Other.into()));
let mut file = File::from_std(file);
let mut t = task::spawn(file.write(HELLO));
@@ -710,17 +866,15 @@ fn sync_data_err_ordered_after_write() {
#[test]
fn open_set_len_ok() {
let (mock, file) = sys::File::mock();
mock.set_len(123);
let mut file = MockFile::default();
file.expect_set_len().with(eq(123)).returning(|_| Ok(()));
let file = File::from_std(file);
let mut t = task::spawn(file.set_len(123));
assert_pending!(t.poll());
assert_eq!(1, mock.remaining());
pool::run_one();
assert_eq!(0, mock.remaining());
assert!(t.is_woken());
assert_ready_ok!(t.poll());
@@ -728,17 +882,17 @@ fn open_set_len_ok() {
#[test]
fn open_set_len_err() {
let (mock, file) = sys::File::mock();
mock.set_len_err(123);
let mut file = MockFile::default();
file.expect_set_len()
.with(eq(123))
.returning(|_| Err(io::ErrorKind::Other.into()));
let file = File::from_std(file);
let mut t = task::spawn(file.set_len(123));
assert_pending!(t.poll());
assert_eq!(1, mock.remaining());
pool::run_one();
assert_eq!(0, mock.remaining());
assert!(t.is_woken());
assert_ready_err!(t.poll());
@@ -746,11 +900,32 @@ fn open_set_len_err() {
#[test]
fn partial_read_set_len_ok() {
let (mock, file) = sys::File::mock();
mock.read(HELLO)
.seek_current_ok(-14, 0)
.set_len(123)
.read(FOO);
let mut file = MockFile::default();
let mut seq = Sequence::new();
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..HELLO.len()].copy_from_slice(HELLO);
Ok(HELLO.len())
});
file.expect_inner_seek()
.once()
.with(eq(SeekFrom::Current(-(HELLO.len() as i64))))
.in_sequence(&mut seq)
.returning(|_| Ok(0));
file.expect_set_len()
.once()
.in_sequence(&mut seq)
.with(eq(123))
.returning(|_| Ok(()));
file.expect_inner_read()
.once()
.in_sequence(&mut seq)
.returning(|buf| {
buf[0..FOO.len()].copy_from_slice(FOO);
Ok(FOO.len())
});
let mut buf = [0; 32];
let mut file = File::from_std(file);
+151
View File
@@ -0,0 +1,151 @@
//! Mock version of std::fs::File;
use mockall::mock;
use crate::sync::oneshot;
use std::{
cell::RefCell,
collections::VecDeque,
fs::{Metadata, Permissions},
future::Future,
io::{self, Read, Seek, SeekFrom, Write},
path::PathBuf,
pin::Pin,
task::{Context, Poll},
};
mock! {
#[derive(Debug)]
pub File {
pub fn create(pb: PathBuf) -> io::Result<Self>;
// These inner_ methods exist because std::fs::File has two
// implementations for each of these methods: one on "&mut self" and
// one on "&&self". Defining both of those in terms of an inner_ method
// allows us to specify the expectation the same way, regardless of
// which method is used.
pub fn inner_flush(&self) -> io::Result<()>;
pub fn inner_read(&self, dst: &mut [u8]) -> io::Result<usize>;
pub fn inner_seek(&self, pos: SeekFrom) -> io::Result<u64>;
pub fn inner_write(&self, src: &[u8]) -> io::Result<usize>;
pub fn metadata(&self) -> io::Result<Metadata>;
pub fn open(pb: PathBuf) -> io::Result<Self>;
pub fn set_len(&self, size: u64) -> io::Result<()>;
pub fn set_permissions(&self, _perm: Permissions) -> io::Result<()>;
pub fn sync_all(&self) -> io::Result<()>;
pub fn sync_data(&self) -> io::Result<()>;
pub fn try_clone(&self) -> io::Result<Self>;
}
#[cfg(windows)]
impl std::os::windows::io::AsRawHandle for File {
fn as_raw_handle(&self) -> std::os::windows::io::RawHandle;
}
#[cfg(windows)]
impl std::os::windows::io::FromRawHandle for File {
unsafe fn from_raw_handle(h: std::os::windows::io::RawHandle) -> Self;
}
#[cfg(unix)]
impl std::os::unix::io::AsRawFd for File {
fn as_raw_fd(&self) -> std::os::unix::io::RawFd;
}
#[cfg(unix)]
impl std::os::unix::io::FromRawFd for File {
unsafe fn from_raw_fd(h: std::os::unix::io::RawFd) -> Self;
}
}
impl Read for MockFile {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
self.inner_read(dst)
}
}
impl Read for &'_ MockFile {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
self.inner_read(dst)
}
}
impl Seek for &'_ MockFile {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.inner_seek(pos)
}
}
impl Write for &'_ MockFile {
fn write(&mut self, src: &[u8]) -> io::Result<usize> {
self.inner_write(src)
}
fn flush(&mut self) -> io::Result<()> {
self.inner_flush()
}
}
thread_local! {
static QUEUE: RefCell<VecDeque<Box<dyn FnOnce() + Send>>> = RefCell::new(VecDeque::new())
}
#[derive(Debug)]
pub(super) struct JoinHandle<T> {
rx: oneshot::Receiver<T>,
}
pub(super) fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = oneshot::channel();
let task = Box::new(move || {
let _ = tx.send(f());
});
QUEUE.with(|cell| cell.borrow_mut().push_back(task));
JoinHandle { rx }
}
pub(super) fn spawn_mandatory_blocking<F, R>(f: F) -> Option<JoinHandle<R>>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = oneshot::channel();
let task = Box::new(move || {
let _ = tx.send(f());
});
QUEUE.with(|cell| cell.borrow_mut().push_back(task));
Some(JoinHandle { rx })
}
impl<T> Future for JoinHandle<T> {
type Output = Result<T, io::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
use std::task::Poll::*;
match Pin::new(&mut self.rx).poll(cx) {
Ready(Ok(v)) => Ready(Ok(v)),
Ready(Err(e)) => panic!("error = {:?}", e),
Pending => Pending,
}
}
}
pub(super) mod pool {
use super::*;
pub(in super::super) fn len() -> usize {
QUEUE.with(|cell| cell.borrow().len())
}
pub(in super::super) fn run_one() {
let task = QUEUE
.with(|cell| cell.borrow_mut().pop_front())
.expect("expected task to run, but none ready");
task();
}
}
+9 -10
View File
@@ -84,6 +84,9 @@ pub use self::write::write;
mod copy;
pub use self::copy::copy;
#[cfg(test)]
mod mocks;
feature! {
#![unix]
@@ -103,12 +106,17 @@ feature! {
use std::io;
#[cfg(not(test))]
use crate::blocking::spawn_blocking;
#[cfg(test)]
use mocks::spawn_blocking;
pub(crate) async fn asyncify<F, T>(f: F) -> io::Result<T>
where
F: FnOnce() -> io::Result<T> + Send + 'static,
T: Send + 'static,
{
match sys::run(f).await {
match spawn_blocking(f).await {
Ok(res) => res,
Err(_) => Err(io::Error::new(
io::ErrorKind::Other,
@@ -116,12 +124,3 @@ where
)),
}
}
/// Types in this module can be mocked out in tests.
mod sys {
pub(crate) use std::fs::File;
// TODO: don't rename
pub(crate) use crate::blocking::spawn_blocking as run;
pub(crate) use crate::blocking::JoinHandle as Blocking;
}
+13 -6
View File
@@ -3,6 +3,13 @@ use crate::fs::{asyncify, File};
use std::io;
use std::path::Path;
#[cfg(test)]
mod mock_open_options;
#[cfg(test)]
use mock_open_options::MockOpenOptions as StdOpenOptions;
#[cfg(not(test))]
use std::fs::OpenOptions as StdOpenOptions;
/// Options and flags which can be used to configure how a file is opened.
///
/// This builder exposes the ability to configure how a [`File`] is opened and
@@ -69,7 +76,7 @@ use std::path::Path;
/// }
/// ```
#[derive(Clone, Debug)]
pub struct OpenOptions(std::fs::OpenOptions);
pub struct OpenOptions(StdOpenOptions);
impl OpenOptions {
/// Creates a blank new set of options ready for configuration.
@@ -89,7 +96,7 @@ impl OpenOptions {
/// let future = options.read(true).open("foo.txt");
/// ```
pub fn new() -> OpenOptions {
OpenOptions(std::fs::OpenOptions::new())
OpenOptions(StdOpenOptions::new())
}
/// Sets the option for read access.
@@ -384,7 +391,7 @@ impl OpenOptions {
}
/// Returns a mutable reference to the underlying `std::fs::OpenOptions`
pub(super) fn as_inner_mut(&mut self) -> &mut std::fs::OpenOptions {
pub(super) fn as_inner_mut(&mut self) -> &mut StdOpenOptions {
&mut self.0
}
}
@@ -423,7 +430,7 @@ feature! {
self
}
/// Pass custom flags to the `flags` argument of `open`.
/// Passes custom flags to the `flags` argument of `open`.
///
/// The bits that define the access mode are masked out with `O_ACCMODE`, to
/// ensure they do not interfere with the access mode set by Rusts options.
@@ -645,8 +652,8 @@ feature! {
}
}
impl From<std::fs::OpenOptions> for OpenOptions {
fn from(options: std::fs::OpenOptions) -> OpenOptions {
impl From<StdOpenOptions> for OpenOptions {
fn from(options: StdOpenOptions) -> OpenOptions {
OpenOptions(options)
}
}
@@ -0,0 +1,38 @@
//! Mock version of std::fs::OpenOptions;
use mockall::mock;
use crate::fs::mocks::MockFile;
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
#[cfg(windows)]
use std::os::windows::fs::OpenOptionsExt;
use std::{io, path::Path};
mock! {
#[derive(Debug)]
pub OpenOptions {
pub fn append(&mut self, append: bool) -> &mut Self;
pub fn create(&mut self, create: bool) -> &mut Self;
pub fn create_new(&mut self, create_new: bool) -> &mut Self;
pub fn open<P: AsRef<Path> + 'static>(&self, path: P) -> io::Result<MockFile>;
pub fn read(&mut self, read: bool) -> &mut Self;
pub fn truncate(&mut self, truncate: bool) -> &mut Self;
pub fn write(&mut self, write: bool) -> &mut Self;
}
impl Clone for OpenOptions {
fn clone(&self) -> Self;
}
#[cfg(unix)]
impl OpenOptionsExt for OpenOptions {
fn custom_flags(&mut self, flags: i32) -> &mut Self;
fn mode(&mut self, mode: u32) -> &mut Self;
}
#[cfg(windows)]
impl OpenOptionsExt for OpenOptions {
fn access_mode(&mut self, access: u32) -> &mut Self;
fn share_mode(&mut self, val: u32) -> &mut Self;
fn custom_flags(&mut self, flags: u32) -> &mut Self;
fn attributes(&mut self, val: u32) -> &mut Self;
fn security_qos_flags(&mut self, flags: u32) -> &mut Self;
}
}
+4
View File
@@ -13,8 +13,12 @@ use std::{io, path::Path};
/// buffer based on the file size when available, so it is generally faster than
/// reading into a vector created with `Vec::new()`.
///
/// This operation is implemented by running the equivalent blocking operation
/// on a separate thread pool using [`spawn_blocking`].
///
/// [`File::open`]: super::File::open
/// [`read_to_end`]: crate::io::AsyncReadExt::read_to_end
/// [`spawn_blocking`]: crate::task::spawn_blocking
///
/// # Errors
///
+23 -5
View File
@@ -1,4 +1,4 @@
use crate::fs::{asyncify, sys};
use crate::fs::asyncify;
use std::ffi::OsString;
use std::fs::{FileType, Metadata};
@@ -10,9 +10,23 @@ use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
#[cfg(test)]
use super::mocks::spawn_blocking;
#[cfg(test)]
use super::mocks::JoinHandle;
#[cfg(not(test))]
use crate::blocking::spawn_blocking;
#[cfg(not(test))]
use crate::blocking::JoinHandle;
/// Returns a stream over the entries within a directory.
///
/// This is an async version of [`std::fs::read_dir`](std::fs::read_dir)
///
/// This operation is implemented by running the equivalent blocking
/// operation on a separate thread pool using [`spawn_blocking`].
///
/// [`spawn_blocking`]: crate::task::spawn_blocking
pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
let path = path.as_ref().to_owned();
let std = asyncify(|| std::fs::read_dir(path)).await?;
@@ -20,7 +34,7 @@ pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
Ok(ReadDir(State::Idle(Some(std))))
}
/// Read the the entries in a directory.
/// Reads the the entries in a directory.
///
/// This struct is returned from the [`read_dir`] function of this module and
/// will yield instances of [`DirEntry`]. Through a [`DirEntry`] information
@@ -45,11 +59,15 @@ pub struct ReadDir(State);
#[derive(Debug)]
enum State {
Idle(Option<std::fs::ReadDir>),
Pending(sys::Blocking<(Option<io::Result<std::fs::DirEntry>>, std::fs::ReadDir)>),
Pending(JoinHandle<(Option<io::Result<std::fs::DirEntry>>, std::fs::ReadDir)>),
}
impl ReadDir {
/// Returns the next entry in the directory stream.
///
/// # Cancel safety
///
/// This method is cancellation safe.
pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
use crate::future::poll_fn;
poll_fn(|cx| self.poll_next_entry(cx)).await
@@ -79,7 +97,7 @@ impl ReadDir {
State::Idle(ref mut std) => {
let mut std = std.take().unwrap();
self.0 = State::Pending(sys::run(move || {
self.0 = State::Pending(spawn_blocking(move || {
let ret = std.next();
(ret, std)
}));
@@ -269,7 +287,7 @@ impl DirEntry {
asyncify(move || std.file_type()).await
}
/// Returns a reference to the underlying `std::fs::DirEntry`
/// Returns a reference to the underlying `std::fs::DirEntry`.
#[cfg(unix)]
pub(super) fn as_inner(&self) -> &std::fs::DirEntry {
&self.0
+4
View File
@@ -7,6 +7,10 @@ use std::{io, path::Path};
///
/// This is the async equivalent of [`std::fs::read_to_string`][std].
///
/// This operation is implemented by running the equivalent blocking operation
/// on a separate thread pool using [`spawn_blocking`].
///
/// [`spawn_blocking`]: crate::task::spawn_blocking
/// [std]: fn@std::fs::read_to_string
///
/// # Examples

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