From 7cc39404c802eb8133fba4b7dd9cf770540bdf0a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 29 Nov 2016 23:31:56 +0530 Subject: [PATCH] Fix exceptions during shutdown --- kitty/main.py | 11 +++++++++++ kitty/tabs.py | 52 +++++++++++++++++++++++++++++-------------------- kitty/window.py | 3 +++ 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/kitty/main.py b/kitty/main.py index f25d3d90d..3724ab215 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -6,6 +6,7 @@ import argparse import tempfile import os import sys +from queue import Empty from gettext import gettext as _ @@ -73,6 +74,16 @@ def run_app(opts, args): tabs.render() window.swap_buffers() glfw_wait_events() + try: + func, args = tabs.pending_ui_thread_calls.get_nowait() + except Empty: + pass + else: + try: + func(*args) + except Exception: + import traceback + traceback.print_exc() finally: tabs.destroy() del window diff --git a/kitty/tabs.py b/kitty/tabs.py index 75a22c784..aa542ea25 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -9,11 +9,11 @@ import signal import struct from collections import deque from functools import partial -from threading import Thread +from threading import Thread, current_thread from queue import Queue, Empty from .child import Child -from .constants import viewport_size, shell_path, appname, set_tab_manager, tab_manager, wakeup, cell_size, MODIFIER_KEYS +from .constants import viewport_size, shell_path, appname, set_tab_manager, tab_manager, wakeup, cell_size, MODIFIER_KEYS, main_thread from .fast_data_types import ( glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS, GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event @@ -113,6 +113,7 @@ class TabManager(Thread): self.read_dispatch_map = {self.signal_fd: self.signal_received, self.read_wakeup_fd: self.on_wakeup} self.all_writers = [] self.timers = Timers() + self.pending_ui_thread_calls = Queue() self.write_dispatch_map = {} set_tab_manager(self) cell_size.width, cell_size.height = set_font_family(opts.font_family, opts.font_size) @@ -144,13 +145,9 @@ class TabManager(Thread): if data: signals = struct.unpack('%uB' % len(data), data) if signal.SIGINT in signals or signal.SIGTERM in signals: - self.shutdown() - - def shutdown(self): - if not self.shutting_down: - self.shutting_down = True - self.glfw_window.set_should_close(True) - glfw_post_empty_event() + if not self.shutting_down: + self.glfw_window.set_should_close(True) + glfw_post_empty_event() def __iter__(self): yield from iter(self.tabs) @@ -192,20 +189,18 @@ class TabManager(Thread): except Exception: pass + def queue_ui_action(self, func, *args): + self.pending_ui_thread_calls.put((func, args)) + glfw_post_empty_event() + def close_window(self, window): - self.remove_child_fd(window.child_fd) - for tab in self.tabs: - if window in tab: - break + ' Can be called in either thread, will first kill the child (with SIGHUP), then remove the window from the gui ' + if current_thread() is main_thread: + self.queue_action(self.close_window, window) else: - return - tab.remove_window(window) - window.destroy() - if len(tab) == 0: - self.tabs.remove(tab) - tab.destroy() - if len(self.tabs) == 0: - self.shutdown() + self.remove_child_fd(window.child_fd) + window.destroy() + self.queue_ui_action(self.gui_close_window, window) def run(self): if self.args.profile: @@ -338,6 +333,21 @@ class TabManager(Thread): with self.cursor_program: active.char_grid.render_cursor(rd, self.cursor_program) + def gui_close_window(self, window): + for tab in self.tabs: + if window in tab: + break + else: + return + tab.remove_window(window) + if len(tab) == 0: + self.tabs.remove(tab) + tab.destroy() + if len(self.tabs) == 0: + if not self.shutting_down: + self.glfw_window.set_should_close(True) + glfw_post_empty_event() + def destroy(self): # Must be called in the main thread as it manipulates signal handlers signal.signal(signal.SIGINT, signal.SIG_DFL) diff --git a/kitty/window.py b/kitty/window.py index a82a824a1..1e299ae2f 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -61,6 +61,9 @@ class Window: def destroy(self): self.child.hangup() self.child.get_child_status() # Ensure child does not become zombie + # At this point this window can still render to screen using its + # existing buffers in char_grid. The rest of the cleanup must be + # performed in the GUI thread. def read_ready(self): if self.read_bytes(self.screen, self.child_fd) is False: