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.

链接地址: http://www.djcxy.com/p/56454.html

上一篇: 尝试运行代码时出现Python EOF错误

下一篇: 在多处理池中使用时,自定义异常不会正确引发