Ich beobachtete ein ähnliches Problem und beschloss, genau zu sehen, was los ist - lassen Sie mich meine Ergebnisse beschreiben. Ich hoffe, jemand wird es nützlich finden.
Kurzgeschichte
Es hängt in der Tat mit dem Affen-Patching des threading
Moduls zusammen. Tatsächlich kann ich die Ausnahme leicht auslösen, indem ich das Threading-Modul vor dem Affen-Patching von Threads importiere. Die folgenden 2 Zeilen reichen aus:
import threading
import gevent.monkey; gevent.monkey.patch_thread()
Wenn es ausgeführt wird, spuckt es die Nachricht über ignoriert KeyError
:
(env)czajnik@autosan:~$ python test.py
Exception KeyError: KeyError(139924387112272,) in <module 'threading' from '/usr/lib/python2.7/threading.pyc'> ignored
Wenn Sie die Importzeilen austauschen, ist das Problem behoben.
Lange Geschichte
Ich könnte mein Debuggen hier beenden, aber ich entschied, dass es sich lohnt, die genaue Ursache des Problems zu verstehen.
Der erste Schritt bestand darin, den Code zu finden, der die Nachricht über ignorierte Ausnahmen druckt. Es war ein wenig hart für mich (greppen zu finden Exception.*ignored
ergab nichts), aber greppen um CPython Quellcode habe ich fand schließlich eine Funktion namens void PyErr_WriteUnraisable(PyObject *obj)
in Python / error.c , mit einem sehr interessanten Kommentar:
/* Call when an exception has occurred but there is no way for Python
to handle it. Examples: exception in __del__ or during GC. */
Ich habe mich entschlossen, mit ein wenig Hilfe von zu überprüfen, wer es anruft, gdb
um den folgenden C-Level-Stack-Trace zu erhalten:
ubp_av=0x7fffffffe5f8, init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7fffffffe5e8) at libc-start.c:226
Jetzt können wir deutlich sehen, dass die Ausnahme ausgelöst wird, während Py_Finalize ausgeführt wird. Dieser Aufruf ist dafür verantwortlich, den Python-Interpreter herunterzufahren, den zugewiesenen Speicher freizugeben usw. Er wird kurz vor dem Beenden aufgerufen.
Der nächste Schritt war das Betrachten von Py_Finalize()
Code (in Python / pythonrun.c ). Der allererste Aufruf ist wait_for_thread_shutdown()
- einen Blick wert, da wir wissen, dass das Problem mit dem Einfädeln zusammenhängt. Diese Funktion ruft wiederum _shutdown
im threading
Modul aufrufbare auf . Gut, wir können jetzt zum Python-Code zurückkehren.
Beim Betrachten threading.py
habe ich folgende interessante Teile gefunden:
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()
_shutdown = _MainThread()._exitfunc
Es ist klar, dass die Verantwortung für den threading._shutdown()
Aufruf darin besteht, alle Nicht-Daemon-Threads zu verbinden und den Haupt-Thread zu löschen (was auch immer das genau bedeutet). Ich habe beschlossen, threading.py
ein bisschen zu patchen - den ganzen _exitfunc()
Körper mit try
/ zu umwickeln except
und den Stack-Trace mit dem Traceback- Modul zu drucken . Dies ergab die folgende Spur:
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
Jetzt kennen wir den genauen Ort, an dem die Ausnahme ausgelöst wird - inside- Thread.__delete()
Methode.
Der Rest der Geschichte ist nach threading.py
einer Weile des Lesens offensichtlich . Das _active
Wörterbuch ordnet Thread-IDs (wie von zurückgegeben _get_ident()
) Thread
Instanzen für alle erstellten Threads zu. Beim threading
Laden des Moduls wird _MainThread
immer eine Klasseninstanz erstellt und hinzugefügt _active
(auch wenn keine anderen Threads explizit erstellt werden).
Das Problem ist, dass eine der Methoden, die durch gevent
das Affen-Patchen gepatcht werden , ist _get_ident()
- das Original, dem man zugeordnet ist, thread.get_ident()
wird durch das Affen-Patchen ersetzt green_thread.get_ident()
. Offensichtlich geben beide Aufrufe unterschiedliche IDs für den Hauptthread zurück.
Wenn nun das threading
Modul vor dem Affen-Patching geladen wird, gibt _get_ident()
call beim _MainThread
Erstellen und Hinzufügen einer Instanz einen Wert zurück _active
, und es wird jeweils ein anderer Wert _exitfunc()
aufgerufen - daher KeyError
in del _active[_get_ident()]
.
Im Gegenteil, wenn Affen Patching zuvor getan wird threading
geladen wird, ist alles in Ordnung ist - im Zeitpunkt _MainThread
Instanz wird hinzugefügt _active
, _get_ident()
bereits gepatcht, und die gleiche Thread - ID an Bereinigungszeit zurückgegeben. Das ist es!
Um sicherzustellen, dass ich Module in der richtigen Reihenfolge importiere, habe ich meinem Code kurz vor dem Aufruf zum Affen-Patching das folgende Snippet hinzugefügt:
import sys
if 'threading' in sys.modules:
raise Exception('threading module loaded before patching!')
import gevent.monkey; gevent.monkey.patch_thread()
Ich hoffe, Sie finden meine Debugging-Geschichte nützlich :)
import gevent.monkey; gevent.monkey.patch_all()
dann sind, was auch immer Sie sonst importieren möchten