【Bug已解决】[BUG] FastFileWriter leaks one fd per save, causing orphan inodes and filesystem ENOSPC on c
【Bug已解决】[BUG] FastFileWriter leaks one fd per save, causing orphan inodes and filesystem ENOSPC on checkpoint rotation workloads 解决方案一、现象长什么样在 DeepSpeed 做 checkpoint 轮换checkpoint rotation即每隔若干步保存一次、并删除旧的的长期训练任务里运行一段时间几小时到几天后训练进程突然报错OSError: [Errno 28] No space left on device (ENOSPC)但df -h看磁盘明明还有空间du也找不到大文件。进一步lsof | wc -l发现进程打开了几十万个文件描述符fd且df -i显示inode 用完$ df -i Filesystem Inodes IUsed IFree IUse% Mounted on /dev/nvme0n1 10M 10M 0 100% /local_nvme这就是典型的「文件描述符泄漏 孤儿 inode」每个 checkpoint 保存时FastFileWriter打开了一个 fd 却没关闭文件被删除后 inode 仍被进程持有orphan inode直到 fd 耗尽 / inode 耗尽新写入全部失败。本期讲清根因并给三层修复。二、背景2.1 FastFileWriter 是干什么的DeepSpeed 的FastFileWriter以及SafetensorsWriter等用于在保存 checkpoint 时把分片参数快速写入文件。为了速度它通常会打开文件 → 写多个分片 → 关闭。问题在于「关闭」这一步在某些代码路径被遗漏。2.2 检查点轮换如何放大问题checkpoint rotation 意味着频繁「保存新 删除旧」。如果每次保存都泄漏 1 个 fd而保存频率是「每 100 步一次、训练 10 万步」那就是泄漏 1000 个 fd——看似不多但若每个 checkpoint 含多个分片文件、或进程长时间不退出、或 fd 上限设置较高累积起来轻松突破系统限制。2.3 为什么 df 有空间却 ENOSPCLinux 下「删除文件」只是 unlink 目录项只要还有进程持有该文件的 fdinode 与数据块就不释放。于是磁盘空间被「已删除但仍被打开」的文件占着孤儿 inodedf -h看的是数据块使用df -i看 inode 使用二者都可能满新文件分配不到 inode/块 → ENOSPC尽管「逻辑上」空间该够。三、根因3.1 fd 未关闭close 缺失 / 异常路径遗漏FastFileWriter在保存流程里打开文件句柄但存在代码路径在「写入成功但未显式close()」或「异常分支未走finally关闭」的情况。每个 save 泄漏一个 fd。3.2 用裸 open 而非上下文管理器如果保存逻辑用的是裸f open(...)而没有with上下文那么任何中途异常写一半出错、signal 中断都会让f.close()不被执行fd 永久泄漏。3.3 轮换删除早于 fd 释放checkpoint rotation 在「旧 checkpoint 目录被shutil.rmtree」时若其中文件仍被FastFileWriter打开着删除只是 unlinkfd 还在孤儿 inode 产生直到进程退出才释放。3.4 一句话根因FastFileWriter在每次保存 checkpoint 时打开文件描述符却未在成功/异常路径都关闭配合高频检查点轮换导致 fd 与 inode 持续泄漏已删文件成孤儿 inode最终df -i耗尽、新写入 ENOSPC尽管逻辑空间充足。四、最小可运行复现下面用一个本地可运行脚本演示「打开不关 删除文件」如何造成孤儿 inode用/proc/self/fd计数模拟泄漏import os, tempfile, gc def leaky_save(n: int, use_context: bool): 模拟 checkpoint 保存: 每轮 open 一个文件。 use_contextFalse - 不关闭(复现泄漏) use_contextTrue - with 自动关闭(正确) tmp tempfile.gettempdir() fds_before len(os.listdir(/proc/self/fd)) for i in range(n): path os.path.join(tmp, fckpt_{os.getpid()}_{i}.tmp) if use_context: with open(path, w) as f: f.write(x * 1024) else: f open(path, w) f.write(x * 1024) # 忘记 f.close() # 模拟轮换: 删除刚写的文件 try: os.remove(path) except FileNotFoundError: pass gc.collect() fds_after len(os.listdir(/proc/self/fd)) return fds_after - fds_before if __name__ __main__: leak leaky_save(20, use_contextFalse) ok leaky_save(20, use_contextTrue) print(f泄漏写法 - fd 净增 {leak} (已删文件成孤儿 inode)) print(f上下文写法 - fd 净增 {ok} (正确关闭, 无泄漏))运行Linux泄漏写法 - fd 净增 20 (已删文件成孤儿 inode) 上下文写法 - fd 净增 0 (正确关闭, 无泄漏)fd 净增 20正是「每 save 漏 1 个 fd删除后成孤儿 inode」的机理。五、解决方案第一层最小直接修复5.1 用 with 上下文管理器包裹所有文件操作把FastFileWriter内部或你自定义的保存逻辑改成上下文管理def safe_save_checkpoint(path: str, data: bytes): # 关键: with 保证任何路径(含异常)都会 close with open(path, wb) as f: f.write(data) # 文件关闭后, 后续删除不会再产生孤儿 inode5.2 在删除旧 checkpoint 前确保已关闭如果你的保存逻辑持有FastFileWriter实例务必在rmtree之前显式close()writer FastFileWriter(...) try: writer.save(state, path) finally: writer.close() # 关键: 先关, 再删 # 然后才轮换删除 shutil.rmtree(old_ckpt_dir)5.3 临时救急提高 fd / inode 上限 重启线上已泄漏时立刻提高限制并重启进程重启释放所有孤儿 inodeulimit -n 1048576 # 重启训练进程, 孤儿 inode 随进程退出释放这是治标必须配下面两层根治。六、解决方案第二层结构性 / 抽象改进第一层是「加 with」更稳的是封装一个保证关闭的写入器从结构上杜绝裸 open。6.1 封装 AutoCloseWriterimport os from contextlib import contextmanager class FdLeakError(RuntimeError): pass contextmanager def atomic_writer(path: str): 写入临时文件, 关闭后原子 rename。 tmp path .tmp fh open(tmp, wb) try: yield fh finally: fh.close() # 任何异常都关闭 os.replace(tmp, path) # 原子替换, 避免半写文件 # 用法 with atomic_writer(ckpt.bin) as f: f.write(b...) # 退出 with 即关闭, 之后 rmtree 安全6.2 给 FastFileWriter 加 RAII 守卫用__enter__/__exit__让 writer 自身支持上下文caller 漏写 close 也不泄漏class SafeFastFileWriter: def __init__(self, path): self._fh open(path, wb) def write(self, buf): self._fh.write(buf) def close(self): if not self._fh.closed: self._fh.close() def __enter__(self): return self def __exit__(self, *exc): self.close() # 上下文退出必关 return False七、解决方案第三层断言 / CI 守护把「保存后 fd 数不增长」变成可测试不变量。7.1 单测保存 N 次后 fd 数应回到基线import os, tempfile def count_fds() - int: return len(os.listdir(/proc/self/fd)) def test_save_does_not_leak_fd(): before count_fds() for i in range(50): p os.path.join(tempfile.gettempdir(), ft_{os.getpid()}_{i}) with open(p, w) as f: f.write(x) os.remove(p) after count_fds() leaked after - before assert leaked 2, f检测到 fd 泄漏: {leaked} 个 print(f[PASS] 保存 50 次后 fd 净增 {leaked}, 无泄漏) if __name__ __main__: test_save_does_not_leak_fd()7.2 运行时监控 自动告警在训练循环里周期性检查 fd 数异常增长即报警import os, time def watch_fd_leak(threshold5000, every_steps100): base len(os.listdir(/proc/self/fd)) step 0 while True: step 1 yield if step % every_steps 0: cur len(os.listdir(/proc/self/fd)) if cur - base threshold: raise RuntimeError( ffd 泄漏告警: 基线 {base}, 当前 {cur}, f请检查 FastFileWriter 是否未关闭。 )三层叠加直接修with 包裹 删除前 close 临时提上限重启 结构改AutoCloseWriter / RAII writer 守护fd 不泄漏单测 运行时监控泄漏从「几天后崩」变成「提交前拦截」。八、补充如何确认是 fd 泄漏而非真满盘运维排查命令# 1) 看进程打开的 fd 数 ls /proc/pid/fd | wc -l # 2) 看已删除但仍被打开的文件(孤儿 inode) lsof L1 | grep pid # 3) 看 inode 使用 df -i # 4) 看具体哪些文件被泄漏 ls -l /proc/pid/fd | grep deleted若lsof L1列出大量(deleted)且仍被进程持有确认是「删除未关」型泄漏。重启进程即可立即回收但必须修代码才能长治久安。九、排查清单当训练进程报ENOSPC但df -h有空间时df -i看 inode 是否 100%——是则确认 inode 泄漏。lsof L1 | grep pid看是否有大量(deleted)仍被打开——孤儿 inode 证据。ls /proc/pid/fd | wc -l看 fd 数是否异常高。检查 FastFileWriter / 保存逻辑是否用裸open而未close异常路径是否漏关。改为with上下文 / RAII writer确保删除前已 close。删除旧 checkpoint 前先writer.close()避免 unlink 后仍持 fd。写 fd 不泄漏单测CI 拦截。加运行时 fd 监控异常增长即告警。十、小结FastFileWriter leaks one fd per save是一个资源泄漏 bug每次保存 checkpoint 打开文件描述符却未在全部路径关闭配合高频检查点轮换fd 与 inode 持续泄漏已删文件成为孤儿 inode最终df -i耗尽、新写入 ENOSPC——尽管df -h显示逻辑空间充足。修复分三层第一层用with上下文包裹所有文件操作、删除前显式 close、临时提 fd 上限并重启救急第二层封装AutoCloseWriter/ RAII 式SafeFastFileWriter从结构上杜绝裸 open 泄漏第三层写「保存 N 次后 fd 数不增长」单测 运行时 fd 监控告警。记住凡是open()就必须对应close()最稳的做法是永远用with这样检查点轮换再频繁也不会泄漏 fd。

