diff --git a/src/cysignals/signals.pxd b/src/cysignals/signals.pxd index 531a9a8..10bcd34 100644 --- a/src/cysignals/signals.pxd +++ b/src/cysignals/signals.pxd @@ -29,6 +29,7 @@ cdef extern from "struct_signals.h": cy_atomic_int block_sigint const char* s PyObject* exc_value + double gc_pause_until cdef extern from "macros.h" nogil: diff --git a/src/cysignals/signals.pyx b/src/cysignals/signals.pyx index c02ca97..50c7f53 100644 --- a/src/cysignals/signals.pyx +++ b/src/cysignals/signals.pyx @@ -29,6 +29,7 @@ from cpython.ref cimport Py_XINCREF, Py_CLEAR from cpython.exc cimport (PyErr_Occurred, PyErr_NormalizeException, PyErr_Fetch, PyErr_Restore) from cpython.version cimport PY_MAJOR_VERSION +import time cimport cython import sys @@ -396,14 +397,19 @@ cdef void verify_exc_value() noexcept: Py_CLEAR(cysigs.exc_value) return - # To be safe, we run the garbage collector because it may clear - # references to our exception. - try: - collect() - except Exception: - # This can happen when Python is shutting down and the gc module - # is not functional anymore. - pass + cdef double cur_time = time.perf_counter(), finish_time + if cysigs.gc_pause_until == 0 or cysigs.gc_pause_until < cur_time: + # To be safe, we run the garbage collector because it may clear + # references to our exception. + try: + collect() + except Exception: + # This can happen when Python is shutting down and the gc module + # is not functional anymore. + pass + + finish_time = time.perf_counter() + cysigs.gc_pause_until = finish_time + (finish_time - cur_time) * 5 # Make sure we still have cysigs.exc_value at all; if this function was # called again during garbage collection it might have already been set diff --git a/src/cysignals/struct_signals.h b/src/cysignals/struct_signals.h index ed3ed1d..9682dae 100644 --- a/src/cysignals/struct_signals.h +++ b/src/cysignals/struct_signals.h @@ -26,6 +26,7 @@ #include #include #include +#include /* Choose sigjmp/longjmp variant */ @@ -92,6 +93,10 @@ typedef struct * This is used by the sig_occurred function. */ PyObject* exc_value; + /* Time until calling garbage collector is allowed. Using Python time.perf_counter. + * See https://github.com/sagemath/cysignals/issues/215. */ + double gc_pause_until; + #if ENABLE_DEBUG_CYSIGNALS int debug_level; #endif diff --git a/src/cysignals/tests.pyx b/src/cysignals/tests.pyx index 5aa02f0..287f274 100644 --- a/src/cysignals/tests.pyx +++ b/src/cysignals/tests.pyx @@ -933,6 +933,8 @@ def test_sig_occurred_finally(): """ TESTS:: + >>> from time import sleep + >>> sleep(0.5) # see PR 227 >>> from cysignals.tests import * >>> test_sig_occurred_finally() No current exception @@ -1022,6 +1024,8 @@ def test_sig_occurred_dealloc_in_gc(): >>> l = [DeallocDebug(), e] >>> l.append(l) >>> gc.disable() + >>> from time import sleep + >>> sleep(0.5) # see PR 227 >>> try: ... del l, e ... print_sig_occurred()