Move signal handling code into the C module

This commit is contained in:
Kovid Goyal 2017-09-06 18:14:08 +05:30
parent 1308bbac2d
commit 94c4c00859
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 42 additions and 60 deletions

View File

@ -2,10 +2,6 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
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()

View File

@ -9,6 +9,7 @@
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <GLFW/glfw3.h>
#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);

View File

@ -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;

View File

@ -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):

View File

@ -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