私は同様の問題を観察し、何が起こっているのかを正確に確認することにしました-私の発見を説明させてください。誰かがそれが役に立つと思うことを願っています。
ショートストーリー
それは確かにthreading
モジュールのモンキーパッチに関連しています。実際、スレッドにモンキーパッチを適用する前にスレッドモジュールをインポートすることで、例外を簡単にトリガーできます。次の2行で十分です。
import threading
import gevent.monkey; gevent.monkey.patch_thread()
実行されると、無視されたというメッセージが表示されKeyError
ます。
(env)czajnik@autosan:~$ python test.py
Exception KeyError: KeyError(139924387112272,) in <module 'threading' from '/usr/lib/python2.7/threading.pyc'> ignored
インポート行を交換すると、問題はなくなります。
長い話
ここでデバッグを停止することはできますが、問題の正確な原因を理解する価値があると判断しました。
最初のステップは、無視された例外に関するメッセージを出力するコードを見つけることでした。それを見つけるのは少し難しかったですが(greping forException.*ignored
は何も得られませんでした)、CPythonソースコードをgrepingすると、最終的void PyErr_WriteUnraisable(PyObject *obj)
にPython / error.cで呼び出される関数が見つかり、非常に興味深いコメントがあります。
/* Call when an exception has occurred but there is no way for Python
to handle it. Examples: exception in __del__ or during GC. */
gdb
次のCレベルのスタックトレースを取得するために、の助けを借りて、誰がそれを呼び出しているかを確認することにしました。
#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 ()
これで、 Py_Finalizeの実行中に例外がスローされることがはっきりとわかります。この呼び出しは、Pythonインタープリターのシャットダウン、割り当てられたメモリの解放などを担当します。終了する直前に呼び出されます。
次Py_Finalize()
のステップは、コードを確認することでした(Python / pythonrun.cにあります)。wait_for_thread_shutdown()
問題がスレッド化に関連していることがわかっているので、最初に呼び出すのは一見の価値があります。この関数は_shutdown
、threading
モジュールで呼び出し可能を呼び出します。これで、Pythonコードに戻ることができます。
見てみるとthreading.py
、次の興味深い部分が見つかりました。
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
明らかに、threading._shutdown()
呼び出しの責任は、デーモン以外のすべてのスレッドに参加し、メインスレッドを削除することです(正確に意味するものは何でも)。私は少しパッチを当てることにしました-全身を/でthreading.py
ラップし、トレースバックモジュールでスタックトレースを印刷します。これにより、次のトレースが得られました。_exitfunc()
try
except
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
Thread.__delete()
これで、例外がスローされる正確な場所、つまりメソッド内がわかりました。
しばらく読んだ後、物語の残りの部分は明らかthreading.py
です。_active
ディクショナリは、作成されたすべてのスレッドについて、スレッドID(によって返される)をインスタンスにマップし_get_ident()
ますThread
。threading
モジュールがロードされると、クラスのインスタンスが_MainThread
常に作成され、追加されます_active
(他のスレッドが明示的に作成されていない場合でも)。
問題は、gevent
のmonkey-patchingによってパッチが適用されたメソッドの1_get_ident()
つが-元のメソッドがにマップされthread.get_ident()
、monkey-patchingがそれをに置き換えることgreen_thread.get_ident()
です。明らかに、両方の呼び出しはメインスレッドに対して異なるIDを返します。
これで、threading
モンキーパッチの前にモジュールがロードされた場合、インスタンスが作成されてに追加され_get_ident()
たときにcallが1つの値を返し、その時点で別の値が呼び出されます。_MainThread
_active
_exitfunc()
KeyError
del _active[_get_ident()]
逆に、threading
ロードされる前にモンキーパッチが実行された場合は、すべて問題ありません_MainThread
。インスタンスがに追加された時点で_active
、_get_ident()
すでにパッチが適用されており、クリーンアップ時に同じスレッドIDが返されます。それでおしまい!
モジュールを正しい順序でインポートするために、モンキーパッチ呼び出しの直前に次のスニペットをコードに追加しました。
import sys
if 'threading' in sys.modules:
raise Exception('threading module loaded before patching!')
import gevent.monkey; gevent.monkey.patch_thread()
私のデバッグストーリーがお役に立てば幸いです:)