← 中文资源

简体中文版本

Python OSError [Errno 24] 打开文件过多错误排查

用 lsof 与句柄计数追踪 Python 文件描述符泄漏,找到缺少 with 或 close 的 open 调用,并全面改用上下文管理器。

阅读英文原文

症状

  errno 非常精确,增长模式进一步坐实:OSError: [Errno 24] Too many open files 总在 open 或 socket 调用处抛出,且失败次数与处理的请求数或文件数相关而非单纯与时间相关。流量运行时实时观察 /proc//fd 计数——每请求加一的增长直指每请求泄漏;按路径分组句柄,几百个句柄指向同一文件就是凭证:代码每个请求打开它,却没有任何路径关闭它。

诊断思路

  先用 ls /proc//fd | wc -l 与 lsof 分组,量化并点名泄漏资源;再看增长与工作的相关性(随请求线性 vs 随墙钟时间),区分每请求泄漏与定时器/日志泄漏;最后 grep 所有不带 with 的 open(),位于 try 块内、函数末尾 close 之前有 return 或 raise 的调用是惯犯。子进程管道的 stdout/stderr 未读到 EOF 也未关闭、tempfile 保留句柄却只用文件名,是两个高频变体。

根因

  文件描述符是每进程有限资源。每个永不关闭的 open、管道或套接字永久消耗一个。稳定流量下计数持续攀升,直到内核拒绝下一次 open(EMFILE),Python 报为 Errno 24。

修复与验证

  把每个文件、管道、临时文件包进 with 块,让关闭成为结构性的而不是手工的——异常与提前 return 都无法绕过;子进程用 Popen 加 communicate() 确定性读尽并关闭双流;只暴露 close 的对象交给 contextlib.closing;测试里用 RLIMIT_NOFILE 调低上限,让泄漏在秒级失败而不是小时级。验证时跑固定请求数后断言 fd 计数精确回到运行前基线;再用故意调低的 ulimit 重复,证明护栏会大声失败。调大 ulimit -n 只是换个大桶:进程仍在泄漏,只是更晚、以更大爆炸半径撞上新的天花板。

代码示例

OSError: [Errno 24] Too many open files: 'data/2026-10-22.ndjson'
watch -n5 "ls /proc/$(pgrep -f app)/fd | wc -l"
# 1042 -> 1089 -> 1137 ...  <- one per request
lsof -p $(pgrep -f app) | awk '{print $NF}' | sort | uniq -c | sort -rn
#  489 data/2026-10-22.ndjson
with open(path, encoding="utf-8") as f:
    for line in f:
        process(line)
# closed here on every path, including raise
with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p:
    out, err = p.communicate()   # reads to EOF, closes both fds
import resource
resource.setrlimit(resource.RLIMIT_NOFILE, (256, 256))