Skip to content

Commit

Permalink
Merge pull request #181 from mkoeppe/add_general_signal_hook_but_keep…
Browse files Browse the repository at this point in the history
…_pari

Add general signal hook but keep pari
  • Loading branch information
dimpase authored Sep 25, 2023
2 parents beb2c5e + fed466d commit 1adcbd9
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Changelog
^^^^^^^^^^^^^^^^^^^^^^^^^

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


1.11.2 (2021-12-15)
Expand Down
55 changes: 55 additions & 0 deletions docs/source/interrupt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,58 @@ can conditionally call ``sig_on()`` and ``sig_off()``::

This should only be needed if both the check (``n > 100`` in the example) and
the code inside the ``sig_on()`` block take very little time.

.. _section_add_custom_signals:

Using custom blocking and signal handlers
-----------------------------------------

The following illustrates how signals can be held back similar to
``sig_block`` and ``sig_unblock``. The number theory libary PARI/GP
defines a variable, which indicates that the execution should not
currently be interrupted. Another variable is used to indicate a pending signal,
so that PARI/GP can treat it.

Other external libraries might use a similar scheme.
Here we indicate this might work::

from cysignals.signals cimport sig_on, sig_off, add_custom_signals

cdef extern from "stdio.h":
void sleep(int)

cdef int SIGINT_block = 0
cdef int SIGINT_pending = 0

cdef int signal_is_blocked():
return SIGINT_block

cdef void signal_unblock():
global SIGINT_block
SIGINT_block = 0

cdef void set_pending_signal(int sig):
global SIGINT_pending
SIGINT_pending = sig

# Use the hook provided by cysignals.
add_custom_signals(&signal_is_blocked, &signal_unblock, &set_pending_signal)

def foo(size_t b, int blocked):
global SIGINT_block, SIGINT_pending
sig_on()
SIGINT_block = blocked
for i in range(b):
sleep(1)
if SIGINT_pending:
SIGINT_block = 0
SIGINT_pending = 0
raise KeyboardInterrupt("interrupt was held back")
SIGINT_block = 0
sig_off()
return

In the above scenario ``foo(10, 0)`` would just wait for 10 seconds,
while allowing interrupts. ``foo(10, 1)`` blocks the interrupt
until the end of the second. The pending signal is then treated
with a custom message.
37 changes: 36 additions & 1 deletion src/cysignals/implementation.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,37 @@ static int PARI_SIGINT_block = 0;
static int PARI_SIGINT_pending = 0;
#define paricfg_version NULL
#endif

// Custom signal handling of other packages.
#define MAX_N_CUSTOM_HANDLERS 16

static int (*custom_signal_is_blocked_pts[MAX_N_CUSTOM_HANDLERS])();
static void (*custom_signal_unblock_pts[MAX_N_CUSTOM_HANDLERS])();
static void (*custom_set_pending_signal_pts[MAX_N_CUSTOM_HANDLERS])(int);
static int n_custom_handlers = 0;

int custom_signal_is_blocked(){
// Check if a custom block is set.
for(int i = 0; i < n_custom_handlers; i++){
if (custom_signal_is_blocked_pts[i]())
return 1;
}
return 0;
}

void custom_signal_unblock(){
// Unset all custom blocks.
for(int i = 0; i < n_custom_handlers; i++)
custom_signal_unblock_pts[i]();
}


void custom_set_pending_signal(int sig){
// Set a pending signal to custom handlers.
for(int i = 0; i < n_custom_handlers; i++)
custom_set_pending_signal_pts[i](sig);
}

#if HAVE_WINDOWS_H
/* We must include <windows.h> after <pari.h>
* See https://github.com/sagemath/cysignals/issues/107 */
Expand Down Expand Up @@ -214,7 +245,7 @@ static void cysigs_interrupt_handler(int sig)

if (cysigs.sig_on_count > 0)
{
if (!cysigs.block_sigint && !PARI_SIGINT_block)
if (!cysigs.block_sigint && !PARI_SIGINT_block && !custom_signal_is_blocked())
{
/* Raise an exception so Python can see it */
do_raise_exception(sig);
Expand All @@ -238,6 +269,7 @@ static void cysigs_interrupt_handler(int sig)
{
cysigs.interrupt_received = sig;
PARI_SIGINT_pending = sig;
custom_set_pending_signal(sig);
}
}

Expand Down Expand Up @@ -400,6 +432,7 @@ static void _sig_on_interrupt_received(void)
cysigs.sig_on_count = 0;
cysigs.interrupt_received = 0;
PARI_SIGINT_pending = 0;
custom_signal_unblock();

#if HAVE_SIGPROCMASK
sigprocmask(SIG_SETMASK, &oldset, NULL);
Expand All @@ -412,9 +445,11 @@ static void _sig_on_recover(void)
{
cysigs.block_sigint = 0;
PARI_SIGINT_block = 0;
custom_signal_unblock();
cysigs.sig_on_count = 0;
cysigs.interrupt_received = 0;
PARI_SIGINT_pending = 0;
custom_set_pending_signal(0);

#if HAVE_SIGPROCMASK
/* Reset signal mask */
Expand Down
4 changes: 4 additions & 0 deletions src/cysignals/signals.pxd.in
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ cdef extern from "macros.h" nogil:
int sig_str_no_except "sig_str"(const char*)
int sig_check_no_except "sig_check"()

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

# This function does nothing, but it is declared cdef except *, so it
# can be used to make Cython check whether there is a pending exception
Expand Down
31 changes: 31 additions & 0 deletions src/cysignals/signals.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ cdef extern from "implementation.c":
# PARI version string; NULL if compiled without PARI support
const char* paricfg_version

int (**custom_signal_is_blocked_pts)()
void (**custom_signal_unblock_pts)()
void (**custom_set_pending_signal_pts)(int)
int n_custom_handlers
int MAX_N_CUSTOM_HANDLERS


def _pari_version():
"""
Expand All @@ -74,6 +80,31 @@ def _pari_version():
return v.decode('ascii')


cdef int add_custom_signals(int (*custom_signal_is_blocked)(),
void (*custom_signal_unblock)(),
void (*custom_set_pending_signal)(int)) except -1:
"""
Add an external block/unblock/pending to cysignals.
INPUT:
- ``custom_signal_is_blocked`` -- returns whether signals are currently blocked.
- ``custom_signal_unblock`` -- unblocks signals
- ``custom_set_pending_signal`` -- set a pending signal in case of blocking
"""
global n_custom_handlers
if n_custom_handlers == MAX_N_CUSTOM_HANDLERS:
raise IndexError("maximal number of custom handlers exceeded")

custom_signal_is_blocked_pts[n_custom_handlers] = custom_signal_is_blocked
custom_signal_unblock_pts[n_custom_handlers] = custom_signal_unblock
custom_set_pending_signal_pts[n_custom_handlers] = custom_set_pending_signal

n_custom_handlers += 1


class AlarmInterrupt(KeyboardInterrupt):
"""
Exception class for :func:`alarm` timeouts.
Expand Down

0 comments on commit 1adcbd9

Please sign in to comment.