Exception thrown in multiprocessing Pool not detected

后端 未结 9 748
灰色年华
灰色年华 2020-11-29 18:44

It seems that when an exception is raised from a multiprocessing.Pool process, there is no stack trace or any other indication that it has failed. Example:

         


        
相关标签:
9条回答
  • 2020-11-29 19:20

    I have a reasonable solution for the problem, at least for debugging purposes. I do not currently have a solution that will raise the exception back in the main processes. My first thought was to use a decorator, but you can only pickle functions defined at the top level of a module, so that's right out.

    Instead, a simple wrapping class and a Pool subclass that uses this for apply_async (and hence apply). I'll leave map_async as an exercise for the reader.

    import traceback
    from multiprocessing.pool import Pool
    import multiprocessing
    
    # Shortcut to multiprocessing's logger
    def error(msg, *args):
        return multiprocessing.get_logger().error(msg, *args)
    
    class LogExceptions(object):
        def __init__(self, callable):
            self.__callable = callable
    
        def __call__(self, *args, **kwargs):
            try:
                result = self.__callable(*args, **kwargs)
    
            except Exception as e:
                # Here we add some debugging help. If multiprocessing's
                # debugging is on, it will arrange to log the traceback
                error(traceback.format_exc())
                # Re-raise the original exception so the Pool worker can
                # clean up
                raise
    
            # It was fine, give a normal answer
            return result
    
    class LoggingPool(Pool):
        def apply_async(self, func, args=(), kwds={}, callback=None):
            return Pool.apply_async(self, LogExceptions(func), args, kwds, callback)
    
    def go():
        print(1)
        raise Exception()
        print(2)
    
    multiprocessing.log_to_stderr()
    p = LoggingPool(processes=1)
    
    p.apply_async(go)
    p.close()
    p.join()
    

    This gives me:

    1
    [ERROR/PoolWorker-1] Traceback (most recent call last):
      File "mpdebug.py", line 24, in __call__
        result = self.__callable(*args, **kwargs)
      File "mpdebug.py", line 44, in go
        raise Exception()
    Exception
    
    0 讨论(0)
  • 2020-11-29 19:24

    Since you have used apply_sync, I guess the use case is want to do some synchronize tasks. Use callback for handling is another option. Please note this option is available only for python3.2 and above and not available on python2.7.

    from multiprocessing import Pool
    
    def callback(result):
        print('success', result)
    
    def callback_error(result):
        print('error', result)
    
    def go():
        print(1)
        raise Exception()
        print(2)
    
    p = Pool()
    p.apply_async(go, callback=callback, error_callback=callback_error)
    
    # You can do another things
    
    p.close()
    p.join()
    
    0 讨论(0)
  • 2020-11-29 19:24

    Since there are already decent answers for multiprocessing.Pool available, I will provide a solution using a different approach for completeness.

    For python >= 3.2 the following solution seems to be the simplest:

    from concurrent.futures import ProcessPoolExecutor, wait
    
    def go():
        print(1)
        raise Exception()
        print(2)
    
    
    futures = []
    with ProcessPoolExecutor() as p:
        for i in range(10):
            futures.append(p.submit(go))
    
    results = [f.result() for f in futures]
    

    Advantages:

    • very little code
    • raises an exception in the main process
    • provides a stack trace
    • no external dependencies

    For more info about the API please check out this

    Additionally, if you are submitting a large number of tasks and you would like your main process to fail as soon as one of your tasks fail, you can use the following snippet:

    from concurrent.futures import ProcessPoolExecutor, wait, FIRST_EXCEPTION, as_completed
    import time
    
    
    def go():
        print(1)
        time.sleep(0.3)
        raise Exception()
        print(2)
    
    
    futures = []
    with ProcessPoolExecutor(1) as p:
        for i in range(10):
            futures.append(p.submit(go))
    
        for f in as_completed(futures):
            if f.exception() is not None:
                for f in futures:
                    f.cancel()
                break
    
    [f.result() for f in futures]
    

    All of the other answers fail only once all tasks have been executed.

    0 讨论(0)
  • 2020-11-29 19:24

    I'd try using pdb:

    import pdb
    import sys
    def handler(type, value, tb):
      pdb.pm()
    sys.excepthook = handler
    
    0 讨论(0)
  • 2020-11-29 19:29

    I've had success logging exceptions with this decorator:

    import traceback, functools, multiprocessing
    
    def trace_unhandled_exceptions(func):
        @functools.wraps(func)
        def wrapped_func(*args, **kwargs):
            try:
                func(*args, **kwargs)
            except:
                print 'Exception in '+func.__name__
                traceback.print_exc()
        return wrapped_func
    

    with the code in the question, it's

    @trace_unhandled_exceptions
    def go():
        print(1)
        raise Exception()
        print(2)
    
    p = multiprocessing.Pool(1)
    
    p.apply_async(go)
    p.close()
    p.join()
    

    Simply decorate the function you pass to your process pool. The key to this working is @functools.wraps(func) otherwise multiprocessing throws a PicklingError.

    code above gives

    1
    Exception in go
    Traceback (most recent call last):
      File "<stdin>", line 5, in wrapped_func
      File "<stdin>", line 4, in go
    Exception
    
    0 讨论(0)
  • 2020-11-29 19:29

    I created a module RemoteException.py that shows the full traceback of a exception in a process. Python2. Download it and add this to your code:

    import RemoteException
    
    @RemoteException.showError
    def go():
        raise Exception('Error!')
    
    if __name__ == '__main__':
        import multiprocessing
        p = multiprocessing.Pool(processes = 1)
        r = p.apply(go) # full traceback is shown here
    
    0 讨论(0)
提交回复
热议问题