From 94c4c0085970c42fb23538e40d268b56d8e7c175 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Sep 2017 18:14:08 +0530 Subject: [PATCH] Move signal handling code into the C module --- kitty/boss.py | 27 ++++++------------------ kitty/child-monitor.c | 48 +++++++++++++++++++++++++++++++------------ kitty/data-types.h | 1 - kitty/main.py | 2 +- kitty/utils.py | 24 ---------------------- 5 files changed, 42 insertions(+), 60 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 01ff6bd41..879a84c5a 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -2,10 +2,6 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -import io -import os -import signal -import struct from gettext import gettext as _ from threading import Thread from time import monotonic @@ -31,7 +27,7 @@ from .keys import ( from .session import create_session from .shaders import Sprites from .tabs import SpecialWindow, TabManager -from .utils import handle_unix_signals, safe_print +from .utils import safe_print if isosx: from .fast_data_types import cocoa_update_title @@ -100,10 +96,9 @@ class Boss(Thread): self.glfw_window_title = None self.resize_gl_viewport = False self.shutting_down = False - self.signal_fd = handle_unix_signals() self.ui_timers = Timers() self.child_monitor = ChildMonitor( - self.signal_fd, opts.repaint_delay / 1000.0, + opts.repaint_delay / 1000.0, self.on_child_death, self.update_screen, self.ui_timers, DumpCommands(args) if args.dump_commands or args.dump_bytes else None) set_boss(self) @@ -129,17 +124,10 @@ class Boss(Thread): self.show_mouse_cursor() self.start_cursor_blink() - def signal_received(self): - try: - data = os.read(self.signal_fd, io.DEFAULT_BUFFER_SIZE) - except BlockingIOError: - return - if data: - signals = struct.unpack('%uB' % len(data), data) - if signal.SIGINT in signals or signal.SIGTERM in signals: - if not self.shutting_down: - self.glfw_window.set_should_close(True) - glfw_post_empty_event() + def close_signal_received(self): + if not self.shutting_down: + self.glfw_window.set_should_close(True) + glfw_post_empty_event() @property def current_tab_bar_height(self): @@ -432,9 +420,6 @@ class Boss(Thread): 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) - signal.signal(signal.SIGTERM, signal.SIG_DFL) self.shutting_down = True self.child_monitor.shutdown() wakeup() diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index f688e01c6..bac5584c7 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #define EXTRA_FDS 2 @@ -41,6 +42,7 @@ static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}}; static pthread_mutex_t children_lock = {{0}}; static bool created = false, signal_received = false; static uint8_t drain_buf[1024]; +static int signal_fds[2], wakeup_fds[2]; // Main thread functions {{{ @@ -52,6 +54,18 @@ static uint8_t drain_buf[1024]; #define INCREF_CHILD(x) XREF_CHILD(x, Py_INCREF) #define DECREF_CHILD(x) XREF_CHILD(x, Py_DECREF) +static void +handle_signal(int sig_num) { + int save_err = errno; + unsigned char byte = (unsigned char)sig_num; + while(true) { + ssize_t ret = write(signal_fds[1], &byte, 1); + if (ret < 0 && errno == EINTR) continue; + break; + } + errno = save_err; +} + static inline bool self_pipe(int fds[2]) { int flags; @@ -70,17 +84,22 @@ static PyObject * new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { ChildMonitor *self; PyObject *dump_callback, *death_notify, *update_screen, *timers; - int signal_fd, ret, wakeup_fds[2]; + int ret; double repaint_delay; if (created) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; } - if (!PyArg_ParseTuple(args, "idOOOO", &signal_fd, &repaint_delay, &death_notify, &update_screen, &timers, &dump_callback)) return NULL; + if (!PyArg_ParseTuple(args, "dOOOO", &repaint_delay, &death_notify, &update_screen, &timers, &dump_callback)) return NULL; created = true; if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) { PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret)); return NULL; } if (!self_pipe(wakeup_fds)) return PyErr_SetFromErrno(PyExc_OSError); + if (!self_pipe(signal_fds)) return PyErr_SetFromErrno(PyExc_OSError); + if (signal(SIGINT, handle_signal) == SIG_ERR) return PyErr_SetFromErrno(PyExc_OSError); + if (signal(SIGTERM, handle_signal) == SIG_ERR) return PyErr_SetFromErrno(PyExc_OSError); + if (siginterrupt(SIGINT, false) != 0) return PyErr_SetFromErrno(PyExc_OSError); + if (siginterrupt(SIGTERM, false) != 0) return PyErr_SetFromErrno(PyExc_OSError); self = (ChildMonitor *)type->tp_alloc(type, 0); if (self == NULL) return PyErr_NoMemory(); self->death_notify = death_notify; Py_INCREF(death_notify); @@ -91,9 +110,8 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { parse_func = parse_worker_dump; } else parse_func = parse_worker; self->count = 0; - fds[0].fd = wakeup_fds[0]; fds[1].fd = signal_fd; + fds[0].fd = wakeup_fds[0]; fds[1].fd = signal_fds[0]; fds[0].events = POLLIN; fds[1].events = POLLIN; - self->write_wakeup_fd = wakeup_fds[1]; self->repaint_delay = repaint_delay; return (PyObject*) self; @@ -115,9 +133,10 @@ dealloc(ChildMonitor* self) { add_queue_count--; FREE_CHILD(add_queue[add_queue_count]); } - close(self->write_wakeup_fd); - close(fds[0].fd); - + close(wakeup_fds[0]); + close(wakeup_fds[1]); + close(signal_fds[0]); + close(signal_fds[1]); } static void @@ -133,9 +152,9 @@ wakeup_(int fd) { } static PyObject * -wakeup(ChildMonitor *self) { +wakeup(ChildMonitor UNUSED *self) { #define wakeup_doc "wakeup() -> wakeup the ChildMonitor I/O thread, forcing it to exit from poll() if it is waiting there." - wakeup_(self->write_wakeup_fd); + wakeup_(wakeup_fds[1]); Py_RETURN_NONE; } @@ -188,6 +207,8 @@ needs_write(ChildMonitor *self, PyObject *args) { static PyObject * shutdown(ChildMonitor *self) { #define shutdown_doc "shutdown() -> Shutdown the monitor loop." + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); self->shutting_down = true; Py_RETURN_NONE; } @@ -197,7 +218,7 @@ do_parse(ChildMonitor *self, Screen *screen, unsigned long child_id) { screen_mutex(lock, read); if (screen->read_buf_sz) { parse_func(screen, self->dump_callback); - if (screen->read_buf_sz >= READ_BUF_SZ) wakeup_(self->write_wakeup_fd); // Ensure the read fd has POLLIN set + if (screen->read_buf_sz >= READ_BUF_SZ) wakeup_(wakeup_fds[1]); // Ensure the read fd has POLLIN set screen->read_buf_sz = 0; PyObject *t = PyObject_CallFunction(self->update_screen, "k", child_id); if (t == NULL) PyErr_Print(); @@ -341,12 +362,12 @@ read_bytes(int fd, Screen *screen) { static inline void -drain_wakeup(int fd) { +drain_fd(int fd) { while(true) { ssize_t len = read(fd, drain_buf, sizeof(drain_buf)); if (len < 0) { if (errno == EINTR) continue; - if (errno != EIO) perror("Call to read() from wakeup fd failed"); + if (errno != EIO) perror("Call to read() from drain fd failed"); break; } break; @@ -399,9 +420,10 @@ loop(ChildMonitor *self) { } ret = poll(fds, self->count + EXTRA_FDS, -1); if (ret > 0) { - if (fds[0].revents && POLLIN) drain_wakeup(fds[0].fd); + if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); if (fds[1].revents && POLLIN) { data_received = true; + drain_fd(fds[1].fd); children_mutex(lock); signal_received = true; children_mutex(unlock); diff --git a/kitty/data-types.h b/kitty/data-types.h index b01eb1c52..9a01f9b75 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -281,7 +281,6 @@ typedef struct { PyObject *dump_callback, *update_screen, *death_notify; Timers *timers; - int write_wakeup_fd; double repaint_delay; unsigned int count; bool shutting_down; diff --git a/kitty/main.py b/kitty/main.py index 932d936ec..8c70f87bb 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -169,7 +169,7 @@ def clear_buffers(window, opts): def dispatch_pending_calls(boss): boss.ui_timers.call() if boss.child_monitor.parse_input(): - boss.signal_received() + boss.close_signal_received() def run_app(opts, args): diff --git a/kitty/utils.py b/kitty/utils.py index a3b48ef3a..f57721129 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -6,7 +6,6 @@ import math import os import re import shlex -import signal import string import subprocess from contextlib import contextmanager @@ -153,29 +152,6 @@ def parse_color_set(raw): continue -def pipe2(): - try: - read_fd, write_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) - except AttributeError: - import fcntl - read_fd, write_fd = os.pipe() - for fd in (read_fd, write_fd): - flag = fcntl.fcntl(fd, fcntl.F_GETFD) - fcntl.fcntl(fd, fcntl.F_SETFD, flag | fcntl.FD_CLOEXEC) - flag = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK) - return read_fd, write_fd - - -def handle_unix_signals(): - read_fd, write_fd = pipe2() - for sig in (signal.SIGINT, signal.SIGTERM): - signal.signal(sig, lambda x, y: None) - signal.siginterrupt(sig, False) - signal.set_wakeup_fd(write_fd) - return read_fd - - def get_primary_selection(): if isosx: return '' # There is no primary selection on OS X