use std::sync::OnceLock;
use thiserror::Error;
use crate::error::ComputerError;
type RuntimeCell = Result<tokio::runtime::Runtime, RuntimeBuildError>;
static RUNTIME: OnceLock<RuntimeCell> = OnceLock::new();
#[derive(Debug, Error)]
pub enum RuntimeBuildError {
#[error("failed to build current-thread tokio runtime: {0}")]
Build(#[source] std::io::Error),
}
pub fn block_on<F>(future: F) -> Result<F::Output, ComputerError>
where
F: std::future::Future + 'static,
F::Output: Send + 'static,
{
let runtime = get_or_build_runtime()?;
Ok(runtime.block_on(future))
}
fn get_or_build_runtime() -> Result<&'static tokio::runtime::Runtime, ComputerError> {
let cell = RUNTIME.get_or_init(|| {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(RuntimeBuildError::Build)
});
cell.as_ref()
.map_err(|build| ComputerError::ExecutionFailed(build.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_on_executes_simple_future() {
let value = block_on(async { 7_u32 + 11 }).unwrap();
assert_eq!(value, 18);
}
#[test]
fn block_on_propagates_inner_err() {
let outcome: Result<i32, &'static str> = block_on(async { Err("boom") }).unwrap();
assert_eq!(outcome, Err("boom"));
}
#[test]
fn handle_is_shared() {
let _ = block_on(async { 1_u32 }).unwrap();
let _ = block_on(async { 2_u32 }).unwrap();
let first = RUNTIME.get().expect("runtime must be initialised");
let second = RUNTIME.get().expect("runtime must be initialised");
assert!(std::ptr::eq(
first.as_ref().unwrap(),
second.as_ref().unwrap()
));
}
}