发布于2026-07-05 阅读(0)
扫一扫,手机访问
Swoole 的信号处理,一直是不少开发者踩坑的重灾区。很多时候你写了信号注册代码,进程却像个石头一样纹丝不动,日志也没任何输出。我先说结论吧,干脆一点:
Swoole 默认会屏蔽所有信号,你必须用 SwooleProcess::signal 在主线程、事件循环启动之前完成注册,而且不能在协程里调用。至于大家熟悉的 pcntl_signal,在 Worker 进程里基本等于废了。
pcntl_signal 在 Swoole 里不触发?Swoole 一启动,底层就会调用 swSignal_none() 把所有线程的信号——包括 Worker 的——统统设置为 SIG_BLOCK。这时候你再调用 pcntl_signal(SIGTERM, $cb),内核压根不会把信号投递给 PHP 层的回调,信号像被扔进了黑洞,直接静默丢弃。
常见的场面就是:kill -SIGTERM {pid} 之后,进程毫无反应,日志没动静,协程该跑还跑。拿 strace 一抓就能看到,信号始终处于阻塞状态,根本发不过去。
要点还得再拎一下:
pcntl_signal 天生是为传统 CLI 那种短生命周期脚本设计的,和 Swoole 的多线程加事件循环模型八字不合。onWorkerStart 里才去注册,也晚了——信号屏蔽早在 Server 启动前就完成了。pcntl_async_signals(true) 对 Swoole 同样无效,它根本打不通 Swoole 的信号拦截层。唯一有效的手段是 SwooleProcess::signal。它不走 PHP 用户层那一套,直接调用 Swoole 底层的 swSignal_add(),把信号写入全局 signals[] 数组,并且绑定到主线程上。硬性前提有三个:
onRequest、go 协程或定时器回调里写。$server->start() 之前就注册好,否则事件循环已经接管,信号监听器压根排不上队。SwooleCoroutine::sleep()、$server->shutdown()、数据库操作等协程 API。标准写法大概是这样:
SwooleProcess::signal(SIGTERM, function () {
echo "SIGTERM received\n";
SwooleG::set(['shutdown' => true]);
});
$server->start(); // signal 必须在这行之前
就算代码写得完全正确,信号还是可能收不到,这时候多半是操作系统级的问题:
tini 作为 PID 1 进程,SIGTERM 很可能被容器 init 直接截胡,Worker 进程压根见不着。KillMode=control-group 会把信号发给整个 cgroup 而不是主进程,得换成 KillMode=mixed。SIGKILL,这玩意儿不可捕获、不可忽略,任何信号注册都没用。频繁出现的话,先查 dmesg -T | grep "killed process"。SIGHUP 在 Swoole 里默认用于重载配置,如果被你覆盖了,热更新功能就会失效。收到信号只是第一步,真正的难点在于怎么把状态协同好。如果直接调用 $server->shutdown(),所有连接会瞬间断开,客户端那边看到的全是 Connection reset by peer。
比较安全的做法是分阶段推进:
$isShuttingDown = true),不执行任何 I/O 操作。onRequest 里检查这个标志,拒绝新请求并返回 503。$fd,先调用 $server->exist($fd) 确认还活着,再执行 close。这里想特别提一下一个容易被忽略的细节:残留的协程可能还在向已经关闭的 $fd 写入数据,这种情况下会报 SWOOLE_ERROR_SESSION_CLOS(2026)。问题的根源通常不是信号没收到,而是“收到之后怎么收尾”这个环节没设计好——状态清理和协程生命周期没对齐。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8