Skip to main content

reovim_kernel/panic/
handler.rs

1//! Custom panic handler infrastructure.
2//!
3//! Linux equivalent: `kernel/panic.c`
4//!
5//! Provides custom panic handling for graceful shutdown and crash recovery.
6
7use std::{
8    panic::{self, PanicHookInfo},
9    path::PathBuf,
10    sync::{
11        OnceLock,
12        atomic::{AtomicBool, Ordering},
13    },
14};
15
16static PANIC_HANDLER_INSTALLED: AtomicBool = AtomicBool::new(false);
17
18/// Recovery callback type.
19///
20/// Called during panic to allow saving state before the process terminates.
21pub type RecoveryCallback = Box<dyn Fn(&PanicHookInfo<'_>) + Send + Sync>;
22
23/// Global recovery callback.
24static RECOVERY_CALLBACK: OnceLock<RecoveryCallback> = OnceLock::new();
25
26/// Debug context collected during panic.
27///
28/// Contains server logs and client dump file paths for crash reports.
29#[derive(Debug, Default)]
30pub struct DebugContext {
31    /// Server debug ring buffer dump (Phase #478).
32    pub server_logs: Option<String>,
33    /// Paths to client debug dump files.
34    pub client_dump_paths: Vec<PathBuf>,
35}
36
37/// Debug context callback type.
38///
39/// Called during panic to collect debug information for the crash report.
40pub type DebugContextCallback = Box<dyn Fn() -> DebugContext + Send + Sync>;
41
42/// Global debug context callback.
43static DEBUG_CONTEXT_CALLBACK: OnceLock<DebugContextCallback> = OnceLock::new();
44
45/// Set the debug context callback.
46///
47/// Called during panic to collect server logs and client dump paths.
48/// Can only be set once.
49///
50/// # Example
51///
52/// ```ignore
53/// set_debug_context_callback(Box::new(|| {
54///     let server_logs = try_debug_ring().and_then(|r| r.try_dump());
55///     DebugContext {
56///         server_logs,
57///         client_dump_paths: Vec::new(),
58///     }
59/// }));
60/// ```
61pub fn set_debug_context_callback(callback: DebugContextCallback) {
62    let _ = DEBUG_CONTEXT_CALLBACK.set(callback);
63}
64
65/// Install the custom panic handler.
66///
67/// Linux equivalent: `panic_notifier_list`
68///
69/// This wraps the default panic handler to:
70/// 1. Generate a crash report
71/// 2. Call the recovery callback (if set) to save state
72/// 3. Log the crash report
73/// 4. Write crash report to file
74/// 5. Call the original panic handler
75///
76/// # Example
77///
78/// ```ignore
79/// use reovim_kernel::panic::{install_panic_handler, set_recovery_callback};
80///
81/// // Set up recovery to save unsaved buffers
82/// set_recovery_callback(Box::new(|info| {
83///     // Save unsaved buffers...
84/// }));
85///
86/// // Install the handler
87/// install_panic_handler();
88/// ```
89///
90/// # Notes
91///
92/// - Can only be called once; subsequent calls are no-ops
93/// - Must be called early in application startup
94pub fn install_panic_handler() {
95    if PANIC_HANDLER_INSTALLED.swap(true, Ordering::SeqCst) {
96        return; // Already installed
97    }
98
99    let default_hook = panic::take_hook();
100
101    panic::set_hook(Box::new(
102        #[cfg_attr(coverage_nightly, coverage(off))]
103        move |info| {
104            // 1. Generate crash report
105            let mut report = super::report::generate_crash_report(info);
106
107            // 2. Collect debug context (server logs, client dumps)
108            if let Some(callback) = DEBUG_CONTEXT_CALLBACK.get() {
109                let ctx = callback();
110                report.server_logs = ctx.server_logs;
111                report.client_dump_paths = ctx.client_dump_paths;
112            }
113
114            // 3. Attempt recovery (save buffers)
115            if let Some(callback) = RECOVERY_CALLBACK.get() {
116                callback(info);
117            }
118
119            // 4. Log crash report
120            crate::pr_err!("PANIC: {}", report.summary());
121
122            // 5. Write crash report to file
123            if let Err(e) = report.write_to_file() {
124                eprintln!("Failed to write crash report: {e}");
125            }
126
127            // 6. Call original handler
128            default_hook(info);
129        },
130    ));
131}
132
133/// Set the recovery callback.
134///
135/// Called during panic to allow saving state. Can only be set once.
136///
137/// # Example
138///
139/// ```ignore
140/// set_recovery_callback(Box::new(|_info| {
141///     // Save all unsaved buffers to recovery directory
142///     for buffer in buffers.iter() {
143///         if buffer.is_modified() {
144///             save_buffer_for_recovery(buffer);
145///         }
146///     }
147/// }));
148/// ```
149pub fn set_recovery_callback(callback: RecoveryCallback) {
150    let _ = RECOVERY_CALLBACK.set(callback);
151}
152
153/// Check if the panic handler has been installed.
154#[must_use]
155pub fn is_handler_installed() -> bool {
156    PANIC_HANDLER_INSTALLED.load(Ordering::SeqCst)
157}