调试Python致命错误:GC对象已被跟踪
我的Python代码出现了“GC对象已经被跟踪”的错误,导致程序崩溃。我正在寻找最好的方法来调试这个崩溃问题。
操作系统:Linux。
- 有没有合适的方法来调试这个问题?
在以下文章中有几个建议:使用GDB进行Python内存调试
我不确定作者使用了哪种方法来解决问题。
- 在这种情况下,有没有办法生成可以分析的内存转储?就像在Windows系统中那样。
我找到了一些关于这个的文章,但并没有完全回答我的问题:http://pfigue.github.io/blog/2012/12/28/where-is-my-core-dump-archlinux/
3 个回答
问题在于你试图将一个对象添加到Python的循环垃圾回收器中两次。
你可以查看这个bug,特别是:
简单来说:如果你设置了Py_TPFLAGS_HAVE_GC
,并且使用的是Python内置的内存分配(标准的tp_alloc
/tp_free
),那么你根本不需要手动调用PyObject_GC_Track()
或PyObject_GC_UnTrack()
。Python会在你不注意的时候自动处理这些事情。
不幸的是,目前这方面的文档并不是很好。一旦你解决了这个问题,欢迎在上面提到的bug报告中提出对这个行为更好文档的建议。
我在使用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;
};
我找到了这个问题的原因(这不一定是导致GC对象崩溃的唯一原因)。我使用了GDB和核心转储来调试这个问题。
我有Python和C扩展代码(在共享对象中)。Python代码会向C扩展代码注册一个回调函数。在某个工作流程中,C扩展代码的一个线程会调用这个在Python代码中注册的回调函数。
通常情况下,这个过程是正常的,但当多个线程同时执行相同的操作时,就会导致崩溃,出现“GC对象已经被跟踪”的错误。
为多个线程同步访问Python对象可以解决这个问题。
感谢所有对此做出回应的人。