diff --git a/actix-rt/CHANGES.md b/actix-rt/CHANGES.md index 483999ce..42879e12 100644 --- a/actix-rt/CHANGES.md +++ b/actix-rt/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* The `spawn` method can now resolve with non-unit outputs. [#369] + +[#369]: https://github.com/actix/actix-net/pull/369 ## 2.2.0 - 2021-03-29 diff --git a/actix-rt/src/lib.rs b/actix-rt/src/lib.rs index 4454b3c4..95afcac9 100644 --- a/actix-rt/src/lib.rs +++ b/actix-rt/src/lib.rs @@ -46,7 +46,10 @@ use tokio::task::JoinHandle; // Cannot define a main macro when compiled into test harness. // Workaround for https://github.com/rust-lang/rust/issues/62127. #[cfg(all(feature = "macros", not(test)))] -pub use actix_macros::{main, test}; +pub use actix_macros::main; + +#[cfg(feature = "macros")] +pub use actix_macros::test; mod arbiter; mod runtime; @@ -155,14 +158,41 @@ pub mod task { pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle}; } -/// Spawns a future on the current thread. +/// Spawns a future on the current thread as a new task. +/// +/// If not immediately awaited, the task can be cancelled using [`JoinHandle::abort`]. +/// +/// The provided future is spawned as a new task; therefore, panics are caught. /// /// # Panics /// Panics if Actix system is not running. +/// +/// # Examples +/// ``` +/// # use std::time::Duration; +/// # actix_rt::Runtime::new().unwrap().block_on(async { +/// // task resolves successfully +/// assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1); +/// +/// // task panics +/// assert!(actix_rt::spawn(async { +/// panic!("panic is caught at task boundary"); +/// }) +/// .await +/// .unwrap_err() +/// .is_panic()); +/// +/// // task is cancelled before completion +/// let handle = actix_rt::spawn(actix_rt::time::sleep(Duration::from_secs(100))); +/// handle.abort(); +/// assert!(handle.await.unwrap_err().is_cancelled()); +/// # }); +/// ``` #[inline] -pub fn spawn(f: Fut) -> JoinHandle<()> +pub fn spawn(f: Fut) -> JoinHandle where - Fut: Future + 'static, + Fut: Future + 'static, + Fut::Output: 'static, { tokio::task::spawn_local(f) } diff --git a/actix-rt/tests/tests.rs b/actix-rt/tests/tests.rs index 839b1fbc..e66696bf 100644 --- a/actix-rt/tests/tests.rs +++ b/actix-rt/tests/tests.rs @@ -1,4 +1,5 @@ use std::{ + future::Future, sync::{ atomic::{AtomicBool, Ordering}, mpsc::channel, @@ -8,7 +9,7 @@ use std::{ time::{Duration, Instant}, }; -use actix_rt::{Arbiter, System}; +use actix_rt::{task::JoinError, Arbiter, System}; use tokio::sync::oneshot; #[test] @@ -298,3 +299,27 @@ fn try_current_no_system() { fn try_current_with_system() { System::new().block_on(async { assert!(System::try_current().is_some()) }); } + +#[allow(clippy::unit_cmp)] +#[test] +fn spawn_local() { + System::new().block_on(async { + // demonstrate that spawn -> R is strictly more capable than spawn -> () + + assert_eq!(actix_rt::spawn(async {}).await.unwrap(), ()); + assert_eq!(actix_rt::spawn(async { 1 }).await.unwrap(), 1); + assert!(actix_rt::spawn(async { panic!("") }).await.is_err()); + + actix_rt::spawn(async { tokio::time::sleep(Duration::from_millis(50)).await }) + .await + .unwrap(); + + fn g>>(_f: F) {} + g(actix_rt::spawn(async {})); + // g(actix_rt::spawn(async { 1 })); // compile err + + fn h>, R>(_f: F) {} + h(actix_rt::spawn(async {})); + h(actix_rt::spawn(async { 1 })); + }) +}