use std::any::Any;
use std::future::Future;
#[derive(Debug, thiserror::Error)]
pub enum BlockingError {
#[error("spawn_blocking closure panicked: {0}")]
Panicked(String),
#[error("spawn_blocking worker ended without sending a result")]
WorkerVanished,
}
pub fn spawn_blocking<T, F>(f: F) -> impl Future<Output = Result<T, BlockingError>>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let (tx, rx) = async_channel::bounded::<Result<T, BlockingError>>(1);
std::thread::Builder::new()
.name("teksilo-async-blocking".to_string())
.spawn(move || {
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f))
.map_err(|payload| BlockingError::Panicked(panic_message(payload)));
let _ = tx.try_send(outcome);
})
.expect("teksilo-async: failed to spawn blocking worker thread");
async move {
rx.recv()
.await
.unwrap_or(Err(BlockingError::WorkerVanished))
}
}
fn panic_message(payload: Box<dyn Any + Send>) -> String {
if let Some(s) = payload.downcast_ref::<&'static str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"<non-string panic payload>".to_string()
}
}