Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ErrorContext for tracking errors across threads #83

Merged
merged 10 commits into from
Dec 9, 2020
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
arrayvec = "0.5.2"
backtrace = "0.3.55"
bincode = "1.3.1"
futures = "0.3.5"
Expand Down
184 changes: 176 additions & 8 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
use crate::AppInstruction;
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
use crate::{AppInstruction, OPENCALLS};
use arrayvec::ArrayVec;
use backtrace::Backtrace;
use std::fmt::{Debug, Error, Formatter};
use std::panic::PanicInfo;
use std::sync::mpsc::SyncSender;
use std::{process, thread};

pub fn handle_panic(info: &PanicInfo<'_>, send_app_instructions: &SyncSender<AppInstruction>) {
const MAX_THREAD_CALL_STACK: usize = 6;

pub fn handle_panic(
info: &PanicInfo<'_>,
send_app_instructions: &SyncSender<(AppInstruction, ErrorContext)>,
) {
let backtrace = Backtrace::new();
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
Expand All @@ -14,35 +23,194 @@ pub fn handle_panic(info: &PanicInfo<'_>, send_app_instructions: &SyncSender<App
None => info.payload().downcast_ref::<String>().map(|s| &**s),
};

let err_ctx: ErrorContext = OPENCALLS.with(|ctx| ctx.borrow().clone());

let backtrace = match (info.location(), msg) {
(Some(location), Some(msg)) => format!(
"\nthread '{}' panicked at '{}': {}:{}\n{:?}",
"{:#?}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}': {}:{}\n\u{1b}[0;0m{:?}",
err_ctx,
thread,
msg,
location.file(),
location.line(),
backtrace
),
(Some(location), None) => format!(
"\nthread '{}' panicked: {}:{}\n{:?}",
"{:#?}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked: {}:{}\n\u{1b}[0;0m{:?}",
err_ctx,
thread,
location.file(),
location.line(),
backtrace
),
(None, Some(msg)) => format!(
"\nthread '{}' panicked at '{}'\n{:?}",
thread, msg, backtrace
"{:#?}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}'\n\u{1b}[0;0m{:?}",
err_ctx, thread, msg, backtrace
),
(None, None) => format!(
"{:#?}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked\n\u{1b}[0;0m{:?}",
err_ctx, thread, 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))
.send((AppInstruction::Error(backtrace), err_ctx))
.unwrap();
}
}

#[derive(Clone)]
pub struct ErrorContext {
calls: ArrayVec<[ContextType; MAX_THREAD_CALL_STACK]>,
}

impl ErrorContext {
pub fn new() -> Self {
Self {
calls: ArrayVec::new(),
}
}

pub fn add_call(&mut self, call: ContextType) {
self.calls.push(call);
OPENCALLS.with(|ctx| *ctx.borrow_mut() = self.clone());
}
}

impl Debug for ErrorContext {
kunalmohan marked this conversation as resolved.
Show resolved Hide resolved
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
writeln!(f, "Originating Thread(s):")?;
for (index, ctx) in self.calls.iter().enumerate() {
writeln!(f, "\u{1b}[0;0m{}. {:?}", index + 1, ctx)?;
}
Ok(())
}
}

#[derive(Copy, Clone)]
pub enum ContextType {
Screen(ScreenContext),
Pty(PtyContext),
App(AppContext),
IPCServer,
StdinHandler,
AsyncTask,
}

impl Debug for ContextType {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
let purple = "\u{1b}[1;35m";
let green = "\u{1b}[0;32m";
match *self {
ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c),
ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c),
ContextType::App(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
ContextType::IPCServer => write!(f, "{}ipc_server: {}AcceptInput", purple, green),
ContextType::StdinHandler => {
write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green)
}
ContextType::AsyncTask => {
write!(f, "{}stream_terminal_bytes: {}AsyncTask", purple, green)
}
}
}
}

#[derive(Debug, Clone, Copy)]
pub enum ScreenContext {
kunalmohan marked this conversation as resolved.
Show resolved Hide resolved
HandlePtyEvent,
Render,
NewPane,
HorizontalSplit,
VerticalSplit,
WriteCharacter,
ResizeLeft,
ResizeRight,
ResizeDown,
ResizeUp,
MoveFocus,
MoveFocusLeft,
MoveFocusDown,
MoveFocusUp,
MoveFocusRight,
Quit,
ScrollUp,
ScrollDown,
ClearScroll,
CloseFocusedPane,
ToggleActiveTerminalFullscreen,
ClosePane,
ApplyLayout,
}

impl From<&ScreenInstruction> for ScreenContext {
fn from(screen_instruction: &ScreenInstruction) -> Self {
match *screen_instruction {
ScreenInstruction::Pty(..) => ScreenContext::HandlePtyEvent,
ScreenInstruction::Render => ScreenContext::Render,
ScreenInstruction::NewPane(_) => ScreenContext::NewPane,
ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit,
ScreenInstruction::VerticalSplit(_) => ScreenContext::VerticalSplit,
ScreenInstruction::WriteCharacter(_) => ScreenContext::WriteCharacter,
ScreenInstruction::ResizeLeft => ScreenContext::ResizeLeft,
ScreenInstruction::ResizeRight => ScreenContext::ResizeRight,
ScreenInstruction::ResizeDown => ScreenContext::ResizeDown,
ScreenInstruction::ResizeUp => ScreenContext::ResizeUp,
ScreenInstruction::MoveFocus => ScreenContext::MoveFocus,
ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft,
ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown,
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight,
ScreenInstruction::Quit => ScreenContext::Quit,
ScreenInstruction::ScrollUp => ScreenContext::ScrollUp,
ScreenInstruction::ScrollDown => ScreenContext::ScrollDown,
ScreenInstruction::ClearScroll => ScreenContext::ClearScroll,
ScreenInstruction::CloseFocusedPane => ScreenContext::CloseFocusedPane,
ScreenInstruction::ToggleActiveTerminalFullscreen => {
ScreenContext::ToggleActiveTerminalFullscreen
}
ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane,
ScreenInstruction::ApplyLayout(_) => ScreenContext::ApplyLayout,
}
}
}

#[derive(Debug, Clone, Copy)]
pub enum PtyContext {
SpawnTerminal,
SpawnTerminalVertically,
SpawnTerminalHorizontally,
ClosePane,
Quit,
}

impl From<&PtyInstruction> for PtyContext {
fn from(pty_instruction: &PtyInstruction) -> Self {
match *pty_instruction {
PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal,
PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically,
PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally,
PtyInstruction::ClosePane(_) => PtyContext::ClosePane,
PtyInstruction::Quit => PtyContext::Quit,
}
}
}

#[derive(Debug, Clone, Copy)]
pub enum AppContext {
Exit,
Error,
}

impl From<&AppInstruction> for AppContext {
fn from(app_instruction: &AppInstruction) -> Self {
match *app_instruction {
AppInstruction::Exit => AppContext::Exit,
AppInstruction::Error(_) => AppContext::Error,
}
}
}
Loading