laravel队列任务中临时文件清理不可依赖finally,因信号中断时php不执行finally;应采用显式生命周期管理:handle开头记录路径、结尾统一删除,并在failed()中兜底清理。

队列任务里临时文件没删干净,finally 块不执行?
不是 finally 失效,而是 Laravel 队列进程被信号中断(比如 php artisan queue:work 被 SIGTERM 杀掉)时,PHP 不会触发 finally —— 这是 PHP 运行时限制,和 Laravel 无关。
常见现象:任务中用 tempnam() 或 sys_get_temp_dir() 创建了临时文件,本地测试正常,上线后磁盘悄悄爆满。
- 别依赖
try...catch...finally做最终清理,它在队列 worker 被强制终止时大概率跳过 - 改用「显式生命周期管理」:在任务逻辑开头生成带唯一标识的临时路径,结尾主动调用清理;同时配合 Laravel 的
Job::delete()或Job::fail()钩子补漏 - 更稳妥的做法是把临时文件写到内存(
php://temp或php://memory),避免落地磁盘
Laravel 队列任务中怎么安全释放临时文件?
核心原则:清理动作必须和任务状态解耦,不能卡在业务逻辑里。Laravel 提供了两个可靠入口点:__destruct() 和 failed() 方法,但要注意它们的触发条件。
-
__destruct()在对象销毁时调用,但队列 worker 可能复用实例(尤其使用queue:listen或horizon时),不一定及时触发 -
failed()只在任务抛出未捕获异常且重试耗尽后执行,成功任务不会进这里 - 推荐做法:在任务
handle()开头记录临时路径到属性(如$this->tempFiles = []),结尾统一array_map('unlink', $this->tempFiles);再加一层兜底 —— 在failed()里也清一次 - 示例:
public function handle() { $tmp = tempnam(sys_get_temp_dir(), 'export_'); $this->tempFiles[] = $tmp; // ...处理逻辑... foreach ($this->tempFiles as $file) { if (file_exists($file)) @unlink($file); } } public function failed(Throwable $exception) { foreach ($this->tempFiles as $file) { if (file_exists($file)) @unlink($file); } }
为什么用 Storage::temporaryUrl() 不能替代本地临时文件清理?
这个方法只解决「对外提供临时访问链接」的问题,和任务内部生成的中间文件完全无关。它不创建本地文件,也不参与你的 handle() 流程。
-
Storage::temporaryUrl()是给 S3/MinIO 等远程存储生成带签名的短期 URL,底层压根没碰本地磁盘 - 如果你的任务要导出 Excel、压缩 ZIP、转码视频,这些操作必然产生本地临时文件,必须自己管
- 混淆这两者,会导致误以为用了临时 URL 就不用清理 —— 实际上临时文件还在
/tmp里躺着,只是你没看见
Horizon 环境下临时文件残留更隐蔽,怎么查?
Horizon 默认启用 supervisor + long-running worker,一个进程可能跑几百个任务,临时文件若没及时删,会越积越多,且日志里几乎不报错。
- 检查方式:
ls -lt /tmp | head -20看是否有大量phpXXXXXX文件,或你自定义前缀的文件(比如export_) - 关键配置项:
horizon.php中的memory_limit和timeout设置过松,会让 worker 活太久,放大遗漏风险 - 建议加一行监控:在
handle()结尾加Log::debug('temp files cleaned: '.count($this->tempFiles));,上线后 grep 日志确认是否真执行了
unlink() 更可靠。真正容易被忽略的,是信号中断场景下的清理失效 —— 它不报错,只沉默地吃掉磁盘空间。











