gdb attach会冻结整个worker进程:向进程发送sigstop导致所有协程、定时器、i/o全部卡住,5秒内未恢复即引发客户端超时、websocket断连、http挂起等服务级中断。

gdb attach 会冻结整个 Worker 进程
当你对正在运行的 Swoole Worker 进程执行 gdb attach $PID,gdb 会向该进程发送 SIGSTOP,导致整个进程暂停——不只是当前协程,而是所有正在运行的协程、定时器、I/O 等待全部卡住。5 秒内未恢复,客户端连接就会触发超时;WebSocket 心跳断连、HTTP 请求挂起、Task 进程积压,问题立刻外溢。
这不是“慢一点”,是服务级中断。Swoole 的多进程模型中,gdb attach 只能作用于单个 Worker,但你无法预判哪个 Worker 正在处理关键请求;一旦 attach 错了,故障就已发生。
实操建议:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 仅在开发环境、无真实流量时使用,且必须搭配
timeout -s SIGCONT 30 gdb -p $PID自动恢复 - 避免在 onReceive / onMessage 等高频回调中 attach,优先选在
onWorkerStart后短暂空闲期 - 用
strace -p $PID -e trace=epoll_wait,write,close替代,限定系统调用范围,降低干扰
Xdebug 断点会让协程调度器彻底失效
Xdebug 的断点不是“暂停协程”,而是让整个 PHP 线程进入同步阻塞态——它强制 Zend 引擎停止执行、等待 IDE 指令。此时 Swoole 的协程调度器(Swoole\Coroutine::schedule())无法 tick,所有协程 stuck 在 yield 前,Co::sleep() 不唤醒、Co::httpGet() 不返回、甚至 echo 都卡住。
更隐蔽的问题是:Xdebug 的钩子(hook)与 Swoole 协程 hook 冲突。比如你在 __construct 打断点,Xdebug 可能抢先拦截 ZEND_INIT_METHOD_CALL,导致 Swoole 来不及注入协程上下文,后续 Co::getcid() 返回 false 或崩溃。
实操建议:
- 禁用
xdebug.start_with_request=yes,改用xdebug.start_with_request=trigger+?XDEBUG_TRIGGER=1按需激活 - 只在启动逻辑(如
onWorkerStart)、同步代码块(非Co::调用路径)中设断点 - 绝对不要在协程内(例如
go(function () { ... }))里放xdebug_break(),它不会生效,还可能引发段错误
两者日志输出和调试上下文完全不同
gdb 看到的是 C 层栈帧:swServer_onAccept、php_coro_create、zif_swoole_http_response_end……你能看到内存地址、寄存器值、Zval 结构体布局,但 PHP 变量名、作用域、对象属性全丢失;而 Xdebug 输出的是完整的 PHP 调用栈,含文件行号、参数值、$this 对象 dump,但它完全看不见协程 ID、当前调度状态、底层事件循环位置。
这意味着:查内存泄漏用 gdb(配合 info proc mappings + dump binary memory),查业务逻辑错用 Xdebug;但想搞清“为什么这个协程没 resume”,两者都帮不上忙——得靠 Swoole\Coroutine::listCoroutines() 和 Co::stats()。
实操建议:
- 用
gdb查段错误、僵尸协程、fd 泄漏时,先set follow-fork-mode child,否则只跟到 Manager 进程就停了 - 用
Xdebug时打开xdebug.collect_params=1(而非 4),避免大数组序列化拖垮响应 - 两者日志都别依赖 stdout/stderr:gdb 输出重定向到
/tmp/gdb.log,Xdebug 日志设xdebug.log=/tmp/xdebug.log,防止被 Swoole 的daemonize吞掉
真正该盯住的不是工具,是协程生命周期
无论是 gdb 还是 Xdebug,它们的设计前提都是“一次请求一个线程”或“短生命周期脚本”。Swoole 的常驻进程+协程复用模型,让传统调试工具的“上下文”概念彻底失效:同一个 PHP 进程里,$cid = Co::getcid() 每次都不同,static 变量跨协程污染,register_shutdown_function 不再可靠。
所以最常被忽略的一点是:你看到的“变量值”,可能属于上一个已结束的协程残留;你打断点的位置,可能被下一个协程复用;你 attach 的 PID,下一秒就被 Manager 杀掉重启——这些都不是工具的问题,是模型 mismatch。










