frankenphp 文件句柄持续增长源于 php worker 或 go 进程未及时关闭文件、socket、eventfd 等资源,需通过 /proc/pid/fd 监控、lsof 分析及代码审查定位泄漏点。

FrankenPHP 运行中文件句柄持续增长,本质是 PHP Worker 或底层 Go 进程未及时关闭打开的文件、socket、eventfd 等资源。它不像传统 FPM 那样请求结束即销毁全部上下文,协程/长生命周期模型下,资源释放必须显式或严格受控——漏掉一次 fclose()、stream_close()、curl_close(),或未 defer 关闭,就可能累积泄漏。
盯住 /proc/PID/fd 实时变化,快速锁定问题进程
FrankenPHP 主进程和 worker 进程都可能是泄漏源。先确认具体 PID:
- 查主进程:
ps aux | grep frankenphp,找带server参数的那行 - 查活跃 worker:
lsof -i :8000 | grep ESTABLISHED | awk '{print $2}' | sort -u(替换为你监听的端口)
对疑似 PID 执行:
-
ls /proc/12345/fd/ | wc -l记下初始值 -
watch -n 1 'ls /proc/12345/fd/ | wc -l'每秒刷新,数字持续上涨即确认泄漏 - 注意 watch 默认高亮变动行,比手动敲更省力
用 lsof 分析 fd 类型分布,识别泄漏模式
单次快照没用,但结合增长趋势看类型占比很关键:
-
lsof -p 12345 | grep socket | wc -l—— 若远超当前并发连接数(比如只有 5 个请求却有 200+ socket),说明连接未 close 或未正确复用 -
lsof -p 12345 | grep anon_inode—— 大量anon_inode:[eventpoll]或[timerfd]表明 Go runtime 或 PHP 扩展中的 epoll 实例、定时器未释放 - 重点看 NAME 列含
(deleted)的条目:文件已被unlink(),但进程仍持 fd,常见于日志轮转后未 reload 或临时文件未清理
检查 PHP 代码里常见泄漏点(尤其协程场景)
FrankenPHP 常搭配 Swoole 或原生协程使用,以下写法极易泄漏:
- 用
fopen()打开文件但没配fclose(),尤其在异常分支或循环中遗漏 - 用
cURL时只调curl_init()和curl_exec(),忘了curl_close() - 协程内打开 stream(如
stream_socket_client())后未显式关闭,或未用defer包裹 - 静态变量或全局数组意外缓存了 resource(如
$GLOBALS['handles'][] = fopen(...)) - 第三方扩展(如 Redis、PDO)未调用
close()或未触发自动回收(某些扩展需主动断连)
启用 FrankenPHP 调试与运行时防护
利用其内置机制辅助定位:
- 启动时加
--debug参数,开启详细日志,观察是否有重复 open、无对应 close 的线索 - 在 PHP 代码中定期输出句柄统计:
echo "FD count: " . count(glob('/proc/' . getmypid() . '/fd/*')) . "\n"; - 若用 Swoole,开启内存协程保护:
swoole.enable_coroutine_memory_guard = 1,配合gc_collect_cycles()观察回收效果 - 临时在关键资源操作前后打点日志,例如
error_log("[OPEN] $path");和error_log("[CLOSE] $path");,确认成对出现
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











