Skip to main content

logicaffeine_system/
failure.rs

1//! Where a failure ends: the program, or the boundary that catches it.
2//!
3//! A generated program reports a runtime failure in its panic hook and ends with exit status 1, as every tier does.
4//! A failure inside a served request, or inside a task the replay scheduler polls, ends there instead: the server
5//! answers `500` and keeps serving, the scheduler fails the task and runs its peers. Every place the runtime catches a
6//! panic does it through [`caught`], and the hook asks [`is_caught`] before it ends the process.
7
8use std::cell::Cell;
9use std::panic::{catch_unwind, UnwindSafe};
10
11thread_local! {
12    static CATCHING: Cell<u32> = const { Cell::new(0) };
13}
14
15/// Run `f`, catching a panic it raises; while it runs, [`is_caught`] answers true on this thread.
16pub fn caught<R>(f: impl FnOnce() -> R + UnwindSafe) -> std::thread::Result<R> {
17    struct Leave;
18    impl Drop for Leave {
19        fn drop(&mut self) {
20            CATCHING.with(|n| n.set(n.get() - 1));
21        }
22    }
23    CATCHING.with(|n| n.set(n.get() + 1));
24    let _leave = Leave;
25    catch_unwind(f)
26}
27
28/// Whether a panic raised on this thread now ends at an enclosing [`caught`] rather than ending the program. Never
29/// in a build that aborts on panic, where no boundary can catch one.
30pub fn is_caught() -> bool {
31    cfg!(panic = "unwind") && CATCHING.with(|n| n.get() > 0)
32}
33
34#[cfg(test)]
35#[path = "../../logicaffeine_tests/unit/logicaffeine_system/failure/tests.rs"]
36mod tests;