必须显式监听 sigterm/sigint 并调用 $server->shutdown(),否则 swoole 不会自动触发优雅停机,因其主循环默认忽略所有系统信号,需开发者主动注册信号处理器以实现可控停机。

必须显式监听 SIGTERM/SIGINT 并调用 $server->shutdown(),否则 Swoole 不会自动触发优雅停机。
为什么 $server->shutdown() 不会自动执行
Swoole 的主循环默认忽略所有系统信号,包括 SIGTERM 和 SIGINT。即使你用 kill -15 发送终止信号,进程也不会响应,除非你主动注册信号处理器。这不是 bug,而是设计使然——Swoole 把信号控制权完全交还给开发者,避免隐式行为干扰业务逻辑。
常见错误现象:ps aux | grep server.php 显示进程还在,curl 却已无法建立新连接(端口监听已关闭),但旧连接卡住不退出、定时器照常运行、task 进程未清理。
实操建议:
- 只在主 Worker(
$workerId === 0)或 Manager 进程中注册信号,避免每个 Worker 都重复监听 - 使用
pcntl_signal()而非Swoole\Process::signal()更稳定,后者在某些 Swoole 版本中存在异步信号丢失风险 - 务必调用
pcntl_async_signals(true)启用异步信号处理,否则信号可能被延迟甚至丢弃
$server->shutdown() 做了什么、没做什么
它只做两件事:关闭监听 socket(拒绝新连接)、标记服务器进入“关闭中”状态。其余全部交由你手动收尾。
它不会:主动关闭已有 TCP 连接、停止定时器、取消协程、等待 task 进程结束、释放全局资源(如 Redis 连接池)。
所以实际停机流程是:shutdown() → 等待活跃请求自然结束 → 手动遍历 $server->connections 关闭长连接 → 调用 $server->tick() 或 $server->clearTimer() 清理定时器 → 显式调用 $server->taskwait() 等待 task 完成 → exit(0)。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
示例关键片段:
$server->on('WorkerStart', function ($server, $workerId) {
if ($workerId === 0) {
pcntl_async_signals(true);
pcntl_signal(SIGTERM, function () use ($server) {
echo "SIGTERM received\n";
$server->shutdown();
// 此处开始手动清理
foreach ($server->connections as $fd) {
$server->close($fd);
}
$server->clearTimer();
exit(0);
});
}
});
Docker / systemd 环境下信号传递容易失效
容器或服务管理器默认不把 SIGTERM 透传给 PHP 主进程。Docker 中 docker stop 发的信号可能被 shell wrapper 拦截;systemd 中若 daemonize = true,主进程 fork 后退出,systemd 认为服务已死,后续信号发给谁都不知道。
实操建议:
- Dockerfile 中确保
CMD ["php", "server.php"]是直接 exec,不要套sh -c - PHP 启动脚本里设
$server->set(['daemonize' => false]),让进程前台运行 - systemd service 文件中写
Type=simple,并加KillSignal=SIGTERM,避免默认用SIGKILL - 验证是否收到信号:在信号回调里
file_put_contents('/tmp/shutdown.log', "got signal\n", FILE_APPEND),然后docker stop后检查日志
超时强制关闭是必须加的兜底逻辑
依赖“请求自然结束”很危险——前端重试、客户端断连异常、协程卡死都会导致进程 hang 住。生产环境必须设硬性超时。
推荐做法:在信号回调里启动一个 $server->tick(1000, ...),每秒检查一次是否还有活跃连接或未完成 task,累计满 15 秒就 exit(1)。
注意点:
- 别用
sleep()或usleep(),会阻塞事件循环,整个服务器假死 -
$server->connection_list()返回的是当前活跃连接 fd 列表,但要注意它不包含已半关闭但尚未close()的连接 - 如果用了 task 进程,必须调用
$server->taskwait(10)等待,否则shutdown()后 task 可能仍在后台跑
真正可靠的优雅停机,从来不是靠一个函数调用,而是信号监听 + 主动清理 + 超时兜底三者缺一不可。最容易被忽略的是 task 进程等待和定时器清理——这两项不做,90% 的“优雅”都只是看起来优雅。










