那天做视频渲染时,系统盘突然变得很满。表面上看,这像是一个熟悉的清理问题:临时文件多了,缓存没有及时删掉,定时任务再勤快一点也许就能解决。可我继续往下追,发现真正占空间的不是普通缓存,而是一批浏览器留下的临时副本。它们是在无头渲染结束以后留下来的,而且每跑一次,可能就多一点。
这件事最值得记录的地方,不是最后删掉了多少文件,而是我不得不重新判断“清理”这件事到底处在问题链条的哪一段。清理可以处理已经发生的结果,却不能阻止下一次渲染再次制造结果。当磁盘问题反复出现时,先找残留是在什么时候产生的,而不是先把清理频率调高。
系统盘变满,只是最后一幕
我一开始看到的是空间告急,手边也确实有日常清理流程。这个流程能够找到浏览器留下的签名临时副本,并把它们清掉,所以从一次执行结果看,它是有效的。问题在于,清理动作发生在渲染之后:浏览器已经启动过、复制过文件、退出过,残留也已经落到了临时目录里。
如果只增加清理频率,最好的结果是空间暂时恢复;最坏的结果是渲染仍在持续产生副本,清理和生成互相追赶。渲染跑得快时,空间可能在两次清理之间又被吃掉;清理跑得不巧,还可能和正在使用的临时文件发生冲突。如果不处理产生残留的关闭流程,最终会导致系统盘再次变满,甚至让后续渲染因为空间不足而失败。事后补救能降低眼前的损失,却不能替代对产生机制的修复。
所以我把问题拆成了两个问题:第一,副本为什么会被创建;第二,什么情况下它没有被回收。只有两个问题都回答,才知道应该改清理流程,还是改渲染流程。这个区分也避免了一个常见误判:看到某个目录里有残留,就以为目录本身是故障源头。
真正的线索在关闭时出现
继续看渲染流程后,线索变得具体起来。原来的脚本优先调用系统浏览器运行无头自动化任务,任务完成后只给浏览器大约15秒的关闭等待时间。如果在这段时间内没有完全退出,流程就会强制终止进程。
单独看“15秒”并不一定荒谬。多数时候浏览器也许能在期限内退出,渲染结果也能正常生成。可浏览器不是一个只负责打开页面的瞬时命令,它可能还在处理页面资源、写入临时文件、回收子进程。此时把“没有及时退出”直接等同于“可以杀掉”,就把一个有限等待变成了不完整的生命周期管理。
强杀本身也不总是错误。真正危险的是,它被当成了默认的关闭方式:先等一个较短的固定时间,超时就杀。浏览器来不及完成收尾时,临时副本可能没有机会回收,于是一次渲染留下一个小尾巴,反复运行以后,小尾巴就变成了系统盘上的大问题。
这件事让我重新意识到,进程关闭不是渲染结束后的附属动作。只要程序会创建临时资源,关闭策略就属于主流程的一部分。 结果文件生成成功,并不代表这次任务完整结束;后台进程、临时目录和子进程也要进入可确认的终态。
先问默认选择是否适合自动化
接下来还有一个容易被忽略的判断:是不是应该继续把系统浏览器作为默认运行时?如果只是因为电脑上已经安装了它,或者手工打开网页时一直在用它,就顺手把它拿来做无头渲染,这个默认选择其实没有经过检查。
系统浏览器面向的是更广泛的日常使用场景。自动化工具自带的浏览器则通常和它的驱动版本、启动参数以及临时文件管理方式配套。对于一条可重复的渲染流水线,我更看重的是生命周期是否可控,而不是“这台电脑上有没有一个熟悉的浏览器”。
因此我选择把自动化工具自带的浏览器改为默认路径。只有调用者明确指定系统浏览器时,才进入系统浏览器分支。默认路径应该优先选择与自动化工具配套、生命周期更容易控制的运行时;显式特例才承担额外的不确定性。
这个决定不是说系统浏览器永远不能用。现实里可能有项目必须依赖某个浏览器,或者需要复现真实用户环境。保留显式选择,意味着这些需求仍然能被满足;但它们不再悄悄影响所有渲染任务。默认值负责保护大多数情况,参数负责承接确实需要的例外。
关闭要分成三级,而不是二选一
浏览器选择调整以后,关闭流程也必须一起改。如果只换默认浏览器,却仍然在15秒后直接强杀,那么同一类生命周期问题仍然可能发生,只是发生概率或残留形式变了。
我采用的是三级关闭策略。第一步,调用浏览器提供的正常关闭动作,让它自己完成页面、子进程和临时资源的收尾。第二步,给它一个更宽裕但有限的等待窗口,并明确等待关闭结果,而不是发出关闭请求后立刻认为任务结束。第三步,只有确认它在等待窗口内确实无法退出,才强制终止,并把这次降级记录成警告。
这里的“有限”很重要。无限等待会让一条渲染流水线卡死,完全不设上限也不是稳健;但上限的作用应该是防止异常进程拖住任务,而不应该成为正常关闭的捷径。正常退出、有限等待、最后强杀,是在资源回收和任务可用性之间留下的一条边界。
我没有把强杀彻底删除,是因为自动化任务仍可能遇到浏览器失去响应、子进程悬挂或外部环境异常。真正需要修复的不是“所有进程都必须优雅退出”这个理想,而是让强杀只出现在它该出现的位置。强制终止应该是故障兜底,而不是正常生命周期的下一步。
验证不能只看视频有没有生成
改完以后,最容易犯的错误是只看渲染结果:视频出来了,脚本返回成功,就认为问题解决。可这次的故障恰好说明,结果文件正常并不代表运行环境干净。浏览器可能已经被强杀,临时副本也可能已经留下,只是暂时没有把空间吃满。
所以我把验证拆成了两条线。第一条线看功能:用小规模任务确认页面能打开、渲染能完成、输出结果正常。第二条线看副作用:在渲染前记录相关残留目录的数量,渲染结束后再比较,确认这次运行没有新增副本。两条线都通过,才说明修复同时覆盖了“能工作”和“不会继续制造问题”。最容易误判的错误捷径,是只增加定时清理频率,却不修复浏览器进程的退出方式;它能让目录暂时变干净,却没有改变残留继续产生的条件。
测试也要覆盖默认路径和显式特例。默认情况下,应确认脚本选择的是配套浏览器;显式指定系统浏览器时,应确认这个选择仍然生效。关闭流程则要覆盖正常退出和超时兜底,至少要能证明强杀不是每次都会执行。这样做不是为了把测试数量堆上去,而是为了防止一次局部修改悄悄改变另一条调用路径。
第 1 次验证中还遇到一个现实边界:如果本机没有安装自动化工具自带的浏览器,默认路径会退回系统浏览器。这个结果不能被误读成“默认选择没有改成功”,也不能被忽略。正确做法是把缺失依赖和代码逻辑分开判断,并在环境准备好后再次确认实际启动的浏览器版本。验证要说明这次到底测到了什么,不能用理想配置替代当前事实。
下次我会怎么做
下次再遇到系统盘持续变满,我会先问3个问题:残留由哪个进程产生,产生在任务的哪个阶段,正常关闭有没有真正完成。问题还没有定位到产生时机以前,我不会先把清理任务改得更频繁,因为那只是把结果处理得更勤快。
然后我会检查默认运行时。它是否为自动化场景设计,是否和驱动、临时目录以及关闭动作配套,显式特例是否真的由调用者表达。默认路径承担的是大多数人的安全边界,不能只因为“电脑里装着”就自然获得优先级。
最后,我会把退出流程固定成正常关闭、有限等待、最后强杀,并在渲染前后比较残留数量。这个比较不复杂,却能把“好像没问题”变成可观察的证据。一个流程成熟的标志,不是它顺利跑通一次,而是它能在下一次少依赖临场猜测。
这次排查让我看到,很多磁盘问题并不是清理工具不够努力,而是上游程序没有把自己的生命周期收好。真正可复用的方法,也不是记住某个目录名,而是先找产生者,再看默认路径,最后检查退出是否完成。只要这条判断链还在,换成别的浏览器、别的渲染任务,甚至别的临时资源问题,也能从同一个起点开始处理。