68

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

4

3 に答える 3

217

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

ショートストーリー

それは確かに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()問題がスレッド化に関連していることがわかっているので、最初に呼び出すのは一見の価値があります。この関数は_shutdownthreadingモジュールで呼び出し可能を呼び出します。これで、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()tryexcept

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()ますThreadthreadingモジュールがロードされると、クラスのインスタンスが_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()KeyErrordel _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()

私のデバッグストーリーがお役に立てば幸いです:)

于 2012-09-28T11:21:18.490 に答える
1

gevent プロトタイプ スクリプトでも同様の問題がありました。

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

于 2012-01-20T01:56:27.747 に答える