From decc38232b7318190f7ce20f9eccff3814bcc68b Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 20 Jan 2021 19:25:35 +0100 Subject: [PATCH] fix(main): do not hang on exit (#150) --- src/main.rs | 613 +++++++++++++++++++++++++------------------------- src/screen.rs | 8 +- 2 files changed, 305 insertions(+), 316 deletions(-) diff --git a/src/main.rs b/src/main.rs index 27e97766..a913de16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -173,7 +173,6 @@ pub enum AppInstruction { pub fn start(mut os_input: Box, opts: CliArgs) { let mut app_state = AppState::default(); - let mut active_threads = vec![]; let command_is_executing = CommandIsExecuting::new(); @@ -225,345 +224,334 @@ pub fn start(mut os_input: Box, opts: CliArgs) { }) }); - active_threads.push( - thread::Builder::new() - .name("pty".to_string()) - .spawn({ - let mut command_is_executing = command_is_executing.clone(); - send_pty_instructions.send(PtyInstruction::NewTab).unwrap(); - move || loop { - let (event, mut err_ctx) = pty_bus - .receive_pty_instructions + let pty_thread = thread::Builder::new() + .name("pty".to_string()) + .spawn({ + let mut command_is_executing = command_is_executing.clone(); + send_pty_instructions.send(PtyInstruction::NewTab).unwrap(); + move || loop { + 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) => { + let pid = pty_bus.spawn_terminal(file_to_open); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::NewPane(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::SpawnTerminalVertically(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::NewTab => { + if let Some(layout) = maybe_layout.clone() { + pty_bus.spawn_terminals_for_layout(layout); + } else { + let pid = pty_bus.spawn_terminal(None); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::NewTab(pid)) + .unwrap(); + } + } + PtyInstruction::ClosePane(id) => { + pty_bus.close_pane(id); + command_is_executing.done_closing_pane(); + } + PtyInstruction::CloseTab(ids) => { + pty_bus.close_tab(ids); + command_is_executing.done_closing_pane(); + } + PtyInstruction::Quit => { + break; + } + } + } + }) + .unwrap(); + + let screen_thread = thread::Builder::new() + .name("screen".to_string()) + .spawn({ + let mut command_is_executing = command_is_executing.clone(); + let os_input = os_input.clone(); + let send_pty_instructions = send_pty_instructions.clone(); + let send_plugin_instructions = send_plugin_instructions.clone(); + let send_app_instructions = send_app_instructions.clone(); + let max_panes = opts.max_panes; + + move || { + let mut screen = Screen::new( + receive_screen_instructions, + send_pty_instructions, + send_plugin_instructions, + send_app_instructions, + &full_screen_ws, + os_input, + max_panes, + ); + loop { + let (event, mut err_ctx) = screen + .receiver .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); + 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 { - PtyInstruction::SpawnTerminal(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::NewPane(PaneId::Terminal(pid))) - .unwrap(); + ScreenInstruction::Pty(pid, vte_event) => { + screen + .get_active_tab_mut() + .unwrap() + .handle_pty_event(pid, vte_event); } - PtyInstruction::SpawnTerminalVertically(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) - .unwrap(); + ScreenInstruction::Render => { + screen.render(); } - PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) - .unwrap(); + ScreenInstruction::NewPane(pid) => { + screen.get_active_tab_mut().unwrap().new_pane(pid); + command_is_executing.done_opening_new_pane(); } - PtyInstruction::NewTab => { - if let Some(layout) = maybe_layout.clone() { - pty_bus.spawn_terminals_for_layout(layout); - } else { - let pid = pty_bus.spawn_terminal(None); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::NewTab(pid)) - .unwrap(); - } + ScreenInstruction::HorizontalSplit(pid) => { + screen.get_active_tab_mut().unwrap().horizontal_split(pid); + command_is_executing.done_opening_new_pane(); } - PtyInstruction::ClosePane(id) => { - pty_bus.close_pane(id); - command_is_executing.done_closing_pane(); + ScreenInstruction::VerticalSplit(pid) => { + screen.get_active_tab_mut().unwrap().vertical_split(pid); + command_is_executing.done_opening_new_pane(); } - PtyInstruction::CloseTab(ids) => { - pty_bus.close_tab(ids); - command_is_executing.done_closing_pane(); + ScreenInstruction::WriteCharacter(bytes) => { + screen + .get_active_tab_mut() + .unwrap() + .write_to_active_terminal(bytes); } - PtyInstruction::Quit => { + ScreenInstruction::ResizeLeft => { + screen.get_active_tab_mut().unwrap().resize_left(); + } + ScreenInstruction::ResizeRight => { + screen.get_active_tab_mut().unwrap().resize_right(); + } + ScreenInstruction::ResizeDown => { + screen.get_active_tab_mut().unwrap().resize_down(); + } + ScreenInstruction::ResizeUp => { + screen.get_active_tab_mut().unwrap().resize_up(); + } + ScreenInstruction::MoveFocus => { + screen.get_active_tab_mut().unwrap().move_focus(); + } + ScreenInstruction::MoveFocusLeft => { + screen.get_active_tab_mut().unwrap().move_focus_left(); + } + ScreenInstruction::MoveFocusDown => { + screen.get_active_tab_mut().unwrap().move_focus_down(); + } + ScreenInstruction::MoveFocusRight => { + screen.get_active_tab_mut().unwrap().move_focus_right(); + } + ScreenInstruction::MoveFocusUp => { + screen.get_active_tab_mut().unwrap().move_focus_up(); + } + ScreenInstruction::ScrollUp => { + screen + .get_active_tab_mut() + .unwrap() + .scroll_active_terminal_up(); + } + ScreenInstruction::ScrollDown => { + screen + .get_active_tab_mut() + .unwrap() + .scroll_active_terminal_down(); + } + ScreenInstruction::ClearScroll => { + screen + .get_active_tab_mut() + .unwrap() + .clear_active_terminal_scroll(); + } + ScreenInstruction::CloseFocusedPane => { + screen.get_active_tab_mut().unwrap().close_focused_pane(); + screen.render(); + } + ScreenInstruction::SetSelectable(id, selectable) => { + screen + .get_active_tab_mut() + .unwrap() + .set_pane_selectable(id, selectable); + // FIXME: Is this needed? + screen.render(); + } + ScreenInstruction::ClosePane(id) => { + screen.get_active_tab_mut().unwrap().close_pane(id); + screen.render(); + } + ScreenInstruction::ToggleActiveTerminalFullscreen => { + screen + .get_active_tab_mut() + .unwrap() + .toggle_active_pane_fullscreen(); + } + ScreenInstruction::NewTab(pane_id) => { + screen.new_tab(pane_id); + command_is_executing.done_opening_new_pane(); + } + ScreenInstruction::SwitchTabNext => screen.switch_tab_next(), + ScreenInstruction::SwitchTabPrev => screen.switch_tab_prev(), + ScreenInstruction::CloseTab => screen.close_tab(), + ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => { + screen.apply_layout(layout, new_pane_pids); + command_is_executing.done_opening_new_pane(); + } + ScreenInstruction::Quit => { break; } } } - }) - .unwrap(), - ); + } + }) + .unwrap(); - active_threads.push( - thread::Builder::new() - .name("screen".to_string()) - .spawn({ - let mut command_is_executing = command_is_executing.clone(); - let os_input = os_input.clone(); - let send_pty_instructions = send_pty_instructions.clone(); - let send_plugin_instructions = send_plugin_instructions.clone(); - let send_app_instructions = send_app_instructions.clone(); - let max_panes = opts.max_panes; + let wasm_thread = thread::Builder::new() + .name("wasm".to_string()) + .spawn({ + let mut send_pty_instructions = send_pty_instructions.clone(); + let mut send_screen_instructions = send_screen_instructions.clone(); + let mut send_app_instructions = send_app_instructions.clone(); - move || { - let mut screen = Screen::new( - receive_screen_instructions, - send_pty_instructions, - send_plugin_instructions, - send_app_instructions, - &full_screen_ws, - os_input, - max_panes, - ); - loop { - 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 - .get_active_tab_mut() - .unwrap() - .handle_pty_event(pid, vte_event); - } - ScreenInstruction::Render => { - screen.render(); - } - ScreenInstruction::NewPane(pid) => { - screen.get_active_tab_mut().unwrap().new_pane(pid); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::HorizontalSplit(pid) => { - screen.get_active_tab_mut().unwrap().horizontal_split(pid); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::VerticalSplit(pid) => { - screen.get_active_tab_mut().unwrap().vertical_split(pid); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::WriteCharacter(bytes) => { - screen - .get_active_tab_mut() - .unwrap() - .write_to_active_terminal(bytes); - } - ScreenInstruction::ResizeLeft => { - screen.get_active_tab_mut().unwrap().resize_left(); - } - ScreenInstruction::ResizeRight => { - screen.get_active_tab_mut().unwrap().resize_right(); - } - ScreenInstruction::ResizeDown => { - screen.get_active_tab_mut().unwrap().resize_down(); - } - ScreenInstruction::ResizeUp => { - screen.get_active_tab_mut().unwrap().resize_up(); - } - ScreenInstruction::MoveFocus => { - screen.get_active_tab_mut().unwrap().move_focus(); - } - ScreenInstruction::MoveFocusLeft => { - screen.get_active_tab_mut().unwrap().move_focus_left(); - } - ScreenInstruction::MoveFocusDown => { - screen.get_active_tab_mut().unwrap().move_focus_down(); - } - ScreenInstruction::MoveFocusRight => { - screen.get_active_tab_mut().unwrap().move_focus_right(); - } - ScreenInstruction::MoveFocusUp => { - screen.get_active_tab_mut().unwrap().move_focus_up(); - } - ScreenInstruction::ScrollUp => { - screen - .get_active_tab_mut() - .unwrap() - .scroll_active_terminal_up(); - } - ScreenInstruction::ScrollDown => { - screen - .get_active_tab_mut() - .unwrap() - .scroll_active_terminal_down(); - } - ScreenInstruction::ClearScroll => { - screen - .get_active_tab_mut() - .unwrap() - .clear_active_terminal_scroll(); - } - ScreenInstruction::CloseFocusedPane => { - screen.get_active_tab_mut().unwrap().close_focused_pane(); - screen.render(); - } - ScreenInstruction::SetSelectable(id, selectable) => { - screen - .get_active_tab_mut() - .unwrap() - .set_pane_selectable(id, selectable); - // FIXME: Is this needed? - screen.render(); - } - ScreenInstruction::ClosePane(id) => { - screen.get_active_tab_mut().unwrap().close_pane(id); - screen.render(); - } - ScreenInstruction::ToggleActiveTerminalFullscreen => { - screen - .get_active_tab_mut() - .unwrap() - .toggle_active_pane_fullscreen(); - } - ScreenInstruction::NewTab(pane_id) => { - screen.new_tab(pane_id); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::SwitchTabNext => screen.switch_tab_next(), - ScreenInstruction::SwitchTabPrev => screen.switch_tab_prev(), - ScreenInstruction::CloseTab => screen.close_tab(), - ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => { - screen.apply_layout(layout, new_pane_pids); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::Quit => { - break; - } - } + let store = Store::default(); + let mut plugin_id = 0; + let mut plugin_map = HashMap::new(); + + move || loop { + let (event, mut err_ctx) = receive_plugin_instructions + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); + send_screen_instructions.update(err_ctx); + send_pty_instructions.update(err_ctx); + send_app_instructions.update(err_ctx); + match event { + PluginInstruction::Load(pid_tx, path) => { + let project_dirs = + ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap(); + let plugin_dir = project_dirs.data_dir().join("plugins/"); + let wasm_bytes = fs::read(&path) + .or_else(|_| fs::read(&path.with_extension("wasm"))) + .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) + .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display())); + + // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that + let module = Module::new(&store, &wasm_bytes).unwrap(); + + let output = Pipe::new(); + let input = Pipe::new(); + let mut wasi_env = WasiState::new("mosaic") + .env("CLICOLOR_FORCE", "1") + .preopen(|p| { + p.directory(".") // FIXME: Change this to a more meaningful dir + .alias(".") + .read(true) + .write(true) + .create(true) + }) + .unwrap() + .stdin(Box::new(input)) + .stdout(Box::new(output)) + .finalize() + .unwrap(); + + let wasi = wasi_env.import_object(&module).unwrap(); + + let plugin_env = PluginEnv { + plugin_id, + send_pty_instructions: send_pty_instructions.clone(), + send_screen_instructions: send_screen_instructions.clone(), + send_app_instructions: send_app_instructions.clone(), + wasi_env, + }; + + let mosaic = mosaic_imports(&store, &plugin_env); + let instance = Instance::new(&module, &mosaic.chain_back(wasi)).unwrap(); + + let start = instance.exports.get_function("_start").unwrap(); + + // This eventually calls the `.init()` method + start.call(&[]).unwrap(); + + plugin_map.insert(plugin_id, (instance, plugin_env)); + pid_tx.send(plugin_id).unwrap(); + plugin_id += 1; } - } - }) - .unwrap(), - ); + PluginInstruction::Draw(buf_tx, pid, rows, cols) => { + let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); - active_threads.push( - thread::Builder::new() - .name("wasm".to_string()) - .spawn({ - let mut send_pty_instructions = send_pty_instructions.clone(); - let mut send_screen_instructions = send_screen_instructions.clone(); - let mut send_app_instructions = send_app_instructions.clone(); + let draw = instance.exports.get_function("draw").unwrap(); - let store = Store::default(); - let mut plugin_id = 0; - let mut plugin_map = HashMap::new(); + draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) + .unwrap(); - move || loop { - let (event, mut err_ctx) = receive_plugin_instructions - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); - send_screen_instructions.update(err_ctx); - send_pty_instructions.update(err_ctx); - send_app_instructions.update(err_ctx); - match event { - PluginInstruction::Load(pid_tx, path) => { - let project_dirs = - ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap(); - let plugin_dir = project_dirs.data_dir().join("plugins/"); - let wasm_bytes = fs::read(&path) - .or_else(|_| fs::read(&path.with_extension("wasm"))) - .or_else(|_| { - fs::read(&plugin_dir.join(&path).with_extension("wasm")) - }) - .unwrap_or_else(|_| { - panic!("cannot find plugin {}", &path.display()) - }); + buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap(); + } + // FIXME: Deduplicate this with the callback below! + PluginInstruction::Input(pid, input_bytes) => { + let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); - // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that - let module = Module::new(&store, &wasm_bytes).unwrap(); - - let output = Pipe::new(); - let input = Pipe::new(); - let mut wasi_env = WasiState::new("mosaic") - .env("CLICOLOR_FORCE", "1") - .preopen(|p| { - p.directory(".") // FIXME: Change this to a more meaningful dir - .alias(".") - .read(true) - .write(true) - .create(true) - }) - .unwrap() - .stdin(Box::new(input)) - .stdout(Box::new(output)) - .finalize() - .unwrap(); - - let wasi = wasi_env.import_object(&module).unwrap(); - - let plugin_env = PluginEnv { - plugin_id, - send_pty_instructions: send_pty_instructions.clone(), - send_screen_instructions: send_screen_instructions.clone(), - send_app_instructions: send_app_instructions.clone(), - wasi_env, - }; - - let mosaic = mosaic_imports(&store, &plugin_env); - let instance = - Instance::new(&module, &mosaic.chain_back(wasi)).unwrap(); - - let start = instance.exports.get_function("_start").unwrap(); - - // This eventually calls the `.init()` method - start.call(&[]).unwrap(); - - plugin_map.insert(plugin_id, (instance, plugin_env)); - pid_tx.send(plugin_id).unwrap(); - plugin_id += 1; + let handle_key = instance.exports.get_function("handle_key").unwrap(); + for key in input_bytes.keys() { + if let Ok(key) = key { + wasi_write_string( + &plugin_env.wasi_env, + &serde_json::to_string(&key).unwrap(), + ); + handle_key.call(&[]).unwrap(); + } } - PluginInstruction::Draw(buf_tx, pid, rows, cols) => { - let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); - let draw = instance.exports.get_function("draw").unwrap(); - - draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) - .unwrap(); - - buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap(); - } - // FIXME: Deduplicate this with the callback below! - PluginInstruction::Input(pid, input_bytes) => { - let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); - - let handle_key = instance.exports.get_function("handle_key").unwrap(); + drop(send_screen_instructions.send(ScreenInstruction::Render)); + } + PluginInstruction::GlobalInput(input_bytes) => { + // FIXME: Set up an event subscription system, and timed callbacks + for (instance, plugin_env) in plugin_map.values() { + let handler = + instance.exports.get_function("handle_global_key").unwrap(); for key in input_bytes.keys() { if let Ok(key) = key { wasi_write_string( &plugin_env.wasi_env, &serde_json::to_string(&key).unwrap(), ); - handle_key.call(&[]).unwrap(); + handler.call(&[]).unwrap(); } } - - drop(send_screen_instructions.send(ScreenInstruction::Render)); } - PluginInstruction::GlobalInput(input_bytes) => { - // FIXME: Set up an event subscription system, and timed callbacks - for (instance, plugin_env) in plugin_map.values() { - let handler = - instance.exports.get_function("handle_global_key").unwrap(); - for key in input_bytes.keys() { - if let Ok(key) = key { - wasi_write_string( - &plugin_env.wasi_env, - &serde_json::to_string(&key).unwrap(), - ); - handler.call(&[]).unwrap(); - } - } - } - drop(send_screen_instructions.send(ScreenInstruction::Render)); - } - PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), - PluginInstruction::Quit => break, + drop(send_screen_instructions.send(ScreenInstruction::Render)); } + PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), + PluginInstruction::Quit => break, } - }) - .unwrap(), - ); + } + }) + .unwrap(); - // TODO: currently we don't push this into active_threads + // TODO: currently we don't wait for this to quit // because otherwise the app will hang. Need to fix this so it both // listens to the ipc-bus and is able to quit cleanly #[cfg(not(test))] @@ -663,8 +651,11 @@ pub fn start(mut os_input: Box, opts: CliArgs) { } AppInstruction::Error(backtrace) => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); + let _ = screen_thread.join(); let _ = send_pty_instructions.send(PtyInstruction::Quit); + let _ = pty_thread.join(); let _ = send_plugin_instructions.send(PluginInstruction::Quit); + let _ = wasm_thread.join(); 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); @@ -672,17 +663,17 @@ pub fn start(mut os_input: Box, opts: CliArgs) { .get_stdout_writer() .write(error.as_bytes()) .unwrap(); - for thread_handler in active_threads { - let _ = thread_handler.join(); - } std::process::exit(1); } } } - for thread_handler in active_threads { - thread_handler.join().unwrap(); - } + let _ = send_screen_instructions.send(ScreenInstruction::Quit); + screen_thread.join().unwrap(); + let _ = send_pty_instructions.send(PtyInstruction::Quit); + pty_thread.join().unwrap(); + let _ = send_plugin_instructions.send(PluginInstruction::Quit); + wasm_thread.join().unwrap(); // cleanup(); let reset_style = "\u{1b}[m"; diff --git a/src/screen.rs b/src/screen.rs index 5e4d1ae9..7fb19b6c 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -146,7 +146,9 @@ impl Screen { .unwrap(); if self.tabs.is_empty() { self.active_tab_index = None; - self.render(); + self.send_app_instructions + .send(AppInstruction::Exit) + .unwrap(); } } pub fn render(&mut self) { @@ -156,10 +158,6 @@ impl Screen { } else { self.close_tab(); } - } else { - self.send_app_instructions - .send(AppInstruction::Exit) - .unwrap(); }; }