- Rename corex -> mytheclipse (corex name taken on crates.io by an unrelated crate published by David-Kunz in 2023) - Flatten corex + corex-core workspace into a single crate; all modules (context/error/io/compute/bg) now live directly under src/ - Update Cargo.toml metadata: real repo URL, docs.rs, author - Update README, example, doc comments, tracing span names - Fix CI: single package dry-run (corex-core no longer separate) - Fix publish workflow: single cargo publish step
61 lines
2.1 KiB
Rust
61 lines
2.1 KiB
Rust
//! Panic-isolated heavy compute on a sized [`rayon::ThreadPool`].
|
|
|
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
|
|
use crate::context::context;
|
|
use crate::error::CorexError;
|
|
|
|
/// Runs `f` on the global compute thread pool and returns its result.
|
|
///
|
|
/// If `f` panics, the panic is caught and converted into
|
|
/// [`CorexError::ComputePanic`] instead of unwinding across the pool
|
|
/// boundary or poisoning the pool; subsequent calls to [`compute`] continue
|
|
/// to work normally.
|
|
///
|
|
/// # Panic-safety caveat
|
|
///
|
|
/// `f` is wrapped in [`AssertUnwindSafe`] so that closures capturing
|
|
/// ordinary references or non-[`UnwindSafe`](std::panic::UnwindSafe) state
|
|
/// can be submitted without a compile error. This is sound with respect to
|
|
/// the compute pool itself, since a panicking closure's stack (and any
|
|
/// locals it holds) is discarded entirely rather than observed afterward.
|
|
/// It does not, however, guarantee exception-safety of state the closure
|
|
/// captured by mutable reference: if `f` panics partway through mutating a
|
|
/// captured `&mut T`, that `T` may be left in an inconsistent state from
|
|
/// the caller's perspective.
|
|
pub fn compute<F, R>(f: F) -> Result<R, CorexError>
|
|
where
|
|
F: FnOnce() -> R + Send,
|
|
R: Send,
|
|
{
|
|
let wrapped = AssertUnwindSafe(f);
|
|
context()
|
|
.compute_pool
|
|
.install(move || catch_unwind(wrapped))
|
|
.map_err(|payload| CorexError::ComputePanic(panic_payload_to_string(payload)))
|
|
}
|
|
|
|
fn panic_payload_to_string(payload: Box<dyn std::any::Any + Send>) -> String {
|
|
if let Some(message) = payload.downcast_ref::<&str>() {
|
|
(*message).to_string()
|
|
} else if let Some(message) = payload.downcast_ref::<String>() {
|
|
message.clone()
|
|
} else {
|
|
"compute closure panicked with a non-string payload".to_string()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn compute_panic_is_isolated_and_pool_survives() {
|
|
let panicked: Result<u32, CorexError> = compute(|| panic!("boom"));
|
|
assert!(matches!(panicked, Err(CorexError::ComputePanic(_))));
|
|
|
|
let recovered = compute(|| 1 + 1);
|
|
assert_eq!(recovered.unwrap(), 2);
|
|
}
|
|
}
|