web-dev-qa-db-ja.com

py.testの実行に成功した後のモジュール 'threading'のKeyError

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を使用しています。)

68
kkurian

私は同様の問題を観察し、何が起こっているかを正確に確認することにしました-私の調査結果を説明させてください。誰かがそれを役に立つと思うことを願っています。

ショートストーリー

実際、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()
_

私のデバッグストーリーが役に立つことを願っています:)

213
Code Painters

これを使用できます:

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

Geventプロトタイプスクリプトでも同様の問題が発生しました。

Greenletコールバックは正常に実行されており、g.join()を介してメインスレッドに同期していました。私の問題のために、私はgevent.shutdown()を呼び出してハブ(私が想定しているもの)をシャットダウンする必要がありました。イベントループを手動でシャットダウンした後、プログラムはそのエラーなしで適切に終了します。

1
Kris