diff --git a/src/cysignals/implementation.c b/src/cysignals/implementation.c index 445a40f..b1dc182 100644 --- a/src/cysignals/implementation.c +++ b/src/cysignals/implementation.c @@ -681,7 +681,14 @@ static void setup_cysignals_handlers(void) sigprocmask(SIG_SETMASK, &default_sigmask, &sigmask_with_sigint); #endif - /* Install signal handlers */ + /* Install signal handlers. We need a separate C-level interrupt handler + * apart from the Python-level interrupt handler because Python-level + * interrupt handler will not be called inside Cython code. + * See init_cysignals and python_check_interrupt for an explanation. + * A downside is the C-level interrupt handler will be overwritten + * when signal(SIGINT, getsignal(SIGINT)) is executed, in that case + * init_cysignals() need to be called again. + */ /* Handlers for interrupt-like signals */ sa.sa_handler = cysigs_interrupt_handler; sa.sa_flags = 0; diff --git a/src/cysignals/signals.pxd b/src/cysignals/signals.pxd index 531a9a8..2775285 100644 --- a/src/cysignals/signals.pxd +++ b/src/cysignals/signals.pxd @@ -26,6 +26,7 @@ cdef extern from "struct_signals.h": ctypedef struct cysigs_t: cy_atomic_int sig_on_count + cy_atomic_int interrupt_received cy_atomic_int block_sigint const char* s PyObject* exc_value diff --git a/src/cysignals/signals.pyx b/src/cysignals/signals.pyx index c02ca97..270afb8 100644 --- a/src/cysignals/signals.pyx +++ b/src/cysignals/signals.pyx @@ -353,6 +353,12 @@ def python_check_interrupt(sig, frame): ``implementation.c``. """ sig_check() + # If this line is reached, a possibility is that something called + # signal(SIGINT, getsignal(SIGINT)), which cause cysigs_interrupt_handler + # to not be called. We reinstall the C-level signal handler. + init_cysignals() + cysigs.interrupt_received = sig + sig_check() cdef void verify_exc_value() noexcept: diff --git a/src/cysignals/tests.pyx b/src/cysignals/tests.pyx index 5aa02f0..4e11c75 100644 --- a/src/cysignals/tests.pyx +++ b/src/cysignals/tests.pyx @@ -35,6 +35,7 @@ from libc.errno cimport errno from posix.signal cimport sigaltstack, stack_t, SS_ONSTACK from cpython cimport PyErr_SetString +from cpython.exc cimport PyErr_CheckSignals from .signals cimport * from .memory cimport * @@ -805,6 +806,13 @@ def test_interrupt_bomb(long n=100, long p=10): i = 0 while True: try: + # For some reason, without the line below, the exception + # will be detected too late (outside the try/except block) + # and the KeyboardInterrupt will be leaked outside, + # making the test fail. + # We can't really call PyErr_CheckSignals() from inside + # sig_on() because it does not hold the GIL. + PyErr_CheckSignals() with nogil: sig_on() ms_sleep(1000) @@ -1124,7 +1132,7 @@ def test_sig_block_outside_sig_on(long delay=DEFAULT_DELAY): sig_unblock() try: - sig_on() # Interrupt caught here + PyErr_CheckSignals() # Interrupt caught here except KeyboardInterrupt: return "Success" abort() # This should not be reached