web-dev-qa-db-fra.com

Exception levée dans le pool de multitraitement non détecté

Il semble que lorsqu'une exception est générée à partir d'un processus multitraitement.Pool, il n'y a aucune trace de pile ou autre indication de son échec. Exemple: 

from multiprocessing import Pool 

def go():
    print(1)
    raise Exception()
    print(2)

p = Pool()
p.apply_async(go)
p.close()
p.join()

affiche 1 et s’arrête silencieusement. Fait intéressant, la levée d’une exception BaseException fonctionne. Est-il possible de rendre le comportement pour toutes les exceptions identique à BaseException?

58
Rob Lourens

J'ai une solution raisonnable pour le problème, au moins à des fins de débogage. Je n'ai pas actuellement de solution qui soulève l'exception dans les processus principaux. Ma première pensée a été d’utiliser un décorateur, mais vous ne pouvez décaper que les fonctions définies au niveau supérieur d’un module , c’est tout.

Au lieu de cela, une classe d'habillage simple et une sous-classe de pool qui l'utilise pour apply_async (et donc apply). Je laisserai map_async comme exercice pour le lecteur.

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()

Cela me donne:

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
25
Rupert Nash

Peut-être qu'il me manque quelque chose, mais n'est-ce pas ce que la méthode get de l'objet Result renvoie? Voir Pools de processus .

classe multiprocessing.pool.AsyncResult

La classe du résultat renvoyé par Pool.apply_async () et Pool.map_async (). Get ([timeout])
Renvoie le résultat quand il arrive. Si timeout n'est pas None et que le résultat n'arrive pas dans timeout secondes, puis le multitraitement.TimeoutError est déclenché. Si la télécommande call a déclenché une exception alors cette exception sera sur-levée par get ().

Donc, en modifiant légèrement votre exemple, on peut faire

from multiprocessing import Pool

def go():
    print(1)
    raise Exception("foobar")
    print(2)

p = Pool()
x = p.apply_async(go)
x.get()
p.close()
p.join()

Ce qui donne comme résultat

1
Traceback (most recent call last):
  File "rob.py", line 10, in <module>
    x.get()
  File "/usr/lib/python2.6/multiprocessing/pool.py", line 422, in get
    raise self._value
Exception: foobar

Ce n'est pas complètement satisfaisant, car cela n'imprime pas le suivi, mais c'est mieux que rien.

MISE À JOUR: Ce bogue a été corrigé dans Python 3.4, gracieuseté de Richard Oudkerk. Voir la méthode issue get de multiprocessing.pool.Async doit renvoyer une trace complète .

47
Faheem Mitha

La solution avec le plus de votes au moment de la rédaction a un problème:

from multiprocessing import Pool

def go():
    print(1)
    raise Exception("foobar")
    print(2)

p = Pool()
x = p.apply_async(go)
x.get()  ## waiting here for go() to complete...
p.close()
p.join()

Comme @dfrankow l'a noté, il attendra x.get(), ce qui gâcherait le point d'exécuter une tâche de manière asynchrone. Donc, pour une meilleure efficacité (en particulier si votre fonction de travail go prend beaucoup de temps), je la changerais en:

from multiprocessing import Pool

def go(x):
    print(1)
    # task_that_takes_a_long_time()
    raise Exception("Can't go anywhere.")
    print(2)
    return x**2

p = Pool()
results = []
for x in range(1000):
    results.append( p.apply_async(go, [x]) )

p.close()

for r in results:
     r.get()

Avantages : la fonction de travail est exécutée de manière asynchrone. Ainsi, par exemple, si vous exécutez plusieurs tâches sur plusieurs cœurs, ce sera beaucoup plus efficace que la solution d'origine.

Inconvénients : s'il existe une exception dans la fonction worker, celle-ci ne sera déclenchée qu'après le pool a terminé toutes les tâches. Cela peut ou peut ne pas être le comportement souhaitable. EDITE selon le commentaire de @ colinfang, qui a corrigé le problème. 

18
gozzilli

J'ai eu du succès en enregistrant des exceptions avec ce décorateur:

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

avec le code dans la question, c'est

@trace_unhandled_exceptions
def go():
    print(1)
    raise Exception()
    print(2)

p = multiprocessing.Pool(1)

p.apply_async(go)
p.close()
p.join()

Décorez simplement la fonction que vous transmettez à votre pool de processus. La clé de ce fonctionnement est @functools.wraps(func) sinon le multitraitement renvoie une PicklingError.

le code ci-dessus donne

1
Exception in go
Traceback (most recent call last):
  File "<stdin>", line 5, in wrapped_func
  File "<stdin>", line 4, in go
Exception
9
Mark Foreman
import logging
from multiprocessing import Pool

def proc_wrapper(func, *args, **kwargs):
    """Print exception because multiprocessing lib doesn't return them right."""
    try:
        return func(*args, **kwargs)
    except Exception as e:
        logging.exception(e)
        raise

def go(x):
    print x
    raise Exception("foobar")

p = Pool()
p.apply_async(proc_wrapper, (go, 5))
p.join()
p.close()
2
erezarnon

J'ai créé un module RemoteException.py qui montre le suivi complet d'une exception dans un processus. Python2. Téléchargez-le et ajoutez-le à votre code:

import RemoteException

@RemoteException.showError
def go():
    raise Exception('Error!')

if __== '__main__':
    import multiprocessing
    p = multiprocessing.Pool(processes = 1)
    r = p.apply(go) # full traceback is shown here
1
User

Depuis que vous avez utilisé apply_sync, je suppose que le cas d'utilisation consiste à effectuer certaines tâches de synchronisation. Utiliser le rappel pour la gestion est une autre option. Veuillez noter que cette option est disponible uniquement pour python 3.2 et supérieur et non disponible pour 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
Asoul

J'essaierais d'utiliser pdb:

import pdb
import sys
def handler(type, value, tb):
  pdb.pm()
sys.excepthook = handler
0
Claris

Puisqu'il existe déjà des réponses correctes pour multiprocessing.Pool, je proposerai une solution en utilisant une approche différente pour la complétude.

Pour python >= 3.2, la solution suivante semble être la plus simple:

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]

Avantages:

  • très peu de code
  • déclenche une exception dans le processus principal
  • fournit une trace de pile
  • pas de dépendances externes

Pour plus d'informations sur l'API, veuillez consulter: https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor

De plus, si vous soumettez un grand nombre de tâches et souhaitez que votre processus principal échoue dès qu'une de vos tâches échoue, vous pouvez utiliser l'extrait de code suivant:

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]

Toutes les autres réponses n'échouent que lorsque toutes les tâches ont été exécutées.

0
Vlad