Watch at Ease看见世界,读懂生活。

科技 · 实用指南

Python 警告“resource_tracker: leaked semaphore objects”?隐藏前先找到失效持有者

这条退出警告是清理症状,不是根因。先保留首个异常,列出每个进程创建的信号量,再证明 close、join 与 unlink 的正常顺序。

Original technical diagram tracing a Python resource-tracker shutdown warning back through the first failure, resource owner, and normal cleanup path.
原创示意图:从最终警告回溯首个故障、资源持有者与清理路径。 Watch at Ease

解释器关闭时,resource tracker 仍保存已登记名称就会发出警告。它不会直接告诉你哪个库创建了资源,也不证明最后一行就是根因;被信号杀死的子进程、初始化失败、未关闭的进程池、未排空的队列或重复 unlink 都可能留下同样警告。

从首个异常和持有者台账开始。记录 Python 版本与启动方式,每次只保留一种资源构造器,证明每个子进程的退出及父进程的 close、shutdown、join、unlink 路径;只有生命周期已被证实时,才讨论是否过滤警告。

先保存首个故障,不要只看最后警告

保存完整 stderr 和退出状态,找到 resource-tracker 行之前最早的 traceback、信号、initializer 失败、超时、取消或强制终止。最后警告可能只是次生清理。

记录 Python 构建、操作系统、启动方式、父子 PID、库版本,以及进程是正常返回、抛错、取消还是被杀。不要先过滤 UserWarning。

列出构造器并分配资源持有者

检查应用与依赖边界中的 multiprocessing.Pool、Process、Queue、Lock、Semaphore、ProcessPoolExecutor、SharedMemory、joblib 或数据加载 worker。记录构造位置、所属进程以及负责关闭或 unlink 的作用域。

需要 spawn-safe import 的平台应把进程创建放进 main guard,但不要把它当通用修复;import 时创建资源、initializer 失败或库内部进程池仍有不同原因。

先证明正常退出,再测试崩溃分支

优先使用上下文管理器;否则先关闭进程池使其停止接收任务,再 join;关闭 executor;生产者结束后关闭队列,并按文档 join feeder thread;SharedMemory 由约定的单一持有者 unlink 一次。

基线运行不要 terminate、kill、os._exit、突然停容器或让测试超时。Python 文档明确指出强制终止可能留下共享资源、锁、管道和队列。

区分真实遗留对象与 tracker 记账

把程序缩减到一个构造器和一个子进程,并记录创建、子进程退出、close、join、unlink 与解释器关闭顺序。若警告消失,再逐层加入库,直到所有权发生变化。

仅在 Linux 上,/dev/shm 可作为 POSIX 命名对象的辅助证据;它不是安全删除清单,其他平台也不同。无法绑定到已停止进程和正式清理持有者的名称不能删除。

测试受支持版本与竞态分支

若生命周期正确,在当前受支持的补丁版本重现最小脚本。CPython issue 显示,initializer 失败、重入清理和重复 unlink 竞态在特定版本或测试中都可能产生相似警告。

不要为了安静而把私有 unregister 调用粘进生产。先确认是重复清理、完全没有清理,还是可在特定版本复现的问题。

用无警告、无遗留对象的运行收尾

收尾标准是:每个子进程退出明确,父进程关闭并 join 所有资源,SharedMemory 只有一个 unlink 持有者,解释器关闭不再产生新警告。还要在原 service manager 或 CI 取消路径重复测试。

只有完成上述证明后,才可为已知无害的第三方警告设置窄过滤,并记录版本、最小复现、上游 issue 与移除条件。

Original decision diagram separating normal Python shutdown, an exception, a killed child, and double cleanup or a version bug.
原创示意图:按正常返回、异常、被杀或竞态选择修复。 Watch at Ease