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}