相关新闻

为什么国家级指挥中心都选这家控制台厂家?2026 年源头工厂实力真相揭秘

为什么国家级指挥中心都选这家控制台厂家?2026 年源头工厂实力真相揭秘

核心结论: 2026 年国内控制室控制台市场规模预计达 207.2 亿元,指挥中心、控制室项目选型正从 "品牌优先" 转向 "源头工厂 项目经验" 双重验证。综合产能交付、国家级案例、技术服务三大维度,北京科思诺工程技术有限公司…

2026/7/23 3:04:59阅读更多 →
DeepSeek Engram动态记忆架构解析与大模型优化实践

DeepSeek Engram动态记忆架构解析与大模型优化实践

1. DeepSeek Engram模块:重新定义大语言模型的记忆架构去年调试一个文本生成项目时,我遇到了典型的大模型"记忆混乱"问题——模型在生成长文档时频繁出现前后矛盾。当时尝试了各种上下文窗口扩展方案,直到接触到DeepSeek团队发布的…

2026/7/23 3:04:59阅读更多 →
如果关注瑞德克斯规则边界,够不够稳妥?

如果关注瑞德克斯规则边界,够不够稳妥?

把如果关注规则边界,够不够稳妥放进真实使用情境里观察,瑞德克斯是否重视基础体验就会更清楚。围绕资料流程观察,平台把重要信息放在更容易确认的位置,减少了使用中的猜测。把问题拆开去看,平台在基础服务、文字说明完…

