Skip to main content

Modules/signalmodule.c

Source:

cpython 3.14 @ ab2d84fe1023/Modules/signalmodule.c

signalmodule.c bridges POSIX signals and Python. It installs C signal handlers that set a flag and optionally write to a wakeup fd. The eval loop checks the flag and dispatches to Python-level handlers.

Map

LinesSymbolRole
1-100Signal tableHandlers[NSIG] — per-signal Python callable + C handler
101-250signal_handlerC-level signal handler: sets flag, writes wakeup fd
251-400signal_signal_implsignal.signal(signum, handler)
401-550signal_raise_signal_implsignal.raise_signal(signum)
551-700signal_set_wakeup_fd_implsignal.set_wakeup_fd(fd)
701-900SIGINT handlingPyErr_SetInterrupt, _PyErr_CheckSignals
901-1100signal_sigwait_implsignal.sigwait(sigset) — block until signal
1101-1400signal_pthread_kill_implsignal.pthread_kill(thread_id, signum)

Reading

Signal handler table

// CPython: Modules/signalmodule.c:82 Handlers
static struct {
_Py_atomic_int tripped; /* set to 1 by C handler */
PyObject *func; /* Python callable or SIG_DFL/SIG_IGN */
} Handlers[NSIG];

static _Py_atomic_int is_tripped; /* any signal tripped */

C-level signal handler

// CPython: Modules/signalmodule.c:140 signal_handler
static void
signal_handler(int sig_num)
{
int save_errno = errno;
/* Mark this signal as pending */
_Py_atomic_store_relaxed(&Handlers[sig_num].tripped, 1);
/* Wake up a blocked select/poll via the wakeup fd */
if (wakeup.fd != INVALID_FD) {
unsigned char byte = (unsigned char)sig_num;
_Py_write_noraise(wakeup.fd, &byte, 1);
}
_Py_atomic_store(&is_tripped, 1);
/* Reinstall on platforms that reset to SIG_DFL after delivery */
#ifdef HAVE_SIGACTION
/* sigaction is persistent — no reinstall needed */
#else
PyOS_setsig(sig_num, signal_handler);
#endif
errno = save_errno;
}

The handler is async-signal-safe: it only writes to an atomic and optionally writes one byte to a fd.

signal.signal()

// CPython: Modules/signalmodule.c:310 signal_signal_impl
static PyObject *
signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)
{
/* Only the main thread can install signal handlers */
if (!_PyMainInterpreterThread_Check(tstate)) {
PyErr_SetString(PyExc_ValueError,
"signal only works in main thread");
return NULL;
}
PyObject *old_handler = Handlers[signalnum].func;
Py_INCREF(handler);
Handlers[signalnum].func = handler;
if (handler == IgnoreHandler)
PyOS_setsig(signalnum, SIG_IGN);
else if (handler == DefaultHandler)
PyOS_setsig(signalnum, SIG_DFL);
else
PyOS_setsig(signalnum, signal_handler);
Py_DECREF(old_handler);
return old_handler;
}

Wakeup fd

// CPython: Modules/signalmodule.c:600 signal_set_wakeup_fd_impl
/* signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True)
Writes signal number as a byte to fd when any signal arrives.
Used to integrate signals with event loops (asyncio, select).
*/
static PyObject *
signal_set_wakeup_fd_impl(PyObject *module, int fd, int warn_on_full_buffer)

asyncio calls set_wakeup_fd with the write end of a socket pair so that select/epoll wakes up when a signal arrives.

PyErr_SetInterrupt / _PyErr_CheckSignals

// CPython: Modules/signalmodule.c:750 PyErr_SetInterrupt
void
PyErr_SetInterrupt(void)
{
/* Simulate a SIGINT — checked by _PyErr_CheckSignals */
_Py_atomic_store(&Handlers[SIGINT].tripped, 1);
_Py_atomic_store(&is_tripped, 1);
}

_PyErr_CheckSignals is called from _Py_HandlePending in the eval loop. It iterates Handlers, calls the Python handler for each tripped signal, and raises KeyboardInterrupt for SIGINT.

gopy notes

gopy handles signals in vm/signals.go. is_tripped maps to vm.signalTripped (an atomic.Bool). _PyErr_CheckSignals is vm.CheckSignals(), called from the eval loop's pending-checks path. signal.set_wakeup_fd writes to vm.wakeupFd.