Skip to content

Commit 0f191e6

Browse files
authored
Fix panic messages being invisible in tui mode (#2226)
* Fix panic messages being invisible in tui mode Currently when a panic happens the message gets printed to the alternate screen which gets erased after the terminal is reset to raw mode in the TuiMetricsRenderer drop code. That leaves users unable to see the panic message (issue #2062). This commit changes TuiMetricsRenderer to reset the terminal first during a panic and then running the panic handler. * Use PanicInfo to support Rust version < 1.82
1 parent a567c6e commit 0f191e6

File tree

1 file changed

+34
-3
lines changed

1 file changed

+34
-3
lines changed

crates/burn-train/src/renderer/tui/renderer.rs

+34-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crossterm::{
77
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
88
};
99
use ratatui::{prelude::*, Terminal};
10+
use std::panic::{set_hook, take_hook};
11+
use std::sync::Arc;
1012
use std::{
1113
error::Error,
1214
io::{self, Stdout},
@@ -23,6 +25,9 @@ pub(crate) type TerminalBackend = CrosstermBackend<Stdout>;
2325
/// The current terminal frame.
2426
pub(crate) type TerminalFrame<'a> = ratatui::Frame<'a>;
2527

28+
#[allow(deprecated)] // `PanicInfo` type is renamed to `PanicHookInfo` in Rust 1.82
29+
type PanicHook = Box<dyn Fn(&std::panic::PanicInfo<'_>) + 'static + Sync + Send>;
30+
2631
const MAX_REFRESH_RATE_MILLIS: u64 = 100;
2732

2833
/// The terminal UI metrics renderer.
@@ -35,6 +40,7 @@ pub struct TuiMetricsRenderer {
3540
status: StatusState,
3641
interuptor: TrainingInterrupter,
3742
popup: PopupState,
43+
previous_panic_hook: Option<Arc<PanicHook>>,
3844
}
3945

4046
impl MetricsRenderer for TuiMetricsRenderer {
@@ -85,6 +91,18 @@ impl TuiMetricsRenderer {
8591
enable_raw_mode().unwrap();
8692
let terminal = Terminal::new(CrosstermBackend::new(stdout)).unwrap();
8793

94+
// Reset the terminal to raw mode on panic before running the panic handler
95+
// This prevents that the panic message is not visible for the user.
96+
let previous_panic_hook = Arc::new(take_hook());
97+
set_hook(Box::new({
98+
let previous_panic_hook = previous_panic_hook.clone();
99+
move |panic_info| {
100+
let _ = disable_raw_mode();
101+
let _ = execute!(io::stdout(), LeaveAlternateScreen);
102+
previous_panic_hook(panic_info);
103+
}
104+
}));
105+
88106
Self {
89107
terminal,
90108
last_update: Instant::now(),
@@ -94,6 +112,7 @@ impl TuiMetricsRenderer {
94112
status: StatusState::default(),
95113
interuptor,
96114
popup: PopupState::Empty,
115+
previous_panic_hook: Some(previous_panic_hook),
97116
}
98117
}
99118

@@ -205,8 +224,20 @@ impl CallbackFn for PopupCancel {
205224

206225
impl Drop for TuiMetricsRenderer {
207226
fn drop(&mut self) {
208-
disable_raw_mode().ok();
209-
execute!(self.terminal.backend_mut(), LeaveAlternateScreen).unwrap();
210-
self.terminal.show_cursor().ok();
227+
// Reset the terminal back to raw mode. This can be skipped during
228+
// panicking because the panic hook has already reset the terminal
229+
if !std::thread::panicking() {
230+
disable_raw_mode().ok();
231+
execute!(self.terminal.backend_mut(), LeaveAlternateScreen).unwrap();
232+
self.terminal.show_cursor().ok();
233+
234+
// Reinstall the previous panic hook
235+
let _ = take_hook();
236+
if let Some(previous_panic_hook) =
237+
Arc::into_inner(self.previous_panic_hook.take().unwrap())
238+
{
239+
set_hook(previous_panic_hook);
240+
}
241+
}
211242
}
212243
}

0 commit comments

Comments
 (0)