(in compiled extension modules)
>>> import numpy as np >>> rng = np.random.default_rng() >>> def g(n=1000000000): ... return rng.random(n) ... >>> g() ^CTraceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in g KeyboardInterrupt >>>
3.5 second delay
PyErr_CheckSignals
You’ll get increasingly more out of it if…
SIGINT
SIGINT
delivered
to Python interpreterKeyboardInterrupt
KeyboardInterrupt
1 Except on Windows
>>> import numpy as np >>> rng = np.random.default_rng() >>> def g(n=1000000000): ... return rng.random(n) ... >>> g() ^C Program received signal SIGINT random_standard_uniform_fill () at 0x00007ffff00b23a7 (gdb) signal SIGINT Continuing with signal SIGINT. Breakpoint 2, signal_handler (sig_num=2) at ./Modules/signalmodule.c:347 (gdb)
Breakpoint 2, signal_handler (sig_num=2) at ./Modules/signalmodule.c:347 (gdb) backtrace #0 signal_handler (sig_num=2) #1 <signal handler called> #2 random_standard_uniform_fill (…) #3 __pyx_f_5numpy_6random_7_common_f… #4 __pyx_pw_5numpy_6random_10_genera… #5 method_vectorcall_FASTCALL_KEYWOR… #6 _PyObject_VectorcallTstate (…) #7 PyObject_Vectorcall (…) #8 _PyEval_EvalFrameDefault (…) #9 PyEval_EvalCode (…)
signal_handler
SIGINT
SIGINT
delivered to Python interpreterKeyboardInterrupt
KeyboardInterrupt
1 Except on Windows
PyErr_CheckSignals
is poorly
documented
int PyErr_CheckSignals()
Part of the Stable ABI.
This function interacts with Python’s signal handling.
If the function is called from the main thread and under the main Python interpreter, it checks whether a signal has been sent to the processes and if so, invokes the corresponding signal handler. If the signal module is supported, this can invoke a signal handler written in Python.
The function attempts to handle all pending signals, and then returns 0. However, if a Python signal handler raises an exception, the error indicator is set and the function returns −1 immediately (such that other pending signals may not have been handled yet: they will be on the next
PyErr_CheckSignals()
invocation).If the function is called from a non-main thread, or under a non-main Python interpreter, it does nothing and returns 0.
This function can be called by long-running C code that wants to be interruptible by user requests (such as by pressing Ctrl-C).
Note: The default Python signal handler for
SIGINT
raises theKeyboardInterrupt
exception.
PyErr_CheckSignals
is poorly
documentedvoid random_standard_uniform_fill( bitgen_t *rng, npy_intp cnt, double *out ) { npy_intp i; for (i = 0; i < cnt; i++) { out[i] = next_double(rng); } }
int random_standard_uniform_fill( bitgen_t *rng, npy_intp cnt, double *out ) { npy_intp i; for (i = 0; i < cnt; i++) { out[i] = next_double(rng); if (PyErr_CheckSignals()) return -1; } return 0; }
int random_standard_uniform_fill( bitgen_t *rng, npy_intp cnt, double *out ) { npy_intp i; for (i = 0; i < cnt; i++) { out[i] = next_double(rng); if (PyErr_CheckSignals()) return -1; } return 0; }
int random_standard_uniform_fill( bitgen_t *rng, npy_intp cnt, double *out ) { PyGILState_STATE st; int err; npy_intp i; for (i = 0; i < cnt; i++) { out[i] = next_double(rng); st = PyGILState_Ensure(); err = PyErr_CheckSignals(); PyGILState_Release(st); if (err) return err; } return 0; }
PyErr_CheckSignals
is poorly
documentedKeyboardInterrupt
directly from its
C-level signal handler
int CheckSignalsOftenEnough(void) { static struct timespec last_check = { 0, 0 }; struct timespec now; clock_gettime(CLOCK_MONOTONIC_COARSE, &now); if (timespec_difference_at_least(&now, &last_check, ONE_MS_IN_NS)) { last_check = now; PyGILState_STATE st = PyGILState_Ensure(); int err = PyErr_CheckSignals(); PyGILState_Release(st); return err; } return 0; }
Full version:
https://github.com/MillionConcepts/cpython-ext-ctrl-c/blob/main/CheckSignalsOftenEnough.c
PyErr_CheckSignals
regularlyPyErr_CheckSignals
PyErr_CheckSignals
reclaim GIL
itself, before running Python signal handlers,
only if flag is setcdef random_standard_uniform_fill( bitgen_t rng, double [:] out, ) nogil: for i in range(len(out)): out[i] = next_double(rng)
int random_standard_uniform_fill( bitgen_t rng, __Pyx_memviewslice out ) { Py_ssize_t i; Py_ssize_t len; len = __Pyx_MemoryView_Len(out); for (i = 0; i < len; i += 1) { *((double *) ((out.data + i * out.strides[0]))) = next_double(rng); if (CheckSignalsOftenEnough()) return -1; } return 0; }
#[pyfunction] async fn random_standard_uniform_fill( #[pyo3(cancel_handle)] mut cancel: CancelHandle, rng: BitGen, out: &mut f64[], ) { futures::select! { cancel.cancelled.fuse() => {}, _ => { for i in 0..out.len() { out[i] = rng.next_double(); } } } }
>>> import numpy as np >>> rng = np.random.default_rng() >>> def g(n=1000000000): ... return rng.random(n) ... >>> g()
Slides, code samples, raw data,
and analysis scripts may be found
at https://github.com/MillionConcepts/cpython-ext-ctrl-c
or https://git.sr.ht/~zackw/cpython-ext-ctrl-c
and may be reused under Million Concepts’ usual 3-clause BSD license
B612 Mono
, Libertinus Sans, and Quando