必须用系统级valgrind包裹python -m pytest进程,编译扩展加-g、运行加--track-origins=yes,只修复definitely lost,禁用pytest-xdist和fork模式,并在CI中禁用GC。

pytest-valgrind 不是标准插件,别装错包
直接 pip install pytest-valgrind 会装到一个早已停更、不兼容 Python 3.9+ 且无法与现代 pytest 协作的废弃包。它不会自动 hook 测试生命周期,也不支持 --valgrind 这类参数。你运行后大概率看到 unrecognized arguments: --valgrind 或静默失败。
真实可行路径只有一条:用系统级 valgrind 包裹整个 python -m pytest 进程,而非依赖任何“集成插件”。
必须用 --track-origins=yes + -g 编译扩展模块
Valgrind 要准确定位 C 扩展中哪行 C 代码漏了 malloc,必须满足两个硬条件:
- Python 扩展模块编译时加
-g(保留调试符号),否则输出只有地址,没有talib/_abstract.c:787这类可读信息 - Valgrind 运行时必须带
--track-origins=yes,否则无法追溯未释放内存块最初从哪个malloc分配而来 - 若扩展使用 Cython,需在
setup.py中显式传extra_compile_args=["-g"];若为纯 C 扩展,确保gcc命令含-g
泄漏报告里只盯 definitely lost,忽略 still reachable
Valgrind 输出中四类泄漏里,只有 definitely lost 是真实问题:
立即学习“Python免费学习笔记(深入)”;
-
definitely lost:指针彻底丢失,比如TA_ParamHolderAlloc分配后没调TA_ParamHolderFree—— 这类必须修复 -
still reachable:Python 解释器退出前仍能访问的全局/静态分配(如PyTypeObject初始化内存),属于正常现象,金融级生产环境也允许存在 -
possibly lost和indirectly lost需结合栈回溯判断:如果源头是definitely lost的父块,修根即可,不必单独处理子块
避免 pytest-xdist / fork 导致泄漏误报
Valgrind 本身不支持多进程并行检测。若测试中启用 pytest-xdist(如 -n2)或 pytest 自动 fork 模式,Valgrind 会因子进程无法继承 instrumentation 状态而崩溃或漏报。
正确做法:
- 强制单进程运行:
pytest --numprocesses=0(禁用 xdist)或干脆不装pytest-xdist - 禁用 fork 模式:
pytest --forked会干扰 Valgrind,应改用--boxed(但更推荐不用) - CI 中务必添加
python -c "import gc; gc.disable()"前置命令 —— GC 干扰会让definitely lost数量浮动,掩盖真实泄漏点
最易被忽略的是:Valgrind 报告的泄漏位置永远指向 C 层,但根源常在 Python 层调用逻辑缺失(比如忘了调 close() 或 free_handle())。看懂 definitely lost 的栈回溯后,要反向检查 Python 测试用例是否完整覆盖了资源释放路径。


















