运行成功的py.test后,模块“线程”中的KeyError
我正在用py.test进行一系列testing。 他们通过。 开心辞典! 但是我收到这个消息:
Exception KeyError: KeyError(4427427920,) in <module 'threading' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.pyc'> ignored
我应该如何追踪这个来源呢? (我不是直接使用线程,而是使用gevent。)
我观察到类似的问题,并决定看看究竟发生了什么 – 让我来描述一下我的发现。 我希望有人会觉得有用。
短篇故事
这确实与猴子修补threading
模块有关。 事实上,我可以通过在monkey-patching线程之前导入线程模块来轻松触发exception。 以下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
如果换了import线,问题就没有了。
很长的故事
我可以在这里停止我的debugging,但我决定了解这个问题的确切原因是值得的。
第一步是find打印关于被忽略的exception的消息的代码。 对于我来说find它有点困难(grep for Exception.*ignored
任何东西),但是仔细考虑CPython源代码我最终在Python / error.c中find了一个名为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执行时引发的exception – 这个调用负责closuresPython解释器,释放分配的内存等等。它就在退出之前调用。
下一步是看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()
调用的责任是join所有非守护进程线程并删除主线程(不pipe是什么意思)。 我决定修补threading.py
有点 – 用try
/ except
包装整个_exitfunc()
体,并用跟踪模块打印堆栈跟踪。 这给了以下的痕迹:
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
现在我们知道引发exception的确切位置 – 在Thread.__delete()
方法内部。
在阅读threading.py
一段时间之后,其余的部分是显而易见的。 对于创build的所有线程, _active
字典将线程ID(由_get_ident()
返回_get_ident()
映射到Thread
实例。 加载threading
模块时,总是创build_MainThread
类的一个实例,并将其添加到_active
(即使没有显式创build其他线程)。
问题是gevent
的monkey-patching修补的方法之一是_get_ident()
– 原始的映射到thread.get_ident()
,monkey-patching用green_thread.get_ident()
replace它。 显然这两个调用都会为主线程返回不同的ID。
现在,如果在monkey-patching之前加载了threading
模块,当_MainThread
实例被创build并添加到_active
时, _get_ident()
调用返回一个值,并调用_exitfunc()
时间的另一个值 – 因此, del _active[_get_ident()]
。
相反,如果在加载threading
之前完成了monkey-patching,则一切正常 – 当_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()
我希望你find我的debugging故事有用:)
你可以使用这个:
import sys if 'threading' in sys.modules: del sys.modules['threading'] import gevent import gevent.socket import gevent.monkey gevent.monkey.patch_all()
我和一个gevent原型脚本有类似的问题。
Greenletcallback执行正常,我通过g.join()同步回主线程。 对于我的问题,我不得不调用gevent.shutdown()closures(我认为是)集线器。 在我手动closures事件循环后,程序正常终止,没有错误。