Python interface to signal handlers

In this module, we distinguish between the “OS-level” signal handler and the “Python-level” signal handler.

The Python function signal.signal() sets both of these: it sets the Python-level signal handler to the function specified by the user. It also sets the OS-level signal handler to a specific C function which calls the Python-level signal handler.

The Python signal module does not allow access to the OS-level signal handler (in particular, it does not allow one to temporarily change a signal handler if the OS-level handler was not the Python one).

class cysignals.pysignals.SigAction

An opaque object representing an OS-level signal handler.

The only legal initializers are signal.SIG_DFL (the default), signal.SIG_IGN and another SigAction object (which is copied).

EXAMPLES:

>>> from cysignals.pysignals import SigAction
>>> SigAction()
<SigAction with sa_handler=SIG_DFL>
>>> import signal
>>> SigAction(signal.SIG_DFL)
<SigAction with sa_handler=SIG_DFL>
>>> SigAction(signal.SIG_IGN)
<SigAction with sa_handler=SIG_IGN>
>>> A = SigAction(signal.SIG_IGN)
>>> SigAction(A)
<SigAction with sa_handler=SIG_IGN>
>>> SigAction(A) == A
True

TESTS:

>>> SigAction(42)
Traceback (most recent call last):
...
TypeError: cannot initialize SigAction from <... 'int'>
class cysignals.pysignals.changesignal

Context to temporarily change a signal handler.

This should be used as follows:

with changesignal(sig, action):
    ...

Inside the context, code behaves as if signal.signal(sig, action) was called. When leaving the context, the signal handler is restored to what it was before. Both the Python-level and OS-level signal handlers are restored.

EXAMPLES:

>>> from cysignals.pysignals import changesignal
>>> import os, signal
>>> def handler(*args):
...     print("got signal")
>>> _ = signal.signal(signal.SIGQUIT, signal.SIG_IGN)
>>> with changesignal(signal.SIGQUIT, handler):
...     os.kill(os.getpid(), signal.SIGQUIT)
got signal
>>> os.kill(os.getpid(), signal.SIGQUIT)
>>> with changesignal(signal.SIGQUIT, handler):
...     setossignal(signal.SIGQUIT, signal.SIG_DFL)
...     raise Exception("just testing")
Traceback (most recent call last):
...
Exception: just testing
>>> os.kill(os.getpid(), signal.SIGQUIT)
class cysignals.pysignals.containsignals

Context to revert any changes to given signal handlers and block those signals.

This should be used as follows:

with containsignals(signals):
    ...

where signals is a list of signals (by default, all signals numbered from 1 to 31 except for SIGKILL and SIGSTOP, which cannot be handled).

When entering the context, the current handlers of those signals are saved. They are restored when exiting the context. This is mainly meant to prevent unwanted changes to signal handlers that other code may make. Both the Python-level and OS-level signal handlers are saved and restored.

Also, the signals from the list signals are blocked. So any newly-installed signal handlers are prevented from being triggered.

EXAMPLES:

>>> from cysignals.pysignals import containsignals
>>> import os, signal
>>> def handler(*args):
...     print("got signal")
>>> _ = signal.signal(signal.SIGBUS, handler)
>>> with containsignals([signal.SIGBUS]):
...     _ = signal.signal(signal.SIGBUS, signal.SIG_DFL)
...     # This signal is delivered when exiting the context
...     os.kill(os.getpid(), signal.SIGBUS)
...     print("no signal yet")
no signal yet
got signal

The same example but now containing all signals:

>>> with containsignals() as C:
...     print("blocked {0} signals".format(len(C.oldhandlers)))
...     _ = signal.signal(signal.SIGBUS, signal.SIG_DFL)
...     # This signal is delivered when exiting the context
...     os.kill(os.getpid(), signal.SIGBUS)
...     print("no signal yet")
blocked 29 signals
no signal yet
got signal

This time, we send a signal which is not contained. We set a new handler, which is not blocked or changed by the context:

>>> def fancyhandler(*args):
...     print("fancy!")
>>> with containsignals([signal.SIGINT]):
...     _ = signal.signal(signal.SIGBUS, fancyhandler)
...     os.kill(os.getpid(), signal.SIGBUS)
fancy!
>>> os.kill(os.getpid(), signal.SIGBUS)
fancy!
cysignals.pysignals.getossignal(sig)

Get the OS-level signal handler.

This returns an opaque object of type SigAction which can only be used in a future call to setossignal().

EXAMPLES:

>>> from cysignals.pysignals import getossignal
>>> import signal
>>> getossignal(signal.SIGINT)
<SigAction with sa_handler=0x...>
>>> getossignal(signal.SIGUSR1)
<SigAction with sa_handler=SIG_DFL>
>>> def handler(*args): pass
>>> _ = signal.signal(signal.SIGUSR1, handler)
>>> getossignal(signal.SIGUSR1)
<SigAction with sa_handler=0x...>

Check whether a signal is handled by the Python signal handler:

>>> from cysignals.pysignals import python_os_handler
>>> getossignal(signal.SIGUSR1) == python_os_handler
True
>>> _ = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
>>> getossignal(signal.SIGUSR1) == python_os_handler
False
>>> getossignal(signal.SIGABRT) == python_os_handler
False

TESTS:

>>> getossignal(None)
Traceback (most recent call last):
...
TypeError: an integer is required
>>> getossignal(-1)
Traceback (most recent call last):
...
OSError: [Errno 22] Invalid argument
cysignals.pysignals.setossignal(sig, action)

Set the OS-level signal handler to action, which should either be signal.SIG_DFL or signal.SIG_IGN or a SigAction object returned by an earlier call to getossignal() or setossignal().

Return the old signal handler.

EXAMPLES:

>>> from cysignals.pysignals import setossignal
>>> import os, signal
>>> def handler(*args): print("got signal")
>>> _ = signal.signal(signal.SIGHUP, handler)
>>> os.kill(os.getpid(), signal.SIGHUP)
got signal
>>> pyhandler = setossignal(signal.SIGHUP, signal.SIG_IGN)
>>> pyhandler
<SigAction with sa_handler=0x...>
>>> os.kill(os.getpid(), signal.SIGHUP)
>>> setossignal(signal.SIGHUP, pyhandler)
<SigAction with sa_handler=SIG_IGN>
>>> os.kill(os.getpid(), signal.SIGHUP)
got signal
>>> setossignal(signal.SIGHUP, signal.SIG_DFL) == pyhandler
True

TESTS:

>>> setossignal(signal.SIGHUP, None)
Traceback (most recent call last):
...
TypeError: cannot initialize SigAction from <... 'NoneType'>
>>> setossignal(-1, signal.SIG_DFL)
Traceback (most recent call last):
...
OSError: [Errno 22] Invalid argument
cysignals.pysignals.setsignal(sig, action, osaction=None)

Set the Python-level signal handler for signal sig to action. If osaction is given, set the OS-level signal handler to osaction. If osaction is None (the default), change only the Python-level handler and keep the OS-level handler.

Return the old Python-level handler.

EXAMPLES:

>>> from cysignals.pysignals import *
>>> def handler(*args): print("got signal")
>>> _ = signal.signal(signal.SIGSEGV, handler)
>>> A = getossignal(signal.SIGILL)
>>> _ = setsignal(signal.SIGILL, getsignal(signal.SIGSEGV))
>>> getossignal(signal.SIGILL) == A
True
>>> _ = setossignal(signal.SIGILL, getossignal(signal.SIGSEGV))
>>> import os
>>> os.kill(os.getpid(), signal.SIGILL)
got signal
>>> setsignal(signal.SIGILL, signal.SIG_DFL)
<function handler at 0x...>
>>> _ = setsignal(signal.SIGALRM, signal.SIG_DFL, signal.SIG_IGN)
>>> os.kill(os.getpid(), signal.SIGALRM)
>>> _ = setsignal(signal.SIGALRM, handler, getossignal(signal.SIGSEGV))
>>> os.kill(os.getpid(), signal.SIGALRM)
got signal

TESTS:

>>> setsignal(-1, signal.SIG_DFL)
Traceback (most recent call last):
...
OSError: [Errno 22] Invalid argument