FrankenPHP Worker模式下$_FILES"xxx"不会自动清理,必须显式调用unlink();register_shutdown_function()可能因goroutine中断失效,安全做法是move_uploaded_file()成功后立即unlink()并忽略ENOENT错误。

FrankenPHP Worker 模式下 $_FILES["xxx"]["tmp_name"] 不会自动清理
是的,必须自己清理。FrankenPHP 在 Worker 模式(即作为 SAPI 运行在 Go HTTP server 内)中,$_FILES 的临时文件行为与传统 PHP-FPM 完全一致:PHP 仅负责把上传文件写入临时目录,**不承诺、也不执行任何自动删除逻辑**。哪怕脚本正常结束,move_uploaded_file() 成功后,原 tmp_name 文件仍留在磁盘上——除非你显式调用 unlink()。
为什么 register_shutdown_function() 在 Worker 模式下可能失效
Worker 模式下每个请求由独立 goroutine 执行,而 PHP 生命周期被嵌入 Go 的 request handler 中。这意味着:
-
register_shutdown_function()注册的回调,只在当前 PHP 请求执行流结束时触发;但如果 handler panic、超时中断、或 Go 层提前 return,PHP 的 shutdown 阶段可能根本没机会运行 - 多个并发请求共用同一临时目录(如
/tmp),shutdown 函数若未绑定具体$tmpFile变量,容易误删其他请求的临时文件 - Worker 模式下无传统“进程退出”语义,
__destruct()也受限于对象生命周期,无法覆盖所有异常路径
安全清理的实操要点
最稳妥的做法是:**在确认处理完成(如 move_uploaded_file() 返回 true)后立即 unlink(),并忽略 ENOENT 错误**。原因如下:
- 不要依赖
is_uploaded_file()后再删——它只校验来源合法性,不保证文件还存在 - 务必检查
unlink()返回值,但对unlink(): No such file or directory这类错误应error_log()记录而非throw,因为文件可能已被 move 或其他 goroutine 清理 - 避免在
try/catch外围写unlink()—— 异常抛出时该行不会执行;应放在finally块,或更推荐:用move_uploaded_file()成功后直接删 - 若使用
tempnam()或fopen('php://temp')等非$_FILES场景,仍需手动unlink();tmpfile()是例外,它返回资源句柄,PHP 进程结束时自动释放(但 Worker 模式下进程长存,不适用)
真正容易被忽略的是磁盘空间释放时机
即使你 unlink() 成功,如果该文件正被其他进程(如日志轮转、备份脚本、甚至另一个卡住的 PHP worker)以 O_RDONLY 方式打开,Linux 下文件 inode 仍被占用,磁盘空间不会立刻释放。此时 lsof +L1 | grep php 会显示 DEL 状态。生产环境必须配合外部清理机制:比如每小时 cron 扫描 sys_get_temp_dir() 下 30 分钟未访问的 php* 文件,且仅当 lsof -w -n -p $(pgrep -f 'frankenphp') | grep -q 不匹配时才删除——否则就是拿空间换稳定性。
立即学习“PHP免费学习笔记(深入)”;



















