Python - Trap all signals
Asked Answered
T

8

38

In python 2.6 under Linux, I can use the following to handle a TERM signal:

import signal
def handleSigTERM():
    shutdown()
signal.signal(signal.SIGTERM, handleSigTERM)    

Is there any way to setup a handler for all signals received by the process, other than just setting them up one-at-a-time?

Tannenwald answered 27/1, 2010 at 17:16 Comment(1)
The answer I believe is going to be "no" and certain signal can't even be trapped (SIGKILL).Viceroy
D
39

You could just loop through the signals in the signal module and set them up.

for i in [x for x in dir(signal) if x.startswith("SIG")]:
  try:
    signum = getattr(signal,i)
    signal.signal(signum,sighandler)
  except (OSError, RuntimeError) as m: #OSError for Python3, RuntimeError for 2
    print ("Skipping {}".format(i))
Dawkins answered 27/1, 2010 at 17:24 Comment(5)
That should be RuntimeError, not RunTimeError. couldn't edit, as just one character change.Roster
Your list comprehension will include SIG_IGN, which is an action not a signal. Since this actions 'value' corresponds to 1, which is the value for SIGHUP, you will be setting this signal twice. For this specific case that doesn't matter, however this code may break down for other similar purposes.Turbine
This answer does not apply to Python 3 since trying to set unblockable signals such as SIGKILL for example throw an OSError now instead of a RuntimeError.Turbine
@BryceGuinta Fixed. This should work for Pythons 2 and 3 now.Dawkins
You will get ValueError for SIG_BLOCK and SIG_DFL in python 3.5Fluorescence
C
49

As of Python 3.5, the signal constants are defined as an enum, enabling a nicer approach:

import signal

catchable_sigs = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP}
for sig in catchable_sigs:
    signal.signal(sig, print)  # Substitute handler of choice for `print`
Crisscross answered 2/1, 2016 at 16:40 Comment(3)
Could the downvoter please leave a comment? Is there something wrong with this answer?Crisscross
This is actually quite useful. Thanks! Can you please confirm whether this works on windows?Buckley
I don't know how signals work on Windows, but it looks like the core functionality is available: docs.python.org/3/library/signal.html#signal.signal Note that even though the signal function only accepts certain values, it looks like the Signals enum is constructed with the appropriate signals for the platform: github.com/python/cpython/blob/master/Lib/signal.py#L8-L13 So I would guess this approach would work, possibly with some minor tweaks. But again, I'm not sure how signals work or even what they mean on Windows, so it might not work at all.Crisscross
D
39

You could just loop through the signals in the signal module and set them up.

for i in [x for x in dir(signal) if x.startswith("SIG")]:
  try:
    signum = getattr(signal,i)
    signal.signal(signum,sighandler)
  except (OSError, RuntimeError) as m: #OSError for Python3, RuntimeError for 2
    print ("Skipping {}".format(i))
Dawkins answered 27/1, 2010 at 17:24 Comment(5)
That should be RuntimeError, not RunTimeError. couldn't edit, as just one character change.Roster
Your list comprehension will include SIG_IGN, which is an action not a signal. Since this actions 'value' corresponds to 1, which is the value for SIGHUP, you will be setting this signal twice. For this specific case that doesn't matter, however this code may break down for other similar purposes.Turbine
This answer does not apply to Python 3 since trying to set unblockable signals such as SIGKILL for example throw an OSError now instead of a RuntimeError.Turbine
@BryceGuinta Fixed. This should work for Pythons 2 and 3 now.Dawkins
You will get ValueError for SIG_BLOCK and SIG_DFL in python 3.5Fluorescence
W
11

If you want to get rid of the try, just ignore signals that cannot be caught.

#!/usr/bin/env python
# https://mcmap.net/q/404030/-python-trap-all-signals
import os
import sys
import time
import signal

SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) \
    for n in dir(signal) if n.startswith('SIG') and '_' not in n )


def receive_signal(signum, stack):
    if signum in [1,2,3,15]:
        print 'Caught signal %s (%s), exiting.' % (SIGNALS_TO_NAMES_DICT[signum], str(signum))
        sys.exit()
    else:
        print 'Caught signal %s (%s), ignoring.' % (SIGNALS_TO_NAMES_DICT[signum], str(signum))

