kku*_*ian 68 python pytest 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
Run Code Online (Sandbox Code Playgroud)
我应该如何追查其来源?(我不是直接使用线程,而是使用gevent.)
Cod*_*ers 214
我发现了一个类似的问题,并决定看看究竟发生了什么 - 让我描述一下我的发现.我希望有人会发现它很有用.
它确实与猴子修补threading模块有关.实际上,我可以通过在猴子修补线程之前导入线程模块来轻松触发异常.以下两行就足够了:
import threading
import gevent.monkey; gevent.monkey.patch_thread()
Run Code Online (Sandbox Code Playgroud)
执行时,它会发出有关忽略的消息KeyError:
(env)czajnik@autosan:~$ python test.py
Exception KeyError: KeyError(139924387112272,) in <module 'threading' from '/usr/lib/python2.7/threading.pyc'> ignored
Run Code Online (Sandbox Code Playgroud)
如果你交换导入行,问题就消失了.
我可以在这里停止调试,但我认为值得理解问题的确切原因.
第一步是找到打印有关忽略异常的消息的代码.我找到它有点困难(grepping 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. */
Run Code Online (Sandbox Code Playgroud)
我决定通过一些帮助来检查谁在调用它,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 ()
Run Code Online (Sandbox Code Playgroud)
现在我们可以清楚地看到Py_Finalize执行时抛出了异常- 这个调用负责关闭Python解释器,释放已分配的内存等.它在退出之前被调用.
下一步是查看Py_Finalize()代码(它在Python/pythonrun.c中).它的第一个调用是wait_for_thread_shutdown()- 值得一看,因为我们知道问题与线程有关.该函数依次_shutdown在threading模块中调用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
Run Code Online (Sandbox Code Playgroud)
显然,threading._shutdown()调用的责任是加入所有非守护程序线程并删除主线程(无论这意味着什么).我决定修补threading.py一下 - _exitfunc()用try/ 包裹整个身体except并用跟踪模块打印堆栈跟踪.这给出了以下痕迹:
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
Run Code Online (Sandbox Code Playgroud)
现在我们知道抛出异常的确切位置 - 内部Thread.__delete()方法.
阅读threading.py一段时间后,其余的故事是显而易见的.该_active字典映射线程ID(如返回_get_ident())到Thread的情况下,对创建的所有线程.当threading模块被加载,实例_MainThread类始终是创建并添加到_active(即使是明确创建其他线程).
问题是,用gevent猴子修补修补的方法之一是_get_ident()- 原始映射到thread.get_ident(),猴子修补取而代之green_thread.get_ident().显然,两个调用都为主线程返回不同的ID.
现在,如果threading在猴子修补之前加载模块,则在创建并添加实例_get_ident()时,调用会返回一个值,并且此时调用另一个值- 因此在._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()
Run Code Online (Sandbox Code Playgroud)
我希望你发现我的调试故事很有用:)
小智 19
你可以用这个:
import sys
if 'threading' in sys.modules:
del sys.modules['threading']
import gevent
import gevent.socket
import gevent.monkey
gevent.monkey.patch_all()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
26799 次 |
| 最近记录: |