Allow using our signal handlers in python event loops via an fd

pythons signal fd only return signal numbers not the full siginfo struct
This commit is contained in:
Kovid Goyal
2022-06-13 18:52:23 +05:30
parent 8fb24fbc1e
commit 5f13946bac
6 changed files with 204 additions and 36 deletions
+128 -30
View File
@@ -31,25 +31,8 @@ handle_signal(int sig_num UNUSED, siginfo_t *si, void *ucontext UNUSED) {
}
#endif
bool
init_loop_data(LoopData *ld, ...) {
ld->num_handled_signals = 0;
va_list valist;
va_start(valist, ld);
while (true) {
int sig = va_arg(valist, int);
if (!sig) break;
ld->handled_signals[ld->num_handled_signals++] = sig;
}
va_end(valist);
#ifdef HAS_EVENT_FD
ld->wakeup_read_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (ld->wakeup_read_fd < 0) return false;
#else
if (!self_pipe(ld->wakeup_fds, true)) return false;
ld->wakeup_read_fd = ld->wakeup_fds[0];
#endif
static bool
init_signal_handlers(LoopData *ld) {
ld->signal_read_fd = -1;
#ifdef HAS_SIGNAL_FD
sigemptyset(&ld->signals);
@@ -72,18 +55,35 @@ init_loop_data(LoopData *ld, ...) {
return true;
}
void
free_loop_data(LoopData *ld) {
#define CLOSE(which, idx) if (ld->which[idx] > -1) safe_close(ld->which[idx], __FILE__, __LINE__); ld->which[idx] = -1;
#ifndef HAS_EVENT_FD
CLOSE(wakeup_fds, 0); CLOSE(wakeup_fds, 1);
bool
init_loop_data(LoopData *ld, ...) {
ld->num_handled_signals = 0;
va_list valist;
va_start(valist, ld);
while (true) {
int sig = va_arg(valist, int);
if (!sig) break;
ld->handled_signals[ld->num_handled_signals++] = sig;
}
va_end(valist);
#ifdef HAS_EVENT_FD
ld->wakeup_read_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (ld->wakeup_read_fd < 0) return false;
#else
if (!self_pipe(ld->wakeup_fds, true)) return false;
ld->wakeup_read_fd = ld->wakeup_fds[0];
#endif
return init_signal_handlers(ld);
}
#define CLOSE(which, idx) if (ld->which[idx] > -1) { safe_close(ld->which[idx], __FILE__, __LINE__); ld->which[idx] = -1; }
static void
remove_signal_handlers(LoopData *ld) {
#ifndef HAS_SIGNAL_FD
signal_write_fd = -1;
CLOSE(signal_fds, 0); CLOSE(signal_fds, 1);
#endif
#undef CLOSE
if (ld->signal_read_fd > -1) {
#ifdef HAS_SIGNAL_FD
safe_close(ld->signal_read_fd, __FILE__, __LINE__);
@@ -91,10 +91,21 @@ free_loop_data(LoopData *ld) {
#endif
for (size_t i = 0; i < ld->num_handled_signals; i++) signal(ld->num_handled_signals, SIG_DFL);
}
ld->signal_read_fd = -1;
ld->num_handled_signals = 0;
}
void
free_loop_data(LoopData *ld) {
#ifndef HAS_EVENT_FD
CLOSE(wakeup_fds, 0); CLOSE(wakeup_fds, 1);
#endif
#undef CLOSE
#ifdef HAS_EVENT_FD
safe_close(ld->wakeup_read_fd, __FILE__, __LINE__);
#endif
ld->signal_read_fd = -1; ld->wakeup_read_fd = -1;
ld->wakeup_read_fd = -1;
remove_signal_handlers(ld);
}
@@ -143,7 +154,7 @@ read_signals(int fd, handle_signal_func callback, void *data) {
si.si_addr = (void*)(uintptr_t)fdsi[i].ssi_addr;
si.si_status = fdsi[i].ssi_status;
si.si_value.sival_int = fdsi[i].ssi_int;
callback(&si, data);
if (!callback(&si, data)) break;
}
}
#else
@@ -157,8 +168,9 @@ read_signals(int fd, handle_signal_func callback, void *data) {
break;
}
buf_pos += len;
while (buf_pos >= sizeof(siginfo_t)) {
callback((siginfo_t*)buf, data);
bool keep_going = true;
while (keep_going && buf_pos >= sizeof(siginfo_t)) {
keep_going = callback((siginfo_t*)buf, data);
memmove(buf, buf + sizeof(siginfo_t), sizeof(siginfo_t));
buf_pos -= sizeof(siginfo_t);
}
@@ -166,3 +178,89 @@ read_signals(int fd, handle_signal_func callback, void *data) {
}
#endif
}
static LoopData python_loop_data = {0};
static PyObject*
init_signal_handlers_py(PyObject *self UNUSED, PyObject *args) {
if (python_loop_data.num_handled_signals) { PyErr_SetString(PyExc_RuntimeError, "signal handlers already initialized"); return NULL; }
#ifndef HAS_SIGNAL_FD
if (signal_write_fd > -1) { PyErr_SetString(PyExc_RuntimeError, "signal handlers already initialized"); return NULL; }
#endif
for (Py_ssize_t i = 0; i < MIN(PyTuple_GET_SIZE(args), (Py_ssize_t)arraysz(python_loop_data.handled_signals)); i++) {
python_loop_data.handled_signals[python_loop_data.num_handled_signals++] = PyLong_AsLong(PyTuple_GET_ITEM(args, i));
}
if (!init_signal_handlers(&python_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
#ifdef HAS_SIGNAL_FD
return Py_BuildValue("ii", python_loop_data.signal_read_fd, -1);
#else
return Py_BuildValue("ii", python_loop_data.signal_fds[0], python_loop_data.signal_fds[1]);
#endif
}
static PyTypeObject SigInfoType;
static PyStructSequence_Field sig_info_fields[] = {
{"si_signo", "Signal number"}, {"si_code", "Signal code"}, {"si_pid", "Sending Process id"},
{"si_uid", "Real user id of sending process"}, {"si_addr", "Address of faulting instruction as int"},
{"si_status", "Exit value or signal"}, {"sival_int", "Signal value as int"}, {"sival_ptr", "Signal value as pointer int"},
{NULL, NULL}
};
static PyStructSequence_Desc sig_info_desc = {"SigInfo", NULL, sig_info_fields, 6};
static bool
handle_signal_callback_py(const siginfo_t* siginfo, void *data) {
if (PyErr_Occurred()) return false;
PyObject *callback = data;
PyObject *ans = PyStructSequence_New(&SigInfoType);
int pos = 0;
#define S(x) { PyObject *t = x; if (t) { PyStructSequence_SET_ITEM(ans, pos, x); } else { Py_CLEAR(ans); return false; } pos++; }
if (ans) {
S(PyLong_FromLong((long)siginfo->si_signo));
S(PyLong_FromLong((long)siginfo->si_code));
S(PyLong_FromLong((long)siginfo->si_pid));
S(PyLong_FromLong((long)siginfo->si_uid));
S(PyLong_FromVoidPtr(siginfo->si_addr));
S(PyLong_FromLong((long)siginfo->si_status));
S(PyLong_FromLong((long)siginfo->si_value.sival_int));
S(PyLong_FromVoidPtr(siginfo->si_value.sival_ptr));
PyObject *ret = PyObject_CallFunctionObjArgs(callback, ans, NULL);
Py_CLEAR(ans); Py_CLEAR(ret);
}
return (PyErr_Occurred()) ? false : true;
#undef S
}
static PyObject*
read_signals_py(PyObject *self UNUSED, PyObject *args) {
int fd; PyObject *callback;
if (!PyArg_ParseTuple(args, "iO", &fd, &callback)) return NULL;
if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; }
read_signals(fd, handle_signal_callback_py, callback);
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
}
static PyObject*
remove_signal_handlers_py(PyObject *self UNUSED, PyObject *args UNUSED) {
if (python_loop_data.num_handled_signals) {
remove_signal_handlers(&python_loop_data);
}
Py_RETURN_NONE;
}
static PyMethodDef methods[] = {
{"install_signal_handlers", init_signal_handlers_py, METH_VARARGS, "Initialize an fd to read signals from" },
{"read_signals", read_signals_py, METH_VARARGS, "Read pending signals from the specified fd" },
{"remove_signal_handlers", remove_signal_handlers_py, METH_NOARGS, "Remove signal handlers" },
{ NULL, NULL, 0, NULL },
};
bool
init_loop_utils(PyObject *module) {
if (PyStructSequence_InitType2(&SigInfoType, &sig_info_desc) != 0) return false;
Py_INCREF((PyObject *) &SigInfoType);
PyModule_AddObject(module, "SigInfo", (PyObject *) &SigInfoType);
return PyModule_AddFunctions(module, methods) == 0;
}