症状
errno 非常精确,增长模式进一步坐实:OSError: [Errno 24] Too many open files 总在 open 或 socket 调用处抛出,且失败次数与处理的请求数或文件数相关而非单纯与时间相关。流量运行时实时观察 /proc/
诊断思路
先用 ls /proc/
根因
文件描述符是每进程有限资源。每个永不关闭的 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))