Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cause python to exit if any thread has an exception

I have a python3 program that starts a second thread (besides the main thread) for handling some events asynchronously. Ideally, my program works without a flaw and never has an unhandled exceptions. But stuff happens. When/if there is an exception, I want the whole interpreter to exit with an error code as if it had been a single thread. Is that possible?

Right now, if an exception occurs on the spawned thread, it prints out the usual error information, but doesn't exit. The main thread just keeps going.

Example

import threading
import time

def countdown(initial):
    while True:
        print(initial[0])
        initial = initial[1:]
        time.sleep(1)

if __name__ == '__main__':
    helper = threading.Thread(target=countdown, args=['failsoon'])
    helper.start()
    time.sleep(0.5)
    #countdown('THISWILLTAKELONGERTOFAILBECAUSEITSMOREDATA')
    countdown('FAST')

The countdown will eventually fail to access [0] from the string because it's been emptied causing an IndexError: string index out of range error. The goal is that whether the main or helper dies first, the whole program dies alltogether, but the stack trace info is still output.

Solutions Tried

After some digging, my thought was to use sys.excepthook. I added the following:

def killAll(etype, value, tb):
    print('KILL ALL')
    traceback.print_exception(etype, value, tb)
    os.kill(os.getpid(), signal.SIGKILL)

sys.excepthook = killAll

This works if the main thread is the one that dies first. But in the other case it does not. This seems to be a known issue (https://bugs.python.org/issue1230540). I will try some of the workarounds there.

While the example shows a main thread and a helper thread which I created, I'm interested in the general case where I may be running someone else's library that launches a thread.

like image 415
Travis Griggs Avatar asked Apr 05 '18 02:04

Travis Griggs


People also ask

Does Python exit after exception?

exit() function. It inherits from BaseException instead of Exception so that it is not accidentally caught by code that catches Exception . This allows the exception to properly propagate up and cause the interpreter to exit. When it is not handled, the Python interpreter exits; no stack traceback is printed.

What happens if there is exception in thread?

An uncaught exception will cause the thread to exit. When it bubbles to the top of Thread. run() it will be handled by the Thread's UncaughtExceptionHandler. By default, this will merely print the stack trace to the console.

How do you force stop a thread in Python?

Using a hidden function _stop() : In order to kill a thread, we use hidden function _stop() this function is not documented but might disappear in the next version of python.

What happens if a thread throws an exception Python?

To catch the exception in the caller thread we maintain a separate variable exc, which is set to the exception raised when the called thread raises an exception. This exc is finally checked in the join() method and if is not None, then join simply raises the same exception.


2 Answers

Well, you could simply raise an error in your thread and have the main thread handle and report that error. From there you could even terminate the program.

For example on your worker thread:

try:
    self.result = self.do_something_dangerous()
except Exception as e:
    import sys
    self.exc_info = sys.exc_info()

and on main thread:

if self.exc_info:
    raise self.exc_info[1].with_traceback(self.exc_info[2])
return self.result

So to give you a more complete picture, your code might look like this:

import threading

class ExcThread(threading.Thread):
    def excRun(self):
        pass
        #Where your core program will run

    def run(self):
        self.exc = None
        try:
        # Possibly throws an exception
            self.excRun()
        except:
            import sys
            self.exc = sys.exc_info()
            # Save details of the exception thrown 
            # DON'T rethrow,
            # just complete the function such as storing
            # variables or states as needed

    def join(self):
        threading.Thread.join(self)
        if self.exc:
            msg = "Thread '%s' threw an exception: %s" % (self.getName(), self.exc[1])
            new_exc = Exception(msg)
            raise new_exc.with_traceback(self.exc[2])

(I added an extra line to keep track of which thread is causing the error in case you have multiple threads, it's also good practice to name them)

like image 96
Haris Nadeem Avatar answered Oct 02 '22 13:10

Haris Nadeem


My solution ended up being a happy marriage between the solution posted here and the SIGKILL solution piece from above. I added the following killall.py submodule to my package:

import threading
import sys
import traceback
import os
import signal


def sendKillSignal(etype, value, tb):
    print('KILL ALL')
    traceback.print_exception(etype, value, tb)
    os.kill(os.getpid(), signal.SIGKILL)


original_init = threading.Thread.__init__
def patched_init(self, *args, **kwargs):
    print("thread init'ed")
    original_init(self, *args, **kwargs)
    original_run = self.run
    def patched_run(*args, **kw):
        try:
            original_run(*args, **kw)
        except:
            sys.excepthook(*sys.exc_info())
    self.run = patched_run


def install():
    sys.excepthook = sendKillSignal
    threading.Thread.__init__ = patched_init

And then ran the install right away before any other threads are launched (of my own creation or from other imported libraries).

like image 45
Travis Griggs Avatar answered Oct 02 '22 13:10

Travis Griggs