当前位置: 首页 > 面试题库 >

py.test成功运行后,模块“线程化”中的KeyError

扶杜吟
2023-03-14
问题内容

我正在使用py.test运行一组测试。他们通过了。pp!但我收到此消息:

Exception KeyError: KeyError(4427427920,) in <module 'threading' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.pyc'> ignored

我应该如何追踪其来源?(我不是直接使用线程,而是在使用gevent。)


问题答案:

我观察到类似的问题,并决定确切地了解发生了什么-让我描述一下我的发现。我希望有人会觉得有用。

短篇故事

它确实与猴子修补threading模块有关。实际上,通过在猴子修补线程之前导入线程模块,我可以轻松触发异常。以下两行就足够了:

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. */

我决定在的少许帮助下检查谁在调用它,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()-值得研究,因为我们知道问题与线程有关。该函数依次调用模块中的_shutdowncallable
threading。好的,我们现在可以回到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猴子的修补方法之一是将_get_ident()原始方法映射到原始方法thread.get_ident(),然后用猴子修补方法将其替换green_thread.get_ident()。显然,两个调用都为主线程返回不同的ID。

现在,如果threading在猴子修补之前加载了模块,则在创建实例并将其添加到实例_get_ident()时,call返回一个值,而此时又调用另一个值-
因此是in 。_MainThread``_active``_exitfunc()``KeyError``del _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()

我希望您发现我的调试故事很有用:)



 类似资料:
  • 问题内容: 我正在尝试测试tensorflow程序。我正在使用参数化的py.test夹具设置tensorflow会话: Tensorflow具有全局状态,因此一些测试启动会污染它。例如,启用急切执行后,无法禁用它。有没有一种方法可以指示py.test为每个测试创建一个新进程?还是使用参数化夹具来配置测试环境的另一种方法?用法示例: 问题答案: 如评论中所建议,使用将是解决方案。该插件设计用于并行或

  • 我对Python和Django相当陌生,所以请让我知道是否有更好的方法来做到这一点。我想做的是拥有每个设备(从模型继承。Model)启动一个长时间运行的后台线程,该线程不断检查该设备的运行状况。然而,当我运行代码时,它似乎不像守护进程那样执行,因为服务器缓慢且不断超时。这个后台线程将(在大多数情况下)运行程序的生命周期。 下面是我代码的简化版本: 这似乎是线程的一个非常简单的用法,但每次我寻找解决

  • 我想在测试套件中的每个测试之前和之后运行其他设置和拆卸检查。我看过赛程,但不确定它们是否是正确的方法。我需要在每次测试之前运行设置代码,并且需要在每次测试后运行拆卸检查。 我的用例是检查没有正确清理的代码:它会留下临时文件。在我的设置中,我将检查文件,在拆解中我也会检查文件。如果有额外的文件,我希望测试失败。

  • 我试着运行一个程序,使用线程显示带有数字的乘法、除法、加法和减法表。 但是我希望数字被乘以或相加等。由用户选择。 也就是说,程序应该在用户为每个操作选择一个数字后运行,然后显示结果。

  • 原始写法 对象写法 立即执行函数写法 放大模式 宽放大模式(Loose augmentation) 输入全局变量 使用构造函数封装私有变量 IIFE封装私有变量 随着网站逐渐变成"互联网应用程序",嵌入网页的JavaiScript代码越来越庞大,越来越复杂。网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑。 JavaS