Skip to main content

Modules/signalmodule.c (part 5)

Source:

cpython 3.14 @ ab2d84fe1023/Modules/signalmodule.c

This annotation covers signal registration and the wakeup fd mechanism. See modules_signal4_detail for signal.getsignal, signal.alarm, signal.pause, and the GIL interaction.

Map

LinesSymbolRole
1-80signal.signalRegister a Python signal handler
81-160signal.set_wakeup_fdWake up select/poll on signal receipt
161-260signal.raise_signalSend a signal to the calling process
261-360signal.sigwaitBlock until one of a set of signals arrives
361-500Signal delivery in the eval loopHow eval_breaker delivers signals

Reading

signal.signal

// CPython: Modules/signalmodule.c:480 signal_signal_impl
static PyObject *
signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)
{
/* Install handler: SIG_IGN, SIG_DFL, or a Python callable */
PyObject *old_handler = Handlers[signalnum].func;
if (PyCallable_Check(handler)) {
struct sigaction sa;
sa.sa_handler = signal_handler; /* C-level trampoline */
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(signalnum, &sa, NULL);
Handlers[signalnum].func = Py_NewRef(handler);
} else if (handler == SIG_IGN_OBJ) {
signal(signalnum, SIG_IGN);
Handlers[signalnum].func = Py_NewRef(SIG_IGN_OBJ);
}
...
return old_handler;
}

signal.signal(SIGTERM, handler) installs signal_handler as the C-level handler. signal_handler sets a flag in Handlers and writes to the wakeup fd. The Python handler is called later in the eval loop, not in the C signal handler (which runs in an async-signal-unsafe context).

signal.set_wakeup_fd

// CPython: Modules/signalmodule.c:560 signal_set_wakeup_fd
static PyObject *
signal_set_wakeup_fd(PyObject *self, PyObject *args, PyObject *kwds)
{
/* Write a byte to this fd when a signal arrives, waking a
blocked select/poll/epoll call. */
int fd = PyLong_AsLong(...);
int old_fd = wakeup.fd;
wakeup.fd = fd;
wakeup.warn_on_full_buffer = warn_on_full_buffer;
return PyLong_FromLong(old_fd);
}

signal.set_wakeup_fd(r) where r is the read end of a pipe: the main loop selects on both r and network fds. On signal, a byte is written to the write end, unblocking the select.

Signal delivery in eval loop

// CPython: Python/ceval.c:480 handle_signals
static int
handle_signals(PyThreadState *tstate)
{
/* Called when eval_breaker is set */
for (int i = 1; i < NSIG; i++) {
if (Handlers[i].tripped) {
Handlers[i].tripped = 0;
PyObject *handler = Handlers[i].func;
PyObject *args = Py_BuildValue("(iO)", i, tstate->frame);
PyObject *result = PyObject_Call(handler, args, NULL);
...
}
}
return 0;
}

Signal handlers are not called in the C signal handler. The C handler sets Handlers[i].tripped = 1 and sets eval_breaker. On the next opcode dispatch, handle_signals is called and invokes the Python handler with (signum, frame).

gopy notes

signal.signal is module/signal.Signal in module/signal/module.go. Go's os/signal.Notify is used to register a channel-based handler. set_wakeup_fd writes to the vm.WakeupFd. Signal delivery calls the Python handler via vm.CallFunction at a safe point in the eval loop.