Compare commits

...
Author SHA1 Message Date
Alice Ryhl 89ccf2ad2b chore: prepare tokio-stream 0.1.9 (#4743) 2022-06-04 22:04:21 +02:00
Alice Ryhl 3f8a690c01 chore: prepare tokio-macros 1.8.0 (#4742) 2022-06-04 22:04:12 +02:00
Alice Ryhl 14c77bc434 chore: prepare tokio-util 0.7.3 (#4744) 2022-06-04 22:04:02 +02:00
Alice Ryhl 0241f1c54d chore: fix changelog typo (#4740) 2022-06-03 19:17:12 +00:00
Alice Ryhl 674d77d4ef chore: prepare Tokio v1.19.0 (#4738) 2022-06-03 20:41:16 +02:00
Max IndenandTaiki Endo 42b4c27b88 net: add take_error to TcpSocket and TcpStream (#4739)
Co-authored-by: Taiki Endo <[email protected]>
2022-06-03 19:50:52 +02:00
David KoloskiandDavid Koloski 0d4d3c34f1 sync: replace non-binding if let statements (#4735)
Found some `if let` statements that don't actually bind any variables.
This can make it somewhat confusing as to whether the pattern is binding
the value or being matched against. Switching to `matches!` and equality
comparisons allow these to be removed.

Co-authored-by: David Koloski <[email protected]>
2022-06-03 09:41:16 +02:00
Alice Ryhl 4941fbf7c4 tokio: add 1.18.x as LTS release (#4733) 2022-06-02 12:57:05 +02:00
David KoloskiandDavid Koloski cc6c2f40cb tokio: check page capacity before obtaining base pointer (#4731)
This doesn't cause any issues in practice because this is a private API
that is only used in ways that cannot trigger UB. Indexing into `slots`
is not sound until after we've asserted that the page is allocated,
since that aliases the first slot which may not be allocated. This PR
also switches to using `as_ptr` to obtain the base pointer for clarity.

Co-authored-by: David Koloski <[email protected]>
2022-06-01 21:18:06 +02:00
Aleksey Kladov 925314ba43 doc: clarify semantics of tasks outliving block_on (#4729) 2022-05-31 19:08:53 +00:00
Alice Ryhl f948cd7b33 chore: fix clippy warnings (#4727) 2022-05-31 20:26:32 +02:00
Name1e5s 83d0e7f8b3 runtime: add is_finished method for JoinHandle and AbortHandle (#4709) 2022-05-31 09:22:09 +00:00
Alice Ryhl 5fd1220c73 task: update return value of JoinSet::join_one (#4726) 2022-05-31 09:15:37 +02:00
Eliza Weisman 2bad98f879 task: add join_set::Builder for configuring JoinSet tasks (#4687) 2022-05-30 18:23:40 +02:00
Piotr Sarna 88e8c6239c task: add consume_budget for cooperative scheduling (#4498)
* task: add consume_budget for cooperative scheduling

For cpu-only computations that do not use any Tokio resources,
budgeting does not really kick in in order to yield and prevent
other tasks from starvation. The new mechanism - consume_budget,
performs a budget check, consumes a unit of it, and yields only
if the task exceeded the budget. That allows cpu-intenstive
computations to define points in the program which indicate that
some significant work was performed. It will yield only if the budget
is gone, which is a much better alternative to unconditional yielding,
which is a potentially heavy operation.

* tests: add a test case for task::consume_budget

The test case ensures that the task::consume_budget utility
actually burns budget and makes the task yield once the whole
budget is gone.
2022-05-30 09:15:54 +00:00
Alan Somers 6c0a9942ba metrics: correctly update atomics in IoDriverMetrics (#4725)
Updating an atomic variable with a load followed by a store is racy.  It
defeats the entire purpose of using atomic variables, and can result in
"lost" updates.  Instead, use fetch_add .
2022-05-30 11:14:56 +02:00
estk f6c0405084 sync: add resubscribe method to broadcast::Receiver (#4607) 2022-05-28 20:15:27 +00:00
Gus Wynn 05cbfae177 stream: add cancel-safety docs to StreamExt::next and try_next (#4715) 2022-05-27 10:28:29 +02:00
Eric Zhang 323b63fa04 time: fix example for MissedTickBehavior::Burst (#4713) 2022-05-22 21:22:03 +02:00
Rafael Bachmann 9f4959580f sync: add broadcast to list of channel types (#4712) 2022-05-22 16:46:34 +00:00
Name1e5s 50bd8ad17b chore: fix cargo audit warning by upgrading mockall (#4710) 2022-05-21 15:17:42 +02:00
Alice Ryhl 4675087090 sync: add Notified::enable (#4705) 2022-05-19 16:31:23 +00:00
Alice Ryhl a2112b47d3 chore: update macro output tests for rust 1.61.0 (#4706) 2022-05-19 15:55:11 +00:00
盏一 931a7773de io: refactor out usage of Weak in the io handle (#4656) 2022-05-19 10:24:27 +02:00
David Barsky 3fce06c2cc chore: add Netlify doc previews (#4699) 2022-05-19 09:30:06 +02:00
Aaron Turon 0b8a3c34a1 runtime: make global queue and event polling intervals configurable (#4671)
Adds knobs to the runtime builder to control the number of ticks
between polling the global task queue (fairness) and the event driver
(I/O prioritization).

Both varieties of scheduler already supported these intervals, but they
were defined by private constants. Some workloads benefit from
customizing these values.

Closes #4651
2022-05-18 11:23:53 +02:00
Alice Ryhl ddf2f5fdf6 rt: fix flaky wake_while_rt_is_dropping test (#4698) 2022-05-17 19:20:34 +02:00
Eliza Weisman 052355f064 task: add #[track_caller] to JoinSet/JoinMap (#4697)
## Motivation

Currently, the various spawning methods on `tokio::task::JoinSet` and
`tokio_util::task::JoinMap` lack `#[track_caller]` attributes, so the
`tracing` spans generated for tasks spawned on `JoinSet`s/`JoinMap`s
will have the `JoinSet` spawning method as their spawn location, rather
than the user code that called that method.

## Solution

This PR fixes that by...adding a bunch of `#[track_caller]` attributes.

## Future Work

In the future, we may also want to consider adding additional data to
the task span indicating that the task is spawned on a `JoinSet`...

Signed-off-by: Eliza Weisman <[email protected]>
2022-05-16 16:36:24 +00:00
Finomnis 454ac1c54f ci: enable ASAN in CI (#4696) 2022-05-16 16:57:03 +02:00
Alice Ryhl fa06605e45 Merge 'tokio-util-0.7.x' into master 2022-05-15 10:09:29 +02:00
Alice Ryhl 038de36132 chore: prepare tokio-util 0.7.2 (#4690) 2022-05-14 21:06:13 +02:00
Alice Ryhl 42d5a9fcd4 Merge 'tokio-util-0.6.x' into 'tokio-util-0.7.x' 2022-05-14 21:03:16 +02:00
Alice Ryhl cdb132d333 Revert "chore: disable warnings in old CI (#4691)"
This reverts commit b24df49a9d so we can
merge the CHANGELOG changes in #4691 into master.
2022-05-14 20:59:52 +02:00
Alice Ryhl 2659adf5fe chore: prepare tokio-util 0.6.10 (#4691) 2022-05-14 20:16:41 +02:00
Finomnis 0105d9971f sync: rewrite CancellationToken (#4652) 2022-05-14 20:16:36 +02:00
Alice Ryhl b24df49a9d chore: disable warnings in old CI (#4691) 2022-05-14 20:16:21 +02:00
Eliza Weisman b1557ea5b2 task: add Builder::{spawn_on, spawn_local_on, spawn_blocking_on} (#4683)
## Motivation

`task::JoinSet` currently has both `spawn`/`spawn_local` methods,
and `spawn_on`/`spawn_local_on` variants of these methods that take a
reference to a runtime `Handle` or to a `LocalSet`, and spawn tasks on
the provided runtime/`LocalSet`, rather than the current one. The
`task::Builder` type is _also_ an API type that can spawn tasks, but it
doesn't have `spawn_on` variants of its methods. It occurred to me that
it would be nice to have similar APIs on `task::Builder`.

## Solution

This branch adds `task::Builder::spawn_on`,
`task::Builder::spawn_local_on`, and `task::Builder::spawn_blocking_on`
methods, similar to those on `JoinSet`. In addition, I did some
refactoring of the internal spawning APIs --- there was a bit of
duplicated code that this PR reduces.

Signed-off-by: Eliza Weisman <[email protected]>
2022-05-14 10:44:47 -07:00
Alice Ryhl ce0e1152ad util: display JoinMap on docs.rs (#4689) 2022-05-14 18:55:26 +02:00
Alice Ryhl cf94ffc6fd windows: add features for winapi (#4663) 2022-05-14 16:44:44 +02:00
Sabrina Jewson f7346f04af util: simplify ReusableBoxFuture (#4675) 2022-05-13 23:39:13 +02:00
Finomnis addf5b5749 sync: rewrite CancellationToken (#4652) 2022-05-13 23:26:15 +02:00
Noah Kennedy 4ec6ba8b76 metrics: fix compilation with unstable, process, and rt (#4682)
Fixes #4681.
2022-05-11 19:31:37 +00:00
Sabrina Jewson 593b042f7b docs: mention that Clippy must be run with the MSRV (#4676)
## Motivation

In #4675 I learnt the hard way that Tokio uses Clippy on its MSRV. 

## Solution

Document this in the contributor's guide.
2022-05-10 17:53:43 +00:00
Alice Ryhl 71e18f7b75 Merge branch 'merge-1.18.2' into master 2022-05-08 23:37:56 +02:00
Alice Ryhl 7aa1566cde chore: prepare Tokio v1.18.2 2022-05-08 21:41:15 +02:00
Alice Ryhl 7c8e552f29 windows: add features for winapi (#4663) 2022-05-08 21:41:15 +02:00
Alice Ryhl 67074c3d44 windows: add features for winapi (#4663) 2022-05-08 20:14:28 +02:00
Alice Ryhl 938b7d6742 util: improve impl Send for ReusableBoxFuture docs (#4658) 2022-05-07 22:32:29 +02:00
Bruno 1872a425e2 macros: avoid starvation in join! and try_join! (#4624)
Fixes: #4612
2022-05-07 12:42:44 +02:00
Erick Tryzelaar c5ff797dcf udp: document and shrink some unsafe blocks (#4655)
This documents why it is safe to convert `bytes::UninitSlice` to `&mut
[MaybeUninit<u8>]`, and shrinks one of the unsafe blocks to make these
functions easier to audit.
2022-05-05 19:48:39 +00:00
Taiki Endo 2a305d2423 ci: update actions/checkout action to v3 (#4646) 2022-05-04 15:54:16 +02:00
Uwe Klotz c14566e9df watch: modify and send value conditionally (#4591)
Add a function that is more versatile than send_modify(). The result of
the passed closure indicates if the mutably borrowed value has actually
been modified or not. Receivers are only notified if the value has been
modified as indicated by the sender.

Signed-off-by: Uwe Klotz <[email protected]>
2022-05-03 06:57:10 +00:00
Alice Ryhl 148bea82ee tokio: prepare Tokio v1.18.1 (#4650) 2022-05-02 17:07:27 +02:00
Alice Ryhl dc54aec1c7 metrics: use mocked AtomicU64 in IO metrics driver (#4649) 2022-05-02 13:32:59 +02:00
Taiki Endo fa665b91a8 macros: always emit return statement (#4636)
Fixes #4635
2022-04-28 07:53:40 +09:00
Eliza Weisman 48183430fb tokio: prepare to release v1.18.0 (#4641)
# 1.18.0 (April 27, 2022)

This release adds a number of new APIs in `tokio::net`, `tokio::signal`, and
`tokio::sync`. In addition, it adds new unstable APIs to `tokio::task` (`Id`s
for uniquely identifying a task, and `AbortHandle` for remotely cancelling a
task), as well as a number of bugfixes.

### Fixed

- blocking: add missing `#[track_caller]` for `spawn_blocking` ([#4616])
- macros: fix `select` macro to process 64 branches ([#4519])
- net: fix `try_io` methods not calling Mio's `try_io` internally ([#4582])
- runtime: recover when OS fails to spawn a new thread ([#4485])

### Added

- macros: support setting a custom crate name for `#[tokio::main]` and
  `#[tokio::test]` ([#4613])
- net: add `UdpSocket::peer_addr` ([#4611])
- net: add `try_read_buf` method for named pipes ([#4626])
- signal: add `SignalKind` `Hash`/`Eq` impls and `c_int` conversion ([#4540])
- signal: add support for signals up to `SIGRTMAX` ([#4555])
- sync: add `watch::Sender::send_modify` method ([#4310])
- sync: add `broadcast::Receiver::len` method ([#4542])
- sync: add `watch::Receiver::same_channel` method ([#4581])
- sync: implement `Clone` for `RecvError` types ([#4560])

### Changed

- update `nix` to 0.24, limit features ([#4631])
- update `mio` to 0.8.1 ([#4582])
- macros: rename `tokio::select!`'s internal `util` module ([#4543])
- runtime: use `Vec::with_capacity` when building runtime ([#4553])

### Documented

- improve docs for `tokio_unstable` ([#4524])
- runtime: include more documentation for thread_pool/worker ([#4511])
- runtime: update `Handle::current`'s docs to mention `EnterGuard` ([#4567])
- time: clarify platform specific timer resolution ([#4474])
- signal: document that `Signal::recv` is cancel-safe ([#4634])
- sync: `UnboundedReceiver` close docs ([#4548])

### Unstable

The following changes only apply when building with `--cfg tokio_unstable`:

- task: add `task::Id` type ([#4630])
- task: add `AbortHandle` type for cancelling tasks in a `JoinSet` ([#4530],
  [#4640])
- task: fix missing `doc(cfg(...))` attributes for `JoinSet` ([#4531])
- task: fix broken link in `AbortHandle` RustDoc ([#4545])
- metrics: add initial IO driver metrics ([#4507])

[#4616]: https://github.com/tokio-rs/tokio/pull/4616
[#4519]: https://github.com/tokio-rs/tokio/pull/4519
[#4582]: https://github.com/tokio-rs/tokio/pull/4582
[#4485]: https://github.com/tokio-rs/tokio/pull/4485
[#4613]: https://github.com/tokio-rs/tokio/pull/4613
[#4611]: https://github.com/tokio-rs/tokio/pull/4611
[#4626]: https://github.com/tokio-rs/tokio/pull/4626
[#4540]: https://github.com/tokio-rs/tokio/pull/4540
[#4555]: https://github.com/tokio-rs/tokio/pull/4555
[#4310]: https://github.com/tokio-rs/tokio/pull/4310
[#4542]: https://github.com/tokio-rs/tokio/pull/4542
[#4581]: https://github.com/tokio-rs/tokio/pull/4581
[#4560]: https://github.com/tokio-rs/tokio/pull/4560
[#4631]: https://github.com/tokio-rs/tokio/pull/4631
[#4582]: https://github.com/tokio-rs/tokio/pull/4582
[#4543]: https://github.com/tokio-rs/tokio/pull/4543
[#4553]: https://github.com/tokio-rs/tokio/pull/4553
[#4524]: https://github.com/tokio-rs/tokio/pull/4524
[#4511]: https://github.com/tokio-rs/tokio/pull/4511
[#4567]: https://github.com/tokio-rs/tokio/pull/4567
[#4474]: https://github.com/tokio-rs/tokio/pull/4474
[#4634]: https://github.com/tokio-rs/tokio/pull/4634
[#4548]: https://github.com/tokio-rs/tokio/pull/4548
[#4630]: https://github.com/tokio-rs/tokio/pull/4630
[#4530]: https://github.com/tokio-rs/tokio/pull/4530
[#4640]: https://github.com/tokio-rs/tokio/pull/4640
[#4531]: https://github.com/tokio-rs/tokio/pull/4531
[#4545]: https://github.com/tokio-rs/tokio/pull/4545
[#4507]: https://github.com/tokio-rs/tokio/pull/4507

Signed-off-by: Eliza Weisman <[email protected]>
2022-04-27 17:08:07 +00:00
Eliza Weisman d456706528 util: implement JoinMap (#4640)
## Motivation

In many cases, it is desirable to spawn a set of tasks associated with
keys, with the ability to cancel them by key. As an example use case for
this sort of thing, see Tower's [`ReadyCache` type][1].

Now that PR #4530 adds a way of cancelling tasks in a
`tokio::task::JoinSet`, we can implement a map-like API based on the
same `IdleNotifiedSet` primitive.

## Solution

This PR adds an implementation of a `JoinMap` type to
`tokio_util::task`, using the `JoinSet` type from `tokio::task`, the
`AbortHandle` type added in #4530, and the new task IDs added in #4630.

Individual tasks can be aborted by key using the `JoinMap::abort`
method, and a set of tasks whose key match a given predicate can be
aborted using `JoinMap::abort_matching`.

When tasks complete, `JoinMap::join_one` returns their associated key
alongside the output from the spawned future, or the key and the
`JoinError` if the task did not complete successfully.

Overall, I think the way this works is pretty straightforward; much of
this PR is just API boilerplate to implement the union of applicable
APIs from `JoinSet` and `HashMap`. Unlike previous iterations on the
`JoinMap` API (e.g. #4538), this version is implemented entirely in
`tokio_util`, using only public APIs from the `tokio` crate. Currently,
the required `tokio` APIs are unstable, but implementing `JoinMap` in
`tokio-util` means we will never have to make stability commitments for
the `JoinMap` API itself.

[1]: https://github.com/tower-rs/tower/blob/master/tower/src/ready_cache/cache.rs

Signed-off-by: Eliza Weisman <[email protected]>
2022-04-26 17:25:48 +00:00
Eliza Weisman 1d3f12304e task: add task IDs (#4630)
## Motivation

PR #4538 adds a prototype implementation of a `JoinMap` API in
`tokio::task`. In [this comment][1] on that PR, @carllerche pointed out
that a much simpler `JoinMap` type could be implemented outside of
`tokio` (either in `tokio-util` or in user code) if we just modified
`JoinSet` to return a task ID type when spawning new tasks, and when
tasks complete. This seems like a better approach for the following
reasons:

* A `JoinMap`-like type need not become a permanent part of `tokio`'s
  stable API
* Task IDs seem like something that could be generally useful outside of
  a `JoinMap` implementation

## Solution

This branch adds a `tokio::task::Id` type that uniquely identifies a
task relative to all other spawned tasks. Task IDs are assigned
sequentially based on an atomic `usize` counter of spawned tasks.

In addition, I modified `JoinSet` to add a `join_with_id` method that
behaves identically to `join_one` but also returns an ID. This can be
used to implement a `JoinMap` type.

Note that because `join_with_id` must return a task ID regardless of
whether the task completes successfully or returns a `JoinError`, I've
also changed `JoinError` to carry the ID of the task that errored, and 
added a `JoinError::id` method for accessing it. Alternatively, we could
have done one of the following:

* have `join_with_id` return `Option<(Id, Result<T, JoinError>)>`, which
  would be inconsistent with the return type of `join_one` (which we've
  [already bikeshedded over once][2]...)
* have `join_with_id` return `Result<Option<(Id, T)>, (Id, JoinError)>>`,
  which just feels gross.

I thought adding the task ID to `JoinError` was the nicest option, and
is potentially useful for other stuff as well, so it's probably a good API to
have anyway.

[1]: https://github.com/tokio-rs/tokio/pull/4538#issuecomment-1065614755
[2]: https://github.com/tokio-rs/tokio/pull/4335#discussion_r773377901

Closes #4538

Signed-off-by: Eliza Weisman <[email protected]>
2022-04-25 17:31:19 +00:00
Aleksey Kladov b4d82c3e70 docs: Signal::recv is cancel-safe (#4634) 2022-04-23 09:12:24 +00:00
cui fliter 1472af5bd4 docs: fix some typos (#4632)
Signed-off-by: cuishuang <[email protected]>
2022-04-21 10:16:19 +00:00
Piepmatz d397e77c90 net: add try_read_buf for named pipes (#4626) 2022-04-21 09:50:12 +02:00
Ryan Zoeller 711d9d0156 chore: upgrade nix to 0.24, limit features (#4631)
This reduces tokio's test compile time by a few seconds.
2022-04-21 04:31:15 +00:00
Carl Lerche 911a0efa87 rt: internally split Handle into two structs (#4629)
Previously, `runtime::Handle` was a single struct composed of the
internal handles for each runtime component. This patch splits the
`Handle` struct into a `HandleInner` which contains everything
**except** the task scheduler handle. Now, `HandleInner` is passed to
the task scheduler during creation and the task scheduler is responsible
for storing it. `Handle` only  needs to hold the scheduler handle and
can access the rest of the component handles by querying the task
scheduler.

The motivation for this change is it now enables the multi-threaded
scheduler to have direct access to the blocking spawner handle.
Previously, when spawning a new thread, the multi-threaded scheduler had
to access the blocking spawner by accessing a thread-local variable.
Now, in theory, the multi-threaded scheduler can use `HandleInner`
directly. However, this change hasn't been done in this PR yet.

Also, now the `Handle` struct is much smaller.

This change is intended to make it easier for the multi-threaded
scheduler to shutdown idle threads and respawn them on demand.
2022-04-20 12:56:55 -07:00
Kezhu Wang d590a369d5 macros: custom crate name for #[tokio::main] and #[tokio::test] (#4613)
This also enables `#[crate::test(crate = "crate")]` in unit tests.

See: rust-lang/cargo#5653
Fixes: #2312
2022-04-18 11:24:27 +02:00
Name1e5s 2fe49a68a4 sync: add panic docs for tokio::sync::broadcast::channel (#4622) 2022-04-17 13:34:51 +02:00
Alan Somers c43832a7b1 ci: update FreeBSD CI image to 12.3 (#4620) 2022-04-15 11:50:40 +02:00
Dom 221bb94b9c blocking: #[track_caller] for spawn_blocking (#4616)
Annotates spawn_blocking() with #[track_caller] in order to produce the
correct tracing span spawn locations that show the caller as the spawn
point.
2022-04-13 15:27:58 +00:00
Bruno 252b0fa9d5 net: add peer_addr to UdpSocket (#4611)
The std UdpSocket has the peer_addr method which can be used to get the address of the remote peer the socket is connected to.

Fixes: #4609
2022-04-10 01:52:45 +00:00
Jedidiah Buck McCready 83477c725a time: clarify platform specific resolution in sleep function docs (#4474) 2022-04-06 15:29:44 +02:00
Stepan Koltsov 3652f71ade sync: add Clone to RecvError types (#4560) 2022-04-06 15:26:45 +02:00
ObsidianMinor b98a7e4d07 runtime: update Handle::current to mention EnterGuard (#4567)
Handle::current docs say it's not possible to call it on any non-runtime thread, but you can call it from a runtime context created by an EnterGuard. This updates the docs to mention EnterGuard as a way to avoid this panic.
2022-04-06 15:23:49 +02:00
Adam Cigánek 7d3b9d73ff stream: expose Timout (#4601) 2022-04-06 15:17:58 +02:00
Paolo Barbolini f8a6cf49cd tracing: don't require default tracing features (#4592) 2022-04-03 11:30:07 +02:00
Arash Sal Moslehian 702d6dccc9 tests: complete TODOs in uds_stream (#4587) 2022-03-28 19:58:06 +00:00
Dirkjan Ochtman a05135a4f8 chore: prepare tokio-util 0.7.1 release (#4521) 2022-03-28 14:34:11 +02:00
masa.koz 2bb97db5e1 net: make try_io methods call mio's try_io internally (#4582) 2022-03-28 09:06:47 +00:00
Matthew Ahrens a8b75dbdf4 sync: add watch::Receiver::same_channel (#4581) 2022-03-25 19:16:47 +00:00
b-naber f84c4d596a io: add StreamReader::into_inner_with_chunk (#4559) 2022-03-23 20:41:09 +01:00
Taiki Endo 121769c762 ci: run reusable_box tests with Miri (#4578) 2022-03-21 23:57:47 +09:00
Jedidiah Buck McCready 0abe825b72 time: clarify platform specific timer resolution (#4474) 2022-03-16 15:49:16 +00:00
Eliza Weisman 61e37c6c8d ci: run doctests for unstable APIs (#4562)
It turns out that the CI job for testing `tokio_unstable` features isn't
actually running doctests for `tokio_unstable`, just lib and integration
tests. This is because RustDoc is responsible for running doctests, and
it needs the unstable cfg passed to it separately from `RUSTFLAGS`.

This means that if the examples for unstable APIs are broken, CI won't
catch this, which is not great!

This commit changes the `test-unstable` CI job to pass `--cfg
tokio_unstable` in `RUSTDOCFLAGS` as well as `RUSTFLAGS`. This way,
doctests for unstable APIs should actually run.

I also fixed a typo in one of the runtime metrics doctests that was
causing a compilation error, which was caught as a result of actually
testing the unstable API docs on CI. :)

Signed-off-by: Eliza Weisman <[email protected]>
2022-03-11 20:13:51 +00:00
Eliza Weisman dee26c92dd chore: fix a bunch of annoying clippy lints (#4558)
## Motivation

Recent Clippy releases have added some new lints that trigger on some
code in Tokio. These aren't a big deal, but seeing them in my editor is
mildly annoying.

## Solution

This branch fixes the following issues flagged by Clippy:

* manual `Option::map` implementation
* use of `.map(...).flatten(...)` that could be replaced with
  `.and_then(...)`
* manual implementation of saturating arithmetic on `Duration`s
* simplify some boolean expressions in assertions (`!res.is_ok()` can be
`res.is_err()`)
* fix redundant field names in initializers
* replace an unnecessary cast to `usize` with an explicitly typed
  integer literal

Signed-off-by: Eliza Weisman <[email protected]>
2022-03-08 12:24:19 -08:00
b-naber 2f944dfa1b sync: add broadcast::Receiver::len (#4542) 2022-03-07 13:47:26 +01:00
weisbrja e8ae65a697 tokio: add support for signals up to SIGRTMAX (#4555)
The POSIX standard not only supports "reliable signals", but also
"real-time signals". This commit adds support for the latter.
2022-03-05 06:33:32 +00:00
Rafael Camargo LeiteandRafael 5b947ca2c7 runtime: include more documentation for thread_pool/worker (#4511)
Co-authored-by: Rafael <[email protected]>
2022-03-02 13:55:15 +01:00
Alisue 014be71cca stream: expose Elapsed error (#4502) 2022-03-02 13:53:21 +01:00
William ba49294bae macros: rename tokio::select!'s internal util module (#4543)
This internal module's identifier can clash with existing identifiers in library users' code and produce confusing error messages.
2022-03-02 13:37:37 +01:00
Alex Saveau fb9a01b362 runtime: use Vec::with_capacity when building runtime (#4553) 2022-03-02 12:41:43 +01:00
Quinn 6f9a586214 sync: unbounded receiver close docs (#4548) 2022-02-27 22:12:29 +01:00
Andy Barron 3dd5a0d3bb signal: add SignalKind Hash/Eq impls and c_int conversion (#4540) 2022-02-26 13:23:28 +01:00
Gus Wynn 413c812ac8 util: switch tokio-util from log to tracing (#4539) 2022-02-26 12:47:04 +01:00
Eliza Weisman ac69d37302 task: fix broken link in AbortHandle RustDoc (#4545)
## Motivation

There's a broken docs link in the docs for `AbortHandle`. Somehow this
managed to slip past CI yesterday when #4530 was merged
(https://github.com/tokio-rs/tokio/runs/5325278596?check_suite_focus=true)
but it's breaking the build now
(https://github.com/tokio-rs/tokio/runs/5337182732?check_suite_focus=true)
which seems really weird to me, but...whatever...

## Solution

This branch fixes the broken link lol.

Signed-off-by: Eliza Weisman <[email protected]>
2022-02-25 19:07:59 +00:00
Cody Casterline 4485921ba7 Improve docs for tokio_unstable. (#4524) 2022-02-25 12:36:37 -05:00
Antonin Amand 70c10bae60 runtime: recover when OS fails to spawn a new thread (#4485) 2022-02-25 12:28:56 +01:00
Eliza Weisman 8e0e56fdf2 task: add AbortHandle type for cancelling tasks in a JoinSet (#4530)
## Motivation

Before we stabilize the `JoinSet` API, we intend to add a method for
individual tasks in the `JoinSet` to be aborted. Because the
`JoinHandle`s for the tasks spawned on a `JoinSet` are owned by the
`JoinSet`, the user can no longer use them to abort tasks on the
`JoinSet`. Therefore, we need another way to cause a remote abort of a
task on a `JoinSet` without holding its `JoinHandle`.

## Solution

This branch adds a new `AbortHandle` type in `tokio::task`, which
represents the owned permission to remotely cancel a task, but _not_ to
await its output. The `AbortHandle` type holds an additional reference
to the task cell.

A crate-private method is added to `JoinHandle` that returns an
`AbortHandle` for the same task, incrementing its ref count.
`AbortHandle` provides a single method, `AbortHandle::abort(self)`, that
remotely cancels the task. Dropping an `AbortHandle` decrements the
task's ref count but does not cancel it. The `AbortHandle` type is
currently marked as unstable.

The spawning methods on `JoinSet` are modified to return an
`AbortHandle` that can be used to cancel the spawned task.

## Future Work

- Currently, the `AbortHandle` type is _only_ available in the public
API through a `JoinSet`. We could also make the
`JoinHandle::abort_handle` method public, to allow users to use the
`AbortHandle` type in other contexts. I didn't do that in this PR,
because I wanted to make the API addition as minimal as possible, but we
could make this method public later.

- Currently, `AbortHandle` is not `Clone`. We could easily make it
`Clone` by incrementing the task's ref count. Since this adds more trait
impls to the API, we may want to be cautious about this, but I see no
obvious reason we would need to remove a `Clone` implementation if one
was added...

- There's been some discussion of adding a `JoinMap` type that allows
aborting tasks by key, and manages a hash map of keys to `AbortHandle`s,
and removes the tasks from the map when they complete. This would make
aborting by key much easier, since the user wouldn't have to worry about
keeping the state of the map of abort handles and the tasks actually
active on the `JoinSet` in sync. After thinking about it a bit, I
thought this is probably best as a `tokio-util` API --- it can currently
be implemented in `tokio-util` with the APIs added in `tokio` in this
PR.

- I noticed while working on this that `JoinSet::join_one` and
`JoinSet::poll_join_one` return a cancelled `JoinError` when a task is
cancelled. I'm not sure if I love this behavior --- it seems like it
would be nicer to just skip cancelled tasks and continue polling. But,
there are currently tests that expect a cancelled `JoinError` to be
returned for each cancelled task, so I didn't want to change it in
_this_ PR. I think this is worth revisiting before stabilizing the API,
though?

Signed-off-by: Eliza Weisman <[email protected]>
2022-02-24 13:08:23 -08:00
Eliza Weisman dfac73d580 macros: update trybuild output for Rust 1.59.0 (#4536)
## Motivation

Rust error messages seem to have changed a bit in 1.59.0

## Solution

Update the `trybuild` stderr output.

This should unbreak the tests on the latest Rust (and fix CI).
2022-02-24 11:52:47 -08:00
Lucio Franco 769fb1547f tokio: Add initial io driver metrics (#4507) 2022-02-24 13:39:37 -05:00
Eliza Weisman 0b97567b49 task: fix missing doc(cfg(...)) attributes for JoinSet (#4531)
## Motivation

The `JoinSet` type is currently missing the `tokio_unstable` and
`feature = "rt"` `doc(cfg(...))` attributes, making it erroneously
appear to be available without the required feature and without unstable
features enabled. This is incorrect.

I believe this is because `doc(cfg(...))` on a re-export doesn't
actually add the required cfgs to the type itself, and the
`cfg_unstable!` is currently only guarding a re-export and module.

## Solution

This PR fixes the missing attributes.
2022-02-23 13:03:41 -08:00
Nikolai Vazquez 3f508d1622 codec: add length_field_type to LengthDelimitedCodec builder (#4508) 2022-02-23 11:46:52 +01:00
Takuya Kajiwara 503ae34cd3 util: fix import path of CancellationToken in example code (#4520) 2022-02-23 11:46:35 +01:00
Alice Ryhl ff8befbc54 ci: fix test not working on wasm (#4527) 2022-02-23 11:46:17 +01:00
Nylonicious 067ddff063 sync: add watch::Sender::send_modify method (#4310) 2022-02-22 21:19:21 +01:00
DevSabbandDevSabb e8f19e771f macros: fix select macro to process 64 branches (#4519)
Co-authored-by: DevSabb <devsabb@local>
2022-02-21 08:41:52 +01:00
Eliza Weisman 43c224ff47 chore: prepare Tokio v1.17.0 release (#4504)
# 1.17.0 (February 16, 2022)

This release updates the minimum supported Rust version (MSRV) to 1.49,
the `mio` dependency to v0.8, and the (optional) `parking_lot`
dependency to v0.12. Additionally, it contains several bug fixes, as
well as internal refactoring and performance improvements.

### Fixed

- time: prevent panicking in `sleep` with large durations ([#4495])
- time: eliminate potential panics in `Instant` arithmetic on platforms
  where `Instant::now` is not monotonic ([#4461])
- io: fix `DuplexStream` not participating in cooperative yielding
  ([#4478])
- rt: fix potential double panic when dropping a `JoinHandle` ([#4430])

### Changed

- update minimum supported Rust version to 1.49 ([#4457])
- update `parking_lot` dependency to v0.12.0 ([#4459])
- update `mio` dependency to v0.8 ([#4449])
- rt: remove an unnecessary lock in the blocking pool ([#4436])
- rt: remove an unnecessary enum in the basic scheduler ([#4462])
- time: use bit manipulation instead of modulo to improve performance
  ([#4480])
- net: use `std::future::Ready` instead of our own `Ready` future
  ([#4271])
- replace deprecated `atomic::spin_loop_hint` with `hint::spin_loop`
  ([#4491])
- fix miri failures in intrusive linked lists ([#4397])

### Documented

- io: add an example for `tokio::process::ChildStdin` ([#4479])

### Unstable

The following changes only apply when building with `--cfg
tokio_unstable`:

- task: fix missing location information in `tracing` spans generated by
  `spawn_local` ([#4483])
- task: add `JoinSet` for managing sets of tasks ([#4335])
- metrics: fix compilation error on MIPS ([#4475])
- metrics: fix compilation error on arm32v7 ([#4453])

[#4495]: https://github.com/tokio-rs/tokio/pull/4495
[#4461]: https://github.com/tokio-rs/tokio/pull/4461
[#4478]: https://github.com/tokio-rs/tokio/pull/4478
[#4430]: https://github.com/tokio-rs/tokio/pull/4430
[#4457]: https://github.com/tokio-rs/tokio/pull/4457
[#4459]: https://github.com/tokio-rs/tokio/pull/4459
[#4449]: https://github.com/tokio-rs/tokio/pull/4449
[#4462]: https://github.com/tokio-rs/tokio/pull/4462
[#4436]: https://github.com/tokio-rs/tokio/pull/4436
[#4480]: https://github.com/tokio-rs/tokio/pull/4480
[#4271]: https://github.com/tokio-rs/tokio/pull/4271
[#4491]: https://github.com/tokio-rs/tokio/pull/4491
[#4397]: https://github.com/tokio-rs/tokio/pull/4397
[#4479]: https://github.com/tokio-rs/tokio/pull/4479
[#4483]: https://github.com/tokio-rs/tokio/pull/4483
[#4335]: https://github.com/tokio-rs/tokio/pull/4335
[#4475]: https://github.com/tokio-rs/tokio/pull/4475
[#4453]: https://github.com/tokio-rs/tokio/pull/4453
2022-02-16 10:50:22 -08:00
Eliza Weisman 8758965206 task: fix unstable API documentation notes (#4503)
## Motivation

PR #4499 made the `JoinSet` API unstable, but did not add a
documentation note explaining unstable features. In general, since the
docs.rs build includes unstable APIs, it's probably worth including
these notes so that users understand what it means for an API to be
unstable.

## Solution

This branch adds a note on unstable APIs to the `JoinSet` type-level
documentation, similar to the notes for `task::Builder` and the runtime
metrics APIs.

Also, I noticed that there was a broken link to the top-level
documentation on unstable APIs in the docs for `task::Builder`, so I
fixed that as well.
2022-02-15 09:57:06 -08:00
Samuel Tardieu 28b983c4bc time: use bit manipulation instead of modulo (#4480)
time: resolve TODO

## Motivation

Existing `TODO` comment in `src/time/driver/wheel/level.rs`.

## Solution

`level_range()` always return a strictly positive power of 2. If `b` is a
strictly positive power of 2, `a - (a % b)` is equal to `a & !(b - 1)`.
2022-02-15 09:26:07 -08:00
Jonathan Johnson 0826f763e0 time: prevent panicking in sleep() with large durations (#4495) 2022-02-15 10:49:41 +01:00
Carl Lerche 37917b821d rt: make JoinSet unstable (#4499) 2022-02-15 10:37:40 +01:00
b-naber 9a3ce91ef5 util: fix waker update condition in CancellationToken (#4497)
There was a missing exclamation mark in the condition we used to test
whether we need a waker update in `check_for_cancellation`.
2022-02-14 13:18:10 -08:00
Thomas de Zeeuw 8fb15da8f8 Update to Mio v0.8
The major breaking change in Mio v0.8 is TcpSocket type being removed.

Replacing Mio's TcpSocket we switch to the socket2 library which
provides a similar type Socket, as well as SockRef, which provide all
options TcpSocket provided (and more!).

Tokio's TcpSocket type is now backed by Socket2 instead of Mio's
TcpSocket. The main pitfall here is that socket2 isn't non-blocking by
default, which Mio obviously is. As a result we have to do potentially
blocking calls more carefully, specifically we need to handle
would-block-like errors when connecting the TcpSocket ourselves.

One benefit for this change is that adding more socket options to
TcpSocket is now merely a single function call away (in most cases
anyway).
2022-02-13 16:56:18 +01:00
Taiki Endo ac0f894dd9 net: use std::future::ready instead of own Ready future (#4271) 2022-02-13 04:54:45 +09:00
Name1e5s 02141db1e1 replace spin_loop_hint with hint::spin_loop (#4491) 2022-02-12 11:47:04 -08:00
Taiki Endo 62274b0710 chore: update minimal mio requirement to 0.7.11 (#4492) 2022-02-12 10:02:46 +01:00
Dirkjan Ochtman 69f135ed60 util: bump tokio dependency to 1.6 to satisfy minimal versions (#4490) 2022-02-12 10:02:07 +01:00
coral ed187ddfb8 doc: Created a simple example for tokio::process::ChildStdin (#4479) 2022-02-11 19:38:12 -08:00
Toby Lawrence e7a0da60cd chore: prepare tokio-util 0.7.0 (#4486) 2022-02-10 12:23:58 -05:00
KestrerandToby Lawrence 9c688ecdc3 util: add lifetime parameter to ReusableBoxFuture (#3762)
Co-authored-by: Toby Lawrence <[email protected]>
2022-02-09 14:29:21 -05:00
Toby Lawrence 52fb93dce9 sync: refactored PollSender<T> to fix a subtly broken Sink<T> implementation (#4214)
Signed-off-by: Toby Lawrence <[email protected]>
2022-02-09 12:09:04 -05:00
Taiki Endo 1be8e9dfb7 miri: make miri accept our intrusive linked lists (#4397) 2022-02-09 11:11:17 +01:00
Eliza Weisman ca51f6a980 task: fix missing #[track_caller] in spawn_local (#4483)
PR #3881 factored out the spawning of local tasks on a `LocalSet` into a
function `spawn_local_inner`, so that the implementation could be shared
with the `task::Builder` API. But, that PR neglected to add a
`#[track_caller]` attribute to `spawn_local_inner`, so the `tracing`
spans for local tasks are all generated with `spawn_local_inner` as
their spawn location, rather than forwarding the actual spawn location
from the calling function.

This causes pretty useless results when using `tokio-console` with code
that spawns a number of local tasks, such as Actix
(https://reddit.com/r/rust/comments/snt5fq/can_tokioconsole_profile_actixrt/)

This commit fixes the issue by adding the missing `#[track_caller]`
attribute.
2022-02-09 10:12:06 +01:00
GongLG fd4d2b0a99 io: make duplex stream cooperative (#4470) (#4478) 2022-02-09 09:59:01 +01:00
Benjamin Saunders cf38ba627a util: remove error case from the infallible DelayQueue::poll_elapsed (#4241) 2022-02-08 21:07:33 -05:00
Sunyeop Lee 0b05ef638d codec: implement Encoder<BytesMut> for BytesCodec (#4465) 2022-02-08 09:11:24 -05:00
Alice Ryhl d6143c9566 io: improve safety comment on FillBuf (#4476) 2022-02-07 10:07:58 +01:00
Name1e5s 5690f0c32e metrics: fix build on mips (#4475) 2022-02-07 10:06:03 +01:00
Oliver Gould fc4deaa1d0 time: eliminate panics from Instant arithmetic (#4461)
`Instant::duration_since`, `Instant::elapsed`, and `Instant::sub` may
panic. This is especially dangerous when `Instant::now` travels back in
time. While this isn't supposed to happen, this behavior is highly
platform-dependent (e.g., rust-lang/rust#86470).

This change modifies the behavior of `tokio::time::Instant` to prevent
this class of panic, as proposed for `std::time::Instant` in
rust-lang/rust#89926.
2022-02-06 16:20:03 +01:00
Carl Lerche bc474f1d81 rt: remove unnecessary enum in basic_scheduler (#4462)
The enum is no longer needed. It was used previously to support multiple
kinds of control messages to the scheduler but that has been refactored
out.
2022-02-03 09:14:22 -08:00
Alice Ryhl 59579465be io: add test for take bug (#4443) 2022-02-02 16:39:16 +01:00
Alice Ryhl 1bb4d23162 task: add JoinSet for managing sets of tasks(#4335)
Adds `JoinSet` for managing multiple spawned tasks and joining them
in completion order.

Closes: #3903
2022-02-01 14:17:09 -08:00
Oliver Gould f602410227 chore: update parking_lot to v0.12.0 (#4459) 2022-01-31 14:55:40 -08:00
Carl Lerche 49fff47111 chore: increase MSRV to 1.49. (#4457)
Rust 1.49 was released on December 31, 2020, which meets our MSRV policy
of a minimum of 6 months.
2022-01-31 13:26:12 -08:00
Riley 77468ae3b0 metrics: add fetch_add for AtomicU64 (#4453) 2022-01-31 10:06:02 +01:00
Carl Lerche 2cee1db20c chore: make it easier to pin Rust versions in CI (#4448)
When backporting patches to LTS branches, we often run into CI failures due to
changes in rust. Newer rust versions add more lints, which break CI. We really
don't want to also have to backport patches that fix CI, so instead, LTS branches
should pin the stable rust version in CI (e.g. #4434).

This PR restructures the CI config files to make it a bit easier to set a specific rust
version in CI.
2022-01-30 10:07:31 -08:00
wspsxing db18e0d39d rt: reduce an unnecessary lock operation (#4436) 2022-01-28 14:01:37 -08:00
Gabriel Grubba b09899832c stream: fix disabled tests (#4441) 2022-01-28 17:30:07 +01:00
Braulio Valdivielso Martínez 111dd66f3e runtime: swallow panics in drop(JoinHandle) (#4430) 2022-01-28 17:21:03 +01:00
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 afb734d189 chore: prepare Tokio v1.8.3 (#3983) 2021-07-26 17:35:40 +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 1eb468be08 task: fix leak in LocalSet (#3978) 2021-07-22 15:26:31 +02:00
Alice Ryhl 378409d15d runtime: add large test and fix leak it found (#3967) 2021-07-22 15:26:31 +02:00
Alice Ryhl 51ff95c144 chore: use the loom mutex wrapper everywhere (#3958) 2021-07-22 15:26:23 +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
Carl Lerche 7147042534 chore: prepare Tokio v1.8.2 release 2021-07-19 11:16:26 -07:00
Diggory Blake 634fcac2d9 runtime: drop future when polling cancelled future (#3965) 2021-07-19 11:16:26 -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
Carl Lerche e0c527f383 chore: prepare Tokio v1.8.1 release 2021-07-06 15:37:17 -07:00
Carl Lerche 1160e8864c Merge branch 'tokio-1.7.x' into merge-1.7.2 2021-07-06 15:36:24 -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
Alice Ryhl 677107d8d9 chore: prepare Tokio v1.8.0 (#3912) 2021-07-02 22:21:10 +02:00
Alice Ryhl c8ecfc894d sync: fix watch borrow_and_update (#3913) 2021-07-02 10:14:34 -07:00
Taiki Endo 08ed41f339 chore: fix typos (#3907) 2021-07-01 02:06:56 +09:00
Alice Ryhl 90e1935c48 test: test with tracing (#3906) 2021-06-30 09:40:10 -07:00
Alice Ryhl b877629cb1 net: handle HUP event with UnixStream (#3898)
Fixes: #3879
2021-06-30 09:39:06 -07:00
Mikhail Zabaluev 0531549b6e io: efficient implementation of vectored writes for BufWriter (#3163) 2021-06-29 20:26:47 +02:00
Alice Ryhl 38204f5fba time: fix Timeout size_hint (#3902) 2021-06-29 20:06:10 +02:00
Jacob Rothstein 8fa29cb00a rt: add tokio::task::Builder (#3881)
Adds a builder API for spawning tasks. Initially, this enables the caller to name the spawned
task in order to provide better visibility into all tasks in the system.
2021-06-29 10:47:30 -07:00
Alice Ryhl b521cc2689 doc: document cancellation safety (#3900)
This patch documents cancellation safety. It also moves the "Avoid racy if preconditions"
section in the select! documentation since otherwise the first code block on the page
shows how not to use it, which seems counterintuitive.
2021-06-29 10:35:42 -07:00
Jake Shadle 57c90c9750 net: add read/try_read etc methods to NamedPipeServer (#3899) 2021-06-29 10:05:20 +02:00
Mikail Bagishov d35ff7064f sync: add drop guard for cancellation token (#3839) 2021-06-28 12:34:58 +02:00
Milo ab0791b817 time: add wait alias to sleep (#3897) 2021-06-28 10:48:07 +02:00
Jake Shadle 959c5c997f net: add ready/try methods to NamedPipeClient (#3866) 2021-06-28 10:43:57 +02:00
sb64 845626410a sync: implement From<T> for OnceCell<T> (#3877) 2021-06-22 15:11:01 +02:00
Carl Lerche 3207479fa7 Merge branch 'tokio-1.7.x' into merge-1.7.1 2021-06-18 16:37:21 -07:00
Alice Ryhl d1aa2df80e sync: add Receiver::borrow_and_update (#3813) 2021-06-16 18:42:05 +02:00
sb64 e979ad7f2d time: allow users to specify Interval behavior when delayed (#3721) 2021-06-16 13:51:27 +02:00
sb64 5ad3dd3378 time: document auto-advancing behavior of runtime (#3763) 2021-06-16 13:51:12 +02:00
teor 60bd40d529 time: fix typo in Instant::saturating_duration_since docs (#3864) 2021-06-16 12:50:30 +02:00
brain0 4a93af4d25 io: add get_{ref,mut} methods to AsyncFdReadyGuard and AsyncFdReadyMutGuard. (#3807) 2021-06-16 12:45:05 +02:00
402 changed files with 27930 additions and 7230 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",
]
+2
View File
@@ -0,0 +1,2 @@
# [build]
# rustflags = ["--cfg", "tokio_unstable"]
+25
View File
@@ -0,0 +1,25 @@
version: 2.1
jobs:
test-arm:
machine:
image: ubuntu-2004:202101-01
resource_class: arm.medium
environment:
# Change to pin rust versino
RUST_STABLE: stable
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 --default-toolchain $RUST_STABLE
source "$HOME"/.cargo/env
# Only run Tokio tests
- run: cargo test --all-features -p tokio
workflows:
ci:
jobs:
- test-arm
+39 -12
View File
@@ -1,19 +1,51 @@
freebsd_instance:
image: freebsd-12-2-release-amd64
image: freebsd-12-3-release-amd64
env:
RUST_STABLE: stable
RUST_NIGHTLY: nightly-2022-03-21
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
env:
LOOM_MAX_PREEMPTIONS: 2
RUSTFLAGS: -Dwarnings
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
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
- . $HOME/.cargo/env
- |
echo "~~~~ rustc --version ~~~~"
rustc --version
test_script:
- . $HOME/.cargo/env
- cargo test --all --all-features
task:
name: FreeBSD docs
env:
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings
setup_script:
- pkg install -y bash curl
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_NIGHTLY
- . $HOME/.cargo/env
- |
echo "~~~~ rustc --version ~~~~"
rustc --version
test_script:
- . $HOME/.cargo/env
- 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
- sh rustup.sh -y --profile minimal --default-toolchain $RUST_STABLE
- . $HOME/.cargo/env
- rustup target add i686-unknown-freebsd
- |
@@ -21,9 +53,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.49"
+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/**/*
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Audit Check
uses: actions-rs/audit-check@v1
-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
+243 -92
View File
@@ -9,8 +9,23 @@ name: CI
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
nightly: nightly-2021-04-25
minrust: 1.45.2
# Change to specific Rust release to pin
rust_stable: stable
rust_nightly: nightly-2022-04-18
rust_clippy: 1.52.0
# When updating this, also update:
# - README.md
# - tokio/README.md
# - CONTRIBUTING.md
# - tokio/Cargo.toml
# - tokio-util/Cargo.toml
# - tokio-test/Cargo.toml
# - tokio-stream/Cargo.toml
rust_min: 1.49.0
defaults:
run:
shell: bash
jobs:
# Depends on all action sthat are required for a "successful" CI run.
@@ -20,6 +35,7 @@ jobs:
needs:
- test
- test-unstable
- test-parking_lot
- miri
- cross
- features
@@ -27,8 +43,11 @@ jobs:
- fmt
- clippy
- docs
- loom
- valgrind
- loom-compile
- check-readme
- test-hyper
- wasm32-unknown-unknown
steps:
- run: exit 0
@@ -42,9 +61,15 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- name: Install Rust
run: rustup update stable
- uses: Swatinem/rust-cache@v1
- name: Install cargo-hack
run: cargo install cargo-hack
@@ -75,13 +100,41 @@ 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@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- 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
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Install Valgrind
run: |
@@ -95,7 +148,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 +157,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
@@ -116,51 +169,64 @@ jobs:
- ubuntu-latest
- macos-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- uses: Swatinem/rust-cache@v1
# Run `tokio` with "unstable" cfg flag.
- name: test tokio full --cfg unstable
run: cargo test --features full
run: cargo test --all-features
working-directory: tokio
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
# in order to run doctests for unstable features, we must also pass
# the unstable cfg to RustDoc
RUSTDOCFLAGS: --cfg tokio_unstable
miri:
name: miri
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_nightly }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.nightly }}
toolchain: ${{ env.rust_nightly }}
components: miri
override: true
- name: Install Miri
run: |
set -e
rustup component add miri
cargo miri setup
rm -rf tokio/tests
- uses: Swatinem/rust-cache@v1
- name: miri
run: cargo miri test --features rt,rt-multi-thread,sync task
working-directory: tokio
san:
name: san
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.nightly }}
override: true
- name: asan
run: cargo test --all-features --target x86_64-unknown-linux-gnu --lib -- --test-threads 1
# Many of tests in tokio/tests and doctests use #[tokio::test] or
# #[tokio::main] that calls epoll_create1 that Miri does not support.
run: cargo miri test --features full --lib --no-fail-fast
working-directory: tokio
env:
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-tag-raw-pointers
PROPTEST_CASES: 10
asan:
name: asan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install llvm
# Required to resolve symbols in sanitizer output
run: sudo apt-get install -y llvm
- name: Install Rust ${{ env.rust_nightly }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_nightly }}
override: true
- uses: Swatinem/rust-cache@v1
- name: asan
run: cargo test --workspace --all-features --target x86_64-unknown-linux-gnu --tests -- --test-threads 1
env:
RUSTFLAGS: -Z sanitizer=address
ASAN_OPTIONS: detect_leaks=0
# Ignore `trybuild` errors as they are irrelevant and flaky on nightly
TRYBUILD: overwrite
cross:
name: cross
@@ -173,34 +239,45 @@ jobs:
- powerpc64-unknown-linux-gnu
- mips-unknown-linux-gnu
- arm-linux-androideabi
- mipsel-unknown-linux-musl
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: ${{ env.rust_stable }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: check
args: --workspace --target ${{ matrix.target }}
args: --workspace --all-features --target ${{ matrix.target }}
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: check
args: --workspace --all-features --target ${{ matrix.target }}
env:
RUSTFLAGS: --cfg tokio_unstable -Dwarnings
features:
name: features
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_nightly }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.nightly }}
toolchain: ${{ env.rust_nightly }}
target: ${{ matrix.target }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Install cargo-hack
run: cargo install cargo-hack
- name: check --each-feature
run: cargo hack check --all --each-feature -Z avoid-dev-deps
# Try with unstable feature flags
- name: check --each-feature --unstable
run: cargo hack check --all --each-feature -Z avoid-dev-deps
@@ -211,12 +288,13 @@ jobs:
name: minrust
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_min }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.minrust }}
toolchain: ${{ env.rust_min }}
override: true
- uses: Swatinem/rust-cache@v1
- name: "test --workspace --all-features"
run: cargo check --workspace --all-features
@@ -224,11 +302,13 @@ jobs:
name: minimal-versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_nightly }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.nightly }}
toolchain: ${{ env.rust_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,23 +319,35 @@ 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
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- name: Install rustfmt
run: rustup component add rustfmt
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
components: 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
@@ -263,12 +355,14 @@ jobs:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update 1.52.1 && rustup default 1.52.1
- name: Install clippy
run: rustup component add clippy
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_clippy }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_clippy }}
override: true
components: clippy
- uses: Swatinem/rust-cache@v1
# Run clippy
- name: "clippy --all"
run: cargo clippy --all --tests --all-features
@@ -277,38 +371,95 @@ jobs:
name: docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_nightly }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.nightly }}
toolchain: ${{ env.rust_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:
RUSTDOCFLAGS: --cfg docsrs -Dwarnings
RUSTFLAGS: --cfg docsrs --cfg tokio_unstable
RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -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: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- 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@v3
- 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@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- 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@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- 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
+45
View File
@@ -0,0 +1,45 @@
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
# Change to specific Rust release to pin
rust_stable: stable
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@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- 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
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install cargo-audit
uses: actions-rs/cargo@v1
+13 -4
View File
@@ -5,6 +5,12 @@ on:
branches:
- master
env:
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
# Change to specific Rust release to pin
rust_stable: stable
jobs:
stess-test:
name: Stress Test
@@ -14,10 +20,13 @@ jobs:
stress-test:
- simple_echo_tcp
steps:
- uses: actions/checkout@v2
- name: Install Rust
run: rustup update stable
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_stable }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.rust_stable }}
override: true
- uses: Swatinem/rust-cache@v1
- name: Install Valgrind
run: |
sudo apt-get update -y
+2
View File
@@ -1,2 +1,4 @@
target
Cargo.lock
.cargo/config.toml
+33 -1
View File
@@ -131,6 +131,24 @@ cargo check --all-features
cargo test --all-features
```
Clippy must be run using the MSRV, so Tokio can avoid having to `#[allow]` new
lints whose fixes would be incompatible with the current MSRV:
<!--
When updating this, also update:
- .github/workflows/ci.yml
- README.md
- tokio/README.md
- tokio/Cargo.toml
- tokio-util/Cargo.toml
- tokio-test/Cargo.toml
- tokio-stream/Cargo.toml
-->
```
cargo +1.49.0 clippy --all-features
```
When building documentation normally, the markers that list the features
required for various parts of Tokio are missing. To build the documentation
correctly, use this command:
@@ -139,6 +157,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 +176,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 }
@@ -165,6 +191,12 @@ LOOM_MAX_PREEMPTIONS=1 RUSTFLAGS="--cfg loom" \
cargo test --lib --release --features full -- --test-threads=1 --nocapture
```
You can run miri tests with
```
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tag-raw-pointers" PROPTEST_CASES=10 \
cargo +nightly miri test --features full --lib
```
### Tests
If the change being proposed alters code (as opposed to only documentation for
+4
View File
@@ -0,0 +1,4 @@
[build.env]
passthrough = [
"RUSTFLAGS",
]
+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
+44 -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.7.0", features = ["full"] }
tokio = { version = "1.19.0", 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,48 @@ 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.
<!--
When updating this, also update:
- .github/workflows/ci.yml
- CONTRIBUTING.md
- README.md
- tokio/README.md
- tokio/Cargo.toml
- tokio-util/Cargo.toml
- tokio-test/Cargo.toml
- tokio-stream/Cargo.toml
-->
Tokio will keep a rolling MSRV (minimum supported rust version) policy of **at
least** 6 months. When increasing the MSRV, the new Rust version must have been
released at least six months ago. The current MSRV is 1.49.0.
## 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.14.x` - LTS release until June 2022.
* `1.18.x` - LTS release until January 2023
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.14.x` patch release, you
can use the following dependency specification:
```text
tokio = { version = "~1.14", 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();
+1 -1
View File
@@ -1,4 +1,4 @@
//! Benchmark implementation details of the theaded scheduler. These benches are
//! Benchmark implementation details of the threaded scheduler. These benches are
//! intended to be used as a form of regression testing and not as a general
//! purpose benchmark demonstrating real-world performance.
+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);
+9 -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"
@@ -84,6 +84,10 @@ path = "custom-executor-tokio-context.rs"
name = "named-pipe"
path = "named-pipe.rs"
[[example]]
name = "named-pipe-ready"
path = "named-pipe-ready.rs"
[[example]]
name = "named-pipe-multi-client"
path = "named-pipe-multi-client.rs"
+161
View File
@@ -0,0 +1,161 @@
use std::io;
#[cfg(windows)]
async fn windows_main() -> io::Result<()> {
use tokio::io::Interest;
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
const PIPE_NAME: &str = r"\\.\pipe\named-pipe-single-client";
let server = ServerOptions::new().create(PIPE_NAME)?;
let server = tokio::spawn(async move {
// Note: we wait for a client to connect.
server.connect().await?;
let buf = {
let mut read_buf = [0u8; 5];
let mut read_buf_cursor = 0;
loop {
server.readable().await?;
let buf = &mut read_buf[read_buf_cursor..];
match server.try_read(buf) {
Ok(n) => {
read_buf_cursor += n;
if read_buf_cursor == read_buf.len() {
break;
}
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(e);
}
}
}
read_buf
};
{
let write_buf = b"pong\n";
let mut write_buf_cursor = 0;
loop {
let buf = &write_buf[write_buf_cursor..];
if buf.is_empty() {
break;
}
server.writable().await?;
match server.try_write(buf) {
Ok(n) => {
write_buf_cursor += n;
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(e);
}
}
}
}
Ok::<_, io::Error>(buf)
});
let client = tokio::spawn(async move {
// There's no need to use a connect loop here, since we know that the
// server is already up - `open` was called before spawning any of the
// tasks.
let client = ClientOptions::new().open(PIPE_NAME)?;
let mut read_buf = [0u8; 5];
let mut read_buf_cursor = 0;
let write_buf = b"ping\n";
let mut write_buf_cursor = 0;
loop {
let mut interest = Interest::READABLE;
if write_buf_cursor < write_buf.len() {
interest |= Interest::WRITABLE;
}
let ready = client.ready(interest).await?;
if ready.is_readable() {
let buf = &mut read_buf[read_buf_cursor..];
match client.try_read(buf) {
Ok(n) => {
read_buf_cursor += n;
if read_buf_cursor == read_buf.len() {
break;
}
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(e);
}
}
}
if ready.is_writable() {
let buf = &write_buf[write_buf_cursor..];
if buf.is_empty() {
continue;
}
match client.try_write(buf) {
Ok(n) => {
write_buf_cursor += n;
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
return Err(e);
}
}
}
}
let buf = String::from_utf8_lossy(&read_buf).into_owned();
Ok::<_, io::Error>(buf)
});
let (server, client) = tokio::try_join!(server, client)?;
assert_eq!(server?, *b"ping\n");
assert_eq!(client?, "pong\n");
Ok(())
}
#[tokio::main]
async fn main() -> io::Result<()> {
#[cfg(windows)]
{
windows_main().await?;
}
#[cfg(not(windows))]
{
println!("Named pipes are only supported on Windows!");
}
Ok(())
}
+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();
}
}
+16
View File
@@ -0,0 +1,16 @@
[build]
command = """
rustup install nightly --profile minimal && cargo doc --no-deps --all-features
"""
publish = "target/doc"
[build.environment]
RUSTDOCFLAGS="""
--cfg docsrs \
--cfg tokio_unstable \
"""
RUSTFLAGS="--cfg tokio_unstable --cfg docsrs"
[[redirects]]
from = "/"
to = "/tokio"
@@ -1,7 +1,7 @@
error: The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled.
--> $DIR/macros_core_no_default.rs:3:1
--> tests/fail/macros_core_no_default.rs:3:1
|
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)
@@ -1,3 +1,5 @@
#![deny(duplicate_macro_attributes)]
use tests_build::tokio;
#[tokio::main]
@@ -33,6 +35,15 @@ async fn test_worker_threads_not_int() {}
#[tokio::test(flavor = "current_thread", worker_threads = 4)]
async fn test_worker_threads_and_current_thread() {}
#[tokio::test(crate = 456)]
async fn test_crate_not_ident_int() {}
#[tokio::test(crate = "456")]
async fn test_crate_not_ident_invalid() {}
#[tokio::test(crate = "abc::edf")]
async fn test_crate_not_ident_path() {}
#[tokio::test]
#[test]
async fn test_has_second_test_attr() {}
@@ -1,71 +1,101 @@
error: the `async` keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:4:1
--> $DIR/macros_invalid_input.rs:6:1
|
4 | fn main_is_not_async() {}
6 | fn main_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
--> $DIR/macros_invalid_input.rs:6:15
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:8:15
|
6 | #[tokio::main(foo)]
8 | #[tokio::main(foo)]
| ^^^
error: Must have specified ident
--> $DIR/macros_invalid_input.rs:9:15
|
9 | #[tokio::main(threadpool::bar)]
| ^^^^^^^^^^^^^^^
--> $DIR/macros_invalid_input.rs:11:15
|
11 | #[tokio::main(threadpool::bar)]
| ^^^^^^^^^^^^^^^
error: the `async` keyword is missing from the function declaration
--> $DIR/macros_invalid_input.rs:13:1
--> $DIR/macros_invalid_input.rs:15:1
|
13 | fn test_is_not_async() {}
15 | fn test_is_not_async() {}
| ^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
--> $DIR/macros_invalid_input.rs:15:15
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:17:15
|
15 | #[tokio::test(foo)]
17 | #[tokio::test(foo)]
| ^^^
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`
--> $DIR/macros_invalid_input.rs:18:15
error: Unknown attribute foo is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`
--> $DIR/macros_invalid_input.rs:20:15
|
18 | #[tokio::test(foo = 123)]
20 | #[tokio::test(foo = 123)]
| ^^^^^^^^^
error: Failed to parse value of `flavor` as string.
--> $DIR/macros_invalid_input.rs:21:24
--> $DIR/macros_invalid_input.rs:23:24
|
21 | #[tokio::test(flavor = 123)]
23 | #[tokio::test(flavor = 123)]
| ^^^
error: No such runtime flavor `foo`. The runtime flavors are `current_thread` and `multi_thread`.
--> $DIR/macros_invalid_input.rs:24:24
--> $DIR/macros_invalid_input.rs:26:24
|
24 | #[tokio::test(flavor = "foo")]
26 | #[tokio::test(flavor = "foo")]
| ^^^^^
error: The `start_paused` option requires the `current_thread` runtime flavor. Use `#[tokio::test(flavor = "current_thread")]`
--> $DIR/macros_invalid_input.rs:27:55
--> $DIR/macros_invalid_input.rs:29:55
|
27 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
29 | #[tokio::test(flavor = "multi_thread", start_paused = false)]
| ^^^^^
error: Failed to parse value of `worker_threads` as integer.
--> $DIR/macros_invalid_input.rs:30:57
--> $DIR/macros_invalid_input.rs:32:57
|
30 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
32 | #[tokio::test(flavor = "multi_thread", worker_threads = "foo")]
| ^^^^^
error: The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[tokio::test(flavor = "multi_thread")]`
--> $DIR/macros_invalid_input.rs:33:59
--> $DIR/macros_invalid_input.rs:35:59
|
33 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
35 | #[tokio::test(flavor = "current_thread", worker_threads = 4)]
| ^
error: second test attribute is supplied
--> $DIR/macros_invalid_input.rs:37:1
error: Failed to parse value of `crate` as ident.
--> $DIR/macros_invalid_input.rs:38:23
|
37 | #[test]
38 | #[tokio::test(crate = 456)]
| ^^^
error: Failed to parse value of `crate` as ident: "456"
--> $DIR/macros_invalid_input.rs:41:23
|
41 | #[tokio::test(crate = "456")]
| ^^^^^
error: Failed to parse value of `crate` as ident: "abc::edf"
--> $DIR/macros_invalid_input.rs:44:23
|
44 | #[tokio::test(crate = "abc::edf")]
| ^^^^^^^^^^
error: second test attribute is supplied
--> $DIR/macros_invalid_input.rs:48:1
|
48 | #[test]
| ^^^^^^^
error: duplicated attribute
--> $DIR/macros_invalid_input.rs:48:1
|
48 | #[test]
| ^^^^^^^
|
note: the lint level is defined here
--> $DIR/macros_invalid_input.rs:1:9
|
1 | #![deny(duplicate_macro_attributes)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
+11 -8
View File
@@ -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,12 +15,21 @@ 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(());
}
// https://github.com/tokio-rs/tokio/issues/4635
#[allow(redundant_semicolons)]
#[rustfmt::skip]
#[tokio::main]
async fn issue_4635() {
return 1;
;
}
fn main() {}
@@ -1,51 +1,47 @@
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:5:5
|
4 | async fn missing_semicolon_or_return_type() {
| - help: a return type might be missing here: `-> _`
5 | Ok(())
| ^^^^^^ expected `()`, found enum `Result`
|
= note: expected unit type `()`
found enum `Result<(), _>`
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
--> $DIR/macros_type_mismatch.rs:10:5
|
16 | return Ok(());
9 | async fn missing_return_type() {
| - help: a return type might be missing here: `-> _`
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
--> $DIR/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(());)
23 ~ Ok(());;
24 + Ok(())
|
29 | Err(Ok(());)
error[E0308]: mismatched types
--> $DIR/macros_type_mismatch.rs:32:5
|
30 | async fn issue_4635() {
| - help: try adding a return type: `-> i32`
31 | return 1;
32 | ;
| ^ expected `()`, found integer
+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(());
}
+1 -1
View File
@@ -20,7 +20,7 @@ required-features = ["rt-process-signal"]
# For mem check
rt-net = ["tokio/rt", "tokio/rt-multi-thread", "tokio/net"]
# For test-process-signal
rt-process-signal = ["rt", "tokio/process", "tokio/signal"]
rt-process-signal = ["rt-net", "tokio/process", "tokio/signal"]
full = [
"macros",
+59
View File
@@ -1,3 +1,62 @@
# 1.8.0 (June 4th, 2022)
- macros: always emit return statement ([#4636])
- macros: support setting a custom crate name for `#[tokio::main]` and `#[tokio::test]` ([#4613])
[#4613]: https://github.com/tokio-rs/tokio/pull/4613
[#4636]: https://github.com/tokio-rs/tokio/pull/4636
# 1.7.0 (December 15th, 2021)
- macros: address remaining `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.8.0"
edition = "2018"
rust-version = "1.49"
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
+137 -49
View File
@@ -1,6 +1,10 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro2::{Ident, 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 {
@@ -25,8 +29,17 @@ struct FinalConfig {
flavor: RuntimeFlavor,
worker_threads: Option<usize>,
start_paused: Option<bool>,
crate_name: Option<String>,
}
/// 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,
crate_name: None,
};
struct Configuration {
rt_multi_thread_available: bool,
default_flavor: RuntimeFlavor,
@@ -34,6 +47,7 @@ struct Configuration {
worker_threads: Option<(usize, Span)>,
start_paused: Option<(bool, Span)>,
is_test: bool,
crate_name: Option<String>,
}
impl Configuration {
@@ -48,6 +62,7 @@ impl Configuration {
worker_threads: None,
start_paused: None,
is_test,
crate_name: None,
}
}
@@ -93,6 +108,15 @@ impl Configuration {
Ok(())
}
fn set_crate_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> {
if self.crate_name.is_some() {
return Err(syn::Error::new(span, "`crate` set multiple times."));
}
let name_ident = parse_ident(name, span, "crate")?;
self.crate_name = Some(name_ident.to_string());
Ok(())
}
fn macro_name(&self) -> &'static str {
if self.is_test {
"tokio::test"
@@ -140,6 +164,7 @@ impl Configuration {
};
Ok(FinalConfig {
crate_name: self.crate_name.clone(),
flavor,
worker_threads,
start_paused,
@@ -174,6 +199,27 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E
}
}
fn parse_ident(lit: syn::Lit, span: Span, field: &str) -> Result<Ident, syn::Error> {
match lit {
syn::Lit::Str(s) => {
let err = syn::Error::new(
span,
format!(
"Failed to parse value of `{}` as ident: \"{}\"",
field,
s.value()
),
);
let path = s.parse::<syn::Path>().map_err(|_| err.clone())?;
path.get_ident().cloned().ok_or(err)
}
_ => Err(syn::Error::new(
span,
format!("Failed to parse value of `{}` as ident.", field),
)),
}
}
fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Error> {
match bool {
syn::Lit::Bool(b) => Ok(b.value),
@@ -184,13 +230,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 +247,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(),
@@ -229,9 +278,15 @@ fn parse_knobs(
let msg = "Attribute `core_threads` is renamed to `worker_threads`";
return Err(syn::Error::new_spanned(namevalue, msg));
}
"crate" => {
config.set_crate_name(
namevalue.lit.clone(),
syn::spanned::Spanned::span(&namevalue.lit),
)?;
}
name => {
let msg = format!(
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`",
"Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`",
name,
);
return Err(syn::Error::new_spanned(namevalue, msg));
@@ -239,12 +294,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!(
@@ -262,7 +316,7 @@ fn parse_knobs(
format!("The `{}` attribute requires an argument.", name)
}
name => {
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`", name)
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`", name)
}
};
return Err(syn::Error::new_spanned(path, msg));
@@ -276,7 +330,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) = {
@@ -296,12 +354,16 @@ fn parse_knobs(
(start, end)
};
let crate_name = config.crate_name.as_deref().unwrap_or("tokio");
let crate_ident = Ident::new(crate_name, last_stmt_start_span);
let mut rt = match config.flavor {
RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
tokio::runtime::Builder::new_current_thread()
#crate_ident::runtime::Builder::new_current_thread()
},
RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
tokio::runtime::Builder::new_multi_thread()
#crate_ident::runtime::Builder::new_multi_thread()
},
};
if let Some(v) = config.worker_threads {
@@ -323,14 +385,18 @@ fn parse_knobs(
let brace_token = input.block.brace_token;
input.block = syn::parse2(quote_spanned! {last_stmt_end_span=>
{
#rt
.enable_all()
.build()
.unwrap()
.block_on(async #body)
let body = async #body;
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
{
return #rt
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(body);
}
}
})
.unwrap();
.expect("Parsing failure");
input.block.brace_token = brace_token;
let result = quote! {
@@ -338,36 +404,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())
}
+67 -23
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))
@@ -169,12 +168,32 @@ use proc_macro::TokenStream;
///
/// Note that `start_paused` requires the `test-util` feature to be enabled.
///
/// ### NOTE:
/// ### Rename package
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
/// If you must rename the current version of Tokio because you're also using an
/// older version of Tokio, you _must_ make the current version of Tokio
/// available as `tokio` in the module where this macro is expanded.
/// ```rust
/// use tokio as tokio1;
///
/// #[tokio1::main(crate = "tokio1")]
/// async fn main() {
/// println!("Hello world");
/// }
/// ```
///
/// Equivalent code not using `#[tokio::main]`
///
/// ```rust
/// use tokio as tokio1;
///
/// fn main() {
/// tokio1::runtime::Builder::new_multi_thread()
/// .enable_all()
/// .build()
/// .unwrap()
/// .block_on(async {
/// println!("Hello world");
/// })
/// }
/// ```
#[proc_macro_attribute]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
@@ -214,12 +233,32 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
/// }
/// ```
///
/// ### NOTE:
/// ### Rename package
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
/// If you must rename the current version of Tokio because you're also using an
/// older version of Tokio, you _must_ make the current version of Tokio
/// available as `tokio` in the module where this macro is expanded.
/// ```rust
/// use tokio as tokio1;
///
/// #[tokio1::main(crate = "tokio1")]
/// async fn main() {
/// println!("Hello world");
/// }
/// ```
///
/// Equivalent code not using `#[tokio::main]`
///
/// ```rust
/// use tokio as tokio1;
///
/// fn main() {
/// tokio1::runtime::Builder::new_multi_thread()
/// .enable_all()
/// .build()
/// .unwrap()
/// .block_on(async {
/// println!("Hello world");
/// })
/// }
/// ```
#[proc_macro_attribute]
#[cfg(not(test))] // Work around for rust-lang/rust#62127
pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
@@ -261,12 +300,16 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream {
///
/// Note that `start_paused` requires the `test-util` feature to be enabled.
///
/// ### NOTE:
/// ### Rename package
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
/// If you must rename the current version of Tokio because you're also using an
/// older version of Tokio, you _must_ make the current version of Tokio
/// available as `tokio` in the module where this macro is expanded.
/// ```rust
/// use tokio as tokio1;
///
/// #[tokio1::test(crate = "tokio1")]
/// async fn my_test() {
/// println!("Hello world");
/// }
/// ```
#[proc_macro_attribute]
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
entry::test(args, item, true)
@@ -282,13 +325,6 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
/// assert!(true);
/// }
/// ```
///
/// ### NOTE:
///
/// If you rename the Tokio crate in your dependencies this macro will not work.
/// If you must rename the current version of Tokio because you're also using an
/// older version of Tokio, you _must_ make the current version of Tokio
/// available as `tokio` in the module where this macro is expanded.
#[proc_macro_attribute]
pub fn test_rt(args: TokenStream, item: TokenStream) -> TokenStream {
entry::test(args, item, false)
@@ -329,3 +365,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);
}
_ => {}
}
}
+40
View File
@@ -1,3 +1,43 @@
# 0.1.9 (June 4, 2022)
- deps: upgrade `tokio-util` dependency to `0.7.x` ([#3762])
- stream: add `StreamExt::map_while` ([#4351])
- stream: add `StreamExt::then` ([#4355])
- stream: add cancel-safety docs to `StreamExt::next` and `try_next` ([#4715])
- stream: expose `Elapsed` error ([#4502])
- stream: expose `Timeout` ([#4601])
- stream: implement `Extend` for `StreamMap` ([#4272])
- sync: add `Clone` to `RecvError` types ([#4560])
[#3762]: https://github.com/tokio-rs/tokio/pull/3762
[#4272]: https://github.com/tokio-rs/tokio/pull/4272
[#4351]: https://github.com/tokio-rs/tokio/pull/4351
[#4355]: https://github.com/tokio-rs/tokio/pull/4355
[#4502]: https://github.com/tokio-rs/tokio/pull/4502
[#4560]: https://github.com/tokio-rs/tokio/pull/4560
[#4601]: https://github.com/tokio-rs/tokio/pull/4601
[#4715]: https://github.com/tokio-rs/tokio/pull/4715
# 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.9"
edition = "2018"
rust-version = "1.49"
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
+3 -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))
@@ -78,6 +77,9 @@ pub mod wrappers;
mod stream_ext;
pub use stream_ext::{collect::FromStream, StreamExt};
cfg_time! {
pub use stream_ext::timeout::{Elapsed, Timeout};
}
mod empty;
pub use empty::{empty, Empty};
+111 -5
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,17 +43,20 @@ 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;
pub(crate) mod timeout;
use timeout::Timeout;
use tokio::time::Duration;
mod throttle;
@@ -106,6 +113,12 @@ pub trait StreamExt: Stream {
/// pinning it to the stack using the `pin_mut!` macro from the `pin_utils`
/// crate.
///
/// # Cancel safety
///
/// This method is cancel safe. The returned future only
/// holds onto a reference to the underlying stream,
/// so dropping it will never lose a value.
///
/// # Examples
///
/// ```
@@ -142,6 +155,12 @@ pub trait StreamExt: Stream {
/// an [`Option<Result<T, E>>`](Option), making for easy use
/// with the [`?`](std::ops::Try) operator.
///
/// # Cancel safety
///
/// This method is cancel safe. The returned future only
/// holds onto a reference to the underlying stream,
/// so dropping it will never lose a value.
///
/// # Examples
///
/// ```
@@ -197,6 +216,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.
///
@@ -515,7 +621,7 @@ pub trait StreamExt: Stream {
/// Skip elements from the underlying stream while the provided predicate
/// resolves to `true`.
///
/// This function, like [`Iterator::skip_while`], will ignore elemets from the
/// This function, like [`Iterator::skip_while`], will ignore elements from the
/// stream until the predicate `f` resolves to `false`. Once one element
/// returns false, the rest of the elements will be yielded.
///
+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)
}
}
+7
View File
@@ -8,6 +8,13 @@ use pin_project_lite::pin_project;
pin_project! {
/// Future for the [`next`](super::StreamExt::next) method.
///
/// # Cancel safety
///
/// This method is cancel safe. It only
/// holds onto a reference to the underlying stream,
/// so dropping it will never lose a value.
///
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Next<'a, St: ?Sized> {
+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)
}
}
+12 -1
View File
@@ -69,7 +69,18 @@ impl<S: Stream> Stream for Timeout<S> {
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.stream.size_hint()
let (lower, upper) = self.stream.size_hint();
// The timeout stream may insert an error before and after each message
// from the underlying stream, but no more than one error between each
// message. Hence the upper bound is computed as 2x+1.
// Using a helper function to enable use of question mark operator.
fn twice_plus_one(value: Option<usize>) -> Option<usize> {
value?.checked_mul(2)?.checked_add(1)
}
(lower, twice_plus_one(upper))
}
}
+6
View File
@@ -9,6 +9,12 @@ use pin_project_lite::pin_project;
pin_project! {
/// Future for the [`try_next`](super::StreamExt::try_next) method.
///
/// # Cancel safety
///
/// This method is cancel safe. It only
/// holds onto a reference to the underlying stream,
/// so dropping it will never lose a value.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct TryNext<'a, St: ?Sized> {
+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};
}
+8 -2
View File
@@ -14,11 +14,11 @@ use std::task::{Context, Poll};
/// [`Stream`]: trait@crate::Stream
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
pub struct BroadcastStream<T> {
inner: ReusableBoxFuture<(Result<T, RecvError>, Receiver<T>)>,
inner: ReusableBoxFuture<'static, (Result<T, RecvError>, Receiver<T>)>,
}
/// An error returned from the inner stream of a [`BroadcastStream`].
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub enum BroadcastStreamRecvError {
/// The receiver lagged too far behind. Attempting to receive again will
/// return the oldest message still retained by the channel.
@@ -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)
}
}
+11 -5
View File
@@ -11,7 +11,7 @@ use tokio::sync::watch::error::RecvError;
/// A wrapper around [`tokio::sync::watch::Receiver`] that implements [`Stream`].
///
/// This stream will always start by yielding the current value when the WatchStream is polled,
/// regardles of whether it was the initial value or sent afterwards.
/// regardless of whether it was the initial value or sent afterwards.
///
/// # Examples
///
@@ -49,7 +49,7 @@ use tokio::sync::watch::error::RecvError;
/// [`Stream`]: trait@crate::Stream
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
pub struct WatchStream<T> {
inner: ReusableBoxFuture<(Result<(), RecvError>, Receiver<T>)>,
inner: ReusableBoxFuture<'static, (Result<(), RecvError>, Receiver<T>)>,
}
async fn make_future<T: Clone + Send + Sync>(
@@ -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)
}
}
+2 -2
View File
@@ -1,10 +1,10 @@
#![cfg(feature = "full")]
#![cfg(all(feature = "time", feature = "sync", feature = "io-util"))]
use tokio::time::{self, sleep, Duration};
use tokio_stream::{self, StreamExt};
use tokio_test::*;
use futures::StreamExt as _;
use futures::stream;
async fn maybe_sleep(idx: i32) -> i32 {
if idx % 2 == 0 {
+1 -1
View File
@@ -1,5 +1,5 @@
#![warn(rust_2018_idioms)]
#![cfg(feature = "full")]
#![cfg(all(feature = "time", feature = "sync", feature = "io-util"))]
use tokio::time;
use tokio_stream::StreamExt;
+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.49"
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 -2
View File
@@ -4,13 +4,12 @@
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))
))]
//! Tokio and Futures based testing utilites
//! Tokio and Futures based testing utilities
pub mod io;
+1 -1
View File
@@ -180,7 +180,7 @@ impl ThreadWaker {
}
}
/// Clears any previously received wakes, avoiding potential spurrious
/// Clears any previously received wakes, avoiding potential spurious
/// wake notifications. This should only be called immediately before running the
/// task.
fn clear(&self) {
+115
View File
@@ -1,3 +1,118 @@
# 0.7.3 (June 4, 2022)
### Changed
- tracing: don't require default tracing features ([#4592])
- util: simplify implementation of `ReusableBoxFuture` ([#4675])
### Added (unstable)
- task: add `JoinMap` ([#4640], [#4697])
[#4592]: https://github.com/tokio-rs/tokio/pull/4592
[#4640]: https://github.com/tokio-rs/tokio/pull/4640
[#4675]: https://github.com/tokio-rs/tokio/pull/4675
[#4697]: https://github.com/tokio-rs/tokio/pull/4697
# 0.7.2 (May 14, 2022)
This release contains a rewrite of `CancellationToken` that fixes a memory leak. ([#4652])
[#4652]: https://github.com/tokio-rs/tokio/pull/4652
# 0.7.1 (February 21, 2022)
### Added
- codec: add `length_field_type` to `LengthDelimitedCodec` builder ([#4508])
- io: add `StreamReader::into_inner_with_chunk()` ([#4559])
### Changed
- switch from log to tracing ([#4539])
### Fixed
- sync: fix waker update condition in `CancellationToken` ([#4497])
- bumped tokio dependency to 1.6 to satisfy minimum requirements ([#4490])
[#4490]: https://github.com/tokio-rs/tokio/pull/4490
[#4497]: https://github.com/tokio-rs/tokio/pull/4497
[#4508]: https://github.com/tokio-rs/tokio/pull/4508
[#4539]: https://github.com/tokio-rs/tokio/pull/4539
[#4559]: https://github.com/tokio-rs/tokio/pull/4559
# 0.7.0 (February 9, 2022)
### Added
- task: add `spawn_pinned` ([#3370])
- time: add `shrink_to_fit` and `compact` methods to `DelayQueue` ([#4170])
- codec: improve `Builder::max_frame_length` docs ([#4352])
- codec: add mutable reference getters for codecs to pinned `Framed` ([#4372])
- net: add generic trait to combine `UnixListener` and `TcpListener` ([#4385])
- codec: implement `Framed::map_codec` ([#4427])
- codec: implement `Encoder<BytesMut>` for `BytesCodec` ([#4465])
### Changed
- sync: add lifetime parameter to `ReusableBoxFuture` ([#3762])
- sync: refactored `PollSender<T>` to fix a subtly broken `Sink<T>` implementation ([#4214])
- time: remove error case from the infallible `DelayQueue::poll_elapsed` ([#4241])
[#3370]: https://github.com/tokio-rs/tokio/pull/3370
[#4170]: https://github.com/tokio-rs/tokio/pull/4170
[#4352]: https://github.com/tokio-rs/tokio/pull/4352
[#4372]: https://github.com/tokio-rs/tokio/pull/4372
[#4385]: https://github.com/tokio-rs/tokio/pull/4385
[#4427]: https://github.com/tokio-rs/tokio/pull/4427
[#4465]: https://github.com/tokio-rs/tokio/pull/4465
[#3762]: https://github.com/tokio-rs/tokio/pull/3762
[#4214]: https://github.com/tokio-rs/tokio/pull/4214
[#4241]: https://github.com/tokio-rs/tokio/pull/4241
# 0.6.10 (May 14, 2021)
This is a backport for the memory leak in `CancellationToken` that was originally fixed in 0.7.2. ([#4652])
[#4652]: https://github.com/tokio-rs/tokio/pull/4652
# 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
+18 -13
View File
@@ -2,17 +2,15 @@
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.3"
edition = "2018"
rust-version = "1.49"
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.
"""
@@ -23,28 +21,31 @@ categories = ["asynchronous"]
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 = []
codec = ["tracing"]
time = ["tokio/time","slab"]
io = []
rt = ["tokio/rt"]
io-util = ["io", "tokio/rt", "tokio/io-util"]
rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
__docs_rs = ["futures-util"]
[dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["sync"] }
tokio = { version = "1.19.0", path = "../tokio", features = ["sync"] }
bytes = "1.0.0"
futures-core = "0.3.0"
futures-sink = "0.3.0"
futures-io = { version = "0.3.0", optional = true }
futures-util = { version = "0.3.0", optional = true }
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`
tracing = { version = "0.1.25", default-features = false, features = ["std"], optional = true }
[target.'cfg(tokio_unstable)'.dependencies]
hashbrown = { version = "0.12.0", optional = true }
[dev-dependencies]
tokio = { version = "1.0.0", path = "../tokio", features = ["full"] }
@@ -57,4 +58,8 @@ futures-test = "0.3.5"
[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", "docsrs", "--cfg", "tokio_unstable"]
+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)*) => {
$(
+1 -1
View File
@@ -234,7 +234,7 @@ impl Default for AnyDelimiterCodec {
}
}
/// An error occured while encoding or decoding a chunk.
/// An error occurred while encoding or decoding a chunk.
#[derive(Debug)]
pub enum AnyDelimiterCodecError {
/// The maximum chunk length was exceeded.
+10
View File
@@ -74,3 +74,13 @@ impl Encoder<Bytes> for BytesCodec {
Ok(())
}
}
impl Encoder<BytesMut> for BytesCodec {
type Error = io::Error;
fn encode(&mut self, data: BytesMut, buf: &mut BytesMut) -> Result<(), io::Error> {
buf.reserve(data.len());
buf.put(data);
Ok(())
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ use std::io;
/// It is up to the Decoder to keep track of a restart after an EOF,
/// and to decide how to handle such an event by, for example,
/// allowing frames to cross EOF boundaries, re-emitting opening frames, or
/// reseting the entire internal state.
/// resetting the entire internal state.
///
/// [`Framed`]: crate::codec::Framed
/// [`FramedRead`]: crate::codec::FramedRead
+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
+61 -31
View File
@@ -7,12 +7,12 @@ use tokio::io::{AsyncRead, AsyncWrite};
use bytes::BytesMut;
use futures_core::ready;
use futures_sink::Sink;
use log::trace;
use pin_project_lite::pin_project;
use std::borrow::{Borrow, BorrowMut};
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tracing::trace;
pin_project! {
#[derive(Debug)]
@@ -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,
}
}
}
@@ -124,48 +128,64 @@ where
// to a combination of the `is_readable` and `eof` flags. States persist across
// loop entries and most state transitions occur with a return.
//
// The intitial state is `reading`.
// 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`.
//
// If `decode` couldn't read a frame and the upstream source has returned eof,
// `decode_eof` will attemp to decode the remaining bytes as closing frames.
// `decode_eof` will attempt to decode the remaining bytes as closing frames.
//
// If the underlying AsyncRead is resumable, we may continue after an EOF,
// but must finish emmiting all of it's associated `decode_eof` frames.
// but must finish emitting all of it's associated `decode_eof` frames.
// Furthermore, we don't want to emit any `decode_eof` frames on retried
// reads after an EOF unless we've actually read more data.
if state.is_readable {
// 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,
@@ -248,7 +278,7 @@ where
while !pinned.state.borrow_mut().buffer.is_empty() {
let WriteFrame { buffer } = pinned.state.borrow_mut();
trace!("writing; remaining={}", buffer.len());
trace!(remaining = buffer.len(), "writing;");
let n = ready!(poll_write_buf(pinned.inner.as_mut(), cx, buffer))?;
+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
+70 -16
View File
@@ -84,7 +84,7 @@
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_length(2)
//! .length_field_type::<u16>()
//! .length_adjustment(0) // default value
//! .num_skip(0) // Do not strip frame header
//! .new_read(io);
@@ -118,7 +118,7 @@
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_length(2)
//! .length_field_type::<u16>()
//! .length_adjustment(0) // default value
//! // `num_skip` is not needed, the default is to skip
//! .new_read(io);
@@ -150,7 +150,7 @@
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! .length_field_offset(0) // default value
//! .length_field_length(2)
//! .length_field_type::<u16>()
//! .length_adjustment(-2) // size of head
//! .num_skip(0)
//! .new_read(io);
@@ -228,7 +228,7 @@
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! .length_field_offset(1) // length of hdr1
//! .length_field_length(2)
//! .length_field_type::<u16>()
//! .length_adjustment(1) // length of hdr2
//! .num_skip(3) // length of hdr1 + LEN
//! .new_read(io);
@@ -274,7 +274,7 @@
//! # fn bind_read<T: AsyncRead>(io: T) {
//! LengthDelimitedCodec::builder()
//! .length_field_offset(1) // length of hdr1
//! .length_field_length(2)
//! .length_field_type::<u16>()
//! .length_adjustment(-3) // length of hdr1 + LEN, negative
//! .num_skip(3)
//! .new_read(io);
@@ -350,7 +350,7 @@
//! # fn write_frame<T: AsyncWrite>(io: T) {
//! # let _ =
//! LengthDelimitedCodec::builder()
//! .length_field_length(2)
//! .length_field_type::<u16>()
//! .new_write(io);
//! # }
//! # pub fn main() {}
@@ -379,7 +379,7 @@ use tokio::io::{AsyncRead, AsyncWrite};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::error::Error as StdError;
use std::io::{self, Cursor};
use std::{cmp, fmt};
use std::{cmp, fmt, mem};
/// Configure length delimited `LengthDelimitedCodec`s.
///
@@ -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,
@@ -486,7 +486,7 @@ impl LengthDelimitedCodec {
// Skip the required bytes
src.advance(self.builder.length_field_offset);
// match endianess
// match endianness
let n = if self.builder.length_field_is_big_endian {
src.get_uint(field_len)
} else {
@@ -629,6 +629,24 @@ impl Default for LengthDelimitedCodec {
// ===== impl Builder =====
mod builder {
/// Types that can be used with `Builder::length_field_type`.
pub trait LengthFieldType {}
impl LengthFieldType for u8 {}
impl LengthFieldType for u16 {}
impl LengthFieldType for u32 {}
impl LengthFieldType for u64 {}
#[cfg(any(
target_pointer_width = "8",
target_pointer_width = "16",
target_pointer_width = "32",
target_pointer_width = "64",
))]
impl LengthFieldType for usize {}
}
impl Builder {
/// Creates a new length delimited codec builder with default configuration
/// values.
@@ -642,7 +660,7 @@ impl Builder {
/// # fn bind_read<T: AsyncRead>(io: T) {
/// LengthDelimitedCodec::builder()
/// .length_field_offset(0)
/// .length_field_length(2)
/// .length_field_type::<u16>()
/// .length_adjustment(0)
/// .num_skip(0)
/// .new_read(io);
@@ -746,7 +764,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 +785,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() {}
@@ -777,6 +795,42 @@ impl Builder {
self
}
/// Sets the unsigned integer type used to represent the length field.
///
/// The default type is [`u32`]. The max type is [`u64`] (or [`usize`] on
/// 64-bit targets).
///
/// # Examples
///
/// ```
/// # use tokio::io::AsyncRead;
/// use tokio_util::codec::LengthDelimitedCodec;
///
/// # fn bind_read<T: AsyncRead>(io: T) {
/// LengthDelimitedCodec::builder()
/// .length_field_type::<u32>()
/// .new_read(io);
/// # }
/// # pub fn main() {}
/// ```
///
/// Unlike [`Builder::length_field_length`], this does not fail at runtime
/// and instead produces a compile error:
///
/// ```compile_fail
/// # use tokio::io::AsyncRead;
/// # use tokio_util::codec::LengthDelimitedCodec;
/// # fn bind_read<T: AsyncRead>(io: T) {
/// LengthDelimitedCodec::builder()
/// .length_field_type::<u128>()
/// .new_read(io);
/// # }
/// # pub fn main() {}
/// ```
pub fn length_field_type<T: builder::LengthFieldType>(&mut self) -> &mut Self {
self.length_field_length(mem::size_of::<T>())
}
/// Sets the number of bytes used to represent the length field
///
/// The default value is `4`. The max value is `8`.
@@ -878,7 +932,7 @@ impl Builder {
/// # pub fn main() {
/// LengthDelimitedCodec::builder()
/// .length_field_offset(0)
/// .length_field_length(2)
/// .length_field_type::<u16>()
/// .length_adjustment(0)
/// .num_skip(0)
/// .new_codec();
@@ -902,7 +956,7 @@ impl Builder {
/// # fn bind_read<T: AsyncRead>(io: T) {
/// LengthDelimitedCodec::builder()
/// .length_field_offset(0)
/// .length_field_length(2)
/// .length_field_type::<u16>()
/// .length_adjustment(0)
/// .num_skip(0)
/// .new_read(io);
@@ -925,7 +979,7 @@ impl Builder {
/// # use tokio_util::codec::LengthDelimitedCodec;
/// # fn write_frame<T: AsyncWrite>(io: T) {
/// LengthDelimitedCodec::builder()
/// .length_field_length(2)
/// .length_field_type::<u16>()
/// .new_write(io);
/// # }
/// # pub fn main() {}
@@ -947,7 +1001,7 @@ impl Builder {
/// # fn write_frame<T: AsyncRead + AsyncWrite>(io: T) {
/// # let _ =
/// LengthDelimitedCodec::builder()
/// .length_field_length(2)
/// .length_field_type::<u16>()
/// .new_framed(io);
/// # }
/// # pub fn main() {}
+2 -2
View File
@@ -203,12 +203,12 @@ impl Default for LinesCodec {
}
}
/// An error occured while encoding or decoding a line.
/// An error occurred while encoding or decoding a line.
#[derive(Debug)]
pub enum LinesCodecError {
/// The maximum line length was exceeded.
MaxLineLengthExceeded,
/// An IO error occured.
/// An IO error occurred.
Io(io::Error),
}
+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 {
+1 -1
View File
@@ -67,7 +67,7 @@ pub enum Either<L, R> {
}
/// A small helper macro which reduces amount of boilerplate in the actual trait method implementation.
/// It takes an invokation of method as an argument (e.g. `self.poll(cx)`), and redirects it to either
/// It takes an invocation of method as an argument (e.g. `self.poll(cx)`), and redirects it to either
/// enum variant held in `self`.
macro_rules! delegate_call {
($self:ident.$method:ident($($args:ident),+)) => {
+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) {
+17 -2
View File
@@ -84,13 +84,24 @@ where
}
/// Do we have a chunk and is it non-empty?
fn has_chunk(self: Pin<&mut Self>) -> bool {
if let Some(chunk) = self.project().chunk {
fn has_chunk(&self) -> bool {
if let Some(ref chunk) = self.chunk {
chunk.remaining() > 0
} else {
false
}
}
/// Consumes this `StreamReader`, returning a Tuple consisting
/// of the underlying stream and an Option of the interal buffer,
/// which is Some in case the buffer contains elements.
pub fn into_inner_with_chunk(self) -> (S, Option<B>) {
if self.has_chunk() {
(self.inner, self.chunk)
} else {
(self.inner, None)
}
}
}
impl<S, B> StreamReader<S, B> {
@@ -118,6 +129,10 @@ impl<S, B> StreamReader<S, B> {
/// Consumes this `BufWriter`, returning the underlying stream.
///
/// Note that any leftover data in the internal buffer is lost.
/// If you additionally want access to the internal buffer use
/// [`into_inner_with_chunk`].
///
/// [`into_inner_with_chunk`]: crate::io::StreamReader::into_inner_with_chunk
pub fn into_inner(self) -> S {
self.inner
}
+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 }
}
}
+5 -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! {
@@ -114,6 +115,9 @@ mod util {
let n = {
let dst = buf.chunk_mut();
// Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a
// transparent wrapper around `[MaybeUninit<u8>]`.
let dst = unsafe { &mut *(dst as *mut _ as *mut [MaybeUninit<u8>]) };
let mut buf = ReadBuf::uninit(dst);
let ptr = buf.filled().as_ptr();
+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)
}
}
+62 -705
View File
@@ -1,15 +1,15 @@
//! An asynchronously awaitable `CancellationToken`.
//! The token allows to signal a cancellation request to one or more tasks.
pub(crate) mod guard;
mod tree_node;
use crate::loom::sync::atomic::AtomicUsize;
use crate::loom::sync::Mutex;
use crate::sync::intrusive_double_linked_list::{LinkedList, ListNode};
use crate::loom::sync::Arc;
use core::future::Future;
use core::pin::Pin;
use core::ptr::NonNull;
use core::sync::atomic::Ordering;
use core::task::{Context, Poll, Waker};
use core::task::{Context, Poll};
use guard::DropGuard;
use pin_project_lite::pin_project;
/// A token which can be used to signal a cancellation request to one or more
/// tasks.
@@ -21,9 +21,9 @@ use core::task::{Context, Poll, Waker};
///
/// # Examples
///
/// ```ignore
/// ```no_run
/// use tokio::select;
/// use tokio::scope::CancellationToken;
/// use tokio_util::sync::CancellationToken;
///
/// #[tokio::main]
/// async fn main() {
@@ -52,31 +52,20 @@ use core::task::{Context, Poll, Waker};
/// }
/// ```
pub struct CancellationToken {
inner: NonNull<CancellationTokenState>,
inner: Arc<tree_node::TreeNode>,
}
// Safety: The CancellationToken is thread-safe and can be moved between threads,
// since all methods are internally synchronized.
unsafe impl Send for CancellationToken {}
unsafe impl Sync for CancellationToken {}
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// was cancelled
#[must_use = "futures do nothing unless polled"]
pub struct WaitForCancellationFuture<'a> {
/// The CancellationToken that is associated with this WaitForCancellationFuture
cancellation_token: Option<&'a CancellationToken>,
/// Node for waiting at the cancellation_token
wait_node: ListNode<WaitQueueEntry>,
/// Whether this future was registered at the token yet as a waiter
is_registered: bool,
pin_project! {
/// A Future that is resolved once the corresponding [`CancellationToken`]
/// is cancelled.
#[must_use = "futures do nothing unless polled"]
pub struct WaitForCancellationFuture<'a> {
cancellation_token: &'a CancellationToken,
#[pin]
future: tokio::sync::futures::Notified<'a>,
}
}
// Safety: Futures can be sent between threads as long as the underlying
// cancellation_token is thread-safe (Sync),
// which allows to poll/register/unregister from a different thread.
unsafe impl<'a> Send for WaitForCancellationFuture<'a> {}
// ===== impl CancellationToken =====
impl core::fmt::Debug for CancellationToken {
@@ -89,43 +78,16 @@ impl core::fmt::Debug for CancellationToken {
impl Clone for CancellationToken {
fn clone(&self) -> Self {
// Safety: The state inside a `CancellationToken` is always valid, since
// is reference counted
let inner = self.state();
// Tokens are cloned by increasing their refcount
let current_state = inner.snapshot();
inner.increment_refcount(current_state);
CancellationToken { inner: self.inner }
tree_node::increase_handle_refcount(&self.inner);
CancellationToken {
inner: self.inner.clone(),
}
}
}
impl Drop for CancellationToken {
fn drop(&mut self) {
let token_state_pointer = self.inner;
// Safety: The state inside a `CancellationToken` is always valid, since
// is reference counted
let inner = unsafe { &mut *self.inner.as_ptr() };
let mut current_state = inner.snapshot();
// We need to safe the parent, since the state might be released by the
// next call
let parent = inner.parent;
// Drop our own refcount
current_state = inner.decrement_refcount(current_state);
// If this was the last reference, unregister from the parent
if current_state.refcount == 0 {
if let Some(mut parent) = parent {
// Safety: Since we still retain a reference on the parent, it must be valid.
let parent = unsafe { parent.as_mut() };
parent.unregister_child(token_state_pointer, current_state);
}
}
tree_node::decrease_handle_refcount(&self.inner);
}
}
@@ -138,29 +100,11 @@ impl Default for CancellationToken {
impl CancellationToken {
/// Creates a new CancellationToken in the non-cancelled state.
pub fn new() -> CancellationToken {
let state = Box::new(CancellationTokenState::new(
None,
StateSnapshot {
cancel_state: CancellationState::NotCancelled,
has_parent_ref: false,
refcount: 1,
},
));
// Safety: We just created the Box. The pointer is guaranteed to be
// not null
CancellationToken {
inner: unsafe { NonNull::new_unchecked(Box::into_raw(state)) },
inner: Arc::new(tree_node::TreeNode::new()),
}
}
/// Returns a reference to the utilized `CancellationTokenState`.
fn state(&self) -> &CancellationTokenState {
// Safety: The state inside a `CancellationToken` is always valid, since
// is reference counted
unsafe { &*self.inner.as_ptr() }
}
/// Creates a `CancellationToken` which will get cancelled whenever the
/// current token gets cancelled.
///
@@ -169,9 +113,9 @@ impl CancellationToken {
///
/// # Examples
///
/// ```ignore
/// ```no_run
/// use tokio::select;
/// use tokio::scope::CancellationToken;
/// use tokio_util::sync::CancellationToken;
///
/// #[tokio::main]
/// async fn main() {
@@ -200,56 +144,8 @@ impl CancellationToken {
/// }
/// ```
pub fn child_token(&self) -> CancellationToken {
let inner = self.state();
// Increment the refcount of this token. It will be referenced by the
// child, independent of whether the child is immediately cancelled or
// not.
let _current_state = inner.increment_refcount(inner.snapshot());
let mut unpacked_child_state = StateSnapshot {
has_parent_ref: true,
refcount: 1,
cancel_state: CancellationState::NotCancelled,
};
let mut child_token_state = Box::new(CancellationTokenState::new(
Some(self.inner),
unpacked_child_state,
));
{
let mut guard = inner.synchronized.lock().unwrap();
if guard.is_cancelled {
// This task was already cancelled. In this case we should not
// insert the child into the list, since it would never get removed
// from the list.
(*child_token_state.synchronized.lock().unwrap()).is_cancelled = true;
unpacked_child_state.cancel_state = CancellationState::Cancelled;
// Since it's not in the list, the parent doesn't need to retain
// a reference to it.
unpacked_child_state.has_parent_ref = false;
child_token_state
.state
.store(unpacked_child_state.pack(), Ordering::SeqCst);
} else {
if let Some(mut first_child) = guard.first_child {
child_token_state.from_parent.next_peer = Some(first_child);
// Safety: We manipulate other child task inside the Mutex
// and retain a parent reference on it. The child token can't
// get invalidated while the Mutex is held.
unsafe {
first_child.as_mut().from_parent.prev_peer =
Some((&mut *child_token_state).into())
};
}
guard.first_child = Some((&mut *child_token_state).into());
}
};
let child_token_ptr = Box::into_raw(child_token_state);
// Safety: We just created the pointer from a `Box`
CancellationToken {
inner: unsafe { NonNull::new_unchecked(child_token_ptr) },
inner: tree_node::child_node(&self.inner),
}
}
@@ -257,42 +153,42 @@ impl CancellationToken {
/// derived from it.
///
/// This will wake up all tasks which are waiting for cancellation.
///
/// Be aware that cancellation is not an atomic operation. It is possible
/// for another thread running in parallel with a call to `cancel` to first
/// receive `true` from `is_cancelled` on one child node, and then receive
/// `false` from `is_cancelled` on another child node. However, once the
/// call to `cancel` returns, all child nodes have been fully cancelled.
pub fn cancel(&self) {
self.state().cancel();
tree_node::cancel(&self.inner);
}
/// Returns `true` if the `CancellationToken` had been cancelled
/// Returns `true` if the `CancellationToken` is cancelled.
pub fn is_cancelled(&self) -> bool {
self.state().is_cancelled()
tree_node::is_cancelled(&self.inner)
}
/// Returns a `Future` that gets fulfilled when cancellation is requested.
///
/// The future will complete immediately if the token is already cancelled
/// when this method is called.
///
/// # Cancel safety
///
/// This method is cancel safe.
pub fn cancelled(&self) -> WaitForCancellationFuture<'_> {
WaitForCancellationFuture {
cancellation_token: Some(self),
wait_node: ListNode::new(WaitQueueEntry::new()),
is_registered: false,
cancellation_token: self,
future: self.inner.notified(),
}
}
unsafe fn register(
&self,
wait_node: &mut ListNode<WaitQueueEntry>,
cx: &mut Context<'_>,
) -> Poll<()> {
self.state().register(wait_node, cx)
}
fn check_for_cancellation(
&self,
wait_node: &mut ListNode<WaitQueueEntry>,
cx: &mut Context<'_>,
) -> Poll<()> {
self.state().check_for_cancellation(wait_node, cx)
}
fn unregister(&self, wait_node: &mut ListNode<WaitQueueEntry>) {
self.state().unregister(wait_node)
/// Creates a `DropGuard` for this token.
///
/// Returned guard will cancel this token (and all its children) on drop
/// unless disarmed.
pub fn drop_guard(self) -> DropGuard {
DropGuard { inner: Some(self) }
}
}
@@ -308,560 +204,21 @@ impl<'a> Future for WaitForCancellationFuture<'a> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
// Safety: We do not move anything out of `WaitForCancellationFuture`
let mut_self: &mut WaitForCancellationFuture<'_> = unsafe { Pin::get_unchecked_mut(self) };
let cancellation_token = mut_self
.cancellation_token
.expect("polled WaitForCancellationFuture after completion");
let poll_res = if !mut_self.is_registered {
// Safety: The `ListNode` is pinned through the Future,
// and we will unregister it in `WaitForCancellationFuture::drop`
// before the Future is dropped and the memory reference is invalidated.
unsafe { cancellation_token.register(&mut mut_self.wait_node, cx) }
} else {
cancellation_token.check_for_cancellation(&mut mut_self.wait_node, cx)
};
if let Poll::Ready(()) = poll_res {
// The cancellation_token was signalled
mut_self.cancellation_token = None;
// A signalled Token means the Waker won't be enqueued anymore
mut_self.is_registered = false;
mut_self.wait_node.task = None;
} else {
// This `Future` and its stored `Waker` stay registered at the
// `CancellationToken`
mut_self.is_registered = true;
}
poll_res
}
}
impl<'a> Drop for WaitForCancellationFuture<'a> {
fn drop(&mut self) {
// If this WaitForCancellationFuture has been polled and it was added to the
// wait queue at the cancellation_token, it must be removed before dropping.
// Otherwise the cancellation_token would access invalid memory.
if let Some(token) = self.cancellation_token {
if self.is_registered {
token.unregister(&mut self.wait_node);
}
}
}
}
/// Tracks how the future had interacted with the [`CancellationToken`]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum PollState {
/// The task has never interacted with the [`CancellationToken`].
New,
/// The task was added to the wait queue at the [`CancellationToken`].
Waiting,
/// The task has been polled to completion.
Done,
}
/// Tracks the WaitForCancellationFuture waiting state.
/// Access to this struct is synchronized through the mutex in the CancellationToken.
struct WaitQueueEntry {
/// The task handle of the waiting task
task: Option<Waker>,
// Current polling state. This state is only updated inside the Mutex of
// the CancellationToken.
state: PollState,
}
impl WaitQueueEntry {
/// Creates a new WaitQueueEntry
fn new() -> WaitQueueEntry {
WaitQueueEntry {
task: None,
state: PollState::New,
}
}
}
struct SynchronizedState {
waiters: LinkedList<WaitQueueEntry>,
first_child: Option<NonNull<CancellationTokenState>>,
is_cancelled: bool,
}
impl SynchronizedState {
fn new() -> Self {
Self {
waiters: LinkedList::new(),
first_child: None,
is_cancelled: false,
}
}
}
/// Information embedded in child tokens which is synchronized through the Mutex
/// in their parent.
struct SynchronizedThroughParent {
next_peer: Option<NonNull<CancellationTokenState>>,
prev_peer: Option<NonNull<CancellationTokenState>>,
}
/// Possible states of a `CancellationToken`
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum CancellationState {
NotCancelled = 0,
Cancelling = 1,
Cancelled = 2,
}
impl CancellationState {
fn pack(self) -> usize {
self as usize
}
fn unpack(value: usize) -> Self {
match value {
0 => CancellationState::NotCancelled,
1 => CancellationState::Cancelling,
2 => CancellationState::Cancelled,
_ => unreachable!("Invalid value"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct StateSnapshot {
/// The amount of references to this particular CancellationToken.
/// `CancellationToken` structs hold these references to a `CancellationTokenState`.
/// Also the state is referenced by the state of each child.
refcount: usize,
/// Whether the state is still referenced by it's parent and can therefore
/// not be freed.
has_parent_ref: bool,
/// Whether the token is cancelled
cancel_state: CancellationState,
}
impl StateSnapshot {
/// Packs the snapshot into a `usize`
fn pack(self) -> usize {
self.refcount << 3 | if self.has_parent_ref { 4 } else { 0 } | self.cancel_state.pack()
}
/// Unpacks the snapshot from a `usize`
fn unpack(value: usize) -> Self {
let refcount = value >> 3;
let has_parent_ref = value & 4 != 0;
let cancel_state = CancellationState::unpack(value & 0x03);
StateSnapshot {
refcount,
has_parent_ref,
cancel_state,
}
}
/// Whether this `CancellationTokenState` is still referenced by any
/// `CancellationToken`.
fn has_refs(&self) -> bool {
self.refcount != 0 || self.has_parent_ref
}
}
/// The maximum permitted amount of references to a CancellationToken. This
/// is derived from the intent to never use more than 32bit in the `Snapshot`.
const MAX_REFS: u32 = (std::u32::MAX - 7) >> 3;
/// Internal state of the `CancellationToken` pair above
struct CancellationTokenState {
state: AtomicUsize,
parent: Option<NonNull<CancellationTokenState>>,
from_parent: SynchronizedThroughParent,
synchronized: Mutex<SynchronizedState>,
}
impl CancellationTokenState {
fn new(
parent: Option<NonNull<CancellationTokenState>>,
state: StateSnapshot,
) -> CancellationTokenState {
CancellationTokenState {
parent,
from_parent: SynchronizedThroughParent {
prev_peer: None,
next_peer: None,
},
state: AtomicUsize::new(state.pack()),
synchronized: Mutex::new(SynchronizedState::new()),
}
}
/// Returns a snapshot of the current atomic state of the token
fn snapshot(&self) -> StateSnapshot {
StateSnapshot::unpack(self.state.load(Ordering::SeqCst))
}
fn atomic_update_state<F>(&self, mut current_state: StateSnapshot, func: F) -> StateSnapshot
where
F: Fn(StateSnapshot) -> StateSnapshot,
{
let mut current_packed_state = current_state.pack();
let mut this = self.project();
loop {
let next_state = func(current_state);
match self.state.compare_exchange(
current_packed_state,
next_state.pack(),
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => {
return next_state;
}
Err(actual) => {
current_packed_state = actual;
current_state = StateSnapshot::unpack(actual);
}
}
}
}
fn increment_refcount(&self, current_state: StateSnapshot) -> StateSnapshot {
self.atomic_update_state(current_state, |mut state: StateSnapshot| {
if state.refcount >= MAX_REFS as usize {
eprintln!("[ERROR] Maximum reference count for CancellationToken was exceeded");
std::process::abort();
}
state.refcount += 1;
state
})
}
fn decrement_refcount(&self, current_state: StateSnapshot) -> StateSnapshot {
let current_state = self.atomic_update_state(current_state, |mut state: StateSnapshot| {
state.refcount -= 1;
state
});
// Drop the State if it is not referenced anymore
if !current_state.has_refs() {
// Safety: `CancellationTokenState` is always stored in refcounted
// Boxes
let _ = unsafe { Box::from_raw(self as *const Self as *mut Self) };
}
current_state
}
fn remove_parent_ref(&self, current_state: StateSnapshot) -> StateSnapshot {
let current_state = self.atomic_update_state(current_state, |mut state: StateSnapshot| {
state.has_parent_ref = false;
state
});
// Drop the State if it is not referenced anymore
if !current_state.has_refs() {
// Safety: `CancellationTokenState` is always stored in refcounted
// Boxes
let _ = unsafe { Box::from_raw(self as *const Self as *mut Self) };
}
current_state
}
/// Unregisters a child from the parent token.
/// The child tokens state is not exactly known at this point in time.
/// If the parent token is cancelled, the child token gets removed from the
/// parents list, and might therefore already have been freed. If the parent
/// token is not cancelled, the child token is still valid.
fn unregister_child(
&mut self,
mut child_state: NonNull<CancellationTokenState>,
current_child_state: StateSnapshot,
) {
let removed_child = {
// Remove the child toke from the parents linked list
let mut guard = self.synchronized.lock().unwrap();
if !guard.is_cancelled {
// Safety: Since the token was not cancelled, the child must
// still be in the list and valid.
let mut child_state = unsafe { child_state.as_mut() };
debug_assert!(child_state.snapshot().has_parent_ref);
if guard.first_child == Some(child_state.into()) {
guard.first_child = child_state.from_parent.next_peer;
}
// Safety: If peers wouldn't be valid anymore, they would try
// to remove themselves from the list. This would require locking
// the Mutex that we currently own.
unsafe {
if let Some(mut prev_peer) = child_state.from_parent.prev_peer {
prev_peer.as_mut().from_parent.next_peer =
child_state.from_parent.next_peer;
}
if let Some(mut next_peer) = child_state.from_parent.next_peer {
next_peer.as_mut().from_parent.prev_peer =
child_state.from_parent.prev_peer;
}
}
child_state.from_parent.prev_peer = None;
child_state.from_parent.next_peer = None;
// The child is no longer referenced by the parent, since we were able
// to remove its reference from the parents list.
true
} else {
// Do not touch the linked list anymore. If the parent is cancelled
// it will move all childs outside of the Mutex and manipulate
// the pointers there. Manipulating the pointers here too could
// lead to races. Therefore leave them just as as and let the
// parent deal with it. The parent will make sure to retain a
// reference to this state as long as it manipulates the list
// pointers. Therefore the pointers are not dangling.
false
}
};
if removed_child {
// If the token removed itself from the parents list, it can reset
// the parent ref status. If it is isn't able to do so, because the
// parent removed it from the list, there is no need to do this.
// The parent ref acts as as another reference count. Therefore
// removing this reference can free the object.
// Safety: The token was in the list. This means the parent wasn't
// cancelled before, and the token must still be alive.
unsafe { child_state.as_mut().remove_parent_ref(current_child_state) };
}
// Decrement the refcount on the parent and free it if necessary
self.decrement_refcount(self.snapshot());
}
fn cancel(&self) {
// Move the state of the CancellationToken from `NotCancelled` to `Cancelling`
let mut current_state = self.snapshot();
let state_after_cancellation = loop {
if current_state.cancel_state != CancellationState::NotCancelled {
// Another task already initiated the cancellation
return;
if this.cancellation_token.is_cancelled() {
return Poll::Ready(());
}
let mut next_state = current_state;
next_state.cancel_state = CancellationState::Cancelling;
match self.state.compare_exchange(
current_state.pack(),
next_state.pack(),
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => break next_state,
Err(actual) => current_state = StateSnapshot::unpack(actual),
// No wakeups can be lost here because there is always a call to
// `is_cancelled` between the creation of the future and the call to
// `poll`, and the code that sets the cancelled flag does so before
// waking the `Notified`.
if this.future.as_mut().poll(cx).is_pending() {
return Poll::Pending;
}
};
// This task cancelled the token
// Take the task list out of the Token
// We do not want to cancel child token inside this lock. If one of the
// child tasks would have additional child tokens, we would recursively
// take locks.
// Doing this action has an impact if the child token is dropped concurrently:
// It will try to deregister itself from the parent task, but can not find
// itself in the task list anymore. Therefore it needs to assume the parent
// has extracted the list and will process it. It may not modify the list.
// This is OK from a memory safety perspective, since the parent still
// retains a reference to the child task until it finished iterating over
// it.
let mut first_child = {
let mut guard = self.synchronized.lock().unwrap();
// Save the cancellation also inside the Mutex
// This allows child tokens which want to detach themselves to detect
// that this is no longer required since the parent cleared the list.
guard.is_cancelled = true;
// Wakeup all waiters
// This happens inside the lock to make cancellation reliable
// If we would access waiters outside of the lock, the pointers
// may no longer be valid.
// Typically this shouldn't be an issue, since waking a task should
// only move it from the blocked into the ready state and not have
// further side effects.
// Use a reverse iterator, so that the oldest waiter gets
// scheduled first
guard.waiters.reverse_drain(|waiter| {
// We are not allowed to move the `Waker` out of the list node.
// The `Future` relies on the fact that the old `Waker` stays there
// as long as the `Future` has not completed in order to perform
// the `will_wake()` check.
// Therefore `wake_by_ref` is used instead of `wake()`
if let Some(handle) = &mut waiter.task {
handle.wake_by_ref();
}
// Mark the waiter to have been removed from the list.
waiter.state = PollState::Done;
});
guard.first_child.take()
};
while let Some(mut child) = first_child {
// Safety: We know this is a valid pointer since it is in our child pointer
// list. It can't have been freed in between, since we retain a a reference
// to each child.
let mut_child = unsafe { child.as_mut() };
// Get the next child and clean up list pointers
first_child = mut_child.from_parent.next_peer;
mut_child.from_parent.prev_peer = None;
mut_child.from_parent.next_peer = None;
// Cancel the child task
mut_child.cancel();
// Drop the parent reference. This `CancellationToken` is not interested
// in interacting with the child anymore.
// This is ONLY allowed once we promised not to touch the state anymore
// after this interaction.
mut_child.remove_parent_ref(mut_child.snapshot());
this.future.set(this.cancellation_token.inner.notified());
}
// The cancellation has completed
// At this point in time tasks which registered a wait node can be sure
// that this wait node already had been dequeued from the list without
// needing to inspect the list.
self.atomic_update_state(state_after_cancellation, |mut state| {
state.cancel_state = CancellationState::Cancelled;
state
});
}
/// Returns `true` if the `CancellationToken` had been cancelled
fn is_cancelled(&self) -> bool {
let current_state = self.snapshot();
current_state.cancel_state != CancellationState::NotCancelled
}
/// Registers a waiting task at the `CancellationToken`.
/// Safety: This method is only safe as long as the waiting waiting task
/// will properly unregister the wait node before it gets moved.
unsafe fn register(
&self,
wait_node: &mut ListNode<WaitQueueEntry>,
cx: &mut Context<'_>,
) -> Poll<()> {
debug_assert_eq!(PollState::New, wait_node.state);
let current_state = self.snapshot();
// Perform an optimistic cancellation check before. This is not strictly
// necessary since we also check for cancellation in the Mutex, but
// reduces the necessary work to be performed for tasks which already
// had been cancelled.
if current_state.cancel_state != CancellationState::NotCancelled {
return Poll::Ready(());
}
// So far the token is not cancelled. However it could be cancelld before
// we get the chance to store the `Waker`. Therfore we need to check
// for cancellation again inside the mutex.
let mut guard = self.synchronized.lock().unwrap();
if guard.is_cancelled {
// Cancellation was signalled
wait_node.state = PollState::Done;
Poll::Ready(())
} else {
// Added the task to the wait queue
wait_node.task = Some(cx.waker().clone());
wait_node.state = PollState::Waiting;
guard.waiters.add_front(wait_node);
Poll::Pending
}
}
fn check_for_cancellation(
&self,
wait_node: &mut ListNode<WaitQueueEntry>,
cx: &mut Context<'_>,
) -> Poll<()> {
debug_assert!(
wait_node.task.is_some(),
"Method can only be called after task had been registered"
);
let current_state = self.snapshot();
if current_state.cancel_state != CancellationState::NotCancelled {
// If the cancellation had been fully completed we know that our `Waker`
// is no longer registered at the `CancellationToken`.
// Otherwise the cancel call may or may not yet have iterated
// through the waiters list and removed the wait nodes.
// If it hasn't yet, we need to remove it. Otherwise an attempt to
// reuse the `wait_node´ might get freed due to the `WaitForCancellationFuture`
// getting dropped before the cancellation had interacted with it.
if current_state.cancel_state != CancellationState::Cancelled {
self.unregister(wait_node);
}
Poll::Ready(())
} else {
// Check if we need to swap the `Waker`. This will make the check more
// expensive, since the `Waker` is synchronized through the Mutex.
// If we don't need to perform a `Waker` update, an atomic check for
// cancellation is sufficient.
let need_waker_update = wait_node
.task
.as_ref()
.map(|waker| waker.will_wake(cx.waker()))
.unwrap_or(true);
if need_waker_update {
let guard = self.synchronized.lock().unwrap();
if guard.is_cancelled {
// Cancellation was signalled. Since this cancellation signal
// is set inside the Mutex, the old waiter must already have
// been removed from the waiting list
debug_assert_eq!(PollState::Done, wait_node.state);
wait_node.task = None;
Poll::Ready(())
} else {
// The WaitForCancellationFuture is already in the queue.
// The CancellationToken can't have been cancelled,
// since this would change the is_cancelled flag inside the mutex.
// Therefore we just have to update the Waker. A follow-up
// cancellation will always use the new waker.
wait_node.task = Some(cx.waker().clone());
Poll::Pending
}
} else {
// Do nothing. If the token gets cancelled, this task will get
// woken again and can fetch the cancellation.
Poll::Pending
}
}
}
fn unregister(&self, wait_node: &mut ListNode<WaitQueueEntry>) {
debug_assert!(
wait_node.task.is_some(),
"waiter can not be active without task"
);
let mut guard = self.synchronized.lock().unwrap();
// WaitForCancellationFuture only needs to get removed if it has been added to
// the wait queue of the CancellationToken.
// This has happened in the PollState::Waiting case.
if let PollState::Waiting = wait_node.state {
// Safety: Due to the state, we know that the node must be part
// of the waiter list
if !unsafe { guard.waiters.remove(wait_node) } {
// Panic if the address isn't found. This can only happen if the contract was
// violated, e.g. the WaitQueueEntry got moved after the initial poll.
panic!("Future could not be removed from wait queue");
}
wait_node.state = PollState::Done;
}
wait_node.task = None;
}
}
@@ -0,0 +1,27 @@
use crate::sync::CancellationToken;
/// A wrapper for cancellation token which automatically cancels
/// it on drop. It is created using `drop_guard` method on the `CancellationToken`.
#[derive(Debug)]
pub struct DropGuard {
pub(super) inner: Option<CancellationToken>,
}
impl DropGuard {
/// Returns stored cancellation token and removes this drop guard instance
/// (i.e. it will no longer cancel token). Other guards for this token
/// are not affected.
pub fn disarm(mut self) -> CancellationToken {
self.inner
.take()
.expect("`inner` can be only None in a destructor")
}
}
impl Drop for DropGuard {
fn drop(&mut self) {
if let Some(inner) = &self.inner {
inner.cancel();
}
}
}
@@ -0,0 +1,373 @@
//! This mod provides the logic for the inner tree structure of the CancellationToken.
//!
//! CancellationTokens are only light handles with references to TreeNode.
//! All the logic is actually implemented in the TreeNode.
//!
//! A TreeNode is part of the cancellation tree and may have one parent and an arbitrary number of
//! children.
//!
//! A TreeNode can receive the request to perform a cancellation through a CancellationToken.
//! This cancellation request will cancel the node and all of its descendants.
//!
//! As soon as a node cannot get cancelled any more (because it was already cancelled or it has no
//! more CancellationTokens pointing to it any more), it gets removed from the tree, to keep the
//! tree as small as possible.
//!
//! # Invariants
//!
//! Those invariants shall be true at any time.
//!
//! 1. A node that has no parents and no handles can no longer be cancelled.
//! This is important during both cancellation and refcounting.
//!
//! 2. If node B *is* or *was* a child of node A, then node B was created *after* node A.
//! This is important for deadlock safety, as it is used for lock order.
//! Node B can only become the child of node A in two ways:
//! - being created with `child_node()`, in which case it is trivially true that
//! node A already existed when node B was created
//! - being moved A->C->B to A->B because node C was removed in `decrease_handle_refcount()`
//! or `cancel()`. In this case the invariant still holds, as B was younger than C, and C
//! was younger than A, therefore B is also younger than A.
//!
//! 3. If two nodes are both unlocked and node A is the parent of node B, then node B is a child of
//! node A. It is important to always restore that invariant before dropping the lock of a node.
//!
//! # Deadlock safety
//!
//! We always lock in the order of creation time. We can prove this through invariant #2.
//! Specifically, through invariant #2, we know that we always have to lock a parent
//! before its child.
//!
use crate::loom::sync::{Arc, Mutex, MutexGuard};
/// A node of the cancellation tree structure
///
/// The actual data it holds is wrapped inside a mutex for synchronization.
pub(crate) struct TreeNode {
inner: Mutex<Inner>,
waker: tokio::sync::Notify,
}
impl TreeNode {
pub(crate) fn new() -> Self {
Self {
inner: Mutex::new(Inner {
parent: None,
parent_idx: 0,
children: vec![],
is_cancelled: false,
num_handles: 1,
}),
waker: tokio::sync::Notify::new(),
}
}
pub(crate) fn notified(&self) -> tokio::sync::futures::Notified<'_> {
self.waker.notified()
}
}
/// The data contained inside a TreeNode.
///
/// This struct exists so that the data of the node can be wrapped
/// in a Mutex.
struct Inner {
parent: Option<Arc<TreeNode>>,
parent_idx: usize,
children: Vec<Arc<TreeNode>>,
is_cancelled: bool,
num_handles: usize,
}
/// Returns whether or not the node is cancelled
pub(crate) fn is_cancelled(node: &Arc<TreeNode>) -> bool {
node.inner.lock().unwrap().is_cancelled
}
/// Creates a child node
pub(crate) fn child_node(parent: &Arc<TreeNode>) -> Arc<TreeNode> {
let mut locked_parent = parent.inner.lock().unwrap();
// Do not register as child if we are already cancelled.
// Cancelled trees can never be uncancelled and therefore
// need no connection to parents or children any more.
if locked_parent.is_cancelled {
return Arc::new(TreeNode {
inner: Mutex::new(Inner {
parent: None,
parent_idx: 0,
children: vec![],
is_cancelled: true,
num_handles: 1,
}),
waker: tokio::sync::Notify::new(),
});
}
let child = Arc::new(TreeNode {
inner: Mutex::new(Inner {
parent: Some(parent.clone()),
parent_idx: locked_parent.children.len(),
children: vec![],
is_cancelled: false,
num_handles: 1,
}),
waker: tokio::sync::Notify::new(),
});
locked_parent.children.push(child.clone());
child
}
/// Disconnects the given parent from all of its children.
///
/// Takes a reference to [Inner] to make sure the parent is already locked.
fn disconnect_children(node: &mut Inner) {
for child in std::mem::take(&mut node.children) {
let mut locked_child = child.inner.lock().unwrap();
locked_child.parent_idx = 0;
locked_child.parent = None;
}
}
/// Figures out the parent of the node and locks the node and its parent atomically.
///
/// The basic principle of preventing deadlocks in the tree is
/// that we always lock the parent first, and then the child.
/// For more info look at *deadlock safety* and *invariant #2*.
///
/// Sadly, it's impossible to figure out the parent of a node without
/// locking it. To then achieve locking order consistency, the node
/// has to be unlocked before the parent gets locked.
/// This leaves a small window where we already assume that we know the parent,
/// but neither the parent nor the node is locked. Therefore, the parent could change.
///
/// To prevent that this problem leaks into the rest of the code, it is abstracted
/// in this function.
///
/// The locked child and optionally its locked parent, if a parent exists, get passed
/// to the `func` argument via (node, None) or (node, Some(parent)).
fn with_locked_node_and_parent<F, Ret>(node: &Arc<TreeNode>, func: F) -> Ret
where
F: FnOnce(MutexGuard<'_, Inner>, Option<MutexGuard<'_, Inner>>) -> Ret,
{
let mut potential_parent = {
let locked_node = node.inner.lock().unwrap();
match locked_node.parent.clone() {
Some(parent) => parent,
// If we locked the node and its parent is `None`, we are in a valid state
// and can return.
None => return func(locked_node, None),
}
};
loop {
// Deadlock safety:
//
// Due to invariant #2, we know that we have to lock the parent first, and then the child.
// This is true even if the potential_parent is no longer the current parent or even its
// sibling, as the invariant still holds.
let locked_parent = potential_parent.inner.lock().unwrap();
let locked_node = node.inner.lock().unwrap();
let actual_parent = match locked_node.parent.clone() {
Some(parent) => parent,
// If we locked the node and its parent is `None`, we are in a valid state
// and can return.
None => {
// Was the wrong parent, so unlock it before calling `func`
drop(locked_parent);
return func(locked_node, None);
}
};
// Loop until we managed to lock both the node and its parent
if Arc::ptr_eq(&actual_parent, &potential_parent) {
return func(locked_node, Some(locked_parent));
}
// Drop locked_parent before reassigning to potential_parent,
// as potential_parent is borrowed in it
drop(locked_node);
drop(locked_parent);
potential_parent = actual_parent;
}
}
/// Moves all children from `node` to `parent`.
///
/// `parent` MUST have been a parent of the node when they both got locked,
/// otherwise there is a potential for a deadlock as invariant #2 would be violated.
///
/// To aquire the locks for node and parent, use [with_locked_node_and_parent].
fn move_children_to_parent(node: &mut Inner, parent: &mut Inner) {
// Pre-allocate in the parent, for performance
parent.children.reserve(node.children.len());
for child in std::mem::take(&mut node.children) {
{
let mut child_locked = child.inner.lock().unwrap();
child_locked.parent = node.parent.clone();
child_locked.parent_idx = parent.children.len();
}
parent.children.push(child);
}
}
/// Removes a child from the parent.
///
/// `parent` MUST be the parent of `node`.
/// To aquire the locks for node and parent, use [with_locked_node_and_parent].
fn remove_child(parent: &mut Inner, mut node: MutexGuard<'_, Inner>) {
// Query the position from where to remove a node
let pos = node.parent_idx;
node.parent = None;
node.parent_idx = 0;
// Unlock node, so that only one child at a time is locked.
// Otherwise we would violate the lock order (see 'deadlock safety') as we
// don't know the creation order of the child nodes
drop(node);
// If `node` is the last element in the list, we don't need any swapping
if parent.children.len() == pos + 1 {
parent.children.pop().unwrap();
} else {
// If `node` is not the last element in the list, we need to
// replace it with the last element
let replacement_child = parent.children.pop().unwrap();
replacement_child.inner.lock().unwrap().parent_idx = pos;
parent.children[pos] = replacement_child;
}
let len = parent.children.len();
if 4 * len <= parent.children.capacity() {
// equal to:
// parent.children.shrink_to(2 * len);
// but shrink_to was not yet stabilized in our minimal compatible version
let old_children = std::mem::replace(&mut parent.children, Vec::with_capacity(2 * len));
parent.children.extend(old_children);
}
}
/// Increases the reference count of handles.
pub(crate) fn increase_handle_refcount(node: &Arc<TreeNode>) {
let mut locked_node = node.inner.lock().unwrap();
// Once no handles are left over, the node gets detached from the tree.
// There should never be a new handle once all handles are dropped.
assert!(locked_node.num_handles > 0);
locked_node.num_handles += 1;
}
/// Decreases the reference count of handles.
///
/// Once no handle is left, we can remove the node from the
/// tree and connect its parent directly to its children.
pub(crate) fn decrease_handle_refcount(node: &Arc<TreeNode>) {
let num_handles = {
let mut locked_node = node.inner.lock().unwrap();
locked_node.num_handles -= 1;
locked_node.num_handles
};
if num_handles == 0 {
with_locked_node_and_parent(node, |mut node, parent| {
// Remove the node from the tree
match parent {
Some(mut parent) => {
// As we want to remove ourselves from the tree,
// we have to move the children to the parent, so that
// they still receive the cancellation event without us.
// Moving them does not violate invariant #1.
move_children_to_parent(&mut node, &mut parent);
// Remove the node from the parent
remove_child(&mut parent, node);
}
None => {
// Due to invariant #1, we can assume that our
// children can no longer be cancelled through us.
// (as we now have neither a parent nor handles)
// Therefore we can disconnect them.
disconnect_children(&mut node);
}
}
});
}
}
/// Cancels a node and its children.
pub(crate) fn cancel(node: &Arc<TreeNode>) {
let mut locked_node = node.inner.lock().unwrap();
if locked_node.is_cancelled {
return;
}
// One by one, adopt grandchildren and then cancel and detach the child
while let Some(child) = locked_node.children.pop() {
// This can't deadlock because the mutex we are already
// holding is the parent of child.
let mut locked_child = child.inner.lock().unwrap();
// Detach the child from node
// No need to modify node.children, as the child already got removed with `.pop`
locked_child.parent = None;
locked_child.parent_idx = 0;
// If child is already cancelled, detaching is enough
if locked_child.is_cancelled {
continue;
}
// Cancel or adopt grandchildren
while let Some(grandchild) = locked_child.children.pop() {
// This can't deadlock because the two mutexes we are already
// holding is the parent and grandparent of grandchild.
let mut locked_grandchild = grandchild.inner.lock().unwrap();
// Detach the grandchild
locked_grandchild.parent = None;
locked_grandchild.parent_idx = 0;
// If grandchild is already cancelled, detaching is enough
if locked_grandchild.is_cancelled {
continue;
}
// For performance reasons, only adopt grandchildren that have children.
// Otherwise, just cancel them right away, no need for another iteration.
if locked_grandchild.children.is_empty() {
// Cancel the grandchild
locked_grandchild.is_cancelled = true;
locked_grandchild.children = Vec::new();
drop(locked_grandchild);
grandchild.waker.notify_waiters();
} else {
// Otherwise, adopt grandchild
locked_grandchild.parent = Some(node.clone());
locked_grandchild.parent_idx = locked_node.children.len();
drop(locked_grandchild);
locked_node.children.push(grandchild);
}
}
// Cancel the child
locked_child.is_cancelled = true;
locked_child.children = Vec::new();
drop(locked_child);
child.waker.notify_waiters();
// Now the child is cancelled and detached and all its children are adopted.
// Just continue until all (including adopted) children are cancelled and detached.
}
// Cancel the node itself.
locked_node.is_cancelled = true;
locked_node.children = Vec::new();
drop(locked_node);
node.waker.notify_waiters();
}
@@ -1,788 +0,0 @@
//! An intrusive double linked list of data
#![allow(dead_code, unreachable_pub)]
use core::{
marker::PhantomPinned,
ops::{Deref, DerefMut},
ptr::NonNull,
};
/// A node which carries data of type `T` and is stored in an intrusive list
#[derive(Debug)]
pub struct ListNode<T> {
/// The previous node in the list. `None` if there is no previous node.
prev: Option<NonNull<ListNode<T>>>,
/// The next node in the list. `None` if there is no previous node.
next: Option<NonNull<ListNode<T>>>,
/// The data which is associated to this list item
data: T,
/// Prevents `ListNode`s from being `Unpin`. They may never be moved, since
/// the list semantics require addresses to be stable.
_pin: PhantomPinned,
}
impl<T> ListNode<T> {
/// Creates a new node with the associated data
pub fn new(data: T) -> ListNode<T> {
Self {
prev: None,
next: None,
data,
_pin: PhantomPinned,
}
}
}
impl<T> Deref for ListNode<T> {
type Target = T;
fn deref(&self) -> &T {
&self.data
}
}
impl<T> DerefMut for ListNode<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.data
}
}
/// An intrusive linked list of nodes, where each node carries associated data
/// of type `T`.
#[derive(Debug)]
pub struct LinkedList<T> {
head: Option<NonNull<ListNode<T>>>,
tail: Option<NonNull<ListNode<T>>>,
}
impl<T> LinkedList<T> {
/// Creates an empty linked list
pub fn new() -> Self {
LinkedList::<T> {
head: None,
tail: None,
}
}
/// Adds a node at the front of the linked list.
/// Safety: This function is only safe as long as `node` is guaranteed to
/// get removed from the list before it gets moved or dropped.
/// In addition to this `node` may not be added to another other list before
/// it is removed from the current one.
pub unsafe fn add_front(&mut self, node: &mut ListNode<T>) {
node.next = self.head;
node.prev = None;
if let Some(mut head) = self.head {
head.as_mut().prev = Some(node.into())
};
self.head = Some(node.into());
if self.tail.is_none() {
self.tail = Some(node.into());
}
}
/// Inserts a node into the list in a way that the list keeps being sorted.
/// Safety: This function is only safe as long as `node` is guaranteed to
/// get removed from the list before it gets moved or dropped.
/// In addition to this `node` may not be added to another other list before
/// it is removed from the current one.
pub unsafe fn add_sorted(&mut self, node: &mut ListNode<T>)
where
T: PartialOrd,
{
if self.head.is_none() {
// First node in the list
self.head = Some(node.into());
self.tail = Some(node.into());
return;
}
let mut prev: Option<NonNull<ListNode<T>>> = None;
let mut current = self.head;
while let Some(mut current_node) = current {
if node.data < current_node.as_ref().data {
// Need to insert before the current node
current_node.as_mut().prev = Some(node.into());
match prev {
Some(mut prev) => {
prev.as_mut().next = Some(node.into());
}
None => {
// We are inserting at the beginning of the list
self.head = Some(node.into());
}
}
node.next = current;
node.prev = prev;
return;
}
prev = current;
current = current_node.as_ref().next;
}
// We looped through the whole list and the nodes data is bigger or equal
// than everything we found up to now.
// Insert at the end. Since we checked before that the list isn't empty,
// tail always has a value.
node.prev = self.tail;
node.next = None;
self.tail.as_mut().unwrap().as_mut().next = Some(node.into());
self.tail = Some(node.into());
}
/// Returns the first node in the linked list without removing it from the list
/// The function is only safe as long as valid pointers are stored inside
/// the linked list.
/// The returned pointer is only guaranteed to be valid as long as the list
/// is not mutated
pub fn peek_first(&self) -> Option<&mut ListNode<T>> {
// Safety: When the node was inserted it was promised that it is alive
// until it gets removed from the list.
// The returned node has a pointer which constrains it to the lifetime
// of the list. This is ok, since the Node is supposed to outlive
// its insertion in the list.
unsafe {
self.head
.map(|mut node| &mut *(node.as_mut() as *mut ListNode<T>))
}
}
/// Returns the last node in the linked list without removing it from the list
/// The function is only safe as long as valid pointers are stored inside
/// the linked list.
/// The returned pointer is only guaranteed to be valid as long as the list
/// is not mutated
pub fn peek_last(&self) -> Option<&mut ListNode<T>> {
// Safety: When the node was inserted it was promised that it is alive
// until it gets removed from the list.
// The returned node has a pointer which constrains it to the lifetime
// of the list. This is ok, since the Node is supposed to outlive
// its insertion in the list.
unsafe {
self.tail
.map(|mut node| &mut *(node.as_mut() as *mut ListNode<T>))
}
}
/// Removes the first node from the linked list
pub fn remove_first(&mut self) -> Option<&mut ListNode<T>> {
#![allow(clippy::debug_assert_with_mut_call)]
// Safety: When the node was inserted it was promised that it is alive
// until it gets removed from the list
unsafe {
let mut head = self.head?;
self.head = head.as_mut().next;
let first_ref = head.as_mut();
match first_ref.next {
None => {
// This was the only node in the list
debug_assert_eq!(Some(first_ref.into()), self.tail);
self.tail = None;
}
Some(mut next) => {
next.as_mut().prev = None;
}
}
first_ref.prev = None;
first_ref.next = None;
Some(&mut *(first_ref as *mut ListNode<T>))
}
}
/// Removes the last node from the linked list and returns it
pub fn remove_last(&mut self) -> Option<&mut ListNode<T>> {
#![allow(clippy::debug_assert_with_mut_call)]
// Safety: When the node was inserted it was promised that it is alive
// until it gets removed from the list
unsafe {
let mut tail = self.tail?;
self.tail = tail.as_mut().prev;
let last_ref = tail.as_mut();
match last_ref.prev {
None => {
// This was the last node in the list
debug_assert_eq!(Some(last_ref.into()), self.head);
self.head = None;
}
Some(mut prev) => {
prev.as_mut().next = None;
}
}
last_ref.prev = None;
last_ref.next = None;
Some(&mut *(last_ref as *mut ListNode<T>))
}
}
/// Returns whether the linked list doesn not contain any node
pub fn is_empty(&self) -> bool {
if self.head.is_some() {
return false;
}
debug_assert!(self.tail.is_none());
true
}
/// Removes the given `node` from the linked list.
/// Returns whether the `node` was removed.
/// It is also only safe if it is known that the `node` is either part of this
/// list, or of no list at all. If `node` is part of another list, the
/// behavior is undefined.
pub unsafe fn remove(&mut self, node: &mut ListNode<T>) -> bool {
#![allow(clippy::debug_assert_with_mut_call)]
match node.prev {
None => {
// This might be the first node in the list. If it is not, the
// node is not in the list at all. Since our precondition is that
// the node must either be in this list or in no list, we check that
// the node is really in no list.
if self.head != Some(node.into()) {
debug_assert!(node.next.is_none());
return false;
}
self.head = node.next;
}
Some(mut prev) => {
debug_assert_eq!(prev.as_ref().next, Some(node.into()));
prev.as_mut().next = node.next;
}
}
match node.next {
None => {
// This must be the last node in our list. Otherwise the list
// is inconsistent.
debug_assert_eq!(self.tail, Some(node.into()));
self.tail = node.prev;
}
Some(mut next) => {
debug_assert_eq!(next.as_mut().prev, Some(node.into()));
next.as_mut().prev = node.prev;
}
}
node.next = None;
node.prev = None;
true
}
/// Drains the list iby calling a callback on each list node
///
/// The method does not return an iterator since stopping or deferring
/// draining the list is not permitted. If the method would push nodes to
/// an iterator we could not guarantee that the nodes do not get utilized
/// after having been removed from the list anymore.
pub fn drain<F>(&mut self, mut func: F)
where
F: FnMut(&mut ListNode<T>),
{
let mut current = self.head;
self.head = None;
self.tail = None;
while let Some(mut node) = current {
// Safety: The nodes have not been removed from the list yet and must
// therefore contain valid data. The nodes can also not be added to
// the list again during iteration, since the list is mutably borrowed.
unsafe {
let node_ref = node.as_mut();
current = node_ref.next;
node_ref.next = None;
node_ref.prev = None;
// Note: We do not reset the pointers from the next element in the
// list to the current one since we will iterate over the whole
// list anyway, and therefore clean up all pointers.
func(node_ref);
}
}
}
/// Drains the list in reverse order by calling a callback on each list node
///
/// The method does not return an iterator since stopping or deferring
/// draining the list is not permitted. If the method would push nodes to
/// an iterator we could not guarantee that the nodes do not get utilized
/// after having been removed from the list anymore.
pub fn reverse_drain<F>(&mut self, mut func: F)
where
F: FnMut(&mut ListNode<T>),
{
let mut current = self.tail;
self.head = None;
self.tail = None;
while let Some(mut node) = current {
// Safety: The nodes have not been removed from the list yet and must
// therefore contain valid data. The nodes can also not be added to
// the list again during iteration, since the list is mutably borrowed.
unsafe {
let node_ref = node.as_mut();
current = node_ref.prev;
node_ref.next = None;
node_ref.prev = None;
// Note: We do not reset the pointers from the next element in the
// list to the current one since we will iterate over the whole
// list anyway, and therefore clean up all pointers.
func(node_ref);
}
}
}
}
#[cfg(all(test, feature = "std"))] // Tests make use of Vec at the moment
mod tests {
use super::*;
fn collect_list<T: Copy>(mut list: LinkedList<T>) -> Vec<T> {
let mut result = Vec::new();
list.drain(|node| {
result.push(**node);
});
result
}
fn collect_reverse_list<T: Copy>(mut list: LinkedList<T>) -> Vec<T> {
let mut result = Vec::new();
list.reverse_drain(|node| {
result.push(**node);
});
result
}
unsafe fn add_nodes(list: &mut LinkedList<i32>, nodes: &mut [&mut ListNode<i32>]) {
for node in nodes.iter_mut() {
list.add_front(node);
}
}
unsafe fn assert_clean<T>(node: &mut ListNode<T>) {
assert!(node.next.is_none());
assert!(node.prev.is_none());
}
#[test]
fn insert_and_iterate() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut setup = |list: &mut LinkedList<i32>| {
assert_eq!(true, list.is_empty());
list.add_front(&mut c);
assert_eq!(31, **list.peek_first().unwrap());
assert_eq!(false, list.is_empty());
list.add_front(&mut b);
assert_eq!(7, **list.peek_first().unwrap());
list.add_front(&mut a);
assert_eq!(5, **list.peek_first().unwrap());
};
let mut list = LinkedList::new();
setup(&mut list);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31].to_vec(), items);
let mut list = LinkedList::new();
setup(&mut list);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([31, 7, 5].to_vec(), items);
}
}
#[test]
fn add_sorted() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut d = ListNode::new(99);
let mut list = LinkedList::new();
list.add_sorted(&mut a);
let items: Vec<i32> = collect_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
list.add_sorted(&mut a);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut c, &mut b]);
list.add_sorted(&mut a);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31, 99].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut c, &mut b]);
list.add_sorted(&mut a);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([99, 31, 7, 5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut c, &mut a]);
list.add_sorted(&mut b);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31, 99].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut c, &mut a]);
list.add_sorted(&mut b);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([99, 31, 7, 5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut b, &mut a]);
list.add_sorted(&mut c);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31, 99].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut d, &mut b, &mut a]);
list.add_sorted(&mut c);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([99, 31, 7, 5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
list.add_sorted(&mut d);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31, 99].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
list.add_sorted(&mut d);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([99, 31, 7, 5].to_vec(), items);
}
}
#[test]
fn drain_and_collect() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let taken_items: Vec<i32> = collect_list(list);
assert_eq!([5, 7, 31].to_vec(), taken_items);
}
}
#[test]
fn peek_last() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let last = list.peek_last();
assert_eq!(31, **last.unwrap());
list.remove_last();
let last = list.peek_last();
assert_eq!(7, **last.unwrap());
list.remove_last();
let last = list.peek_last();
assert_eq!(5, **last.unwrap());
list.remove_last();
let last = list.peek_last();
assert!(last.is_none());
}
}
#[test]
fn remove_first() {
unsafe {
// We iterate forward and backwards through the manipulated lists
// to make sure pointers in both directions are still ok.
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_list(list);
assert_eq!([7, 31].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([31, 7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_list(list);
assert_eq!([7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(list.is_empty());
let items: Vec<i32> = collect_list(list);
assert!(items.is_empty());
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
let removed = list.remove_first().unwrap();
assert_clean(removed);
assert!(list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert!(items.is_empty());
}
}
#[test]
fn remove_last() {
unsafe {
// We iterate forward and backwards through the manipulated lists
// to make sure pointers in both directions are still ok.
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([7, 5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(!list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(list.is_empty());
let items: Vec<i32> = collect_list(list);
assert!(items.is_empty());
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
let removed = list.remove_last().unwrap();
assert_clean(removed);
assert!(list.is_empty());
let items: Vec<i32> = collect_reverse_list(list);
assert!(items.is_empty());
}
}
#[test]
fn remove_by_address() {
unsafe {
let mut a = ListNode::new(5);
let mut b = ListNode::new(7);
let mut c = ListNode::new(31);
{
// Remove first
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
// a should be no longer there and can't be removed twice
assert_eq!(false, list.remove(&mut a));
assert_eq!(Some((&mut b).into()), list.head);
assert_eq!(Some((&mut c).into()), b.next);
assert_eq!(Some((&mut b).into()), c.prev);
let items: Vec<i32> = collect_list(list);
assert_eq!([7, 31].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
// a should be no longer there and can't be removed twice
assert_eq!(false, list.remove(&mut a));
assert_eq!(Some((&mut c).into()), b.next);
assert_eq!(Some((&mut b).into()), c.prev);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([31, 7].to_vec(), items);
}
{
// Remove middle
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut b));
assert_clean((&mut b).into());
assert_eq!(Some((&mut c).into()), a.next);
assert_eq!(Some((&mut a).into()), c.prev);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 31].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut b));
assert_clean((&mut b).into());
assert_eq!(Some((&mut c).into()), a.next);
assert_eq!(Some((&mut a).into()), c.prev);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([31, 5].to_vec(), items);
}
{
// Remove last
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut c));
assert_clean((&mut c).into());
assert!(b.next.is_none());
assert_eq!(Some((&mut b).into()), list.tail);
let items: Vec<i32> = collect_list(list);
assert_eq!([5, 7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut c, &mut b, &mut a]);
assert_eq!(true, list.remove(&mut c));
assert_clean((&mut c).into());
assert!(b.next.is_none());
assert_eq!(Some((&mut b).into()), list.tail);
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([7, 5].to_vec(), items);
}
{
// Remove first of two
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
// a should be no longer there and can't be removed twice
assert_eq!(false, list.remove(&mut a));
assert_eq!(Some((&mut b).into()), list.head);
assert_eq!(Some((&mut b).into()), list.tail);
assert!(b.next.is_none());
assert!(b.prev.is_none());
let items: Vec<i32> = collect_list(list);
assert_eq!([7].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
// a should be no longer there and can't be removed twice
assert_eq!(false, list.remove(&mut a));
assert_eq!(Some((&mut b).into()), list.head);
assert_eq!(Some((&mut b).into()), list.tail);
assert!(b.next.is_none());
assert!(b.prev.is_none());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([7].to_vec(), items);
}
{
// Remove last of two
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
assert_eq!(true, list.remove(&mut b));
assert_clean((&mut b).into());
assert_eq!(Some((&mut a).into()), list.head);
assert_eq!(Some((&mut a).into()), list.tail);
assert!(a.next.is_none());
assert!(a.prev.is_none());
let items: Vec<i32> = collect_list(list);
assert_eq!([5].to_vec(), items);
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut b, &mut a]);
assert_eq!(true, list.remove(&mut b));
assert_clean((&mut b).into());
assert_eq!(Some((&mut a).into()), list.head);
assert_eq!(Some((&mut a).into()), list.tail);
assert!(a.next.is_none());
assert!(a.prev.is_none());
let items: Vec<i32> = collect_reverse_list(list);
assert_eq!([5].to_vec(), items);
}
{
// Remove last item
let mut list = LinkedList::new();
add_nodes(&mut list, &mut [&mut a]);
assert_eq!(true, list.remove(&mut a));
assert_clean((&mut a).into());
assert!(list.head.is_none());
assert!(list.tail.is_none());
let items: Vec<i32> = collect_list(list);
assert!(items.is_empty());
}
{
// Remove missing
let mut list = LinkedList::new();
list.add_front(&mut b);
list.add_front(&mut a);
assert_eq!(false, list.remove(&mut c));
}
}
}
}
+2 -4
View File
@@ -1,12 +1,10 @@
//! Synchronization primitives
mod cancellation_token;
pub use cancellation_token::{CancellationToken, WaitForCancellationFuture};
mod intrusive_double_linked_list;
pub use cancellation_token::{guard::DropGuard, CancellationToken, WaitForCancellationFuture};
mod mpsc;
pub use mpsc::PollSender;
pub use mpsc::{PollSendError, PollSender};
mod poll_semaphore;
pub use poll_semaphore::PollSemaphore;
+206 -144
View File
@@ -1,221 +1,283 @@
use futures_core::ready;
use futures_sink::Sink;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::sync::mpsc::{error::SendError, Sender};
use std::{fmt, mem};
use tokio::sync::mpsc::OwnedPermit;
use tokio::sync::mpsc::Sender;
use super::ReusableBoxFuture;
// This implementation was chosen over something based on permits because to get a
// `tokio::sync::mpsc::Permit` out of the `inner` future, you must transmute the
// lifetime on the permit to `'static`.
/// Error returned by the `PollSender` when the channel is closed.
#[derive(Debug)]
pub struct PollSendError<T>(Option<T>);
impl<T> PollSendError<T> {
/// Consumes the stored value, if any.
///
/// If this error was encountered when calling `start_send`/`send_item`, this will be the item
/// that the caller attempted to send. Otherwise, it will be `None`.
pub fn into_inner(self) -> Option<T> {
self.0
}
}
impl<T> fmt::Display for PollSendError<T> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "channel closed")
}
}
impl<T: fmt::Debug> std::error::Error for PollSendError<T> {}
#[derive(Debug)]
enum State<T> {
Idle(Sender<T>),
Acquiring,
ReadyToSend(OwnedPermit<T>),
Closed,
}
/// A wrapper around [`mpsc::Sender`] that can be polled.
///
/// [`mpsc::Sender`]: tokio::sync::mpsc::Sender
#[derive(Debug)]
pub struct PollSender<T> {
/// is none if closed
sender: Option<Arc<Sender<T>>>,
is_sending: bool,
inner: ReusableBoxFuture<Result<(), SendError<T>>>,
sender: Option<Sender<T>>,
state: State<T>,
acquire: ReusableBoxFuture<'static, Result<OwnedPermit<T>, PollSendError<T>>>,
}
// By reusing the same async fn for both Some and None, we make sure every
// future passed to ReusableBoxFuture has the same underlying type, and hence
// the same size and alignment.
async fn make_future<T>(data: Option<(Arc<Sender<T>>, T)>) -> Result<(), SendError<T>> {
// Creates a future for acquiring a permit from the underlying channel. This is used to ensure
// there's capacity for a send to complete.
//
// By reusing the same async fn for both `Some` and `None`, we make sure every future passed to
// ReusableBoxFuture has the same underlying type, and hence the same size and alignment.
async fn make_acquire_future<T>(
data: Option<Sender<T>>,
) -> Result<OwnedPermit<T>, PollSendError<T>> {
match data {
Some((sender, value)) => sender.send(value).await,
None => unreachable!(
"This future should not be pollable, as is_sending should be set to false."
),
Some(sender) => sender
.reserve_owned()
.await
.map_err(|_| PollSendError(None)),
None => unreachable!("this future should not be pollable in this state"),
}
}
impl<T: Send + 'static> PollSender<T> {
/// Create a new `PollSender`.
/// Creates a new `PollSender`.
pub fn new(sender: Sender<T>) -> Self {
Self {
sender: Some(Arc::new(sender)),
is_sending: false,
inner: ReusableBoxFuture::new(make_future(None)),
sender: Some(sender.clone()),
state: State::Idle(sender),
acquire: ReusableBoxFuture::new(make_acquire_future(None)),
}
}
/// Start sending a new item.
fn take_state(&mut self) -> State<T> {
mem::replace(&mut self.state, State::Closed)
}
/// Attempts to prepare the sender to receive a value.
///
/// This method panics if a send is currently in progress. To ensure that no
/// send is in progress, call `poll_send_done` first until it returns
/// `Poll::Ready`.
/// This method must be called and return `Poll::Ready(Ok(()))` prior to each call to
/// `send_item`.
///
/// If this method returns an error, that indicates that the channel is
/// closed. Note that this method is not guaranteed to return an error if
/// the channel is closed, but in that case the error would be reported by
/// the first call to `poll_send_done`.
pub fn start_send(&mut self, value: T) -> Result<(), SendError<T>> {
if self.is_sending {
panic!("start_send called while not ready.");
}
match self.sender.clone() {
Some(sender) => {
self.inner.set(make_future(Some((sender, value))));
self.is_sending = true;
Ok(())
/// This method returns `Poll::Ready` once the underlying channel is ready to receive a value,
/// by reserving a slot in the channel for the item to be sent. If this method returns
/// `Poll::Pending`, the current task is registered to be notified (via
/// `cx.waker().wake_by_ref()`) when `poll_reserve` should be called again.
///
/// # Errors
///
/// If the channel is closed, an error will be returned. This is a permanent state.
pub fn poll_reserve(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), PollSendError<T>>> {
loop {
let (result, next_state) = match self.take_state() {
State::Idle(sender) => {
// Start trying to acquire a permit to reserve a slot for our send, and
// immediately loop back around to poll it the first time.
self.acquire.set(make_acquire_future(Some(sender)));
(None, State::Acquiring)
}
State::Acquiring => match self.acquire.poll(cx) {
// Channel has capacity.
Poll::Ready(Ok(permit)) => {
(Some(Poll::Ready(Ok(()))), State::ReadyToSend(permit))
}
// Channel is closed.
Poll::Ready(Err(e)) => (Some(Poll::Ready(Err(e))), State::Closed),
// Channel doesn't have capacity yet, so we need to wait.
Poll::Pending => (Some(Poll::Pending), State::Acquiring),
},
// We're closed, either by choice or because the underlying sender was closed.
s @ State::Closed => (Some(Poll::Ready(Err(PollSendError(None)))), s),
// We're already ready to send an item.
s @ State::ReadyToSend(_) => (Some(Poll::Ready(Ok(()))), s),
};
self.state = next_state;
if let Some(result) = result {
return result;
}
None => Err(SendError(value)),
}
}
/// If a send is in progress, poll for its completion. If no send is in progress,
/// this method returns `Poll::Ready(Ok(()))`.
/// Sends an item to the channel.
///
/// This method can return the following values:
/// Before calling `send_item`, `poll_reserve` must be called with a successful return
/// value of `Poll::Ready(Ok(()))`.
///
/// - `Poll::Ready(Ok(()))` if the in-progress send has been completed, or there is
/// no send in progress (even if the channel is closed).
/// - `Poll::Ready(Err(err))` if the in-progress send failed because the channel has
/// been closed.
/// - `Poll::Pending` if a send is in progress, but it could not complete now.
/// # Errors
///
/// When this method returns `Poll::Pending`, the current task is scheduled
/// to receive a wakeup when the message is sent, or when the entire channel
/// is closed (but not if just this sender is closed by
/// `close_this_sender`). Note that on multiple calls to `poll_send_done`,
/// only the `Waker` from the `Context` passed to the most recent call is
/// scheduled to receive a wakeup.
/// If the channel is closed, an error will be returned. This is a permanent state.
///
/// If this method returns `Poll::Ready`, then `start_send` is guaranteed to
/// not panic.
pub fn poll_send_done(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), SendError<T>>> {
if !self.is_sending {
return Poll::Ready(Ok(()));
}
/// # Panics
///
/// If `poll_reserve` was not successfully called prior to calling `send_item`, then this method
/// will panic.
pub fn send_item(&mut self, value: T) -> Result<(), PollSendError<T>> {
let (result, next_state) = match self.take_state() {
State::Idle(_) | State::Acquiring => {
panic!("`send_item` called without first calling `poll_reserve`")
}
// We have a permit to send our item, so go ahead, which gets us our sender back.
State::ReadyToSend(permit) => (Ok(()), State::Idle(permit.send(value))),
// We're closed, either by choice or because the underlying sender was closed.
State::Closed => (Err(PollSendError(Some(value))), State::Closed),
};
let result = self.inner.poll(cx);
if result.is_ready() {
self.is_sending = false;
}
if let Poll::Ready(Err(_)) = &result {
self.sender = None;
}
// Handle deferred closing if `close` was called between `poll_reserve` and `send_item`.
self.state = if self.sender.is_some() {
next_state
} else {
State::Closed
};
result
}
/// Check whether the channel is ready to send more messages now.
/// Checks whether this sender is been closed.
///
/// If this method returns `true`, then `start_send` is guaranteed to not
/// panic.
///
/// If the channel is closed, this method returns `true`.
pub fn is_ready(&self) -> bool {
!self.is_sending
}
/// Check whether the channel has been closed.
/// The underlying channel that this sender was wrapping may still be open.
pub fn is_closed(&self) -> bool {
match &self.sender {
Some(sender) => sender.is_closed(),
None => true,
}
matches!(self.state, State::Closed) || self.sender.is_none()
}
/// Clone the underlying `Sender`.
/// Gets a reference to the `Sender` of the underlying channel.
///
/// If this method returns `None`, then the channel is closed. (But it is
/// not guaranteed to return `None` if the channel is closed.)
pub fn clone_inner(&self) -> Option<Sender<T>> {
self.sender.as_ref().map(|sender| (&**sender).clone())
/// If `PollSender` has been closed, `None` is returned. The underlying channel that this sender
/// was wrapping may still be open.
pub fn get_ref(&self) -> Option<&Sender<T>> {
self.sender.as_ref()
}
/// Access the underlying `Sender`.
/// Closes this sender.
///
/// If this method returns `None`, then the channel is closed. (But it is
/// not guaranteed to return `None` if the channel is closed.)
pub fn inner_ref(&self) -> Option<&Sender<T>> {
self.sender.as_deref()
}
// This operation is supported because it is required by the Sink trait.
/// Close this sender. No more messages can be sent from this sender.
/// No more messages will be able to be sent from this sender, but the underlying channel will
/// remain open until all senders have dropped, or until the [`Receiver`] closes the channel.
///
/// Note that this only closes the channel from the view-point of this
/// sender. The channel remains open until all senders have gone away, or
/// until the [`Receiver`] closes the channel.
///
/// If there is a send in progress when this method is called, that send is
/// unaffected by this operation, and `poll_send_done` can still be called
/// to complete that send.
/// If a slot was previously reserved by calling `poll_reserve`, then a final call can be made
/// to `send_item` in order to consume the reserved slot. After that, no further sends will be
/// possible. If you do not intend to send another item, you can release the reserved slot back
/// to the underlying sender by calling [`abort_send`].
///
/// [`abort_send`]: crate::sync::PollSender::abort_send
/// [`Receiver`]: tokio::sync::mpsc::Receiver
pub fn close_this_sender(&mut self) {
pub fn close(&mut self) {
// Mark ourselves officially closed by dropping our main sender.
self.sender = None;
// If we're already idle, closed, or we haven't yet reserved a slot, we can quickly
// transition to the closed state. Otherwise, leave the existing permit in place for the
// caller if they want to complete the send.
match self.state {
State::Idle(_) => self.state = State::Closed,
State::Acquiring => {
self.acquire.set(make_acquire_future(None));
self.state = State::Closed;
}
_ => {}
}
}
/// Abort the current in-progress send, if any.
/// Aborts the current in-progress send, if any.
///
/// Returns `true` if a send was aborted.
/// Returns `true` if a send was aborted. If the sender was closed prior to calling
/// `abort_send`, then the sender will remain in the closed state, otherwise the sender will be
/// ready to attempt another send.
pub fn abort_send(&mut self) -> bool {
if self.is_sending {
self.inner.set(make_future(None));
self.is_sending = false;
true
} else {
false
}
// We may have been closed in the meantime, after a call to `poll_reserve` already
// succeeded. We'll check if `self.sender` is `None` to see if we should transition to the
// closed state when we actually abort a send, rather than resetting ourselves back to idle.
let (result, next_state) = match self.take_state() {
// We're currently trying to reserve a slot to send into.
State::Acquiring => {
// Replacing the future drops the in-flight one.
self.acquire.set(make_acquire_future(None));
// If we haven't closed yet, we have to clone our stored sender since we have no way
// to get it back from the acquire future we just dropped.
let state = match self.sender.clone() {
Some(sender) => State::Idle(sender),
None => State::Closed,
};
(true, state)
}
// We got the permit. If we haven't closed yet, get the sender back.
State::ReadyToSend(permit) => {
let state = if self.sender.is_some() {
State::Idle(permit.release())
} else {
State::Closed
};
(true, state)
}
s => (false, s),
};
self.state = next_state;
result
}
}
impl<T> Clone for PollSender<T> {
/// Clones this `PollSender`. The resulting clone will not have any
/// in-progress send operations, even if the current `PollSender` does.
/// Clones this `PollSender`.
///
/// The resulting `PollSender` will have an initial state identical to calling `PollSender::new`.
fn clone(&self) -> PollSender<T> {
let (sender, state) = match self.sender.clone() {
Some(sender) => (Some(sender.clone()), State::Idle(sender)),
None => (None, State::Closed),
};
Self {
sender: self.sender.clone(),
is_sending: false,
inner: ReusableBoxFuture::new(async { unreachable!() }),
sender,
state,
// We don't use `make_acquire_future` here because our relaxed bounds on `T` are not
// compatible with the transitive bounds required by `Sender<T>`.
acquire: ReusableBoxFuture::new(async { unreachable!() }),
}
}
}
impl<T: Send + 'static> Sink<T> for PollSender<T> {
type Error = SendError<T>;
type Error = PollSendError<T>;
/// This is equivalent to calling [`poll_send_done`].
///
/// [`poll_send_done`]: PollSender::poll_send_done
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::into_inner(self).poll_send_done(cx)
Pin::into_inner(self).poll_reserve(cx)
}
/// This is equivalent to calling [`poll_send_done`].
///
/// [`poll_send_done`]: PollSender::poll_send_done
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::into_inner(self).poll_send_done(cx)
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
/// This is equivalent to calling [`start_send`].
///
/// [`start_send`]: PollSender::start_send
fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
Pin::into_inner(self).start_send(item)
Pin::into_inner(self).send_item(item)
}
/// This method will first flush the `PollSender`, and then close it by
/// calling [`close_this_sender`].
///
/// If a send fails while flushing because the [`Receiver`] has gone away,
/// then this function returns an error. The channel is still successfully
/// closed in this situation.
///
/// [`close_this_sender`]: PollSender::close_this_sender
/// [`Receiver`]: tokio::sync::mpsc::Receiver
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
ready!(self.as_mut().poll_flush(cx))?;
Pin::into_inner(self).close_this_sender();
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::into_inner(self).close();
Poll::Ready(Ok(()))
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ use super::ReusableBoxFuture;
/// [`Semaphore`]: tokio::sync::Semaphore
pub struct PollSemaphore {
semaphore: Arc<Semaphore>,
permit_fut: Option<ReusableBoxFuture<Result<OwnedSemaphorePermit, AcquireError>>>,
permit_fut: Option<ReusableBoxFuture<'static, Result<OwnedSemaphorePermit, AcquireError>>>,
}
impl PollSemaphore {
+105 -85
View File
@@ -1,33 +1,29 @@
use std::alloc::Layout;
use std::fmt;
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::marker::PhantomData;
use std::mem::{self, ManuallyDrop};
use std::pin::Pin;
use std::ptr::{self, NonNull};
use std::ptr;
use std::task::{Context, Poll};
use std::{fmt, panic};
/// A reusable `Pin<Box<dyn Future<Output = T> + Send>>`.
/// A reusable `Pin<Box<dyn Future<Output = T> + Send + 'a>>`.
///
/// This type lets you replace the future stored in the box without
/// reallocating when the size and alignment permits this.
pub struct ReusableBoxFuture<T> {
boxed: NonNull<dyn Future<Output = T> + Send>,
pub struct ReusableBoxFuture<'a, T> {
boxed: Pin<Box<dyn Future<Output = T> + Send + 'a>>,
}
impl<T> ReusableBoxFuture<T> {
impl<'a, T> ReusableBoxFuture<'a, T> {
/// Create a new `ReusableBoxFuture<T>` containing the provided future.
pub fn new<F>(future: F) -> Self
where
F: Future<Output = T> + Send + 'static,
F: Future<Output = T> + Send + 'a,
{
let boxed: Box<dyn Future<Output = T> + Send> = Box::new(future);
let boxed = Box::into_raw(boxed);
// SAFETY: Box::into_raw does not return null pointers.
let boxed = unsafe { NonNull::new_unchecked(boxed) };
Self { boxed }
Self {
boxed: Box::pin(future),
}
}
/// Replace the future currently stored in this box.
@@ -36,7 +32,7 @@ impl<T> ReusableBoxFuture<T> {
/// different from the layout of the currently stored future.
pub fn set<F>(&mut self, future: F)
where
F: Future<Output = T> + Send + 'static,
F: Future<Output = T> + Send + 'a,
{
if let Err(future) = self.try_set(future) {
*self = Self::new(future);
@@ -50,64 +46,31 @@ impl<T> ReusableBoxFuture<T> {
/// future.
pub fn try_set<F>(&mut self, future: F) -> Result<(), F>
where
F: Future<Output = T> + Send + 'static,
F: Future<Output = T> + Send + 'a,
{
// SAFETY: The pointer is not dangling.
let self_layout = {
let dyn_future: &(dyn Future<Output = T> + Send) = unsafe { self.boxed.as_ref() };
Layout::for_value(dyn_future)
};
if Layout::new::<F>() == self_layout {
// SAFETY: We just checked that the layout of F is correct.
unsafe {
self.set_same_layout(future);
}
Ok(())
} else {
Err(future)
// If we try to inline the contents of this function, the type checker complains because
// the bound `T: 'a` is not satisfied in the call to `pending()`. But by putting it in an
// inner function that doesn't have `T` as a generic parameter, we implicitly get the bound
// `F::Output: 'a` transitively through `F: 'a`, allowing us to call `pending()`.
#[inline(always)]
fn real_try_set<'a, F>(
this: &mut ReusableBoxFuture<'a, F::Output>,
future: F,
) -> Result<(), F>
where
F: Future + Send + 'a,
{
// future::Pending<T> is a ZST so this never allocates.
let boxed = mem::replace(&mut this.boxed, Box::pin(Pending(PhantomData)));
reuse_pin_box(boxed, future, |boxed| this.boxed = Pin::from(boxed))
}
}
/// Set the current future.
///
/// # Safety
///
/// This function requires that the layout of the provided future is the
/// same as `self.layout`.
unsafe fn set_same_layout<F>(&mut self, future: F)
where
F: Future<Output = T> + Send + 'static,
{
// Drop the existing future, catching any panics.
let result = panic::catch_unwind(AssertUnwindSafe(|| {
ptr::drop_in_place(self.boxed.as_ptr());
}));
// Overwrite the future behind the pointer. This is safe because the
// allocation was allocated with the same size and alignment as the type F.
let self_ptr: *mut F = self.boxed.as_ptr() as *mut F;
ptr::write(self_ptr, future);
// Update the vtable of self.boxed. The pointer is not null because we
// just got it from self.boxed, which is not null.
self.boxed = NonNull::new_unchecked(self_ptr);
// If the old future's destructor panicked, resume unwinding.
match result {
Ok(()) => {}
Err(payload) => {
panic::resume_unwind(payload);
}
}
real_try_set(self, future)
}
/// Get a pinned reference to the underlying future.
pub fn get_pin(&mut self) -> Pin<&mut (dyn Future<Output = T> + Send)> {
// SAFETY: The user of this box cannot move the box, and we do not move it
// either.
unsafe { Pin::new_unchecked(self.boxed.as_mut()) }
self.boxed.as_mut()
}
/// Poll the future stored inside this box.
@@ -116,7 +79,7 @@ impl<T> ReusableBoxFuture<T> {
}
}
impl<T> Future for ReusableBoxFuture<T> {
impl<T> Future for ReusableBoxFuture<'_, T> {
type Output = T;
/// Poll the future stored inside this box.
@@ -125,27 +88,84 @@ impl<T> Future for ReusableBoxFuture<T> {
}
}
// The future stored inside ReusableBoxFuture<T> must be Send.
unsafe impl<T> Send for ReusableBoxFuture<T> {}
// The only method called on self.boxed is poll, which takes &mut self, so this
// struct being Sync does not permit any invalid access to the Future, even if
// the future is not Sync.
unsafe impl<T> Sync for ReusableBoxFuture<T> {}
unsafe impl<T> Sync for ReusableBoxFuture<'_, T> {}
// Just like a Pin<Box<dyn Future>> is always Unpin, so is this type.
impl<T> Unpin for ReusableBoxFuture<T> {}
impl<T> Drop for ReusableBoxFuture<T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.boxed.as_ptr()));
}
}
}
impl<T> fmt::Debug for ReusableBoxFuture<T> {
impl<T> fmt::Debug for ReusableBoxFuture<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReusableBoxFuture").finish()
}
}
fn reuse_pin_box<T: ?Sized, U, O, F>(boxed: Pin<Box<T>>, new_value: U, callback: F) -> Result<O, U>
where
F: FnOnce(Box<U>) -> O,
{
let layout = Layout::for_value::<T>(&*boxed);
if layout != Layout::new::<U>() {
return Err(new_value);
}
// SAFETY: We don't ever construct a non-pinned reference to the old `T` from now on, and we
// always drop the `T`.
let raw: *mut T = Box::into_raw(unsafe { Pin::into_inner_unchecked(boxed) });
// When dropping the old value panics, we still want to call `callback` — so move the rest of
// the code into a guard type.
let guard = CallOnDrop::new(|| {
let raw: *mut U = raw.cast::<U>();
unsafe { raw.write(new_value) };
// SAFETY:
// - `T` and `U` have the same layout.
// - `raw` comes from a `Box` that uses the same allocator as this one.
// - `raw` points to a valid instance of `U` (we just wrote it in).
let boxed = unsafe { Box::from_raw(raw) };
callback(boxed)
});
// Drop the old value.
unsafe { ptr::drop_in_place(raw) };
// Run the rest of the code.
Ok(guard.call())
}
struct CallOnDrop<O, F: FnOnce() -> O> {
f: ManuallyDrop<F>,
}
impl<O, F: FnOnce() -> O> CallOnDrop<O, F> {
fn new(f: F) -> Self {
let f = ManuallyDrop::new(f);
Self { f }
}
fn call(self) -> O {
let mut this = ManuallyDrop::new(self);
let f = unsafe { ManuallyDrop::take(&mut this.f) };
f()
}
}
impl<O, F: FnOnce() -> O> Drop for CallOnDrop<O, F> {
fn drop(&mut self) {
let f = unsafe { ManuallyDrop::take(&mut self.f) };
f();
}
}
/// The same as `std::future::Pending<T>`; we can't use that type directly because on rustc
/// versions <1.60 it didn't unconditionally implement `Send`.
// FIXME: use `std::future::Pending<T>` once the MSRV is >=1.60
struct Pending<T>(PhantomData<fn() -> T>);
impl<T> Future for Pending<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Pending
}
}
+810
View File
@@ -0,0 +1,810 @@
use hashbrown::hash_map::RawEntryMut;
use hashbrown::HashMap;
use std::borrow::Borrow;
use std::collections::hash_map::RandomState;
use std::fmt;
use std::future::Future;
use std::hash::{BuildHasher, Hash, Hasher};
use tokio::runtime::Handle;
use tokio::task::{AbortHandle, Id, JoinError, JoinSet, LocalSet};
/// A collection of tasks spawned on a Tokio runtime, associated with hash map
/// keys.
///
/// This type is very similar to the [`JoinSet`] type in `tokio::task`, with the
/// addition of a set of keys associated with each task. These keys allow
/// [cancelling a task][abort] or [multiple tasks][abort_matching] in the
/// `JoinMap` based on their keys, or [test whether a task corresponding to a
/// given key exists][contains] in the `JoinMap`.
///
/// In addition, when tasks in the `JoinMap` complete, they will return the
/// associated key along with the value returned by the task, if any.
///
/// A `JoinMap` can be used to await the completion of some or all of the tasks
/// in the map. The map is not ordered, and the tasks will be returned in the
/// order they complete.
///
/// All of the tasks must have the same return type `V`.
///
/// When the `JoinMap` is dropped, all tasks in the `JoinMap` are immediately aborted.
///
/// **Note**: This type depends on Tokio's [unstable API][unstable]. See [the
/// documentation on unstable features][unstable] for details on how to enable
/// Tokio's unstable features.
///
/// # Examples
///
/// Spawn multiple tasks and wait for them:
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// #[tokio::main]
/// async fn main() {
/// let mut map = JoinMap::new();
///
/// for i in 0..10 {
/// // Spawn a task on the `JoinMap` with `i` as its key.
/// map.spawn(i, async move { /* ... */ });
/// }
///
/// let mut seen = [false; 10];
///
/// // When a task completes, `join_one` returns the task's key along
/// // with its output.
/// while let Some((key, res)) = map.join_one().await {
/// seen[key] = true;
/// assert!(res.is_ok(), "task {} completed successfully!", key);
/// }
///
/// for i in 0..10 {
/// assert!(seen[i]);
/// }
/// }
/// ```
///
/// Cancel tasks based on their keys:
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// #[tokio::main]
/// async fn main() {
/// let mut map = JoinMap::new();
///
/// map.spawn("hello world", async move { /* ... */ });
/// map.spawn("goodbye world", async move { /* ... */});
///
/// // Look up the "goodbye world" task in the map and abort it.
/// let aborted = map.abort("goodbye world");
///
/// // `JoinMap::abort` returns `true` if a task existed for the
/// // provided key.
/// assert!(aborted);
///
/// while let Some((key, res)) = map.join_one().await {
/// if key == "goodbye world" {
/// // The aborted task should complete with a cancelled `JoinError`.
/// assert!(res.unwrap_err().is_cancelled());
/// } else {
/// // Other tasks should complete normally.
/// assert!(res.is_ok());
/// }
/// }
/// }
/// ```
///
/// [`JoinSet`]: tokio::task::JoinSet
/// [unstable]: tokio#unstable-features
/// [abort]: fn@Self::abort
/// [abort_matching]: fn@Self::abort_matching
/// [contains]: fn@Self::contains_key
#[cfg_attr(docsrs, doc(cfg(all(feature = "rt", tokio_unstable))))]
pub struct JoinMap<K, V, S = RandomState> {
/// A map of the [`AbortHandle`]s of the tasks spawned on this `JoinMap`,
/// indexed by their keys and task IDs.
///
/// The [`Key`] type contains both the task's `K`-typed key provided when
/// spawning tasks, and the task's IDs. The IDs are stored here to resolve
/// hash collisions when looking up tasks based on their pre-computed hash
/// (as stored in the `hashes_by_task` map).
tasks_by_key: HashMap<Key<K>, AbortHandle, S>,
/// A map from task IDs to the hash of the key associated with that task.
///
/// This map is used to perform reverse lookups of tasks in the
/// `tasks_by_key` map based on their task IDs. When a task terminates, the
/// ID is provided to us by the `JoinSet`, so we can look up the hash value
/// of that task's key, and then remove it from the `tasks_by_key` map using
/// the raw hash code, resolving collisions by comparing task IDs.
hashes_by_task: HashMap<Id, u64, S>,
/// The [`JoinSet`] that awaits the completion of tasks spawned on this
/// `JoinMap`.
tasks: JoinSet<V>,
}
/// A [`JoinMap`] key.
///
/// This holds both a `K`-typed key (the actual key as seen by the user), _and_
/// a task ID, so that hash collisions between `K`-typed keys can be resolved
/// using either `K`'s `Eq` impl *or* by checking the task IDs.
///
/// This allows looking up a task using either an actual key (such as when the
/// user queries the map with a key), *or* using a task ID and a hash (such as
/// when removing completed tasks from the map).
#[derive(Debug)]
struct Key<K> {
key: K,
id: Id,
}
impl<K, V> JoinMap<K, V> {
/// Creates a new empty `JoinMap`.
///
/// The `JoinMap` is initially created with a capacity of 0, so it will not
/// allocate until a task is first spawned on it.
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
/// let map: JoinMap<&str, i32> = JoinMap::new();
/// ```
#[inline]
#[must_use]
pub fn new() -> Self {
Self::with_hasher(RandomState::new())
}
/// Creates an empty `JoinMap` with the specified capacity.
///
/// The `JoinMap` will be able to hold at least `capacity` tasks without
/// reallocating.
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
/// let map: JoinMap<&str, i32> = JoinMap::with_capacity(10);
/// ```
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
JoinMap::with_capacity_and_hasher(capacity, Default::default())
}
}
impl<K, V, S: Clone> JoinMap<K, V, S> {
/// Creates an empty `JoinMap` which will use the given hash builder to hash
/// keys.
///
/// The created map has the default initial capacity.
///
/// Warning: `hash_builder` is normally randomly generated, and
/// is designed to allow `JoinMap` to be resistant to attacks that
/// cause many collisions and very poor performance. Setting it
/// manually using this function can expose a DoS attack vector.
///
/// The `hash_builder` passed should implement the [`BuildHasher`] trait for
/// the `JoinMap` to be useful, see its documentation for details.
#[inline]
#[must_use]
pub fn with_hasher(hash_builder: S) -> Self {
Self::with_capacity_and_hasher(0, hash_builder)
}
/// Creates an empty `JoinMap` with the specified capacity, using `hash_builder`
/// to hash the keys.
///
/// The `JoinMap` will be able to hold at least `capacity` elements without
/// reallocating. If `capacity` is 0, the `JoinMap` will not allocate.
///
/// Warning: `hash_builder` is normally randomly generated, and
/// is designed to allow HashMaps to be resistant to attacks that
/// cause many collisions and very poor performance. Setting it
/// manually using this function can expose a DoS attack vector.
///
/// The `hash_builder` passed should implement the [`BuildHasher`] trait for
/// the `JoinMap`to be useful, see its documentation for details.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio_util::task::JoinMap;
/// use std::collections::hash_map::RandomState;
///
/// let s = RandomState::new();
/// let mut map = JoinMap::with_capacity_and_hasher(10, s);
/// map.spawn(1, async move { "hello world!" });
/// # }
/// ```
#[inline]
#[must_use]
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
Self {
tasks_by_key: HashMap::with_capacity_and_hasher(capacity, hash_builder.clone()),
hashes_by_task: HashMap::with_capacity_and_hasher(capacity, hash_builder),
tasks: JoinSet::new(),
}
}
/// Returns the number of tasks currently in the `JoinMap`.
pub fn len(&self) -> usize {
let len = self.tasks_by_key.len();
debug_assert_eq!(len, self.hashes_by_task.len());
len
}
/// Returns whether the `JoinMap` is empty.
pub fn is_empty(&self) -> bool {
let empty = self.tasks_by_key.is_empty();
debug_assert_eq!(empty, self.hashes_by_task.is_empty());
empty
}
/// Returns the number of tasks the map can hold without reallocating.
///
/// This number is a lower bound; the `JoinMap` might be able to hold
/// more, but is guaranteed to be able to hold at least this many.
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// let map: JoinMap<i32, i32> = JoinMap::with_capacity(100);
/// assert!(map.capacity() >= 100);
/// ```
#[inline]
pub fn capacity(&self) -> usize {
let capacity = self.tasks_by_key.capacity();
debug_assert_eq!(capacity, self.hashes_by_task.capacity());
capacity
}
}
impl<K, V, S> JoinMap<K, V, S>
where
K: Hash + Eq,
V: 'static,
S: BuildHasher,
{
/// Spawn the provided task and store it in this `JoinMap` with the provided
/// key.
///
/// If a task previously existed in the `JoinMap` for this key, that task
/// will be cancelled and replaced with the new one. The previous task will
/// be removed from the `JoinMap`; a subsequent call to [`join_one`] will
/// *not* return a cancelled [`JoinError`] for that task.
///
/// # Panics
///
/// This method panics if called outside of a Tokio runtime.
///
/// [`join_one`]: Self::join_one
#[track_caller]
pub fn spawn<F>(&mut self, key: K, task: F)
where
F: Future<Output = V>,
F: Send + 'static,
V: Send,
{
let task = self.tasks.spawn(task);
self.insert(key, task)
}
/// Spawn the provided task on the provided runtime and store it in this
/// `JoinMap` with the provided key.
///
/// If a task previously existed in the `JoinMap` for this key, that task
/// will be cancelled and replaced with the new one. The previous task will
/// be removed from the `JoinMap`; a subsequent call to [`join_one`] will
/// *not* return a cancelled [`JoinError`] for that task.
///
/// [`join_one`]: Self::join_one
#[track_caller]
pub fn spawn_on<F>(&mut self, key: K, task: F, handle: &Handle)
where
F: Future<Output = V>,
F: Send + 'static,
V: Send,
{
let task = self.tasks.spawn_on(task, handle);
self.insert(key, task);
}
/// Spawn the provided task on the current [`LocalSet`] and store it in this
/// `JoinMap` with the provided key.
///
/// If a task previously existed in the `JoinMap` for this key, that task
/// will be cancelled and replaced with the new one. The previous task will
/// be removed from the `JoinMap`; a subsequent call to [`join_one`] will
/// *not* return a cancelled [`JoinError`] for that task.
///
/// # Panics
///
/// This method panics if it is called outside of a `LocalSet`.
///
/// [`LocalSet`]: tokio::task::LocalSet
/// [`join_one`]: Self::join_one
#[track_caller]
pub fn spawn_local<F>(&mut self, key: K, task: F)
where
F: Future<Output = V>,
F: 'static,
{
let task = self.tasks.spawn_local(task);
self.insert(key, task);
}
/// Spawn the provided task on the provided [`LocalSet`] and store it in
/// this `JoinMap` with the provided key.
///
/// If a task previously existed in the `JoinMap` for this key, that task
/// will be cancelled and replaced with the new one. The previous task will
/// be removed from the `JoinMap`; a subsequent call to [`join_one`] will
/// *not* return a cancelled [`JoinError`] for that task.
///
/// [`LocalSet`]: tokio::task::LocalSet
/// [`join_one`]: Self::join_one
#[track_caller]
pub fn spawn_local_on<F>(&mut self, key: K, task: F, local_set: &LocalSet)
where
F: Future<Output = V>,
F: 'static,
{
let task = self.tasks.spawn_local_on(task, local_set);
self.insert(key, task)
}
fn insert(&mut self, key: K, abort: AbortHandle) {
let hash = self.hash(&key);
let id = abort.id();
let map_key = Key {
id: id.clone(),
key,
};
// Insert the new key into the map of tasks by keys.
let entry = self
.tasks_by_key
.raw_entry_mut()
.from_hash(hash, |k| k.key == map_key.key);
match entry {
RawEntryMut::Occupied(mut occ) => {
// There was a previous task spawned with the same key! Cancel
// that task, and remove its ID from the map of hashes by task IDs.
let Key { id: prev_id, .. } = occ.insert_key(map_key);
occ.insert(abort).abort();
let _prev_hash = self.hashes_by_task.remove(&prev_id);
debug_assert_eq!(Some(hash), _prev_hash);
}
RawEntryMut::Vacant(vac) => {
vac.insert(map_key, abort);
}
};
// Associate the key's hash with this task's ID, for looking up tasks by ID.
let _prev = self.hashes_by_task.insert(id, hash);
debug_assert!(_prev.is_none(), "no prior task should have had the same ID");
}
/// Waits until one of the tasks in the map completes and returns its
/// output, along with the key corresponding to that task.
///
/// Returns `None` if the map is empty.
///
/// # Cancel Safety
///
/// This method is cancel safe. If `join_one` is used as the event in a [`tokio::select!`]
/// statement and some other branch completes first, it is guaranteed that no tasks were
/// removed from this `JoinMap`.
///
/// # Returns
///
/// This function returns:
///
/// * `Some((key, Ok(value)))` if one of the tasks in this `JoinMap` has
/// completed. The `value` is the return value of that ask, and `key` is
/// the key associated with the task.
/// * `Some((key, Err(err))` if one of the tasks in this JoinMap` has
/// panicked or been aborted. `key` is the key associated with the task
/// that panicked or was aborted.
/// * `None` if the `JoinMap` is empty.
///
/// [`tokio::select!`]: tokio::select
pub async fn join_one(&mut self) -> Option<(K, Result<V, JoinError>)> {
let (res, id) = match self.tasks.join_one_with_id().await {
Some(Ok((id, output))) => (Ok(output), id),
Some(Err(e)) => {
let id = e.id();
(Err(e), id)
}
None => return None,
};
let key = self.remove_by_id(id)?;
Some((key, res))
}
/// Aborts all tasks and waits for them to finish shutting down.
///
/// Calling this method is equivalent to calling [`abort_all`] and then calling [`join_one`] in
/// a loop until it returns `None`.
///
/// This method ignores any panics in the tasks shutting down. When this call returns, the
/// `JoinMap` will be empty.
///
/// [`abort_all`]: fn@Self::abort_all
/// [`join_one`]: fn@Self::join_one
pub async fn shutdown(&mut self) {
self.abort_all();
while self.join_one().await.is_some() {}
}
/// Abort the task corresponding to the provided `key`.
///
/// If this `JoinMap` contains a task corresponding to `key`, this method
/// will abort that task and return `true`. Otherwise, if no task exists for
/// `key`, this method returns `false`.
///
/// # Examples
///
/// Aborting a task by key:
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut map = JoinMap::new();
///
/// map.spawn("hello world", async move { /* ... */ });
/// map.spawn("goodbye world", async move { /* ... */});
///
/// // Look up the "goodbye world" task in the map and abort it.
/// map.abort("goodbye world");
///
/// while let Some((key, res)) = map.join_one().await {
/// if key == "goodbye world" {
/// // The aborted task should complete with a cancelled `JoinError`.
/// assert!(res.unwrap_err().is_cancelled());
/// } else {
/// // Other tasks should complete normally.
/// assert!(res.is_ok());
/// }
/// }
/// # }
/// ```
///
/// `abort` returns `true` if a task was aborted:
/// ```
/// use tokio_util::task::JoinMap;
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut map = JoinMap::new();
///
/// map.spawn("hello world", async move { /* ... */ });
/// map.spawn("goodbye world", async move { /* ... */});
///
/// // A task for the key "goodbye world" should exist in the map:
/// assert!(map.abort("goodbye world"));
///
/// // Aborting a key that does not exist will return `false`:
/// assert!(!map.abort("goodbye universe"));
/// # }
/// ```
pub fn abort<Q: ?Sized>(&mut self, key: &Q) -> bool
where
Q: Hash + Eq,
K: Borrow<Q>,
{
match self.get_by_key(key) {
Some((_, handle)) => {
handle.abort();
true
}
None => false,
}
}
/// Aborts all tasks with keys matching `predicate`.
///
/// `predicate` is a function called with a reference to each key in the
/// map. If it returns `true` for a given key, the corresponding task will
/// be cancelled.
///
/// # Examples
/// ```
/// use tokio_util::task::JoinMap;
///
/// # // use the current thread rt so that spawned tasks don't
/// # // complete in the background before they can be aborted.
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// let mut map = JoinMap::new();
///
/// map.spawn("hello world", async move {
/// // ...
/// # tokio::task::yield_now().await; // don't complete immediately, get aborted!
/// });
/// map.spawn("goodbye world", async move {
/// // ...
/// # tokio::task::yield_now().await; // don't complete immediately, get aborted!
/// });
/// map.spawn("hello san francisco", async move {
/// // ...
/// # tokio::task::yield_now().await; // don't complete immediately, get aborted!
/// });
/// map.spawn("goodbye universe", async move {
/// // ...
/// # tokio::task::yield_now().await; // don't complete immediately, get aborted!
/// });
///
/// // Abort all tasks whose keys begin with "goodbye"
/// map.abort_matching(|key| key.starts_with("goodbye"));
///
/// let mut seen = 0;
/// while let Some((key, res)) = map.join_one().await {
/// seen += 1;
/// if key.starts_with("goodbye") {
/// // The aborted task should complete with a cancelled `JoinError`.
/// assert!(res.unwrap_err().is_cancelled());
/// } else {
/// // Other tasks should complete normally.
/// assert!(key.starts_with("hello"));
/// assert!(res.is_ok());
/// }
/// }
///
/// // All spawned tasks should have completed.
/// assert_eq!(seen, 4);
/// # }
/// ```
pub fn abort_matching(&mut self, mut predicate: impl FnMut(&K) -> bool) {
// Note: this method iterates over the tasks and keys *without* removing
// any entries, so that the keys from aborted tasks can still be
// returned when calling `join_one` in the future.
for (Key { ref key, .. }, task) in &self.tasks_by_key {
if predicate(key) {
task.abort();
}
}
}
/// Returns `true` if this `JoinMap` contains a task for the provided key.
///
/// If the task has completed, but its output hasn't yet been consumed by a
/// call to [`join_one`], this method will still return `true`.
///
/// [`join_one`]: fn@Self::join_one
pub fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool
where
Q: Hash + Eq,
K: Borrow<Q>,
{
self.get_by_key(key).is_some()
}
/// Returns `true` if this `JoinMap` contains a task with the provided
/// [task ID].
///
/// If the task has completed, but its output hasn't yet been consumed by a
/// call to [`join_one`], this method will still return `true`.
///
/// [`join_one`]: fn@Self::join_one
/// [task ID]: tokio::task::Id
pub fn contains_task(&self, task: &Id) -> bool {
self.get_by_id(task).is_some()
}
/// Reserves capacity for at least `additional` more tasks to be spawned
/// on this `JoinMap` without reallocating for the map of task keys. The
/// collection may reserve more space to avoid frequent reallocations.
///
/// Note that spawning a task will still cause an allocation for the task
/// itself.
///
/// # Panics
///
/// Panics if the new allocation size overflows [`usize`].
///
/// # Examples
///
/// ```
/// use tokio_util::task::JoinMap;
///
/// let mut map: JoinMap<&str, i32> = JoinMap::new();
/// map.reserve(10);
/// ```
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.tasks_by_key.reserve(additional);
self.hashes_by_task.reserve(additional);
}
/// Shrinks the capacity of the `JoinMap` as much as possible. It will drop
/// down as much as possible while maintaining the internal rules
/// and possibly leaving some space in accordance with the resize policy.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio_util::task::JoinMap;
///
/// let mut map: JoinMap<i32, i32> = JoinMap::with_capacity(100);
/// map.spawn(1, async move { 2 });
/// map.spawn(3, async move { 4 });
/// assert!(map.capacity() >= 100);
/// map.shrink_to_fit();
/// assert!(map.capacity() >= 2);
/// # }
/// ```
#[inline]
pub fn shrink_to_fit(&mut self) {
self.hashes_by_task.shrink_to_fit();
self.tasks_by_key.shrink_to_fit();
}
/// Shrinks the capacity of the map with a lower limit. It will drop
/// down no lower than the supplied limit while maintaining the internal rules
/// and possibly leaving some space in accordance with the resize policy.
///
/// If the current capacity is less than the lower limit, this is a no-op.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio_util::task::JoinMap;
///
/// let mut map: JoinMap<i32, i32> = JoinMap::with_capacity(100);
/// map.spawn(1, async move { 2 });
/// map.spawn(3, async move { 4 });
/// assert!(map.capacity() >= 100);
/// map.shrink_to(10);
/// assert!(map.capacity() >= 10);
/// map.shrink_to(0);
/// assert!(map.capacity() >= 2);
/// # }
/// ```
#[inline]
pub fn shrink_to(&mut self, min_capacity: usize) {
self.hashes_by_task.shrink_to(min_capacity);
self.tasks_by_key.shrink_to(min_capacity)
}
/// Look up a task in the map by its key, returning the key and abort handle.
fn get_by_key<'map, Q: ?Sized>(&'map self, key: &Q) -> Option<(&'map Key<K>, &'map AbortHandle)>
where
Q: Hash + Eq,
K: Borrow<Q>,
{
let hash = self.hash(key);
self.tasks_by_key
.raw_entry()
.from_hash(hash, |k| k.key.borrow() == key)
}
/// Look up a task in the map by its task ID, returning the key and abort handle.
fn get_by_id<'map>(&'map self, id: &Id) -> Option<(&'map Key<K>, &'map AbortHandle)> {
let hash = self.hashes_by_task.get(id)?;
self.tasks_by_key
.raw_entry()
.from_hash(*hash, |k| &k.id == id)
}
/// Remove a task from the map by ID, returning the key for that task.
fn remove_by_id(&mut self, id: Id) -> Option<K> {
// Get the hash for the given ID.
let hash = self.hashes_by_task.remove(&id)?;
// Remove the entry for that hash.
let entry = self
.tasks_by_key
.raw_entry_mut()
.from_hash(hash, |k| k.id == id);
let (Key { id: _key_id, key }, handle) = match entry {
RawEntryMut::Occupied(entry) => entry.remove_entry(),
_ => return None,
};
debug_assert_eq!(_key_id, id);
debug_assert_eq!(id, handle.id());
self.hashes_by_task.remove(&id);
Some(key)
}
/// Returns the hash for a given key.
#[inline]
fn hash<Q: ?Sized>(&self, key: &Q) -> u64
where
Q: Hash,
{
let mut hasher = self.tasks_by_key.hasher().build_hasher();
key.hash(&mut hasher);
hasher.finish()
}
}
impl<K, V, S> JoinMap<K, V, S>
where
V: 'static,
{
/// Aborts all tasks on this `JoinMap`.
///
/// This does not remove the tasks from the `JoinMap`. To wait for the tasks to complete
/// cancellation, you should call `join_one` in a loop until the `JoinMap` is empty.
pub fn abort_all(&mut self) {
self.tasks.abort_all()
}
/// Removes all tasks from this `JoinMap` without aborting them.
///
/// The tasks removed by this call will continue to run in the background even if the `JoinMap`
/// is dropped. They may still be aborted by key.
pub fn detach_all(&mut self) {
self.tasks.detach_all();
self.tasks_by_key.clear();
self.hashes_by_task.clear();
}
}
// Hand-written `fmt::Debug` implementation in order to avoid requiring `V:
// Debug`, since no value is ever actually stored in the map.
impl<K: fmt::Debug, V, S> fmt::Debug for JoinMap<K, V, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// format the task keys and abort handles a little nicer by just
// printing the key and task ID pairs, without format the `Key` struct
// itself or the `AbortHandle`, which would just format the task's ID
// again.
struct KeySet<'a, K: fmt::Debug, S>(&'a HashMap<Key<K>, AbortHandle, S>);
impl<K: fmt::Debug, S> fmt::Debug for KeySet<'_, K, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.0.keys().map(|Key { key, id }| (key, id)))
.finish()
}
}
f.debug_struct("JoinMap")
// The `tasks_by_key` map is the only one that contains information
// that's really worth formatting for the user, since it contains
// the tasks' keys and IDs. The other fields are basically
// implementation details.
.field("tasks", &KeySet(&self.tasks_by_key))
.finish()
}
}
impl<K, V> Default for JoinMap<K, V> {
fn default() -> Self {
Self::new()
}
}
// === impl Key ===
impl<K: Hash> Hash for Key<K> {
// Don't include the task ID in the hash.
#[inline]
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.key.hash(hasher);
}
}
// Because we override `Hash` for this type, we must also override the
// `PartialEq` impl, so that all instances with the same hash are equal.
impl<K: PartialEq> PartialEq for Key<K> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl<K: Eq> Eq for Key<K> {}
+10
View File
@@ -0,0 +1,10 @@
//! Extra utilities for spawning tasks
#[cfg(tokio_unstable)]
mod join_map;
mod spawn_pinned;
pub use spawn_pinned::LocalPoolHandle;
#[cfg(tokio_unstable)]
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt"))))]
pub use join_map::JoinMap;

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