68 votes

KeyError dans le module 'threading' après une exécution réussie de py.test

J'exécute un ensemble de tests avec py.test. Ils passent. Youpi ! Mais je reçois ce message :

Exception KeyError: KeyError(4427427920,) in <module 'threading' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.pyc'> ignored

Comment dois-je m'y prendre pour retrouver la source de tout ça ? (Je n'utilise pas directement le threading, mais j'utilise gevent).

214voto

Code Painters Points 3646

J'ai observé un problème similaire et j'ai décidé de voir ce qui se passe exactement - laissez-moi vous décrire mes conclusions. J'espère que quelqu'un trouvera cela utile.

Histoire courte

Il est en effet lié à monkey-Parcheando le threading module. En fait, je peux facilement déclencher l'exception en important le module threading avant les threads de monkey-Parcheando. Les 2 lignes suivantes sont suffisantes :

import threading
import gevent.monkey; gevent.monkey.patch_thread()

Lorsqu'il est exécuté, il renvoie le message sur les éléments ignorés. KeyError :

(env)czajnik@autosan:~$ python test.py 
Exception KeyError: KeyError(139924387112272,) in <module 'threading' from '/usr/lib/python2.7/threading.pyc'> ignored

Si vous intervertissez les lignes d'importation, le problème disparaît.

Longue histoire

Je pourrais arrêter mon débogage ici, mais j'ai décidé que cela valait la peine de comprendre la cause exacte du problème.

La première étape a été de trouver le code qui imprime le message sur l'exception ignorée. J'ai eu un peu de mal à le trouver (en recherchant Exception.*ignored n'a rien donné), mais en parcourant le code source de CPython, j'ai fini par trouver une fonction appelée void PyErr_WriteUnraisable(PyObject *obj) en Python/error.c avec un commentaire très intéressant :

/* Call when an exception has occurred but there is no way for Python
   to handle it.  Examples: exception in __del__ or during GC. */

J'ai décidé de vérifier qui l'appelle, avec un peu d'aide de gdb pour obtenir la trace de la pile au niveau C suivante :

#0  0x0000000000542c40 in PyErr_WriteUnraisable ()
#1  0x00000000004af2d3 in Py_Finalize ()
#2  0x00000000004aa72e in Py_Main ()
#3  0x00007ffff68e576d in __libc_start_main (main=0x41b980 <main>, argc=2,
    ubp_av=0x7fffffffe5f8, init=<optimized out>, fini=<optimized out>, 
    rtld_fini=<optimized out>, stack_end=0x7fffffffe5e8) at libc-start.c:226
#4  0x000000000041b9b1 in _start ()

Maintenant, nous pouvons clairement voir que l'exception est levée alors que Py_Finalize executes - cet appel est responsable de l'arrêt de l'interpréteur Python, de la libération de la mémoire allouée, etc. Il est appelé juste avant de quitter.

L'étape suivante consistait à examiner Py_Finalize() (il se trouve dans Python/pythonrun.c ). Le tout premier appel qu'il fait est wait_for_thread_shutdown() - qui vaut la peine d'être examinée, car nous savons que le problème est lié au threading. Cette fonction appelle à son tour _shutdown appelable dans le threading module. Bien, nous pouvons retourner au code python maintenant.

Regarder threading.py J'ai trouvé les parties suivantes intéressantes :

class _MainThread(Thread):

    def _exitfunc(self):
        self._Thread__stop()
        t = _pickSomeNonDaemonThread()
        if t:
            if __debug__:
                self._note("%s: waiting for other threads", self)
        while t:
            t.join()
            t = _pickSomeNonDaemonThread()
        if __debug__:
            self._note("%s: exiting", self)
        self._Thread__delete()

# Create the main thread object,
# and make it available for the interpreter
# (Py_Main) as threading._shutdown.

_shutdown = _MainThread()._exitfunc

Il est clair que la responsabilité de threading._shutdown() L'appel est de joindre tous les threads non-daemon et de supprimer le thread principal (quoi que cela signifie exactement). J'ai décidé de corriger threading.py un peu - envelopper le tout _exitfunc() corps avec try / except et imprimer la trace de la pile avec Traceback module. Cela a donné la trace suivante :

Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 785, in _exitfunc
    self._Thread__delete()
  File "/usr/lib/python2.7/threading.py", line 639, in __delete
    del _active[_get_ident()]
KeyError: 26805584

Maintenant nous savons l'endroit exact où l'exception est levée - à l'intérieur de Thread.__delete() méthode.

Le reste de l'histoire est évident après avoir lu threading.py pendant un certain temps. Le site _active Le dictionnaire fait correspondre les ID des threads (tels que retournés par _get_ident() ) à Thread pour tous les fils créés. Lorsque threading est chargé, une instance du module _MainThread est toujours créée et ajoutée à la classe _active (même si aucun autre fil n'est explicitement créé).

Le problème est que l'une des méthodes patchées par gevent Le singeParcheando de 's est _get_ident() - L'original correspond à thread.get_ident() , monkey-Parcheando le remplace par green_thread.get_ident() . Évidemment, les deux appels renvoient des ID différents pour le thread principal.

Maintenant, si threading est chargé avant monkey-Parcheando, _get_ident() renvoie une valeur lorsque _MainThread est créée et ajoutée à _active et une autre valeur à l'époque _exitfunc() est appelé - d'où KeyError en del _active[_get_ident()] .

Au contraire, si monkey-Parcheando est fait avant threading est chargé, tout va bien - au moment où _MainThread est ajoutée à _active , _get_ident() est déjà corrigé, et le même ID de thread est renvoyé au moment du nettoyage. Voilà, c'est fait !

Pour m'assurer que j'importe les modules dans le bon ordre, j'ai ajouté le snippet suivant à mon code, juste avant l'appel à monkey-Parcheando :

import sys
if 'threading' in sys.modules:
        raise Exception('threading module loaded before patching!')
import gevent.monkey; gevent.monkey.patch_thread()

J'espère que mon histoire de débogage vous sera utile :)

19voto

user2719944 Points 71

Vous pourriez utiliser ceci :

import sys
if 'threading' in sys.modules:
    del sys.modules['threading']
import gevent
import gevent.socket
import gevent.monkey
gevent.monkey.patch_all()

1voto

Kris Points 11

J'ai eu un problème similaire avec un prototype gevent script.

Le callback Greenlet s'exécutait correctement et je me synchronisais avec le thread principal via g.join(). Pour mon problème, j'ai dû appeler gevent.shutdown() pour arrêter (ce que je suppose être) le Hub. Après avoir fermé manuellement la boucle d'événement, le programme se termine correctement sans cette erreur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X