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

21 投票
3 回答
20722 浏览
提问于 2025-04-18 03:28

我的Python代码出现了“GC对象已经被跟踪”的错误,导致程序崩溃。我正在寻找最好的方法来调试这个崩溃问题。

操作系统:Linux。

  • 有没有合适的方法来调试这个问题?

在以下文章中有几个建议:使用GDB进行Python内存调试

我不确定作者使用了哪种方法来解决问题。

  • 在这种情况下,有没有办法生成可以分析的内存转储?就像在Windows系统中那样。

我找到了一些关于这个的文章,但并没有完全回答我的问题:http://pfigue.github.io/blog/2012/12/28/where-is-my-core-dump-archlinux/

3 个回答

7

问题在于你试图将一个对象添加到Python的循环垃圾回收器中两次。

你可以查看这个bug,特别是:

简单来说:如果你设置了Py_TPFLAGS_HAVE_GC,并且使用的是Python内置的内存分配(标准的tp_alloc/tp_free),那么你根本不需要手动调用PyObject_GC_Track()PyObject_GC_UnTrack()。Python会在你不注意的时候自动处理这些事情。

不幸的是,目前这方面的文档并不是很好。一旦你解决了这个问题,欢迎在上面提到的bug报告中提出对这个行为更好文档的建议。

9

我在使用boost::python时遇到了一个问题,当我们的C++代码触发一个Python回调时,偶尔会出现“GC对象已经被跟踪”的错误,程序就会崩溃。

在出错之前,我能用GDB工具连接到这个进程。有趣的是,在Python代码中,我们用functools的partial函数包裹了这个回调,这实际上掩盖了真正出错的地方。后来我把这个partial替换成了一个简单的可调用包装类,结果“GC对象已经被跟踪”的错误不再出现了,但我却遇到了一个段错误(segfault)。

在我们的boost::python包装器中,我们使用了lambda函数来处理C++的回调,而这个lambda函数捕获了boost::python::object的回调函数。结果发现,不知道为什么,在lambda的析构函数中,它并不总是能正确获取GIL(全局解释器锁),这导致了段错误。

解决办法是不要使用lambda函数,而是创建一个functor(函数对象),确保在析构函数中获取GIL,然后再对boost::python::object调用PyDECREF()。

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;
};
15

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

我有Python和C扩展代码(在共享对象中)。Python代码会向C扩展代码注册一个回调函数。在某个工作流程中,C扩展代码的一个线程会调用这个在Python代码中注册的回调函数。

通常情况下,这个过程是正常的,但当多个线程同时执行相同的操作时,就会导致崩溃,出现“GC对象已经被跟踪”的错误。

为多个线程同步访问Python对象可以解决这个问题。

感谢所有对此做出回应的人。

撰写回答