task: stabilise JoinMap (#7075)

This commit is contained in:
Conrad Ludgate
2025-08-03 07:58:28 +00:00
committed by GitHub
parent 9741c90f9f
commit 416e36b0df
5 changed files with 37 additions and 36 deletions
+3 -4
View File
@@ -21,7 +21,7 @@ categories = ["asynchronous"]
default = [] default = []
# Shorthand for enabling everything # Shorthand for enabling everything
full = ["codec", "compat", "io-util", "time", "net", "rt"] full = ["codec", "compat", "io-util", "time", "net", "rt", "join-map"]
net = ["tokio/net"] net = ["tokio/net"]
compat = ["futures-io"] compat = ["futures-io"]
@@ -29,7 +29,8 @@ codec = []
time = ["tokio/time", "slab"] time = ["tokio/time", "slab"]
io = [] io = []
io-util = ["io", "tokio/rt", "tokio/io-util"] io-util = ["io", "tokio/rt", "tokio/io-util"]
rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"] rt = ["tokio/rt", "tokio/sync", "futures-util"]
join-map = ["rt", "hashbrown"]
__docs_rs = ["futures-util"] __docs_rs = ["futures-util"]
@@ -43,8 +44,6 @@ futures-util = { version = "0.3.0", optional = true }
pin-project-lite = "0.2.11" pin-project-lite = "0.2.11"
slab = { version = "0.4.4", optional = true } # Backs `DelayQueue` slab = { version = "0.4.4", optional = true } # Backs `DelayQueue`
tracing = { version = "0.1.29", default-features = false, features = ["std"], optional = true } tracing = { version = "0.1.29", default-features = false, features = ["std"], optional = true }
[target.'cfg(tokio_unstable)'.dependencies]
hashbrown = { version = "0.15.0", default-features = false, optional = true } hashbrown = { version = "0.15.0", default-features = false, optional = true }
[dev-dependencies] [dev-dependencies]
+3 -1
View File
@@ -45,9 +45,11 @@ cfg_io! {
cfg_rt! { cfg_rt! {
pub mod context; pub mod context;
pub mod task;
} }
#[cfg(feature = "rt")]
pub mod task;
cfg_time! { cfg_time! {
pub mod time; pub mod time;
} }
+9 -14
View File
@@ -29,10 +29,6 @@ use tokio::task::{AbortHandle, Id, JoinError, JoinSet, LocalSet};
/// ///
/// When the `JoinMap` is dropped, all tasks in the `JoinMap` are immediately aborted. /// 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 /// # Examples
/// ///
/// Spawn multiple tasks and wait for them: /// Spawn multiple tasks and wait for them:
@@ -96,11 +92,9 @@ use tokio::task::{AbortHandle, Id, JoinError, JoinSet, LocalSet};
/// ``` /// ```
/// ///
/// [`JoinSet`]: tokio::task::JoinSet /// [`JoinSet`]: tokio::task::JoinSet
/// [unstable]: tokio#unstable-features
/// [abort]: fn@Self::abort /// [abort]: fn@Self::abort
/// [abort_matching]: fn@Self::abort_matching /// [abort_matching]: fn@Self::abort_matching
/// [contains]: fn@Self::contains_key /// [contains]: fn@Self::contains_key
#[cfg_attr(docsrs, doc(cfg(all(feature = "rt", tokio_unstable))))]
pub struct JoinMap<K, V, S = RandomState> { pub struct JoinMap<K, V, S = RandomState> {
/// A map of the [`AbortHandle`]s of the tasks spawned on this `JoinMap`, /// A map of the [`AbortHandle`]s of the tasks spawned on this `JoinMap`,
/// indexed by their keys. /// indexed by their keys.
@@ -541,9 +535,9 @@ where
/// assert!(!map.abort("goodbye universe")); /// assert!(!map.abort("goodbye universe"));
/// # } /// # }
/// ``` /// ```
pub fn abort<Q: ?Sized>(&mut self, key: &Q) -> bool pub fn abort<Q>(&mut self, key: &Q) -> bool
where where
Q: Hash + Eq, Q: ?Sized + Hash + Eq,
K: Borrow<Q>, K: Borrow<Q>,
{ {
match self.get_by_key(key) { match self.get_by_key(key) {
@@ -638,9 +632,9 @@ where
/// call to [`join_next`], this method will still return `true`. /// call to [`join_next`], this method will still return `true`.
/// ///
/// [`join_next`]: fn@Self::join_next /// [`join_next`]: fn@Self::join_next
pub fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool pub fn contains_key<Q>(&self, key: &Q) -> bool
where where
Q: Hash + Eq, Q: ?Sized + Hash + Eq,
K: Borrow<Q>, K: Borrow<Q>,
{ {
self.get_by_key(key).is_some() self.get_by_key(key).is_some()
@@ -744,9 +738,9 @@ where
} }
/// Look up a task in the map by its key, returning the key and abort handle. /// 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 (K, AbortHandle)> fn get_by_key<'map, Q>(&'map self, key: &Q) -> Option<&'map (K, AbortHandle)>
where where
Q: Hash + Eq, Q: ?Sized + Hash + Eq,
K: Borrow<Q>, K: Borrow<Q>,
{ {
let hash_builder = self.hashes_by_task.hasher(); let hash_builder = self.hashes_by_task.hasher();
@@ -774,9 +768,10 @@ where
/// Returns the hash for a given key. /// Returns the hash for a given key.
#[inline] #[inline]
fn hash_one<S: BuildHasher, Q: ?Sized>(hash_builder: &S, key: &Q) -> u64 fn hash_one<S, Q>(hash_builder: &S, key: &Q) -> u64
where where
Q: Hash, Q: ?Sized + Hash,
S: BuildHasher,
{ {
let mut hasher = hash_builder.build_hasher(); let mut hasher = hash_builder.build_hasher();
key.hash(&mut hasher); key.hash(&mut hasher);
+17 -12
View File
@@ -1,16 +1,21 @@
//! Extra utilities for spawning tasks //! Extra utilities for spawning tasks
//!
//! This module is only available when the `rt` feature is enabled. Note that enabling the
//! `join-map` feature will automatically also enable the `rt` feature.
#[cfg(tokio_unstable)] cfg_rt! {
mod spawn_pinned;
pub use spawn_pinned::LocalPoolHandle;
pub mod task_tracker;
pub use task_tracker::TaskTracker;
mod abort_on_drop;
pub use abort_on_drop::AbortOnDropHandle;
}
#[cfg(feature = "join-map")]
mod join_map; mod join_map;
mod spawn_pinned; #[cfg(feature = "join-map")]
pub use spawn_pinned::LocalPoolHandle; #[cfg_attr(docsrs, doc(cfg(feature = "join-map")))]
#[cfg(tokio_unstable)]
#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt"))))]
pub use join_map::{JoinMap, JoinMapKeys}; pub use join_map::{JoinMap, JoinMapKeys};
pub mod task_tracker;
pub use task_tracker::TaskTracker;
mod abort_on_drop;
pub use abort_on_drop::AbortOnDropHandle;
+5 -5
View File
@@ -1,5 +1,5 @@
#![warn(rust_2018_idioms)] #![warn(rust_2018_idioms)]
#![cfg(all(feature = "rt", tokio_unstable))] #![cfg(feature = "join-map")]
use std::panic::AssertUnwindSafe; use std::panic::AssertUnwindSafe;
@@ -26,7 +26,7 @@ async fn test_with_sleep() {
map.detach_all(); map.detach_all();
assert_eq!(map.len(), 0); assert_eq!(map.len(), 0);
assert!(matches!(map.join_next().await, None)); assert!(map.join_next().await.is_none());
for i in 0..10 { for i in 0..10 {
map.spawn(i, async move { map.spawn(i, async move {
@@ -45,7 +45,7 @@ async fn test_with_sleep() {
for was_seen in &seen { for was_seen in &seen {
assert!(was_seen); assert!(was_seen);
} }
assert!(matches!(map.join_next().await, None)); assert!(map.join_next().await.is_none());
// Do it again. // Do it again.
for i in 0..10 { for i in 0..10 {
@@ -64,7 +64,7 @@ async fn test_with_sleep() {
for was_seen in &seen { for was_seen in &seen {
assert!(was_seen); assert!(was_seen);
} }
assert!(matches!(map.join_next().await, None)); assert!(map.join_next().await.is_none());
} }
#[tokio::test] #[tokio::test]
@@ -250,7 +250,7 @@ async fn join_map_coop() {
loop { loop {
match map.join_next().now_or_never() { match map.join_next().now_or_never() {
Some(Some((key, Ok(i)))) => assert_eq!(key, i), Some(Some((key, Ok(i)))) => assert_eq!(key, i),
Some(Some((key, Err(err)))) => panic!("failed[{}]: {}", key, err), Some(Some((key, Err(err)))) => panic!("failed[{key}]: {err}"),
None => { None => {
coop_count += 1; coop_count += 1;
tokio::task::yield_now().await; tokio::task::yield_now().await;