我正在使用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。)
我正在使用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。)
这确实与monkey-patching 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
void PyErr_WriteUnraisable(PyObject *obj)
的函数,并附有非常有趣的注释:/* 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/pythonrun.c中)。它所做的第一个调用是wait_for_thread_shutdown()
- 值得一看,因为我们知道问题与线程有关。这个函数又调用了threading
模块中的_shutdown
可调用对象。好的,现在我们可以回到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
进行一些修补 - 用 try
/except
包装整个 _exitfunc()
主体,并使用 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
的monkey-patching修补程序之一是_get_ident()
方法 - 原始方法映射到thread.get_ident()
,monkey-patching将其替换为green_thread.get_ident()
。显然,这两个调用返回主线程的不同ID。threading
模块,则_get_ident()
调用在创建并添加到_active
的_MainThread
实例时返回一个值,在调用_exitfunc()
时返回另一个值 - 因此在del _active[_get_ident()]
中发生KeyError
。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()
我希望你会发现我的调试故事很有用 :)
你可以使用这个:
import sys
if 'threading' in sys.modules:
del sys.modules['threading']
import gevent
import gevent.socket
import gevent.monkey
gevent.monkey.patch_all()
import sys
:) - casey我曾经遇到过一个与gevent原型脚本类似的问题。
Greenlet回调函数执行得很好,我通过g.join()将其与主线程同步。对于我的问题,我不得不调用gevent.shutdown()来关闭(我认为是)Hub。在我手动关闭事件循环之后,程序可以正常终止而没有出现错误。
nose
运行测试时,我也遇到了使用gevent
的相同问题。奇怪的是,当所有测试都通过时,我不会看到错误,但是当测试失败时我会看到它。我正在使用monkey.patch_all()
。值得注意的是,当我执行monkey.patch_all(thread=False)
时,错误消失了。 - millerdev
import gevent.monkey; gevent.monkey.patch_all()
,然后再导入其他内容。 - cerberos