diff --git a/src/errors.rs b/src/errors.rs index 437db0dd79..8478969dfb 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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) { +const MAX_THREAD_CALL_STACK: usize = 6; + +pub fn handle_panic( + info: &PanicInfo<'_>, + send_app_instructions: &SenderWithContext, +) { let backtrace = Backtrace::new(); let thread = thread::current(); let thread = thread.name().unwrap_or("unnamed"); @@ -14,9 +21,12 @@ pub fn handle_panic(info: &PanicInfo<'_>, send_app_instructions: &SyncSender info.payload().downcast_ref::().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(), @@ -24,17 +34,21 @@ pub fn handle_panic(info: &PanicInfo<'_>, send_app_instructions: &SyncSender 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" { @@ -46,3 +60,165 @@ pub fn handle_panic(info: &PanicInfo<'_>, send_app_instructions: &SyncSender 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, + } + } +} diff --git a/src/input.rs b/src/input.rs index ea2c4d41ac..6d82f4f921 100644 --- a/src/input.rs +++ b/src/input.rs @@ -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, command_is_executing: CommandIsExecuting, - send_screen_instructions: Sender, - send_pty_instructions: Sender, - send_app_instructions: SyncSender, + send_screen_instructions: SenderWithContext, + send_pty_instructions: SenderWithContext, + send_app_instructions: SenderWithContext, } impl InputHandler { fn new( os_input: Box, command_is_executing: CommandIsExecuting, - send_screen_instructions: Sender, - send_pty_instructions: Sender, - send_app_instructions: SyncSender, + send_screen_instructions: SenderWithContext, + send_pty_instructions: SenderWithContext, + send_app_instructions: SenderWithContext, ) -> Self { InputHandler { mode: InputMode::Normal, @@ -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(), @@ -263,9 +267,9 @@ pub enum InputMode { pub fn input_loop( os_input: Box, command_is_executing: CommandIsExecuting, - send_screen_instructions: Sender, - send_pty_instructions: Sender, - send_app_instructions: SyncSender, + send_screen_instructions: SenderWithContext, + send_pty_instructions: SenderWithContext, + send_app_instructions: SenderWithContext, ) { let _handler = InputHandler::new( os_input, diff --git a/src/main.rs b/src/main.rs index 0ad09ed00c..27deb0933a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,13 +15,14 @@ mod utils; use std::io::Write; use std::os::unix::net::UnixStream; use std::path::PathBuf; -use std::sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender}; +use std::sync::mpsc::{channel, sync_channel, Receiver, SendError, Sender, SyncSender}; use std::thread; use serde::{Deserialize, Serialize}; use structopt::StructOpt; use crate::command_is_executing::CommandIsExecuting; +use crate::errors::{AppContext, ContextType, ErrorContext, PtyContext, ScreenContext}; use crate::input::input_loop; use crate::layout::Layout; use crate::os_input_output::{get_os_input, OsApi}; @@ -31,6 +32,9 @@ use crate::utils::{ consts::{MOSAIC_IPC_PIPE, MOSAIC_TMP_DIR, MOSAIC_TMP_LOG_DIR}, logging::*, }; +use std::cell::RefCell; + +thread_local!(static OPENCALLS: RefCell = RefCell::new(ErrorContext::new())); #[derive(Serialize, Deserialize, Debug)] enum ApiCommand { @@ -40,6 +44,38 @@ enum ApiCommand { MoveFocus, } +#[derive(Clone)] +enum SenderType { + Sender(Sender<(T, ErrorContext)>), + SyncSender(SyncSender<(T, ErrorContext)>), +} + +#[derive(Clone)] +pub struct SenderWithContext { + err_ctx: ErrorContext, + sender: SenderType, +} + +impl SenderWithContext { + fn new(err_ctx: ErrorContext, sender: SenderType) -> Self { + Self { err_ctx, sender } + } + + pub fn send(&self, event: T) -> Result<(), SendError<(T, ErrorContext)>> { + match self.sender { + SenderType::Sender(ref s) => s.send((event, self.err_ctx)), + SenderType::SyncSender(ref s) => s.send((event, self.err_ctx)), + } + } + + pub fn update(&mut self, new_ctx: ErrorContext) { + self.err_ctx = new_ctx; + } +} + +unsafe impl Send for SenderWithContext {} +unsafe impl Sync for SenderWithContext {} + #[derive(StructOpt, Debug, Default)] #[structopt(name = "mosaic")] pub struct Opt { @@ -94,6 +130,7 @@ pub fn main() { } } +#[derive(Clone)] pub enum AppInstruction { Exit, Error(String), @@ -107,17 +144,24 @@ pub fn start(mut os_input: Box, opts: Opt) { let full_screen_ws = os_input.get_terminal_size_using_fd(0); os_input.into_raw_mode(0); let (send_screen_instructions, receive_screen_instructions): ( - Sender, - Receiver, + Sender<(ScreenInstruction, ErrorContext)>, + Receiver<(ScreenInstruction, ErrorContext)>, ) = channel(); + let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); + let mut send_screen_instructions = + SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions)); let (send_pty_instructions, receive_pty_instructions): ( - Sender, - Receiver, + Sender<(PtyInstruction, ErrorContext)>, + Receiver<(PtyInstruction, ErrorContext)>, ) = channel(); + let mut send_pty_instructions = + SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_instructions)); let (send_app_instructions, receive_app_instructions): ( - SyncSender, - Receiver, + SyncSender<(AppInstruction, ErrorContext)>, + Receiver<(AppInstruction, ErrorContext)>, ) = sync_channel(0); + let send_app_instructions = + SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions)); let mut screen = Screen::new( receive_screen_instructions, send_pty_instructions.clone(), @@ -156,10 +200,12 @@ pub fn start(mut os_input: Box, opts: Opt) { } loop { - let event = pty_bus + let (event, mut err_ctx) = pty_bus .receive_pty_instructions .recv() .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); + pty_bus.send_screen_instructions.update(err_ctx); match event { PtyInstruction::SpawnTerminal(file_to_open) => { pty_bus.spawn_terminal(file_to_open); @@ -190,10 +236,13 @@ pub fn start(mut os_input: Box, opts: Opt) { .spawn({ let mut command_is_executing = command_is_executing.clone(); move || loop { - let event = screen + let (event, mut err_ctx) = screen .receiver .recv() .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); + screen.send_app_instructions.update(err_ctx); + screen.send_pty_instructions.update(err_ctx); match event { ScreenInstruction::Pty(pid, vte_event) => { screen.handle_pty_event(pid, vte_event); @@ -388,12 +437,16 @@ pub fn start(mut os_input: Box, opts: Opt) { .name("ipc_server".to_string()) .spawn({ use std::io::Read; - let send_pty_instructions = send_pty_instructions.clone(); - let send_screen_instructions = send_screen_instructions.clone(); + let mut send_pty_instructions = send_pty_instructions.clone(); + let mut send_screen_instructions = send_screen_instructions.clone(); move || { std::fs::remove_file(MOSAIC_IPC_PIPE).ok(); let listener = std::os::unix::net::UnixListener::bind(MOSAIC_IPC_PIPE) .expect("could not listen on ipc socket"); + let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); + err_ctx.add_call(ContextType::IPCServer); + send_pty_instructions.update(err_ctx); + send_screen_instructions.update(err_ctx); for stream in listener.incoming() { match stream { @@ -456,9 +509,13 @@ pub fn start(mut os_input: Box, opts: Opt) { #[warn(clippy::never_loop)] loop { - let app_instruction = receive_app_instructions + let (app_instruction, mut err_ctx) = receive_app_instructions .recv() .expect("failed to receive app instruction on channel"); + + err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction))); + send_screen_instructions.update(err_ctx); + send_pty_instructions.update(err_ctx); match app_instruction { AppInstruction::Exit => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); @@ -466,10 +523,15 @@ pub fn start(mut os_input: Box, opts: Opt) { 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); + os_input.unset_raw_mode(0); + let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); + let error = format!("{}\n{}", goto_start_of_last_line, backtrace); + let _ = os_input + .get_stdout_writer() + .write(error.as_bytes()) + .unwrap(); for thread_handler in active_threads { let _ = thread_handler.join(); } diff --git a/src/pty_bus.rs b/src/pty_bus.rs index 078e327152..758271c355 100644 --- a/src/pty_bus.rs +++ b/src/pty_bus.rs @@ -4,15 +4,16 @@ use ::async_std::task::*; use ::std::collections::HashMap; use ::std::os::unix::io::RawFd; use ::std::pin::*; -use ::std::sync::mpsc::{Receiver, Sender}; +use ::std::sync::mpsc::Receiver; use ::std::time::{Duration, Instant}; use ::vte; use std::path::PathBuf; +use crate::errors::{ContextType, ErrorContext}; use crate::layout::Layout; use crate::os_input_output::OsApi; use crate::utils::logging::debug_to_file; -use crate::ScreenInstruction; +use crate::{ScreenInstruction, SenderWithContext, OPENCALLS}; pub struct ReadFromPid { pid: RawFd, @@ -75,11 +76,11 @@ pub enum VteEvent { struct VteEventSender { id: RawFd, - sender: Sender, + sender: SenderWithContext, } impl VteEventSender { - pub fn new(id: RawFd, sender: Sender) -> Self { + pub fn new(id: RawFd, sender: SenderWithContext) -> Self { VteEventSender { id, sender } } } @@ -151,8 +152,8 @@ pub enum PtyInstruction { } pub struct PtyBus { - pub send_screen_instructions: Sender, - pub receive_pty_instructions: Receiver, + pub send_screen_instructions: SenderWithContext, + pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, pub id_to_child_pid: HashMap, os_input: Box, debug_to_file: bool, @@ -160,12 +161,15 @@ pub struct PtyBus { fn stream_terminal_bytes( pid: RawFd, - send_screen_instructions: Sender, + mut send_screen_instructions: SenderWithContext, os_input: Box, debug: bool, ) { + let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); task::spawn({ async move { + err_ctx.add_call(ContextType::AsyncTask); + send_screen_instructions.update(err_ctx); let mut vte_parser = vte::Parser::new(); let mut vte_event_sender = VteEventSender::new(pid, send_screen_instructions.clone()); let mut terminal_bytes = ReadFromPid::new(&pid, os_input); @@ -233,8 +237,8 @@ fn stream_terminal_bytes( impl PtyBus { pub fn new( - receive_pty_instructions: Receiver, - send_screen_instructions: Sender, + receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, + send_screen_instructions: SenderWithContext, os_input: Box, debug_to_file: bool, ) -> Self { diff --git a/src/screen.rs b/src/screen.rs index e00cc0a9a2..79442a97f8 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -1,16 +1,16 @@ use std::collections::{BTreeMap, HashSet}; use std::io::Write; use std::os::unix::io::RawFd; -use std::sync::mpsc::{Receiver, Sender, SyncSender}; +use std::sync::mpsc::Receiver; use crate::boundaries::Boundaries; use crate::boundaries::Rect; +use crate::errors::ErrorContext; use crate::layout::Layout; use crate::os_input_output::OsApi; use crate::pty_bus::{PtyInstruction, VteEvent}; use crate::terminal_pane::{PositionAndSize, TerminalPane}; -use crate::utils::logging::debug_log_to_file; -use crate::AppInstruction; +use crate::{AppInstruction, SenderWithContext}; /* * Screen @@ -78,10 +78,10 @@ pub enum ScreenInstruction { } pub struct Screen { - pub receiver: Receiver, + pub receiver: Receiver<(ScreenInstruction, ErrorContext)>, max_panes: Option, - send_pty_instructions: Sender, - send_app_instructions: SyncSender, + pub send_pty_instructions: SenderWithContext, + pub send_app_instructions: SenderWithContext, full_screen_ws: PositionAndSize, terminals: BTreeMap, // BTreeMap because we need a predictable order when changing focus panes_to_hide: HashSet, @@ -92,9 +92,9 @@ pub struct Screen { impl Screen { pub fn new( - receive_screen_instructions: Receiver, - send_pty_instructions: Sender, - send_app_instructions: SyncSender, + receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>, + send_pty_instructions: SenderWithContext, + send_app_instructions: SenderWithContext, full_screen_ws: &PositionAndSize, os_api: Box, max_panes: Option, diff --git a/src/terminal_pane/terminal_pane.rs b/src/terminal_pane/terminal_pane.rs index 348a9fe0c8..9f8ddcfa6b 100644 --- a/src/terminal_pane/terminal_pane.rs +++ b/src/terminal_pane/terminal_pane.rs @@ -9,7 +9,7 @@ use crate::terminal_pane::terminal_character::{ AnsiCode, CharacterStyles, NamedColor, TerminalCharacter, }; use crate::terminal_pane::Scroll; -use crate::utils::logging::{debug_log_to_file, debug_log_to_file_pid_3}; +use crate::utils::logging::debug_log_to_file; use crate::VteEvent; #[derive(Clone, Copy, Debug)] diff --git a/src/utils/logging.rs b/src/utils/logging.rs index 84a2da8fab..05af455c06 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -37,7 +37,7 @@ pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> { file.write_all(message.as_bytes()) } -pub fn debug_log_to_file_pid_3(message: String, pid: RawFd) -> io::Result<()> { +pub fn _debug_log_to_file_pid_3(message: String, pid: RawFd) -> io::Result<()> { if pid == 3 { debug_log_to_file(message) } else {