Skip to content

Commit

Permalink
feat(infrastructure): introduce ErrorContext for tracking errors acro…
Browse files Browse the repository at this point in the history
…ss threads (#83)

* Implement ErrorContext for tracking errors across threads

* reorder Instruction and ErrorContext

* Add ContextType, AppContext, ScreenContext, PtyContext

* Use ArrayVec in ErrorContext

* increase MAX_THREAD_CALL_STACK to 6

* Custom implement Debug for ErrorContext ad ContextType and color output

* Use os_input instead of println!()

* Use array instead of ArrayVec and some cleanup

* Introduce SenderWithContext

* Keep arrayvec at v0.5.1
  • Loading branch information
kunalmohan authored Dec 9, 2020
1 parent 0a05e4e commit 92d1bcf
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 53 deletions.
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 {
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
Loading

0 comments on commit 92d1bcf

Please sign in to comment.