0

Is it possible to catch exceptions for all threads of a Python application with a common exception handler?

Consider the following example. I'd like to catch CTRL+C from the main thread, but sometimes it gets caught by one of the worker threads which doesn't terminate the other threads. This is the output in such a case:

^CTraceback (most recent call last):
  File "thread-example.py", line 50, in <module>
    main()
  File "thread-example.py", line 30, in main
    t.join(1)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 675, in join
    self.__block.wait(delay)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 263, in wait
    _sleep(delay)
KeyboardInterrupt

This is the test application.

#!/usr/bin/python

import os
import sys
import threading
import time
import subprocess
import signal


class Worker(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.kill_received = False
        self.daemon = True
        self.name = name

    def run(self):
        while not self.kill_received:
            time.sleep(1)
            print self.name

def main():
    threads = []

    for i in xrange(10):
        t = Worker('worker-%d' % i)
        threads.append(t)
        t.start()
        t.join(1)

    while len(threads) > 0:
        try:
            threads = [t for t in threads if t is not None and t.isAlive()]
            time.sleep(1)
        except KeyboardInterrupt:
            print "Ctrl-c received! Sending kill to threads..."
            for t in threads:
                t.kill_received = True
            # wait for threads to finish gracefully, then kill them anyway.
            # since all threads are daemons, they should finish once the main 
            # loop terminates
            for i in xrange(5):
                print '%i ...' % (5-i)
                time.sleep(1)
            print 'Exit!'
            os._exit(1)

if __name__ == '__main__':
    main()
orange
  • 7,755
  • 14
  • 75
  • 139
  • You'd need to pass exceptions on using the usual inter-thread communication techniques. See [Catch a thread's exception in the caller thread in Python](http://stackoverflow.com/q/2829329) for an example. – Martijn Pieters Apr 14 '13 at 11:27

1 Answers1

1

Ctrl-C itself is not an exception, but rather a signal. What you can do is set all the spawned threads other than the "main" one to ignore it, like this:

import signal
signal.signal(signal.SIGINT, signal.SIG_IGN)
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Is this portable or just for POSIX systems? Other than that, wasn't there a way to specifically wait for a signal that would prevent it from being passed to other threads? – Ulrich Eckhardt Apr 14 '13 at 12:22
  • I don't know if it works on Windows or not. Try it. :) You can specifically wait for signals using `signalfd` but I doubt you'd want to in Python or in this particular case. – John Zwinck Apr 14 '13 at 12:42
  • I added the line in the constructor of the threads. Now I cannot kill the whole program any more... Is this line thread specific or does it mean the signals are ignored globally? The latter seems to be the case... – orange Apr 18 '13 at 08:28
  • After a different experiment, I realised that there's only 1 call of this method necessary in which I can kill the other threads. – orange Apr 18 '13 at 08:37