use futures::FutureExt as _;
use crate::error;
use crate::tasks::TaskHandle;
pub(crate) fn spawn<F>(future: F) -> TaskHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
TaskHandle::from_future(tokio::spawn(future).map(|r| r.map_err(join_error)))
}
pub(crate) fn spawn_blocking<F>(future: F) -> impl Future<Output = TaskHandle<F::Output>>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let handle =
tokio::task::spawn_blocking(move || tokio::runtime::Handle::current().block_on(future));
let task = TaskHandle::from_future(handle.map(|r| r.map_err(join_error)));
std::future::ready(task)
}
fn join_error(err: tokio::task::JoinError) -> error::Error {
error::ErrorKind::TaskError(Box::new(err)).into()
}
#[cfg(test)]
#[allow(clippy::panic_in_result_fn, reason = "assertions in a fallible test")]
mod tests {
use std::time::Duration;
use super::*;
const TIMEOUT: Duration = Duration::from_secs(10);
fn is_task_error<T>(result: &Result<T, error::Error>) -> bool {
result
.as_ref()
.is_err_and(|err| matches!(err.kind(), error::ErrorKind::TaskError(_)))
}
fn unwind() {
std::panic::resume_unwind(Box::new("task unwound"));
}
#[tokio::test]
async fn spawn_yields_the_tasks_output() -> Result<(), error::Error> {
assert_eq!(spawn(async { 42 }).await?, 42);
Ok(())
}
#[tokio::test]
async fn spawn_yields_a_task_error_if_the_task_panics() {
assert!(is_task_error(&spawn(async { unwind() }).await));
}
#[tokio::test]
async fn spawn_blocking_yields_the_tasks_output() -> Result<(), error::Error> {
assert_eq!(spawn_blocking(async { 42 }).await.await?, 42);
Ok(())
}
#[tokio::test]
async fn spawn_blocking_yields_a_task_error_if_the_task_panics() {
assert!(is_task_error(
&spawn_blocking(async { unwind() }).await.await
));
}
#[tokio::test]
async fn spawn_blocking_does_not_hold_up_the_caller() -> Result<(), error::Error> {
let (sender, receiver) = std::sync::mpsc::channel();
let task = spawn_blocking(async move { receiver.recv_timeout(TIMEOUT).is_ok() }).await;
let _ = sender.send(());
assert!(task.await?, "the task should have been signaled");
Ok(())
}
}