# Interrupt handling

cysignals provides two related mechanisms to deal with interrupts:

- Use sig_check() if you are writing mixed Cython/Python code. Typically this is code with (nested) loops where every individual statement takes little time.

- Use sig_on() and sig_off() if you are calling external C libraries or inside pure Cython code (without any Python functions) where even an individual statement, like a library call, can take a long time.

The functions `sig_check()`, `sig_on()` and `sig_off()` can be put in all kinds of Cython functions: `def`, `cdef` or `cpdef`. You cannot put them in pure Python code (files with extension `.py`).

## Basic example

The `sig_check()` in the loop below ensures that the loop can be interrupted by `CTRL-C`:

    from cysignals.signals cimport sig_check
    from libc.math cimport sin

    def sine_sum(double x, long count):
        cdef double s = 0
        for i in range(count):
            sig_check()
            s += sin(i*x)
        return s

See the example directory for this complete working example.

Note

Cython `cdef` or `cpdef` functions with a return type (like `cdef int myfunc():`) need to have an except value to propagate exceptions. Remember this whenever you write `sig_check()` or `sig_on()` inside such a function, otherwise you will see a message like `Exception KeyboardInterrupt: KeyboardInterrupt() in <function name> ignored`.

## Using `sig_check()`

`sig_check()` can be used to check for pending interrupts. If an interrupt happens during the execution of C or Cython code, it will be caught by the next `sig_check()`, the next `sig_on()` or possibly the next Python statement. With the latter we mean that certain Python statements also check for interrupts, an example of this is the `print` statement. The following loop can be interrupted:

    >>> while True:
    ...     print("Hello")

The typical use case for `sig_check()` is within tight loops doing complicated stuff (mixed Python and Cython code, potentially raising exceptions). It is reasonably safe to use and gives a lot of control, because in your Cython code, a `KeyboardInterrupt` can only be raised during `sig_check()`:

    from cysignals.signals cimport sig_check
    def sig_check_example():
        for x in foo:
            # (one loop iteration which does not take a long time)
            sig_check()

This `KeyboardInterrupt` is treated like any other Python exception and can be handled as usual:

    from cysignals.signals cimport sig_check
    def catch_interrupts():
        try:
            while some_condition():
                sig_check()
                do_something()
        except KeyboardInterrupt:
            # (handle interrupt)

Of course, you can also put the `try`/`except` inside the loop in the example above.

The function `sig_check()` is an extremely fast inline function which should have no measurable effect on performance.

## Using `sig_on()` and `sig_off()`

Another mechanism for interrupt handling is the pair of functions `sig_on()` and `sig_off()`. It is more powerful than `sig_check()` but also a lot more dangerous. You should put `sig_on()` before and `sig_off()` after any Cython code which could potentially take a long time. These two must always be called in pairs, i.e. every `sig_on()` must be matched by a closing `sig_off()`.

In practice your function will probably look like:

    from cysignals.signals cimport sig_on, sig_off
    def sig_example():
        # (some harmless initialization)
        sig_on()
        # (a long computation here, potentially calling a C library)
        sig_off()
        # (some harmless post-processing)
        return something

It is possible to put `sig_on()` and `sig_off()` in different functions, provided that `sig_off()` is called before the function which calls `sig_on()` returns. The reason is that `sig_on()` is implemented using `setjmp()`, which requires that the stack frame is kept alive. Therefore, the following code is invalid:

    # INVALID code because we return from function foo()
    # without calling sig_off() first.
    cdef foo():
        sig_on()

    def f1():
        foo()
        sig_off()

But the following is valid since you cannot call `foo` interactively:

    from cysignals.signals cimport sig_on, sig_off

    cdef int foo():
        sig_off()
        return 2+2

    def f1():
        sig_on()
        return foo()

For clarity however, it is best to avoid this.

A common mistake is to put `sig_off()` towards the end of a function (before the `return`) when the function has multiple `return` statements. So make sure there is a `sig_off()` before every `return` (and also before every `raise`).

Warning

The code inside `sig_on()` should be pure C or Cython code. If you call any Python code or manipulate any Python object (even something trivial like `x = []`), an interrupt can mess up Python’s internal state. When in doubt, try to use sig_check() instead.

Also, when an interrupt occurs inside `sig_on()`, code execution immediately stops without cleaning up. For example, any memory allocated inside `sig_on()` is lost. See Signal handling without exceptions for ways to deal with this.

When the user presses `CTRL-C` inside `sig_on()`, execution will jump back to `sig_on()` (the first one if there is a stack) and `sig_on()` will raise `KeyboardInterrupt`. As with `sig_check()`, this exception can be handled in the usual way:

    from cysignals.signals cimport sig_on, sig_off
    def catch_interrupts():
        try:
            sig_on()  # This must be INSIDE the try
            # (some long computation)
            sig_off()
        except KeyboardInterrupt:
            # (handle interrupt)

It is possible to stack `sig_on()` and `sig_off()`. If you do this, the effect is exactly the same as if only the outer `sig_on()`/`sig_off()` was there. The inner ones will just change a reference counter and otherwise do nothing. Make sure that the number of `sig_on()` calls equal the number of `sig_off()` calls:

    from cysignals.signals cimport sig_on, sig_off

    def f1():
        sig_on()
        x = f2()
        sig_off()

    cdef f2():
        sig_on()
        # ...
        sig_off()
        return ans

Extra care must be taken with exceptions raised inside `sig_on()`. The problem is that, if you do not do anything special, the `sig_off()` will never be called if there is an exception. If you need to raise an exception yourself, call a `sig_off()` before it:

    from cysignals.signals cimport sig_on, sig_off
    def raising_an_exception():
        sig_on()
        # (some long computation)
        if (something_failed):
            sig_off()
            raise RuntimeError("something failed")
        # (some more computation)
        sig_off()
        return something

Alternatively, you can use `try`/`finally` which will also catch exceptions raised by subroutines inside the `try`:

    from cysignals.signals cimport sig_on, sig_off
    def try_finally_example():
        sig_on()  # This must be OUTSIDE the try
        try:
            # (some long computation, potentially raising exceptions)
            return something
        finally:
            sig_off()

If you also want to catch this exception, you need a nested `try`:

    from cysignals.signals cimport sig_on, sig_off
    def try_finally_and_catch_example():
        try:
            sig_on()
            try:
                # (some long computation, potentially raising exceptions)
            finally:
                sig_off()
        except Exception:
            print("Trouble! Trouble!")

`sig_on()` is implemented using the C library call `setjmp()` which takes a very small but still measurable amount of time. In very time-critical code, one can conditionally call `sig_on()` and `sig_off()`:

    from cysignals.signals cimport sig_on, sig_off
    def conditional_sig_on_example(long n):
        if n > 100:
            sig_on()
        # (do something depending on n)
        if n > 100:
            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.

## 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.