Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/anyspawn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ repository = "https://github.com/microsoft/oxidizer/tree/main/crates/anyspawn"

[package.metadata.cargo_check_external_types]
allowed_external_types = [
"tokio::runtime::handle::Handle",
"thread_aware::core::ThreadAware",
"thread_aware::affinity::MemoryAffinity",
"thread_aware::affinity::PinnedAffinity",
Expand Down
12 changes: 7 additions & 5 deletions crates/anyspawn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,22 @@ details and examples.

## Features

* `tokio` (default): Enables the [`Spawner::new_tokio`][__link4] constructor
* `custom`: Enables [`Spawner::new_custom`][__link5] and [`CustomSpawnerBuilder`][__link6]
* `tokio` (default): Enables the [`Spawner::new_tokio`][__link4] and
[`Spawner::new_tokio_with_handle`][__link5] constructors
* `custom`: Enables [`Spawner::new_custom`][__link6] and [`CustomSpawnerBuilder`][__link7]
Comment thread
martintmk marked this conversation as resolved.


<hr/>
<sub>
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/oxidizer/tree/main/crates/anyspawn">source code</a>.
</sub>

[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEGzZggGALBWlVGxKbYKO3c4jHG8sNRx0SKZpBG_ofiAdiJ08dYWSCgmhhbnlzcGF3bmUwLjIuMIJsdGhyZWFkX2F3YXJlZTAuNi4y
[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG9wiT4mviSlSG7bkeqiO1sYsG8AqKBp2b7sGG8q0eDukfWyeYWSCgmhhbnlzcGF3bmUwLjIuMIJsdGhyZWFkX2F3YXJlZTAuNi4y
[__link0]: https://docs.rs/anyspawn/0.2.0/anyspawn/?search=Spawner
[__link1]: https://docs.rs/thread_aware/0.6.2/thread_aware/?search=ThreadAware
[__link2]: https://docs.rs/anyspawn/0.2.0/anyspawn/?search=Spawner::new_thread_aware
[__link3]: Spawner#thread-aware-support
[__link4]: https://docs.rs/anyspawn/0.2.0/anyspawn/?search=Spawner::new_tokio
[__link5]: https://docs.rs/anyspawn/0.2.0/anyspawn/?search=Spawner::new_custom
[__link6]: https://docs.rs/anyspawn/0.2.0/anyspawn/?search=CustomSpawnerBuilder
[__link5]: https://docs.rs/anyspawn/0.2.0/anyspawn/?search=Spawner::new_tokio_with_handle
[__link6]: https://docs.rs/anyspawn/0.2.0/anyspawn/?search=Spawner::new_custom
[__link7]: https://docs.rs/anyspawn/0.2.0/anyspawn/?search=CustomSpawnerBuilder
34 changes: 34 additions & 0 deletions crates/anyspawn/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,40 @@ impl CustomSpawnerBuilder<()> {
}
}

/// Creates a builder using an explicit Tokio runtime handle as the base
/// spawn function.
///
/// Unlike [`tokio()`](Self::tokio), this does not require an ambient Tokio
/// runtime context. Tasks are spawned directly on the provided
/// [`Handle`](::tokio::runtime::Handle).
///
/// The spawner is named `"tokio"` in [`Debug`] output.
///
/// # Examples
///
/// ```rust
/// use anyspawn::CustomSpawnerBuilder;
///
/// # #[tokio::main]
/// # async fn main() {
/// let handle = tokio::runtime::Handle::current();
/// let spawner = CustomSpawnerBuilder::tokio_with_handle(handle).build();
/// let result = spawner.spawn(async { 42 }).await;
/// assert_eq!(result, 42);
/// # }
Comment thread
martintmk marked this conversation as resolved.
/// ```
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(all(feature = "tokio", feature = "custom"))))]
#[must_use]
pub fn tokio_with_handle(handle: ::tokio::runtime::Handle) -> CustomSpawnerBuilder<impl Fn(BoxedFuture) + Send + Sync + 'static> {
CustomSpawnerBuilder {
spawn_fn: move |fut: BoxedFuture| {
handle.spawn(fut);
},
name: "tokio",
}
}
Comment thread
martintmk marked this conversation as resolved.

/// Creates a builder with a custom base spawn function.
///
/// The spawner is named `"custom"` by default in [`Debug`] output.
Expand Down
3 changes: 2 additions & 1 deletion crates/anyspawn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
//!
//! # Features
//!
//! - `tokio` (default): Enables the [`Spawner::new_tokio`] constructor
//! - `tokio` (default): Enables the [`Spawner::new_tokio`] and
//! [`Spawner::new_tokio_with_handle`] constructors
//! - `custom`: Enables [`Spawner::new_custom`] and [`CustomSpawnerBuilder`]