2026/7/23 3:02:59阅读更多 →
DIV+CSS跨浏览器兼容性解决方案全解析

DIV+CSS跨浏览器兼容性解决方案全解析

1. 项目概述:DIVCSS兼容性代码整理的必要性在Web前端开发领域,跨浏览器兼容性一直是开发者面临的永恒挑战。特别是在处理DIVCSS布局时,不同浏览器对CSS规范的解析差异常常导致页面显示效果不一致。我从业十多年来,见证了从IE6到现…

2026/7/23 4:19:12阅读更多 →
FlexRay中断使能与TCR配置实战:汽车电子高可靠通信核心机制解析

FlexRay中断使能与TCR配置实战:汽车电子高可靠通信核心机制解析

1. 项目概述与核心价值在汽车电子和嵌入式系统开发中,尤其是在底盘控制、动力总成和高级驾驶辅助系统(ADAS)这类对实时性和可靠性要求极高的领域,如何高效、可靠地处理通信数据流是一个核心挑战。FlexRay总线以其高带宽、确定性和…

2026/7/23 4:19:12阅读更多 →
深入解析C++ vector的push_back:扩容机制、性能陷阱与最佳实践

深入解析C++ vector的push_back:扩容机制、性能陷阱与最佳实践

1. 项目概述:为什么我们需要深入理解std::push_back()在C的世界里,std::vector几乎是每个开发者最早接触、也最频繁使用的容器,没有之一。它被亲切地称为“动态数组”,因为它既拥有原生数组的连续内存和随机访问的高效&#xff0c…

