Skip to main content

sva_cli/
panic.rs

1// Concern: runs a call on its own stack and turns whatever stopped it into one response | Non-concern: what the call does, the envelope's shape (sva-core) | IO: (a call) -> its value or why it stopped
2
3use std::sync::{Arc, Mutex};
4
5use sva_core::{Diagnostic, error_envelope};
6
7#[derive(Debug)]
8pub enum Stopped {
9    Panicked(String),
10    NoWorker(String),
11}
12
13/// The default hook leaves a stack trace on stderr and 101, not a JSON response.
14pub fn caught<T: Send + 'static>(
15    stack_bytes: usize,
16    call: impl FnOnce() -> T + Send + 'static,
17) -> Result<T, Stopped> {
18    let held = Arc::new(Mutex::new(String::new()));
19    let seen = Arc::clone(&held);
20    let before = std::panic::take_hook();
21    std::panic::set_hook(Box::new(move |info| {
22        *seen.lock().unwrap_or_else(|e| e.into_inner()) = info.to_string();
23    }));
24    let ran = match std::thread::Builder::new()
25        .stack_size(stack_bytes)
26        .spawn(call)
27    {
28        Ok(worker) => worker
29            .join()
30            .map_err(|_| Stopped::Panicked(held.lock().unwrap_or_else(|e| e.into_inner()).clone())),
31        Err(e) => Err(Stopped::NoWorker(format!(
32            "no thread with a {stack_bytes}-byte stack: {e}"
33        ))),
34    };
35    std::panic::set_hook(before);
36    ran
37}
38
39/// One `internal_error` either way, under the code and the advice that fit what stopped it.
40pub fn stopped(why: &Stopped) -> String {
41    let (message, code, text, help) = match why {
42        Stopped::Panicked(text) => (
43            "sva-cli panicked",
44            "cli.panicked",
45            text,
46            "this is a defect in sva-cli, not in the composition; report it with the command \
47             that reached it",
48        ),
49        Stopped::NoWorker(text) => (
50            "sva-cli could not start its worker",
51            "cli.no_worker",
52            text,
53            "the machine refused the thread this CLI runs on; raise the process's memory or \
54             thread limit and run it again",
55        ),
56    };
57    error_envelope(
58        "internal_error",
59        message,
60        &[Diagnostic::new(code, text.clone()).helped(help)],
61    )
62}