Catch an exception raised by a child process in a parent process - python

I'm trying to catch an Exception raised by a child process, and have been running into some issues. The gist of my code is:
class CustomException(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
def update(partition):
if os.getpid() % 2 == 0:
raise CustomException('PID was divisible by 2!')
else:
# Do something fancy
if __name__ == '__main__':
try:
some_response = get_response_from_another_method()
partition_size = 100
p = Pool(config.NUMBER_OF_PROCESSES)
for i in range(0, NUMBER_OF_PROCESSES):
partition = get_partition(some_response, partition_size)
x = p.apply_async(update, args=(partition,))
x.get()
p.close()
p.join()
except CustomException as e:
log.error('There was an error')
if email_notifier.send_notification(e.msg):
log.debug('Email notification sent')
else:
log.error('An error occurred while sending an email.')
When I run this, I am seeing:
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 532, in __bootstrap_inner
self.run()
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 484, in run
self.__target(*self.__args, **self.__kwargs)
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/multiprocessing/pool.py", line 259, in _handle_results
task = get()
TypeError: ('__init__() takes exactly 2 arguments (1 given)', <class 'CustomException'>, ())
Is there some facility to do this? Thanks!!

In short, this is something of a quirk in Python 2, and a related issue is referenced in this bug report. It has to do with how exceptions are pickled. The simplest solution is perhaps to alter CustomException so that it calls its parent class initializer. Alternatively, if you're able, I'd suggest moving to Python 3.
For example, this code works fine in both Python 2 and Python 3:
from multiprocessing import Pool
class CustomException(Exception):
pass
def foo():
raise CustomException('PID was divisible by 2!')
pool = Pool()
result = pool.apply_async(foo, [])
But if we alter CustomException so that it has a required argument:
class CustomException(Exception):
def __init__(self, required):
self.required = required
The above example results in a TypeError being raised under Python 2. It works under Python 3.
The problem is that CustomException inherits Exception's __reduce__ method, which tells Python how to pickle an instance. The inherited __reduce__ knows nothing about CustomException's call signature, so unpickling isn't done correctly.
A quick fix is to simply call the parent class's __init__:
class CustomException(Exception):
def __init__(self, msg):
super(Exception, self).__init__()
self.msg = msg
But since you really aren't doing anything special with the message, why not just define:
class CustomException(Exception):
pass

Related

Python unittest and multithreading

I am using python's unittest and would like to write a test that starts a few threads and waits for them to finish. The threads execute a function that has some unittest assertions. If any of the assertions fail, I wish the test to, well, fail. This does not seem to be the case.
EDIT: Minimal runnable example (python3)
import unittest
import threading
class MyTests(unittest.TestCase):
def test_sample(self):
t = threading.Thread(target=lambda: self.fail())
t.start()
t.join()
if __name__ == '__main__':
unittest.main()
and the output is:
sh-4.3$ python main.py -v
test_sample (__main__.MyTests) ... Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib64/python2.7/threading.py", line 813, in __bootstrap_inner
self.run()
File "/usr/lib64/python2.7/threading.py", line 766, in run
self.__target(*self.__args, **self.__kwargs)
File "main.py", line 7, in <lambda>
t = threading.Thread(target=lambda: self.fail())
File "/usr/lib64/python2.7/unittest/case.py", line 450, in fail
raise self.failureException(msg)
AssertionError: None
ok
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
use a concurrent.futures.ThreadPoolExecutor or https://docs.python.org/3/library/threading.html#threading.excepthook to collect exceptions thrown in threads
import unittest
import threading
from concurrent import futures
class catch_threading_exception:
"""
https://docs.python.org/3/library/test.html#test.support.catch_threading_exception
Context manager catching threading.Thread exception using
threading.excepthook.
Attributes set when an exception is catched:
* exc_type
* exc_value
* exc_traceback
* thread
See threading.excepthook() documentation for these attributes.
These attributes are deleted at the context manager exit.
Usage:
with support.catch_threading_exception() as cm:
# code spawning a thread which raises an exception
...
# check the thread exception, use cm attributes:
# exc_type, exc_value, exc_traceback, thread
...
# exc_type, exc_value, exc_traceback, thread attributes of cm no longer
# exists at this point
# (to avoid reference cycles)
"""
def __init__(self):
self.exc_type = None
self.exc_value = None
self.exc_traceback = None
self.thread = None
self._old_hook = None
def _hook(self, args):
self.exc_type = args.exc_type
self.exc_value = args.exc_value
self.exc_traceback = args.exc_traceback
self.thread = args.thread
def __enter__(self):
self._old_hook = threading.excepthook
threading.excepthook = self._hook
return self
def __exit__(self, *exc_info):
threading.excepthook = self._old_hook
del self.exc_type
del self.exc_value
del self.exc_traceback
del self.thread
class MyTests(unittest.TestCase):
def test_tpe(self):
with futures.ThreadPoolExecutor() as pool:
pool.submit(self.fail).result()
def test_t_excepthook(self):
with catch_threading_exception() as cm:
t = threading.Thread(target=self.fail)
t.start()
t.join()
if cm.exc_value is not None:
raise cm.exc_value
if __name__ == '__main__':
unittest.main()
on pytest these are collected for you: https://docs.pytest.org/en/latest/how-to/failures.html?highlight=unraisable#warning-about-unraisable-exceptions-and-unhandled-thread-exceptions
Your test isn't failing for the same reason that this code will print "no exception"
import threading
def raise_err():
raise Exception()
try:
t = threading.Thread(target=raise_err)
t.start()
t.join()
print('no exception')
except:
print('caught exception')
When unittest runs your test function, it determines pass/fail by seeing if the code execution results in some exception. If the exception occurs inside the thread, there still is no exception in the main thread.
You could do something like this if you think you HAVE to get a pass/fail result from running something in a thread. But this is really not how unittest is designed to work, and there's probably a much easier way to do what you're trying to accomplish.
import threading
import unittest
def raise_err():
raise Exception()
def no_err():
return
class Runner():
def __init__(self):
self.threads = {}
self.thread_results = {}
def add(self, target, name):
self.threads[name] = threading.Thread(target = self.run, args = [target, name])
self.threads[name].start()
def run(self, target, name):
self.thread_results[name] = 'fail'
target()
self.thread_results[name] = 'pass'
def check_result(self, name):
self.threads[name].join()
assert(self.thread_results[name] == 'pass')
runner = Runner()
class MyTests(unittest.TestCase):
#classmethod
def setUpClass(cls):
runner.add(raise_err, 'test_raise_err')
runner.add(no_err, 'test_no_err')
def test_raise_err(self):
runner.check_result('test_raise_err')
def test_no_err(self):
runner.check_result('test_no_err')
if __name__ == '__main__':
unittest.main()
Python unittest assertions are communicated by exceptions, so you have to ensure that the exceptions end up in the main thread. So for a thread that means you have to run .join(), as that will throw the exception from the thread over into the main thread:
t = threading.Thread(target=lambda: self.assertTrue(False))
t.start()
t.join()
Also make sure that you don't have any try/except blocks that might eat up the exception before the unittest can register them.
Edit: self.fail() is indeed not communicated when called from a thread, even if .join() is present. Not sure what's up with that.

Python inheritance, super call order and AttributeError

I am confused about how inherited class attributes are initialized. I get an AttributeError on a child attribute that I thought was well defined.
I noticed that swapping the order of the super call to the parent after the definition does not yield an error but I do not understand why.
I have not been able to reproduce that error without using processes (I can post that test code too but I think this is lengthy enough), it seems then that the order in which the super is called does not matter.
Edit : It is probably because of running a process before counter is even defined that yiels the exception, but the problem does not happen if I use Thread instead of Process.
I might be missing something about how initializers work or doing something wrong.
Why do I get an Attribute error?
Why swapping the two line solves (or masks) the problem?
Why does this problem does not happen using threads instead of processes?
I am using the code below.
Thanks for your help.
Here is the code that reproduces the issue. I tried my best to make it stand-alone.
from multiprocessing import Process
from multiprocessing import Queue
class Parent(object):
def __init__(self):
self._stuff_queue = Queue()
self._process = Process(target=self._do_something)
self._process.start()
def _do_something(self):
while True:
stuff = self._stuff_queue.get()
if stuff is not None:
self.something(stuff)
else:
break
def feed(self, stuff):
self._stuff_queue.put(stuff)
def something(self):
raise NotImplementedError("Implement this something !")
class Child(Parent):
def __init__(self):
# --- Swapping those two lines avoids getting the AttributeError --- #
super(Child, self).__init__() # Same thing using Parent.__init__(self)
self.counter = 0
def something(self, stuff):
self.counter += 1
print "Got stuff"
c = Child()
c.feed("Hi SO!")
c.feed(None) # Just to stop
Here is the message I get :
File "/home/process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "test_process.py", line 14, in _do_something
self.something(stuff)
File "test_process.py", line 30, in something
self.counter += 1
AttributeError: 'Child' object has no attribute 'counter'

Custom exceptions are not raised properly when used in Multiprocessing Pool

Question
I am observing behavior in Python 3.3.4 that I would like help understanding: Why are my exceptions properly raised when a function is executed normally, but not when the function is executed in a pool of workers?
Code
import multiprocessing
class AllModuleExceptions(Exception):
"""Base class for library exceptions"""
pass
class ModuleException_1(AllModuleExceptions):
def __init__(self, message1):
super(ModuleException_1, self).__init__()
self.e_string = "Message: {}".format(message1)
return
class ModuleException_2(AllModuleExceptions):
def __init__(self, message2):
super(ModuleException_2, self).__init__()
self.e_string = "Message: {}".format(message2)
return
def func_that_raises_exception(arg1, arg2):
result = arg1 + arg2
raise ModuleException_1("Something bad happened")
def func(arg1, arg2):
try:
result = func_that_raises_exception(arg1, arg2)
except ModuleException_1:
raise ModuleException_2("We need to halt main") from None
return result
pool = multiprocessing.Pool(2)
results = pool.starmap(func, [(1,2), (3,4)])
pool.close()
pool.join()
print(results)
This code produces this error:
Exception in thread Thread-3:
Traceback (most recent call last):
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/threading.py", line 921, in _bootstrap_inner
self.run()
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/threading.py", line 869, in run
self._target(*self._args, **self._kwargs)
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/multiprocessing/pool.py", line 420, in _handle_results
task = get()
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/multiprocessing/connection.py", line 251, in recv
return ForkingPickler.loads(buf.getbuffer())
TypeError: __init__() missing 1 required positional argument: 'message2'
Conversely, if I simply call the function, it seems to handle the exception properly:
print(func(1, 2))
Produces:
Traceback (most recent call last):
File "exceptions.py", line 40, in
print(func(1, 2))
File "exceptions.py", line 30, in func
raise ModuleException_2("We need to halt main") from None
__main__.ModuleException_2
Why does ModuleException_2 behave differently when it is run in a process pool?
The issue is that your exception classes have non-optional arguments in their __init__ methods, but that when you call the superclass __init__ method you don't pass those arguments along. This causes a new exception when your exception instances are unpickled by the multiprocessing code.
This has been a long-standing issue with Python exceptions, and you can read quite a bit of the history of the issue in this bug report (in which a part of the underlying issue with pickling exceptions was fixed, but not the part you're hitting).
To summarize the issue: Python's base Exception class puts all the arguments it's __init__ method receives into an attribute named args. Those arguments are put into the pickle data and when the stream is unpickled, they're passed to the __init__ method of the newly created object. If the number of arguments received by Exception.__init__ is not the same as a child class expects, you'll get at error at unpickling time.
A workaround for the issue is to pass all the arguments you custom exception classes require in their __init__ methods to the superclass __init__:
class ModuleException_2(AllModuleExceptions):
def __init__(self, message2):
super(ModuleException_2, self).__init__(message2) # the change is here!
self.e_string = "Message: {}".format(message2)
Another possible fix would be to not call the superclass __init__ method at all (this is what the fix in the bug linked above allows), but since that's usually poor behavior for a subclass, I can't really recommend it.
Your ModuleException_2.__init__ fails while beeing unpickled.
I was able to fix the problem by changing the signature to
class ModuleException_2(AllModuleExceptions):
def __init__(self, message2=None):
super(ModuleException_2, self).__init__()
self.e_string = "Message: {}".format(message2)
return
but better have a look at Pickling Class Instances to ensure a clean implementation.

Post mortem debugging of the multi-thread scripts

I am trying to debugging multi-thread script. Once the exception is
raised I want to:
report it to monitoring system (just print in following example)
stop whole script (including all other threads)
call post mortem debugger prompt in a perspective raised exception
I prepare pretty complicated example to show how I tried to solve it:
#!/usr/bin/env python
import threading
import inspect
import traceback
import sys
import os
import time
def POST_PORTEM_DEBUGGER(type, value, tb):
traceback.print_exception(type, value, tb)
print
if hasattr(sys, 'ps1') or not sys.stderr.isatty():
import rpdb
rpdb.pdb.pm()
else:
import pdb
pdb.pm()
sys.excepthook = POST_PORTEM_DEBUGGER
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.exception = None
self.info = None
self.the_calling_script_name = os.path.abspath(inspect.currentframe().f_back.f_code.co_filename)
def main(self):
"Virtual method to be implemented by inherited worker"
return self
def run(self):
try:
self.main()
except Exception as exception:
self.exception = exception
self.info = traceback.extract_tb(sys.exc_info()[2])[-1]
# because of bug http://bugs.python.org/issue1230540
# I cannot use just "raise" under threading.Thread
sys.excepthook(*sys.exc_info())
def __del__(self):
print 'MyThread via {} catch "{}: {}" in {}() from {}:{}: {}'.format(self.the_calling_script_name, type(self.exception).__name__, str(self.exception), self.info[2], os.path.basename(self.info[0]), self.info[1], self.info[3])
class Worker(MyThread):
def __init__(self):
super(Worker, self).__init__()
def main(self):
""" worker job """
counter = 0
while True:
counter += 1
print self
time.sleep(1.0)
if counter == 3:
pass # print 1/0
def main():
Worker().start()
counter = 1
while True:
counter += 1
time.sleep(1.0)
if counter == 3:
pass # print 1/0
if __name__ == '__main__':
main()
The trick with
sys.excepthook = POST_PORTEM_DEBUGGER
works perfectly if no threads are involved. I found that in case of
multi-thread script I can use rpdb for debuggig by calling:
import rpdb; rpdb.set_trace()
It works perfectly for defined breakpoint but I want to debug
multi-thread script post mortem (after the uncatched exception is
raised). When I try to use rpdb in the POST_PORTEM_DEBUGGER function
with multi-thread application I get following:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
self.run()
File "./demo.py", line 49, in run
sys.excepthook(*sys.exc_info())
File "./demo.py", line 22, in POST_PORTEM_DEBUGGER
pdb.pm()
File "/usr/lib/python2.7/pdb.py", line 1270, in pm
post_mortem(sys.last_traceback)
AttributeError: 'module' object has no attribute 'last_traceback'
I looks like the
sys.excepthook(*sys.exc_info())
did not set up all what the raise command does.
I want the same behavior if the exception is raised in main() even
under started thread.
(I haven't tested my answer, but it seems to me that...)
The call to pdb.pm (pm="post mortem") fails simply because there had been no "mortem" prior to it. I.e. the program is still running.
Looking at the pdb source code, you find the implementation of pdb.pm:
def pm():
post_mortem(sys.last_traceback)
which makes me guess that what you actually want to do is call pdb.post_mortem() with no args. Looks like the default behavior does exactly what you need.
Some more source code (notice the t = sys.exc_info()[2] line):
def post_mortem(t=None):
# handling the default
if t is None:
# sys.exc_info() returns (type, value, traceback) if an exception is
# being handled, otherwise it returns None
t = sys.exc_info()[2]
if t is None:
raise ValueError("A valid traceback must be passed if no "
"exception is being handled")
p = Pdb()
p.reset()
p.interaction(None, t)
Building on #shx2's above, I now use the following pattern in the context of multithreading.
import sys, pdb
try:
... # logic that may fail
except exception as exc:
pdb.post_mortem(exc.__traceback__)
Here is a more verbose alternative:
import sys, pdb
try:
... # logic that may fail
except exception as exc:
if hasattr(sys, "last_traceback"):
pdb.pm()
else:
pdb.post_mortem(exc.__traceback__)
This can help:
import sys
from IPython.core import ultratb
sys.excepthook = ultratb.FormattedTB(mode='Verbose', color_scheme='Linux',
call_pdb=True, ostream=sys.__stdout__)

Python magic method to alter the way raising an object is handled

I was wondering, is there a simple magic method in python that allows customization of the behaviour of an exception-derived object when it is raised? I'm looking for something like __raise__ if that exists. If no such magic methods exist, is there any way I could do something like the following (it's just an example to prove my point):
class SpecialException(Exception):
def __raise__(self):
print('Error!')
raise SpecialException() #this is the part of the code that must stay
Is it possible?
I don't know about such magic method but even if it existed it is just some piece of code that gets executed before actually raising the exception object. Assuming that its a good practice to raise exception objects that are instantiated in-place you can put such code into the __init__ of the exception. Another workaround: instead of raising your exception directly you call an error handling method/function that executes special code and then finally raises an exception.
import time
from functools import wraps
def capture_exception(callback=None, *c_args, **c_kwargs):
"""捕获到异常后执行回调函数"""
assert callable(callback), "callback 必须是可执行对象"
def _out(func):
#wraps(func)
def _inner(*args, **kwargs):
try:
res = func(*args, **kwargs)
return res
except Exception as e:
callback(*c_args, **c_kwargs)
raise e
return _inner
return _out
def send_warning():
print("warning message..............")
class A(object):
#capture_exception(callback=send_warning)
def run(self):
print('run')
raise SystemError("测试异常捕获回调功能")
time.sleep(0.2)
if __name__ == '__main__':
a = A()
a.run()

Categories

Resources