feat(infra): add custom panic hook. Print backtrace and thread, error info on panic. (#75)

* Add custom panic hook. Print backtrace and thread, error info on panic.

* use sync_channel and SyncSender

* nit fixes and cleanup

* disable custom panic hook while running tests

* make separate errors.rs file and address other review comments

* improve panic message

* debug: does increasing time between snapshots make tests pass? (this is temporary)

* fix(tests): suspend before sending quit command

* fix(tests): add missing use

* style(format): commas are important apparently

* fix(tests): can we get away with reducing the QUIT suspense time?

* fix(tests): can we get away with 50?

Co-authored-by: Aram Drevekenin <aram@poor.dev>
This commit is contained in:
Kunal Mohan 2020-12-03 20:05:16 +05:30 committed by GitHub
parent c3fa9fb40e
commit 56c53d2487
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 117 additions and 32 deletions

1
Cargo.lock generated
View File

@ -862,6 +862,7 @@ name = "mosaic"
version = "0.1.0"
dependencies = [
"async-std",
"backtrace",
"bincode",
"futures",
"insta",

View File

@ -7,19 +7,20 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
termios = "0.3"
backtrace = "0.3.55"
bincode = "1.3.1"
futures = "0.3.5"
libc = "0.2"
nix = "0.17.0"
signal-hook = "0.1.10"
unicode-width = "0.1.8"
unicode-truncate = "0.1.1"
vte = "0.8.0"
futures = "0.3.5"
serde = { version = "1.0", features = ["derive"] }
bincode = "1.3.1"
structopt = "0.3"
serde_yaml = "0.8"
serde_json = "1.0"
serde_yaml = "0.8"
signal-hook = "0.1.10"
structopt = "0.3"
termios = "0.3"
unicode-truncate = "0.1.1"
unicode-width = "0.1.8"
vte = "0.8.0"
[dependencies.async-std]
version = "1.3.0"

56
src/errors.rs Normal file
View File

@ -0,0 +1,56 @@
use crate::AppInstruction;
use backtrace::Backtrace;
use std::panic::PanicInfo;
use std::sync::mpsc::SyncSender;
use std::{process, thread};
pub fn handle_panic(info: &PanicInfo<'_>, send_app_instructions: &SyncSender<AppInstruction>) {
let backtrace = Backtrace::new();
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => Some(*s),
None => info.payload().downcast_ref::<String>().map(|s| &**s),
};
let backtrace = match (info.location(), msg) {
(Some(location), Some(msg)) => {
format!(
"\nthread '{}' panicked at '{}': {}:{}\n{:?}",
thread,
msg,
location.file(),
location.line(),
backtrace
)
}
(Some(location), None) => {
format!(
"\nthread '{}' panicked: {}:{}\n{:?}",
thread,
location.file(),
location.line(),
backtrace
)
}
(None, Some(msg)) => {
format!(
"\nthread '{}' panicked at '{}'\n{:?}",
thread, msg, backtrace
)
}
(None, None) => {
format!("\nthread '{}' panicked\n{:?}", thread, backtrace)
}
};
if thread == "main" {
println!("{}", backtrace);
process::exit(1);
} else {
send_app_instructions
.send(AppInstruction::Error(backtrace))
.unwrap();
}
}

View File

@ -1,5 +1,5 @@
/// Module for handling input
use std::sync::mpsc::Sender;
use std::sync::mpsc::{Sender, SyncSender};
use crate::os_input_output::OsApi;
use crate::pty_bus::PtyInstruction;
@ -13,7 +13,7 @@ struct InputHandler {
command_is_executing: CommandIsExecuting,
send_screen_instructions: Sender<ScreenInstruction>,
send_pty_instructions: Sender<PtyInstruction>,
send_app_instructions: Sender<AppInstruction>,
send_app_instructions: SyncSender<AppInstruction>,
}
impl InputHandler {
@ -22,7 +22,7 @@ impl InputHandler {
command_is_executing: CommandIsExecuting,
send_screen_instructions: Sender<ScreenInstruction>,
send_pty_instructions: Sender<PtyInstruction>,
send_app_instructions: Sender<AppInstruction>,
send_app_instructions: SyncSender<AppInstruction>,
) -> Self {
InputHandler {
mode: InputMode::Normal,
@ -241,7 +241,7 @@ pub fn input_loop(
command_is_executing: CommandIsExecuting,
send_screen_instructions: Sender<ScreenInstruction>,
send_pty_instructions: Sender<PtyInstruction>,
send_app_instructions: Sender<AppInstruction>,
send_app_instructions: SyncSender<AppInstruction>,
) {
let _handler = InputHandler::new(
os_input,

View File

@ -3,6 +3,7 @@ mod tests;
mod boundaries;
mod command_is_executing;
mod errors;
mod input;
mod layout;
mod os_input_output;
@ -14,7 +15,7 @@ mod utils;
use std::io::Write;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender};
use std::thread;
use serde::{Deserialize, Serialize};
@ -95,6 +96,7 @@ pub fn main() {
pub enum AppInstruction {
Exit,
Error(String),
}
pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
@ -113,9 +115,9 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
Receiver<PtyInstruction>,
) = channel();
let (send_app_instructions, receive_app_instructions): (
Sender<AppInstruction>,
SyncSender<AppInstruction>,
Receiver<AppInstruction>,
) = channel();
) = sync_channel(0);
let mut screen = Screen::new(
receive_screen_instructions,
send_pty_instructions.clone(),
@ -132,6 +134,15 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
);
let maybe_layout = opts.layout.map(Layout::new);
#[cfg(not(test))]
std::panic::set_hook({
use crate::errors::handle_panic;
let send_app_instructions = send_app_instructions.clone();
Box::new(move |info| {
handle_panic(info, &send_app_instructions);
})
});
active_threads.push(
thread::Builder::new()
.name("pty".to_string())
@ -419,7 +430,6 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
.spawn({
let send_screen_instructions = send_screen_instructions.clone();
let send_pty_instructions = send_pty_instructions.clone();
let send_app_instructions = send_app_instructions.clone();
let os_input = os_input.clone();
move || {
input_loop(
@ -443,6 +453,16 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
let _ = send_pty_instructions.send(PtyInstruction::Quit);
break;
}
AppInstruction::Error(backtrace) => {
os_input.unset_raw_mode(0);
println!("{}", backtrace);
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = send_pty_instructions.send(PtyInstruction::Quit);
for thread_handler in active_threads {
let _ = thread_handler.join();
}
std::process::exit(1);
}
}
}

View File

@ -1,7 +1,7 @@
use std::collections::{BTreeMap, HashSet};
use std::io::Write;
use std::os::unix::io::RawFd;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::mpsc::{Receiver, Sender, SyncSender};
use crate::boundaries::Boundaries;
use crate::layout::Layout;
@ -75,7 +75,7 @@ pub struct Screen {
pub receiver: Receiver<ScreenInstruction>,
max_panes: Option<usize>,
send_pty_instructions: Sender<PtyInstruction>,
send_app_instructions: Sender<AppInstruction>,
send_app_instructions: SyncSender<AppInstruction>,
full_screen_ws: PositionAndSize,
terminals: BTreeMap<RawFd, TerminalPane>, // BTreeMap because we need a predictable order when changing focus
panes_to_hide: HashSet<RawFd>,
@ -88,7 +88,7 @@ impl Screen {
pub fn new(
receive_screen_instructions: Receiver<ScreenInstruction>,
send_pty_instructions: Sender<PtyInstruction>,
send_app_instructions: Sender<AppInstruction>,
send_app_instructions: SyncSender<AppInstruction>,
full_screen_ws: &PositionAndSize,
os_api: Box<dyn OsApi>,
max_panes: Option<usize>,

View File

@ -1,5 +1,5 @@
use ::std::collections::VecDeque;
use ::std::fmt::{self, Debug, Formatter};
use std::collections::VecDeque;
use std::fmt::{self, Debug, Formatter};
use crate::terminal_pane::terminal_character::{
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,

View File

@ -1,16 +1,18 @@
use crate::terminal_pane::PositionAndSize;
use ::std::collections::{HashMap, VecDeque};
use ::std::io::Write;
use ::std::os::unix::io::RawFd;
use ::std::path::PathBuf;
use ::std::sync::atomic::{AtomicBool, Ordering};
use ::std::sync::{Arc, Mutex};
use ::std::time::{Duration, Instant};
use std::collections::{HashMap, VecDeque};
use std::io::Write;
use std::os::unix::io::RawFd;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use crate::os_input_output::OsApi;
use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes};
use crate::tests::utils::commands::QUIT;
const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(50);
const WAIT_TIME_BEFORE_QUITTING: Duration = Duration::from_millis(50);
#[derive(Clone)]
pub enum IoEvent {
@ -207,7 +209,12 @@ impl OsApi for FakeInputOutput {
}
}
match self.stdin_commands.lock().unwrap().pop_front() {
Some(command) => command,
Some(command) => {
if command == QUIT {
std::thread::sleep(WAIT_TIME_BEFORE_QUITTING);
}
command
}
None => {
// what is happening here?
//

View File

@ -1,4 +1,4 @@
use ::insta::assert_snapshot;
use insta::assert_snapshot;
use std::path::PathBuf;
use crate::terminal_pane::PositionAndSize;

View File

@ -1,4 +1,4 @@
use ::insta::assert_snapshot;
use insta::assert_snapshot;
use crate::terminal_pane::PositionAndSize;
use crate::tests::fakes::FakeInputOutput;