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