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
192 changes: 184 additions & 8 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
use crate::AppInstruction;
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
use crate::{AppInstruction, SenderWithContext, OPENCALLS};
use backtrace::Backtrace;
use std::fmt::{Display, 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: &SenderWithContext<AppInstruction>,
) {
let backtrace = Backtrace::new();
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
Expand All @@ -14,27 +21,34 @@ pub fn handle_panic(info: &PanicInfo<'_>, send_app_instructions: &SyncSender<App
None => info.payload().downcast_ref::<String>().map(|s| &**s),
};

let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());

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" {
Expand All @@ -46,3 +60,165 @@ pub fn handle_panic(info: &PanicInfo<'_>, send_app_instructions: &SyncSender<App
.unwrap();
}
}

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

impl ErrorContext {
pub fn new() -> Self {
Self {
calls: [ContextType::Empty; MAX_THREAD_CALL_STACK],
}
}

pub fn add_call(&mut self, call: ContextType) {
for ctx in self.calls.iter_mut() {
if *ctx == ContextType::Empty {
*ctx = call;
break;
}
}
OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self);
}
}

impl Display for ErrorContext {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
writeln!(f, "Originating Thread(s):")?;
for (index, ctx) in self.calls.iter().enumerate() {
if *ctx == ContextType::Empty {
break;
}
writeln!(f, "\u{1b}[0;0m{}. {}", index + 1, ctx)?;
}
Ok(())
}
}

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

impl Display 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)
}
ContextType::Empty => write!(f, ""),
}
}
}

#[derive(Debug, Clone, Copy, PartialEq)]
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, PartialEq)]
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, PartialEq)]
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,
}
}
}
28 changes: 16 additions & 12 deletions src/input.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
/// Module for handling input
use std::sync::mpsc::{Sender, SyncSender};

use crate::errors::ContextType;
use crate::os_input_output::OsApi;
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
use crate::AppInstruction;
use crate::CommandIsExecuting;
use crate::{AppInstruction, SenderWithContext, OPENCALLS};

struct InputHandler {
mode: InputMode,
os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting,
send_screen_instructions: Sender<ScreenInstruction>,
send_pty_instructions: Sender<PtyInstruction>,
send_app_instructions: SyncSender<AppInstruction>,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
}

impl InputHandler {
fn new(
os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting,
send_screen_instructions: Sender<ScreenInstruction>,
send_pty_instructions: Sender<PtyInstruction>,
send_app_instructions: SyncSender<AppInstruction>,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) -> Self {
InputHandler {
mode: InputMode::Normal,
Expand All @@ -36,6 +35,11 @@ impl InputHandler {

/// Main event loop
fn get_input(&mut self) {
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
err_ctx.add_call(ContextType::StdinHandler);
self.send_pty_instructions.update(err_ctx);
self.send_app_instructions.update(err_ctx);
self.send_screen_instructions.update(err_ctx);
loop {
match self.mode {
InputMode::Normal => self.read_normal_mode(),
Expand Down Expand Up @@ -263,9 +267,9 @@ pub enum InputMode {
pub fn input_loop(
os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting,
send_screen_instructions: Sender<ScreenInstruction>,
send_pty_instructions: Sender<PtyInstruction>,
send_app_instructions: SyncSender<AppInstruction>,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) {
let _handler = InputHandler::new(
os_input,
Expand Down