strace 能看到 Workerman 进程卡在哪个系统调用上,核心是观察 epoll_wait(或 select/poll)是否空转(耗时极短、高频重复)或异常阻塞(耗时数秒),结合 read/write/accept 等调用耗时判断是 PHP 层死循环还是同步 I/O 阻塞。

strace 能看到 Workerman 进程卡在哪个系统调用上
Workerman 进程不响应、CPU 占用飙高但无日志输出,大概率是 PHP 层面的死循环(比如 while (true) 或条件永远为真),而 strace 可以帮你确认它是否真的卡在某个系统调用里,而非 PHP 解释器内部空转。
执行前先找到目标 worker 进程 PID:
ps aux | grep "php.*start.php" | grep -v grep
然后对其中一个 PID 执行:
strace -p <PID> -T -tt -e trace=select,poll,epoll_wait,read,write,accept
-
epoll_wait(Linux)或select/poll(兼容模式)是 Workerman 事件循环等待 I/O 的核心调用;如果它反复出现且耗时极短(如),说明事件循环在“空转”,大概率是 PHP 层逻辑没让出控制权 - 如果某次
epoll_wait耗时异常长(比如几秒以上),反而说明它真正在等 I/O —— 死循环嫌疑降低,要查网络或磁盘阻塞 - 避免用
-e trace=all,噪音太大;聚焦 I/O 和调度相关系统调用更有效
结合 phpdbg 或 xdebug 快速定位到死循环的 PHP 行号
strace 只能告诉你“卡在循环里”,不能告诉你“哪行 PHP 代码在循环”。这时候得切回 PHP 层调试:
启动 Workerman 时加 -d 参数启用调试模式,并用 phpdbg 挂载:
phpdbg -qrr start.php start
或直接在代码中插入断点(需提前开启 xdebug):
if ($i > 10000) { xdebug_break(); }- Workerman 默认关闭
display_errors和错误报告,死循环中的 Notice/Warning 不会打印;务必在start.php开头加error_reporting(E_ALL); ini_set('display_errors', '1'); - 不要依赖
echo或var_dump定位循环体 —— 如果循环里没 flush、没 sleep,输出可能被缓冲甚至根本刷不出来 - 检查
onMessage、onConnect、onWorkerStart回调里是否写了未设退出条件的while,尤其注意嵌套回调或闭包中引用了外部变量导致条件恒真
常见死循环场景:定时器 + 异步操作没 await 或没清任务
Workerman 的 Timer::add() 是异步的,但如果在定时器回调里又同步调用了阻塞函数(比如 file_get_contents、curl_exec),或忘了用 Timer::del() 清理,就容易形成逻辑死锁:
$timerId = Timer::add(1, function() { file_get_contents('http://slow-api'); });- 上面这段代码不会报错,但会让当前 worker 进程在每次触发时卡住 1 秒以上,看起来像“假死”
- 用
strace会看到大量read调用耗时陡增,而非epoll_wait空转 —— 这说明问题不在循环结构本身,而在同步 I/O 阻塞了事件循环 - 正确做法是改用异步 HTTP 客户端(如
Workerman\Connection\AsyncTcpConnection)或协程方案(配合Workerman\Worker::$processType = Worker::PROCESS_TYPE_COROUTINE)
别忽略信号处理和全局状态污染
Workerman 多进程模型下,onWorkerStart 中的全局变量或静态属性,可能被多个子进程共享或误复用,导致条件判断失常:
static $flag = false; if (!$flag) { $flag = true; while (true) { /* ... */ } }这种写法在单进程测试时正常,但在多 worker 场景下极易因 fork 后状态不一致引发不可预测的循环行为。
- 用
strace -p <PID> -e trace=clone,fork可观察子进程创建过程,辅助判断是否因 fork 导致状态异常 - 所有全局状态必须显式初始化,避免依赖“第一次运行”的隐含假设;优先用
Worker::$pid做上下文隔离 - 最隐蔽的死循环往往不出现在业务代码里,而是出现在第三方库的 autoload 或扩展的
RINIT阶段 —— 如果 strace 显示进程刚启动就卡住,优先检查composer autoload或extension加载逻辑


















