Skip to content

Commit 2c32e71

Browse files
Jonathan KliemMatthias Koeppe
Jonathan Kliem
authored and
Matthias Koeppe
committed
Add general signal hook
1 parent d441cbf commit 2c32e71

File tree

5 files changed

+127
-1
lines changed

5 files changed

+127
-1
lines changed

README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Changelog
4747
^^^^^^^^^^^^^^^^^^^^^^^^^
4848

4949
* Replace `fprintf` by calls to `write`, which is async-signal-safe according to POSIX. [#162]
50+
* Introduce a general hook to interface with custom signal handling
5051

5152

5253
1.11.2 (2021-12-15)

docs/source/interrupt.rst

+55
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,58 @@ can conditionally call ``sig_on()`` and ``sig_off()``::
249249

250250
This should only be needed if both the check (``n > 100`` in the example) and
251251
the code inside the ``sig_on()`` block take very little time.
252+
253+
.. _section_add_custom_signals:
254+
255+
Using custom blocking and signal handlers
256+
-----------------------------------------
257+
258+
The following illustrates how signals can be held back similar to
259+
``sig_block`` and ``sig_unblock``. The number theory libary PARI/GP
260+
defines a variable, which indicates that the execution should not
261+
currently be interrupted. Another variable is used to indicate a pending signal,
262+
so that PARI/GP can treat it.
263+
264+
Other external libraries might use a similar scheme.
265+
Here we indicate this might work::
266+
267+
from cysignals.signals cimport sig_on, sig_off, add_custom_signals
268+
269+
cdef extern from "stdio.h":
270+
void sleep(int)
271+
272+
cdef int SIGINT_block = 0
273+
cdef int SIGINT_pending = 0
274+
275+
cdef int signal_is_blocked():
276+
return SIGINT_block
277+
278+
cdef void signal_unblock():
279+
global SIGINT_block
280+
SIGINT_block = 0
281+
282+
cdef void set_pending_signal(int sig):
283+
global SIGINT_pending
284+
SIGINT_pending = sig
285+
286+
# Use the hook provided by cysignals.
287+
add_custom_signals(&signal_is_blocked, &signal_unblock, &set_pending_signal)
288+
289+
def foo(size_t b, int blocked):
290+
global SIGINT_block, SIGINT_pending
291+
sig_on()
292+
SIGINT_block = blocked
293+
for i in range(b):
294+
sleep(1)
295+
if SIGINT_pending:
296+
SIGINT_block = 0
297+
SIGINT_pending = 0
298+
raise KeyboardInterrupt("interrupt was held back")
299+
SIGINT_block = 0
300+
sig_off()
301+
return
302+
303+
In the above scenario ``foo(10, 0)`` would just wait for 10 seconds,
304+
while allowing interrupts. ``foo(10, 1)`` blocks the interrupt
305+
until the end of the second. The pending signal is then treated
306+
with a custom message.

src/cysignals/implementation.c

+36-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,37 @@ static int PARI_SIGINT_block = 0;
6262
static int PARI_SIGINT_pending = 0;
6363
#define paricfg_version NULL
6464
#endif
65+
66+
// Custom signal handling of other packages.
67+
#define MAX_N_CUSTOM_HANDLERS 16
68+
69+
static int (*custom_signal_is_blocked_pts[MAX_N_CUSTOM_HANDLERS])();
70+
static void (*custom_signal_unblock_pts[MAX_N_CUSTOM_HANDLERS])();
71+
static void (*custom_set_pending_signal_pts[MAX_N_CUSTOM_HANDLERS])(int);
72+
static int n_custom_handlers = 0;
73+
74+
int custom_signal_is_blocked(){
75+
// Check if a custom block is set.
76+
for(int i = 0; i < n_custom_handlers; i++){
77+
if (custom_signal_is_blocked_pts[i]())
78+
return 1;
79+
}
80+
return 0;
81+
}
82+
83+
void custom_signal_unblock(){
84+
// Unset all custom blocks.
85+
for(int i = 0; i < n_custom_handlers; i++)
86+
custom_signal_unblock_pts[i]();
87+
}
88+
89+
90+
void custom_set_pending_signal(int sig){
91+
// Set a pending signal to custom handlers.
92+
for(int i = 0; i < n_custom_handlers; i++)
93+
custom_set_pending_signal_pts[i](sig);
94+
}
95+
6596
#if HAVE_WINDOWS_H
6697
/* We must include <windows.h> after <pari.h>
6798
* See https://github.com/sagemath/cysignals/issues/107 */
@@ -214,7 +245,7 @@ static void cysigs_interrupt_handler(int sig)
214245

215246
if (cysigs.sig_on_count > 0)
216247
{
217-
if (!cysigs.block_sigint && !PARI_SIGINT_block)
248+
if (!cysigs.block_sigint && !PARI_SIGINT_block && !custom_signal_is_blocked())
218249
{
219250
/* Raise an exception so Python can see it */
220251
do_raise_exception(sig);
@@ -238,6 +269,7 @@ static void cysigs_interrupt_handler(int sig)
238269
{
239270
cysigs.interrupt_received = sig;
240271
PARI_SIGINT_pending = sig;
272+
custom_set_pending_signal(sig);
241273
}
242274
}
243275

@@ -400,6 +432,7 @@ static void _sig_on_interrupt_received(void)
400432
cysigs.sig_on_count = 0;
401433
cysigs.interrupt_received = 0;
402434
PARI_SIGINT_pending = 0;
435+
custom_signal_unblock();
403436

404437
#if HAVE_SIGPROCMASK
405438
sigprocmask(SIG_SETMASK, &oldset, NULL);
@@ -412,9 +445,11 @@ static void _sig_on_recover(void)
412445
{
413446
cysigs.block_sigint = 0;
414447
PARI_SIGINT_block = 0;
448+
custom_signal_unblock();
415449
cysigs.sig_on_count = 0;
416450
cysigs.interrupt_received = 0;
417451
PARI_SIGINT_pending = 0;
452+
custom_set_pending_signal(0);
418453

419454
#if HAVE_SIGPROCMASK
420455
/* Reset signal mask */

src/cysignals/signals.pxd.in

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ cdef extern from "macros.h" nogil:
4949
int sig_str_no_except "sig_str"(const char*)
5050
int sig_check_no_except "sig_check"()
5151

52+
# This function adds custom block/unblock/pending.
53+
cdef int add_custom_signals(int (*custom_signal_is_blocked)(),
54+
void (*custom_signal_unblock)(),
55+
void (*custom_set_pending_signal)(int)) except -1
5256

5357
# This function does nothing, but it is declared cdef except *, so it
5458
# can be used to make Cython check whether there is a pending exception

src/cysignals/signals.pyx

+31
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ cdef extern from "implementation.c":
5454
# PARI version string; NULL if compiled without PARI support
5555
const char* paricfg_version
5656

57+
int (**custom_signal_is_blocked_pts)()
58+
void (**custom_signal_unblock_pts)()
59+
void (**custom_set_pending_signal_pts)(int)
60+
int n_custom_handlers
61+
int MAX_N_CUSTOM_HANDLERS
62+
5763

5864
def _pari_version():
5965
"""
@@ -74,6 +80,31 @@ def _pari_version():
7480
return v.decode('ascii')
7581

7682

83+
cdef int add_custom_signals(int (*custom_signal_is_blocked)(),
84+
void (*custom_signal_unblock)(),
85+
void (*custom_set_pending_signal)(int)) except -1:
86+
"""
87+
Add an external block/unblock/pending to cysignals.
88+
89+
INPUT:
90+
91+
- ``custom_signal_is_blocked`` -- returns whether signals are currently blocked.
92+
93+
- ``custom_signal_unblock`` -- unblocks signals
94+
95+
- ``custom_set_pending_signal`` -- set a pending signal in case of blocking
96+
"""
97+
global n_custom_handlers
98+
if n_custom_handlers == MAX_N_CUSTOM_HANDLERS:
99+
raise IndexError("maximal number of custom handlers exceeded")
100+
101+
custom_signal_is_blocked_pts[n_custom_handlers] = custom_signal_is_blocked
102+
custom_signal_unblock_pts[n_custom_handlers] = custom_signal_unblock
103+
custom_set_pending_signal_pts[n_custom_handlers] = custom_set_pending_signal
104+
105+
n_custom_handlers += 1
106+
107+
77108
class AlarmInterrupt(KeyboardInterrupt):
78109
"""
79110
Exception class for :func:`alarm` timeouts.

0 commit comments

Comments
 (0)