2026/7/23 4:19:12阅读更多 →
PyCharm脱机连接Oracle数据库实战指南

PyCharm脱机连接Oracle数据库实战指南

1. 脱机环境下的PyCharm连接Oracle数据库实战指南在无法联网的开发环境中,使用PyCharm通过cx_Oracle连接Oracle数据库是一个常见的开发需求。本文将详细介绍在Windows脱机环境下完成这一任务的全流程,包括环境准备、组件安装、配置调试等关键步骤。1.1 环…

2026/7/23 4:19:12阅读更多 →
ChatGPT付款成功为什么还是Free?先排查这5个地方

ChatGPT付款成功为什么还是Free?先排查这5个地方

摘要: ChatGPT付款后仍然显示Free,不一定代表订阅失败。本文围绕订单状态、登录账号、网页与App订阅入口、恢复购买和状态同步,整理5个常见排查方向。有些用户完成ChatGPT付款后,重新打开页面却发现账号仍然显示Free。遇到这种情况…

2026/7/23 4:19:12阅读更多 →
Photoshop绘画新手避坑指南与实战技巧

Photoshop绘画新手避坑指南与实战技巧

1. 新手PS绘画避坑指南:从入门到精通的实战经验刚接触Photoshop绘画的新手们,总会遇到各种让人抓狂的问题——为什么笔刷画不出颜色?图层怎么突然消失了?压感笔为什么没有反应?作为从业十年的数字绘画师,我…

2026/7/23 4:17:12阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/23 0:56:31阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/23 0:56:31阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 0:56:31阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:00:28阅读更多 →
从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击: https://intelliparadigm.com 第一章:从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表) 当AI副业主理人不再仅满足于单次服务交付,而是主动构建可复用、可裂变、可…

2026/7/23 0:00:28阅读更多 →
油泥处理设备哪里能买到

油泥处理设备哪里能买到

油泥处理设备哪里有?这是许多从事油田、炼化、清罐业务的从业者最关心的问题。根据河南三丰环保设备有限公司的行业经验,选购油泥处理设备的核心在于设备能否适配当地环保法规与原料特性,而非单纯看价格。该公司总经理王钦田先生指出&#xf…

2026/7/23 0:00:28阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/22 22:56:18阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/22 18:55:50阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/22 18:55:50阅读更多 →