#![doc(html_logo_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/anyspawn/logo.png")]
Expand Down
41 changes: 37 additions & 4 deletions crates/anyspawn/src/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ pub struct Spawner(SpawnerKind);
#[derive(Clone, ThreadAware)]
enum SpawnerKind {
#[cfg(feature = "tokio")]
Tokio,
Tokio(#[thread_aware(skip)] Option<::tokio::runtime::Handle>),
#[cfg(feature = "custom")]
Custom(CustomSpawner),
ThreadAware(thread_aware::Arc<Spawner, PerCore>),
Expand Down Expand Up @@ -153,7 +153,32 @@ impl Spawner {
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub fn new_tokio() -> Self {
Self(SpawnerKind::Tokio)
Self(SpawnerKind::Tokio(None))
}

/// Creates a spawner that uses the given Tokio runtime handle for spawning.
///
/// Unlike [`new_tokio`](Self::new_tokio), this spawner does not require an
/// ambient Tokio runtime context. It spawns tasks directly on the provided
/// [`Handle`](::tokio::runtime::Handle).
///
/// # Examples
///
/// ```rust
/// use anyspawn::Spawner;
///
/// # #[tokio::main]
/// # async fn main() {
/// let handle = tokio::runtime::Handle::current();
/// let spawner = Spawner::new_tokio_with_handle(handle);
/// let result = spawner.spawn(async { 42 }).await;
/// assert_eq!(result, 42);
/// # }
/// ```
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub fn new_tokio_with_handle(handle: ::tokio::runtime::Handle) -> Self {
Self(SpawnerKind::Tokio(Some(handle)))
}
Comment thread
martintmk marked this conversation as resolved.
Comment thread
martintmk marked this conversation as resolved.

/// Creates a custom spawner from a closure.
Expand Down Expand Up @@ -270,7 +295,13 @@ impl Spawner {
pub fn spawn<T: Send + 'static>(&self, work: impl Future<Output = T> + Send + 'static) -> JoinHandle<T> {
match &self.0 {
#[cfg(feature = "tokio")]
SpawnerKind::Tokio => JoinHandle(JoinHandleInner::Tokio(::tokio::spawn(work))),
SpawnerKind::Tokio(handle) => {
let jh = match handle {
Some(h) => h.spawn(work),
None => ::tokio::spawn(work),
};
JoinHandle(JoinHandleInner::Tokio(jh))
}
#[cfg(feature = "custom")]
SpawnerKind::Custom(c) => JoinHandle(JoinHandleInner::Custom(c.call(work))),
SpawnerKind::ThreadAware(ta) => ta.spawn(work),
Expand All @@ -282,7 +313,9 @@ impl Debug for Spawner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
#[cfg(feature = "tokio")]
SpawnerKind::Tokio => f.debug_tuple("Spawner").field(&"tokio").finish(),
SpawnerKind::Tokio(None) => f.debug_tuple("Spawner").field(&"tokio").finish(),
#[cfg(feature = "tokio")]
SpawnerKind::Tokio(Some(_)) => f.debug_tuple("Spawner").field(&"tokio(handle)").finish(),
#[cfg(feature = "custom")]
SpawnerKind::Custom(c) => f.debug_tuple("Spawner").field(c).finish(),
SpawnerKind::ThreadAware(_) => f.debug_tuple("Spawner").field(&"thread_aware").finish(),
Expand Down
17 changes: 17 additions & 0 deletions crates/anyspawn/tests/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ fn tokio_spawner_debug() {
insta::assert_snapshot!(format!("{spawner:?}"), @r#"Spawner("tokio")"#);
}

#[test]
fn tokio_with_handle_spawner_debug() {
let rt = tokio::runtime::Runtime::new().unwrap();
let spawner = CustomSpawnerBuilder::tokio_with_handle(rt.handle().clone()).build();
insta::assert_snapshot!(format!("{spawner:?}"), @r#"Spawner(CustomSpawner { name: "tokio" })"#);
}

#[test]
fn custom_spawner_debug() {
let spawner = Spawner::new_custom("my-runtime", |_| {});
Expand Down Expand Up @@ -66,6 +73,16 @@ fn built_spawner_debug_no_layers() {
insta::assert_snapshot!(format!("{spawner:?}"), @r#"Spawner(CustomSpawner { name: "tokio" })"#);
}

#[test]
fn tokio_with_handle_spawner_still_works() {
let rt = tokio::runtime::Runtime::new().unwrap();
let spawner = CustomSpawnerBuilder::tokio_with_handle(rt.handle().clone()).build();

// Spawning with an explicit handle works even outside a Tokio runtime context.
let result = rt.block_on(spawner.spawn(async { 42 }));
assert_eq!(result, 42);
}

#[tokio::test]
async fn layered_spawner_still_works() {
let spawner = CustomSpawnerBuilder::tokio()
Expand Down
20 changes: 20 additions & 0 deletions crates/anyspawn/tests/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ async fn tokio_spawn_fire_and_forget() {
assert_eq!(rx.await.unwrap(), 42);
}

#[cfg(feature = "tokio")]
#[test]
fn tokio_with_handle_spawn_and_await() {
let rt = tokio::runtime::Runtime::new().unwrap();
let spawner = Spawner::new_tokio_with_handle(rt.handle().clone());

// Spawning with an explicit handle works even outside a Tokio runtime context.
let result = rt.block_on(spawner.spawn(async { 42 }));
assert_eq!(result, 42);
}

#[cfg(feature = "tokio")]
#[test]
fn tokio_with_handle_spawner_debug() {
let rt = tokio::runtime::Runtime::new().unwrap();
let spawner = Spawner::new_tokio_with_handle(rt.handle().clone());
let debug_str = format!("{spawner:?}");
assert_eq!(debug_str, r#"Spawner("tokio(handle)")"#);
}

#[cfg(feature = "custom")]
#[test]
fn custom_spawn_and_await() {
Expand Down
Loading