def main():
    uncatchable = ['SIG_DFL','SIGSTOP','SIGKILL']
    for i in [x for x in dir(signal) if x.startswith("SIG")]:
        if not i in uncatchable:
            signum = getattr(signal,i)
            signal.signal(signum,receive_signal)
    print('My PID: %s' % os.getpid())
    while True:
        time.sleep(1)
main()
Whitesell answered 14/4, 2011 at 20:6 Comment(1)
The code is not properly indented but I cannot edit it because SO don't let me make a change that small. All the for should be indented with one level of indentation less.Wabble
T
3

Works on Windows 10 and Python 3.7:

import signal
import time

def sighandler(signal,frame):
    print("signal",sig,frame)
    return

catchable_sigs = set(signal.Signals)
for sig in catchable_sigs:
    try:
        signal.signal(sig, sighandler)
        print("Setting ",sig)
        print ("value {}".format(sig))
    except (ValueError, OSError, RuntimeError) as m:
        print("Skipping ",sig)
        print ("Value {}".format(sig))


# press some keys or issue kill
x = 0
while x < 5:
    time.sleep(4)
    x += 1

Results:

Skipping  Signals.CTRL_C_EVENT
Value 0
Skipping  Signals.CTRL_BREAK_EVENT
Value 1
Setting  Signals.SIGINT
value 2
Setting  Signals.SIGILL
value 4
Setting  Signals.SIGFPE
value 8
Setting  Signals.SIGSEGV
value 11
Setting  Signals.SIGTERM
value 15
Setting  Signals.SIGBREAK
value 21
Setting  Signals.SIGABRT
value 22
Tinfoil answered 17/12, 2018 at 14:30 Comment(0)
T
1

Here's a 2/3 compatible way which doesn't have as many pitfalls as the others:

from itertools import count
import signal

def set_all_signal_signals(handler):
    """Set all signals to a particular handler."""
    for signalnum in count(1):
        try:
            signal.signal(signalnum, handler)
            print("set {}".format(signalnum))
        except (OSError, RuntimeError):
            # Invalid argument such as signals that can't be blocked
            pass
        except ValueError:
            # Signal out of range
            break

Since signalnum is just a number, iterate over 1 to out of range setting the signal to a particular handle.

Turbine answered 16/5, 2017 at 10:25 Comment(0)
A
1

In Python3.8 we've got a new function signal.valid_signals() https://docs.python.org/3/library/signal.html#signal.valid_signals

import signal
for sig in signal.valid_signals():
    print(f"{sig:2d}",sig)
Alewife answered 10/7, 2020 at 8:10 Comment(0)
O
-1

For Python 3:

for sig in signal.Signals:
    try:
        signal.signal(sig, sighandler)
    except OSError:
        print('Skipping', sig)
Overseas answered 28/2, 2020 at 15:47 Comment(0)
A
-2

That code won't work in the current version of python. There are many variables starting with SIG with the same value. For instance, SIGHUP and SIG_UNBLOCK are both 1. The only way I could think of to get a list of actual signals was to just make it myself.

from signal import *    
signals = {
        SIGABRT: 'SIGABRT',
        SIGALRM: 'SIGALRM',
        SIGBUS: 'SIGBUS',
        SIGCHLD: 'SIGCHLD',
        SIGCONT: 'SIGCONT',
        SIGFPE: 'SIGFPE',
        SIGHUP: 'SIGHUP',
        SIGILL: 'SIGILL',
        SIGINT: 'SIGINT',
        SIGPIPE: 'SIGPIPE',
        SIGPOLL: 'SIGPOLL',
        SIGPROF: 'SIGPROF',
        SIGQUIT: 'SIGQUIT',
        SIGSEGV: 'SIGSEGV',
        SIGSYS: 'SIGSYS',
        SIGTERM: 'SIGTERM',
        SIGTRAP: 'SIGTRAP',
        SIGTSTP: 'SIGTSTP',
        SIGTTIN: 'SIGTTIN',
        SIGTTOU: 'SIGTTOU',
        SIGURG: 'SIGURG',
        SIGUSR1: 'SIGUSR1',
        SIGUSR2: 'SIGUSR2',
        SIGVTALRM: 'SIGVTALRM',
        SIGXCPU: 'SIGXCPU',
        SIGXFSZ: 'SIGXFSZ',
        }

for num in signals:
    signal(num, h)
Annulus answered 11/3, 2015 at 23:24 Comment(1)
It's not necessary. All the signals still start with SIG but you need to add a double check to make sure that you ignore the SIG_ ones.Dawkins

© 2022 - 2024 — McMap. All rights reserved.