调试 Python 致命错误:已跟踪 GC 对象

Posted

技术标签:

【中文标题】调试 Python 致命错误:已跟踪 GC 对象【英文标题】:Debugging Python Fatal Error: GC Object already Tracked 【发布时间】:2014-06-04 09:48:18 【问题描述】:

我的 python 代码因错误 'GC Object already Tracked' 而崩溃。试图找出调试此崩溃的最佳方法。

操作系统:Linux。

是否有适当的方法来调试此问题。

在下面的文章中有几个建议。 Python memory debugging with GDB

不确定哪种方法对作者有效。

有没有办法在这种情况下生成可以分析的内存转储。就像在 Windows 世界中一样。

找到了一些关于此的文章。但不能完全回答我的问题: http://pfigue.github.io/blog/2012/12/28/where-is-my-core-dump-archlinux/

【问题讨论】:

是的,可以生成转储。实际上,转储是在您上面提到的文章中描述的崩溃(段错误)时自动生成的。但是您可以通过使用kill 发送过程信号来手动强制操作。顺便说一句,你看过pyrit.wordpress.com/2010/02/18/385 吗? 一旦我们设置了核心转储,你知道当进程崩溃和消失时转储文件在哪里生成吗? 转储存储在进程的当前工作目录中。 【参考方案1】:

在我的场景中找到了这个问题的原因(不一定是 GC 对象崩溃的唯一原因)。 我使用 GDB 和核心转储来调试这个问题。

我有 Python 和 C 扩展代码(在共享对象中)。 Python 代码使用 C 扩展代码注册回调例程。 在某个工作流中,来自 C 扩展代码的线程正在调用 Python 代码中注册的回调例程。

这通常工作得很好,但是当多个线程同时执行相同的操作时,会导致崩溃并显示“GC 对象已被跟踪”。

同步多个线程对 python 对象的访问确实解决了这个问题。

感谢任何对此的回应。

【讨论】:

在这种情况下如何同步访问?【参考方案2】:

当我们的 C++ 代码触发 python 回调时,我使用 boost::python 遇到了这个问题。我偶尔会收到“已跟踪 GC 对象”并且程序会终止。

我能够在触发错误之前将 GDB 附加到进程。一件有趣的事情,在 python 代码中,我们用 一个 functools 部分,它实际上掩盖了真正的错误发生的地方。用一个简单的可调用包装类替换部分后。 “GC 对象已跟踪错误”不再弹出,而是我现在只是遇到了段错误。

在我们的 boost::python 包装器中,我们有 lambda 函数来处理 C++ 回调,并且 lambda 函数捕获了 boost::python::object 回调函数。事实证明,无论出于何种原因,在 lambda 的析构函数中,在销毁导致段错误的 boost::python::object 时并不总是正确获取 GIL。

解决方法是不使用 lambda 函数,而是创建一个仿函数,确保在对 boost::python::object 调用 PyDECREF() 之前在析构函数中获取 GIL。

class callback_wrapper

public:
    callback_wrapper(object cb): _cb(cb), _destroyed(false) 
    

    callback_wrapper(const callback_wrapper& other) 
        _destroyed = other._destroyed;
        Py_INCREF(other._cb.ptr());
        _cb = other._cb;
    

    ~callback_wrapper() 
        std::lock_guard<std::recursive_mutex> guard(_mutex);
        PyGILState_STATE state = PyGILState_Ensure();
        Py_DECREF(_cb.ptr());
        PyGILState_Release(state);
        _destroyed = true;
    

    void operator ()(topic_ptr topic) 
        std::lock_guard<std::recursive_mutex> guard(_mutex);
        if(_destroyed) 
            return;
        
        PyGILState_STATE state = PyGILState_Ensure();
        try 
            _cb(topic);
        
        catch(error_already_set)  PyErr_Print(); 
        PyGILState_Release(state);
    

    object _cb;
    std::recursive_mutex _mutex;
    bool _destroyed;
;

【讨论】:

【参考方案3】:

问题是您尝试将对象添加到 Python 的循环垃圾收集器跟踪两次。

查看this bug,具体来说:

Python 中的documentation for supporting cyclic garbage collection My documentation patch for the issue 和 My explanation in the bug report itself

长话短说:如果您设置了Py_TPFLAGS_HAVE_GC 并且您使用的是Python 的内置内存分配(标准tp_alloc/tp_free),那么您不必手动调用PyObject_GC_Track()PyObject_GC_UnTrack() . Python 会在你背后处理这一切。

不幸的是,目前没有很好的记录。解决问题后,请随时加入错误报告(上面链接),以更好地记录此行为。

【讨论】:

感谢克里斯提供的信息。我会调查一下并填写结果。

以上是关于调试 Python 致命错误:已跟踪 GC 对象的主要内容,如果未能解决你的问题,请参考以下文章

Python GC 中跟踪的任何对象都没有引用者吗?

PhpUnit 未显示 php 致命错误的堆栈跟踪

PhpUnit 未显示 php 致命错误的堆栈跟踪

错误:目标文件为空 .git/objects/../.. 为空 - 致命:松散的对象...已损坏

仅当存在警告或更严重级别的日志事件时记录所有级别

python标准库介绍——14 gc 模块详解