商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Swoole如何处理信号屏蔽与配置

Swoole如何处理信号屏蔽与配置

  发布于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 启动前就完成了。
  • PHP 7.4+ 的 pcntl_async_signals(true) 对 Swoole 同样无效,它根本打不通 Swoole 的信号拦截层。

正确注册信号的方式与时机

唯一有效的手段是 SwooleProcess::signal。它不走 PHP 用户层那一套,直接调用 Swoole 底层的 swSignal_add(),把信号写入全局 signals[] 数组,并且绑定到主线程上。硬性前提有三个:

  • 必须在主线程(Manager 或 Worker 的主循环线程)中调用,不能在 onRequestgo 协程或定时器回调里写。
  • 必须在 $server->start() 之前就注册好,否则事件循环已经接管,信号监听器压根排不上队。
  • 回调必须是纯同步逻辑,禁止出现 SwooleCoroutine::sleep()$server->shutdown()、数据库操作等协程 API。

标准写法大概是这样:

SwooleProcess::signal(SIGTERM, function () {
    echo "SIGTERM received\n";
    SwooleG::set(['shutdown' => true]);
});

$server->start(); // signal 必须在这行之前

操作系统层面容易忽略的干扰项

就算代码写得完全正确,信号还是可能收不到,这时候多半是操作系统级的问题:

  • 在 Docker 容器里,如果没把 tini 作为 PID 1 进程,SIGTERM 很可能被容器 init 直接截胡,Worker 进程压根见不着。
  • systemd 服务单元里,默认的 KillMode=control-group 会把信号发给整个 cgroup 而不是主进程,得换成 KillMode=mixed
  • OOM Killer 发送的是 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)。问题的根源通常不是信号没收到,而是“收到之后怎么收尾”这个环节没设计好——状态清理和协程生命周期没对齐。

本文转载于:https://www.php.cn/faq/2739139.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注