use std::future::Future;
use std::panic::{catch_unwind, AssertUnwindSafe};
use thiserror::Error;
use tokio::task;
use tokio::runtime::Runtime;
#[derive(Error, Debug)]
pub enum IsolationError {
#[error("Function panicked: {0}")]
Panic(String),
#[error("Function failed: {0}")]
Error(#[from] Box<dyn std::error::Error + Send + Sync>),
}
pub type IsolationResult<T> = Result<T, IsolationError>;
pub fn isolate<F, T>(f: F) -> IsolationResult<T>
where
F: FnOnce() -> T + std::panic::UnwindSafe + Send + 'static,
T: Send + 'static,
{
let rt = Runtime::new().unwrap();
rt.block_on(task::spawn_blocking(move || {
catch_unwind(f).map_err(|e| {
let msg = if let Some(s) = e.downcast_ref::<String>() {
s.clone()
} else if let Some(s) = e.downcast_ref::<&str>() {
s.to_string()
} else {
"Unknown panic".to_string()
};
IsolationError::Panic(msg)
})
}))
.unwrap()
}
pub async fn isolate_async<F, Fut, T>(f: F) -> IsolationResult<T>
where
F: FnOnce() -> Fut + std::panic::UnwindSafe + Send + 'static,
Fut: Future<Output = T> + std::panic::UnwindSafe + Send + 'static,
T: Send + 'static,
{
let future = catch_unwind(AssertUnwindSafe(f))
.map_err(|e| IsolationError::Panic(format!("{:?}", e)))?;
Ok(task::spawn(AssertUnwindSafe(future)).await.unwrap())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_isolate_success() {
let result = isolate(|| {
println!("Running in separate thread!");
42
});
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_isolate_panic() {
let result = isolate(|| panic!("test panic"));
assert!(matches!(result, Err(IsolationError::Panic(_))));
}
#[tokio::test]
async fn test_isolate_async_success() {
let result = isolate_async(|| async {
println!("Running async in separate task!");
42
}).await;
assert_eq!(result.unwrap(), 42);
}
#[tokio::test]
async fn test_isolate_async_panic() {
let result = isolate_async(|| async { panic!("test panic") }).await;
assert!(matches!(result, Err(IsolationError::Panic(_))));
}
}