Skip to content

Commit ff3bd3f

Browse files
committed
Some very basic support for the signal module.
Catching signals while another interpreter has registered `signal` module handlers still does not work, because we don't have a good way to make the callbacks per-interpreter. We'd need something like PEP 788's weak reference API to safely keep references to interpreters without worrying about concurrent deallocation during signal handling.
1 parent 00c8ccb commit ff3bd3f

File tree

3 files changed

+31
-8
lines changed

3 files changed

+31
-8
lines changed

Doc/library/signal.rst

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,13 @@ This has consequences:
6161
Signals and threads
6262
^^^^^^^^^^^^^^^^^^^
6363

64-
Python signal handlers are always executed in the main Python thread of the main interpreter,
64+
Python signal handlers are always executed in the main Python thread of
65+
intepreters that support signal handling (:c:member:`PyInterpreterConfig.can_handle_signals`),
6566
even if the signal was received in another thread. This means that signals
6667
can't be used as a means of inter-thread communication. You can use
6768
the synchronization primitives from the :mod:`threading` module instead.
6869

69-
Besides, only the main thread of the main interpreter is allowed to set a new signal handler.
70+
Besides, only the main thread is allowed to set a new signal handler.
7071

7172

7273
Module contents
@@ -421,7 +422,7 @@ The :mod:`signal` module defines the following functions:
421422
same process as the caller. The target thread can be executing any code
422423
(Python or not). However, if the target thread is executing the Python
423424
interpreter, the Python signal handlers will be :ref:`executed by the main
424-
thread of the main interpreter <signals-and-threads>`. Therefore, the only point of sending a
425+
thread of a supporting interpreter <signals-and-threads>`. Therefore, the only point of sending a
425426
signal to a particular Python thread would be to force a running system call
426427
to fail with :exc:`InterruptedError`.
427428

@@ -523,7 +524,7 @@ The :mod:`signal` module defines the following functions:
523524
any bytes from *fd* before calling poll or select again.
524525

525526
When threads are enabled, this function can only be called
526-
from :ref:`the main thread of the main interpreter <signals-and-threads>`;
527+
from :ref:`the main thread of a supporting interpreter <signals-and-threads>`;
527528
attempting to call it from other threads will cause a :exc:`ValueError`
528529
exception to be raised.
529530

@@ -578,7 +579,7 @@ The :mod:`signal` module defines the following functions:
578579
above). (See the Unix man page :manpage:`signal(2)` for further information.)
579580

580581
When threads are enabled, this function can only be called
581-
from :ref:`the main thread of the main interpreter <signals-and-threads>`;
582+
from :ref:`the main thread of a supporting interpreter <signals-and-threads>`;
582583
attempting to call it from other threads will cause a :exc:`ValueError`
583584
exception to be raised.
584585

Lib/test/test_interpreters/test_api.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,6 +2364,8 @@ def test_set___main___attrs(self):
23642364
)
23652365
self.assertEqual(rc, 0)
23662366

2367+
2368+
class SignalTests(TestBase):
23672369
@support.requires_subprocess()
23682370
@unittest.skipIf(os.name == 'nt', 'SIGINT not supported on windows')
23692371
def test_interpreter_handles_signals(self):
@@ -2431,6 +2433,25 @@ def test_legacy_interpreter_does_not_handle_signals(self):
24312433
self.assertIn(b"KeyboardInterrupt", stderr)
24322434
self.assertNotIn(b"AssertionError", stderr)
24332435

2436+
@unittest.skipIf(os.name == 'nt', 'SIGUSR1 not supported')
2437+
def test_signal_module_in_subinterpreters(self):
2438+
read, write = self.pipe()
2439+
interp = interpreters.create()
2440+
interp.exec(f"""if True:
2441+
import signal
2442+
import os
2443+
2444+
def sig(signum, stack):
2445+
signame = signal.Signals(signum).name
2446+
assert signame == "SIGUSR1"
2447+
os.write({write}, b'x')
2448+
2449+
signal.signal(signal.SIGUSR1, sig)
2450+
signal.raise_signal(signal.SIGUSR1)
2451+
""")
2452+
self.assertEqual(os.read(read, 1), b'x')
2453+
2454+
24342455

24352456
if __name__ == '__main__':
24362457
# Test needs to be a package, so we can do relative imports.

Modules/signalmodule.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ trip_signal(int sig_num)
303303
int fd = wakeup.fd;
304304
if (fd != INVALID_FD) {
305305
PyInterpreterState *interp = _PyInterpreterState_Main();
306+
assert(interp != NULL);
306307
unsigned char byte = (unsigned char)sig_num;
307308
#ifdef MS_WINDOWS
308309
if (wakeup.use_send) {
@@ -507,7 +508,7 @@ signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)
507508
if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
508509
_PyErr_SetString(tstate, PyExc_ValueError,
509510
"signal only works in main thread "
510-
"of the main interpreter");
511+
"of interpreters that support it");
511512
return NULL;
512513
}
513514
if (signalnum < 1 || signalnum >= Py_NSIG) {
@@ -751,7 +752,7 @@ signal_set_wakeup_fd_impl(PyObject *module, PyObject *fdobj,
751752
if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
752753
_PyErr_SetString(tstate, PyExc_ValueError,
753754
"set_wakeup_fd only works in main thread "
754-
"of the main interpreter");
755+
"of supporting interpreters");
755756
return NULL;
756757
}
757758

@@ -1661,7 +1662,7 @@ signal_module_exec(PyObject *m)
16611662
#endif
16621663

16631664
PyThreadState *tstate = _PyThreadState_GET();
1664-
if (_Py_IsMainInterpreter(tstate->interp)) {
1665+
if (_Py_ThreadCanHandleSignals(tstate->interp)) {
16651666
if (signal_get_set_handlers(state, d) < 0) {
16661667
return -1;
16671668
}

0 commit comments

Comments
 (0)