From 416e36b0dff3214a9fd9dee26f9e554d7d162b46 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Sun, 3 Aug 2025 08:58:28 +0100 Subject: [PATCH] task: stabilise `JoinMap` (#7075) --- tokio-util/Cargo.toml | 7 +++---- tokio-util/src/lib.rs | 4 +++- tokio-util/src/task/join_map.rs | 23 +++++++++-------------- tokio-util/src/task/mod.rs | 29 +++++++++++++++++------------ tokio-util/tests/task_join_map.rs | 10 +++++----- 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/tokio-util/Cargo.toml b/tokio-util/Cargo.toml index e8e004c00..f55376481 100644 --- a/tokio-util/Cargo.toml +++ b/tokio-util/Cargo.toml @@ -21,7 +21,7 @@ categories = ["asynchronous"] default = [] # 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"] compat = ["futures-io"] @@ -29,7 +29,8 @@ codec = [] time = ["tokio/time", "slab"] io = [] 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"] @@ -43,8 +44,6 @@ futures-util = { version = "0.3.0", optional = true } pin-project-lite = "0.2.11" slab = { version = "0.4.4", optional = true } # Backs `DelayQueue` 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 } [dev-dependencies] diff --git a/tokio-util/src/lib.rs b/tokio-util/src/lib.rs index 677027d6e..dbcbc9450 100644 --- a/tokio-util/src/lib.rs +++ b/tokio-util/src/lib.rs @@ -45,9 +45,11 @@ cfg_io! { cfg_rt! { pub mod context; - pub mod task; } +#[cfg(feature = "rt")] +pub mod task; + cfg_time! { pub mod time; } diff --git a/tokio-util/src/task/join_map.rs b/tokio-util/src/task/join_map.rs index bf4165620..b96a3821b 100644 --- a/tokio-util/src/task/join_map.rs +++ b/tokio-util/src/task/join_map.rs @@ -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. /// -/// **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: @@ -96,11 +92,9 @@ use tokio::task::{AbortHandle, Id, JoinError, JoinSet, LocalSet}; /// ``` /// /// [`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 { /// A map of the [`AbortHandle`]s of the tasks spawned on this `JoinMap`, /// indexed by their keys. @@ -541,9 +535,9 @@ where /// assert!(!map.abort("goodbye universe")); /// # } /// ``` - pub fn abort(&mut self, key: &Q) -> bool + pub fn abort(&mut self, key: &Q) -> bool where - Q: Hash + Eq, + Q: ?Sized + Hash + Eq, K: Borrow, { match self.get_by_key(key) { @@ -638,9 +632,9 @@ where /// call to [`join_next`], this method will still return `true`. /// /// [`join_next`]: fn@Self::join_next - pub fn contains_key(&self, key: &Q) -> bool + pub fn contains_key(&self, key: &Q) -> bool where - Q: Hash + Eq, + Q: ?Sized + Hash + Eq, K: Borrow, { 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. - 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 - Q: Hash + Eq, + Q: ?Sized + Hash + Eq, K: Borrow, { let hash_builder = self.hashes_by_task.hasher(); @@ -774,9 +768,10 @@ where /// Returns the hash for a given key. #[inline] -fn hash_one(hash_builder: &S, key: &Q) -> u64 +fn hash_one(hash_builder: &S, key: &Q) -> u64 where - Q: Hash, + Q: ?Sized + Hash, + S: BuildHasher, { let mut hasher = hash_builder.build_hasher(); key.hash(&mut hasher); diff --git a/tokio-util/src/task/mod.rs b/tokio-util/src/task/mod.rs index 6d0c379fe..393e8c928 100644 --- a/tokio-util/src/task/mod.rs +++ b/tokio-util/src/task/mod.rs @@ -1,16 +1,21 @@ //! 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 spawn_pinned; -pub use spawn_pinned::LocalPoolHandle; - -#[cfg(tokio_unstable)] -#[cfg_attr(docsrs, doc(cfg(all(tokio_unstable, feature = "rt"))))] +#[cfg(feature = "join-map")] +#[cfg_attr(docsrs, doc(cfg(feature = "join-map")))] 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; diff --git a/tokio-util/tests/task_join_map.rs b/tokio-util/tests/task_join_map.rs index 2dcb18804..b19e3d887 100644 --- a/tokio-util/tests/task_join_map.rs +++ b/tokio-util/tests/task_join_map.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "rt", tokio_unstable))] +#![cfg(feature = "join-map")] use std::panic::AssertUnwindSafe; @@ -26,7 +26,7 @@ async fn test_with_sleep() { map.detach_all(); assert_eq!(map.len(), 0); - assert!(matches!(map.join_next().await, None)); + assert!(map.join_next().await.is_none()); for i in 0..10 { map.spawn(i, async move { @@ -45,7 +45,7 @@ async fn test_with_sleep() { for was_seen in &seen { assert!(was_seen); } - assert!(matches!(map.join_next().await, None)); + assert!(map.join_next().await.is_none()); // Do it again. for i in 0..10 { @@ -64,7 +64,7 @@ async fn test_with_sleep() { for was_seen in &seen { assert!(was_seen); } - assert!(matches!(map.join_next().await, None)); + assert!(map.join_next().await.is_none()); } #[tokio::test] @@ -250,7 +250,7 @@ async fn join_map_coop() { loop { match map.join_next().now_or_never() { 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 => { coop_count += 1; tokio::task::yield_now().await;