Py.testで一連のテストを実行しています。彼らは通ります。イッピー!しかし、私はこのメッセージを受け取っています:
Exception KeyError: KeyError(4427427920,) in <module 'threading' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.pyc'> ignored
その原因を追跡するにはどうすればよいですか? (スレッドを直接使用していませんが、geventを使用しています。)
私は同様の問題を観察し、何が起こっているかを正確に確認することにしました-私の調査結果を説明させてください。誰かがそれを役に立つと思うことを願っています。
実際、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
_
インポート行を交換すると、問題はなくなります。
ここでデバッグを停止できましたが、問題の正確な原因を理解する価値があると判断しました。
最初のステップは、無視された例外に関するメッセージを出力するコードを見つけることでした。それを見つけるのは少し困難でした(_Exception.*ignored
_を取得しても何も得られませんでした)が、CPythonのソースコードを片付けると、 に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. */
_
次のCレベルのスタックトレースを取得するために、gdb
の助けを借りて、誰が呼び出しているかを確認することにしました。
_#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()
です。問題はスレッドに関連していることがわかっているので、見てみる価値があります。この関数は、threading
モジュールの__shutdown
_ callableを順番に呼び出します。いいです、今すぐ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でスタックトレースを出力します モジュール。これにより、次のトレースが得られました。
_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
のモンキーパッチによって修正されたメソッドの1つが_get_ident()
-元のメソッドはthread.get_ident()
にマッピングされ、モンキーパッチはgreen_thread.get_ident()
に置き換えられることです。 。明らかに、両方の呼び出しはメインスレッドに対して異なるIDを返します。
これで、モンキーパッチの前にthreading
モジュールがロードされた場合、_get_ident()
呼び出しは__MainThread
_インスタンスが作成され___active
_に追加されたときに1つの値を返し、 time _exitfunc()
が呼び出されます-したがって、del _active[_get_ident()]
のKeyError
。
反対に、threading
がロードされる前にモンキーパッチが行われた場合はすべて問題ありません。__MainThread
_インスタンスが__active
_に追加されるとき、_get_ident()
は既にパッチが適用されており、クリーンアップ時に同じスレッドIDが返されます。それでおしまい!
モジュールを正しい順序でインポートするために、monkey-patching呼び出しの直前に、コードに次のスニペットを追加しました。
_import sys
if 'threading' in sys.modules:
raise Exception('threading module loaded before patching!')
import gevent.monkey; gevent.monkey.patch_thread()
_
私のデバッグストーリーが役に立つことを願っています:)
これを使用できます:
import sys
if 'threading' in sys.modules:
del sys.modules['threading']
import gevent
import gevent.socket
import gevent.monkey
gevent.monkey.patch_all()
Geventプロトタイプスクリプトでも同様の問題が発生しました。
Greenletコールバックは正常に実行されており、g.join()を介してメインスレッドに同期していました。私の問題のために、私はgevent.shutdown()を呼び出してハブ(私が想定しているもの)をシャットダウンする必要がありました。イベントループを手動でシャットダウンした後、プログラムはそのエラーなしで適切に終了します。