PyPy自定义C++扩展抛出Python异常时内存泄漏求助
PyPy中C++扩展自定义类型抛异常导致内存泄漏的问题解决
问题描述
我正在编写支持CPython和PyPy的C++扩展,其中包含带调用接口的自定义类型。但在PyPy中抛出Python异常时出现内存泄漏,CPython下则无此问题。已隔离出泄漏代码,构建后可通过python test.py或pypy test.py验证。
C++扩展类代码
struct custom_function { PyObject_HEAD }; PyObject* custom_call(PyObject *self, PyObject *args, PyObject *kwargs) { std::cout << "Calling custom function with except, simpler" << std::endl; PyErr_SetString(PyExc_SystemError, "Fancier exception for custom fancy func"); return nullptr; }; PyTypeObject custom_function_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "custom_function", .tp_basicsize = sizeof(custom_function), .tp_itemsize = 0, .tp_call = custom_call, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("Custom function type"), .tp_new = PyType_GenericNew, };
内存泄漏检测代码
import helloworld as m import gc import weakref class ExampleClass: def __init__(self): pass def simple_test(f): it = ExampleClass() a = weakref.ref(it) try: f(it) except Exception as e: print("Got exception!", e) del e assert a() is not None del it gc.collect() assert a() is None simple_test(m.custom_fancy_func)
问题原因
PyPy的对象模型与CPython存在差异:当自定义类型的tp_call方法抛出异常(通过PyErr_SetString设置并返回nullptr)时,PyPy不会自动清理传入的args和kwargs参数的引用计数。而CPython会在异常处理逻辑中自动释放这些参数的引用,导致PyPy中ExampleClass实例的引用计数无法归零,最终引发内存泄漏。
解决方案
在custom_call方法抛出异常返回nullptr前,必须手动减少args和kwargs的引用计数。修改后的代码如下:
PyObject* custom_call(PyObject *self, PyObject *args, PyObject *kwargs) { std::cout << "Calling custom function with except, simpler" << std::endl; PyErr_SetString(PyExc_SystemError, "Fancier exception for custom fancy func"); // 手动释放参数引用,Py_XDECREF在参数为NULL时不会出错 Py_XDECREF(args); Py_XDECREF(kwargs); return nullptr; };
说明
- 使用
Py_XDECREF而非Py_DECREF,是因为kwargs可能为NULL,Py_XDECREF能安全处理这种情况。 - 这一步在CPython中不会产生负面影响,因为CPython会额外处理参数引用,但PyPy依赖扩展作者手动管理这些参数的引用,所以必须显式释放。
内容的提问来源于stack exchange,提问作者Lalaland
相关产品推荐
相关产品推荐

