fix(main): do not hang on exit (#150)

This commit is contained in:
Aram Drevekenin 2021-01-20 19:25:35 +01:00 committed by GitHub
parent 81af57b15d
commit decc38232b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 305 additions and 316 deletions

View file

@ -173,7 +173,6 @@ pub enum AppInstruction {
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let mut app_state = AppState::default(); let mut app_state = AppState::default();
let mut active_threads = vec![];
let command_is_executing = CommandIsExecuting::new(); let command_is_executing = CommandIsExecuting::new();
@ -225,345 +224,334 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
}) })
}); });
active_threads.push( let pty_thread = thread::Builder::new()
thread::Builder::new() .name("pty".to_string())
.name("pty".to_string()) .spawn({
.spawn({ let mut command_is_executing = command_is_executing.clone();
let mut command_is_executing = command_is_executing.clone(); send_pty_instructions.send(PtyInstruction::NewTab).unwrap();
send_pty_instructions.send(PtyInstruction::NewTab).unwrap(); move || loop {
move || loop { let (event, mut err_ctx) = pty_bus
let (event, mut err_ctx) = pty_bus .receive_pty_instructions
.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() .recv()
.expect("failed to receive event on channel"); .expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
pty_bus.send_screen_instructions.update(err_ctx); screen.send_app_instructions.update(err_ctx);
screen.send_pty_instructions.update(err_ctx);
match event { match event {
PtyInstruction::SpawnTerminal(file_to_open) => { ScreenInstruction::Pty(pid, vte_event) => {
let pid = pty_bus.spawn_terminal(file_to_open); screen
pty_bus .get_active_tab_mut()
.send_screen_instructions .unwrap()
.send(ScreenInstruction::NewPane(PaneId::Terminal(pid))) .handle_pty_event(pid, vte_event);
.unwrap();
} }
PtyInstruction::SpawnTerminalVertically(file_to_open) => { ScreenInstruction::Render => {
let pid = pty_bus.spawn_terminal(file_to_open); screen.render();
pty_bus
.send_screen_instructions
.send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
.unwrap();
} }
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { ScreenInstruction::NewPane(pid) => {
let pid = pty_bus.spawn_terminal(file_to_open); screen.get_active_tab_mut().unwrap().new_pane(pid);
pty_bus command_is_executing.done_opening_new_pane();
.send_screen_instructions
.send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
.unwrap();
} }
PtyInstruction::NewTab => { ScreenInstruction::HorizontalSplit(pid) => {
if let Some(layout) = maybe_layout.clone() { screen.get_active_tab_mut().unwrap().horizontal_split(pid);
pty_bus.spawn_terminals_for_layout(layout); command_is_executing.done_opening_new_pane();
} else {
let pid = pty_bus.spawn_terminal(None);
pty_bus
.send_screen_instructions
.send(ScreenInstruction::NewTab(pid))
.unwrap();
}
} }
PtyInstruction::ClosePane(id) => { ScreenInstruction::VerticalSplit(pid) => {
pty_bus.close_pane(id); screen.get_active_tab_mut().unwrap().vertical_split(pid);
command_is_executing.done_closing_pane(); command_is_executing.done_opening_new_pane();
} }
PtyInstruction::CloseTab(ids) => { ScreenInstruction::WriteCharacter(bytes) => {
pty_bus.close_tab(ids); screen
command_is_executing.done_closing_pane(); .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; break;
} }
} }
} }
}) }
.unwrap(), })
); .unwrap();
active_threads.push( let wasm_thread = thread::Builder::new()
thread::Builder::new() .name("wasm".to_string())
.name("screen".to_string()) .spawn({
.spawn({ let mut send_pty_instructions = send_pty_instructions.clone();
let mut command_is_executing = command_is_executing.clone(); let mut send_screen_instructions = send_screen_instructions.clone();
let os_input = os_input.clone(); let mut send_app_instructions = send_app_instructions.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 store = Store::default();
let mut screen = Screen::new( let mut plugin_id = 0;
receive_screen_instructions, let mut plugin_map = HashMap::new();
send_pty_instructions,
send_plugin_instructions, move || loop {
send_app_instructions, let (event, mut err_ctx) = receive_plugin_instructions
&full_screen_ws, .recv()
os_input, .expect("failed to receive event on channel");
max_panes, err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
); send_screen_instructions.update(err_ctx);
loop { send_pty_instructions.update(err_ctx);
let (event, mut err_ctx) = screen send_app_instructions.update(err_ctx);
.receiver match event {
.recv() PluginInstruction::Load(pid_tx, path) => {
.expect("failed to receive event on channel"); let project_dirs =
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap();
screen.send_app_instructions.update(err_ctx); let plugin_dir = project_dirs.data_dir().join("plugins/");
screen.send_pty_instructions.update(err_ctx); let wasm_bytes = fs::read(&path)
match event { .or_else(|_| fs::read(&path.with_extension("wasm")))
ScreenInstruction::Pty(pid, vte_event) => { .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
screen .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
.get_active_tab_mut()
.unwrap() // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
.handle_pty_event(pid, vte_event); let module = Module::new(&store, &wasm_bytes).unwrap();
}
ScreenInstruction::Render => { let output = Pipe::new();
screen.render(); let input = Pipe::new();
} let mut wasi_env = WasiState::new("mosaic")
ScreenInstruction::NewPane(pid) => { .env("CLICOLOR_FORCE", "1")
screen.get_active_tab_mut().unwrap().new_pane(pid); .preopen(|p| {
command_is_executing.done_opening_new_pane(); p.directory(".") // FIXME: Change this to a more meaningful dir
} .alias(".")
ScreenInstruction::HorizontalSplit(pid) => { .read(true)
screen.get_active_tab_mut().unwrap().horizontal_split(pid); .write(true)
command_is_executing.done_opening_new_pane(); .create(true)
} })
ScreenInstruction::VerticalSplit(pid) => { .unwrap()
screen.get_active_tab_mut().unwrap().vertical_split(pid); .stdin(Box::new(input))
command_is_executing.done_opening_new_pane(); .stdout(Box::new(output))
} .finalize()
ScreenInstruction::WriteCharacter(bytes) => { .unwrap();
screen
.get_active_tab_mut() let wasi = wasi_env.import_object(&module).unwrap();
.unwrap()
.write_to_active_terminal(bytes); let plugin_env = PluginEnv {
} plugin_id,
ScreenInstruction::ResizeLeft => { send_pty_instructions: send_pty_instructions.clone(),
screen.get_active_tab_mut().unwrap().resize_left(); send_screen_instructions: send_screen_instructions.clone(),
} send_app_instructions: send_app_instructions.clone(),
ScreenInstruction::ResizeRight => { wasi_env,
screen.get_active_tab_mut().unwrap().resize_right(); };
}
ScreenInstruction::ResizeDown => { let mosaic = mosaic_imports(&store, &plugin_env);
screen.get_active_tab_mut().unwrap().resize_down(); let instance = Instance::new(&module, &mosaic.chain_back(wasi)).unwrap();
}
ScreenInstruction::ResizeUp => { let start = instance.exports.get_function("_start").unwrap();
screen.get_active_tab_mut().unwrap().resize_up();
} // This eventually calls the `.init()` method
ScreenInstruction::MoveFocus => { start.call(&[]).unwrap();
screen.get_active_tab_mut().unwrap().move_focus();
} plugin_map.insert(plugin_id, (instance, plugin_env));
ScreenInstruction::MoveFocusLeft => { pid_tx.send(plugin_id).unwrap();
screen.get_active_tab_mut().unwrap().move_focus_left(); plugin_id += 1;
}
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;
}
}
} }
} PluginInstruction::Draw(buf_tx, pid, rows, cols) => {
}) let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
.unwrap(),
);
active_threads.push( let draw = instance.exports.get_function("draw").unwrap();
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 store = Store::default(); draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
let mut plugin_id = 0; .unwrap();
let mut plugin_map = HashMap::new();
move || loop { buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap();
let (event, mut err_ctx) = receive_plugin_instructions }
.recv() // FIXME: Deduplicate this with the callback below!
.expect("failed to receive event on channel"); PluginInstruction::Input(pid, input_bytes) => {
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
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 handle_key = instance.exports.get_function("handle_key").unwrap();
let module = Module::new(&store, &wasm_bytes).unwrap(); for key in input_bytes.keys() {
if let Ok(key) = key {
let output = Pipe::new(); wasi_write_string(
let input = Pipe::new(); &plugin_env.wasi_env,
let mut wasi_env = WasiState::new("mosaic") &serde_json::to_string(&key).unwrap(),
.env("CLICOLOR_FORCE", "1") );
.preopen(|p| { handle_key.call(&[]).unwrap();
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;
} }
PluginInstruction::Draw(buf_tx, pid, rows, cols) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let draw = instance.exports.get_function("draw").unwrap(); drop(send_screen_instructions.send(ScreenInstruction::Render));
}
draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) PluginInstruction::GlobalInput(input_bytes) => {
.unwrap(); // FIXME: Set up an event subscription system, and timed callbacks
for (instance, plugin_env) in plugin_map.values() {
buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap(); let handler =
} instance.exports.get_function("handle_global_key").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();
for key in input_bytes.keys() { for key in input_bytes.keys() {
if let Ok(key) = key { if let Ok(key) = key {
wasi_write_string( wasi_write_string(
&plugin_env.wasi_env, &plugin_env.wasi_env,
&serde_json::to_string(&key).unwrap(), &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)); drop(send_screen_instructions.send(ScreenInstruction::Render));
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Quit => break,
} }
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 // because otherwise the app will hang. Need to fix this so it both
// listens to the ipc-bus and is able to quit cleanly // listens to the ipc-bus and is able to quit cleanly
#[cfg(not(test))] #[cfg(not(test))]
@ -663,8 +651,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
} }
AppInstruction::Error(backtrace) => { AppInstruction::Error(backtrace) => {
let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = screen_thread.join();
let _ = send_pty_instructions.send(PtyInstruction::Quit); let _ = send_pty_instructions.send(PtyInstruction::Quit);
let _ = pty_thread.join();
let _ = send_plugin_instructions.send(PluginInstruction::Quit); let _ = send_plugin_instructions.send(PluginInstruction::Quit);
let _ = wasm_thread.join();
os_input.unset_raw_mode(0); os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); 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 error = format!("{}\n{}", goto_start_of_last_line, backtrace);
@ -672,17 +663,17 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
.get_stdout_writer() .get_stdout_writer()
.write(error.as_bytes()) .write(error.as_bytes())
.unwrap(); .unwrap();
for thread_handler in active_threads {
let _ = thread_handler.join();
}
std::process::exit(1); std::process::exit(1);
} }
} }
} }
for thread_handler in active_threads { let _ = send_screen_instructions.send(ScreenInstruction::Quit);
thread_handler.join().unwrap(); 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(); // cleanup();
let reset_style = "\u{1b}[m"; let reset_style = "\u{1b}[m";

View file

@ -146,7 +146,9 @@ impl Screen {
.unwrap(); .unwrap();
if self.tabs.is_empty() { if self.tabs.is_empty() {
self.active_tab_index = None; self.active_tab_index = None;
self.render(); self.send_app_instructions
.send(AppInstruction::Exit)
.unwrap();
} }
} }
pub fn render(&mut self) { pub fn render(&mut self) {
@ -156,10 +158,6 @@ impl Screen {
} else { } else {
self.close_tab(); self.close_tab();
} }
} else {
self.send_app_instructions
.send(AppInstruction::Exit)
.unwrap();
}; };
} }