* 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.
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 .
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
## 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 <eliza@buoyant.io>
## 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 <eliza@buoyant.io>
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.
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 <uwe.klotz@slowtec.de>
## 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 <eliza@buoyant.io>
## 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_r773377901Closes#4538
Signed-off-by: Eliza Weisman <eliza@buoyant.io>