Co::set() 的 hook_flags 必须在 Co::run() 回调开头、所有 go() 前调用,否则因上下文错误报 Invalid argument;它仅作用于当前协程树,不全局生效。
Co::set() 的 hook_flags 必须在 Co::run() 内部、且所有 go() 之前调用,否则直接报错或 Hook 失效——这不是配置问题,是执行上下文错了。
错误不是出在 flag 值上,而是调用位置。Swoole 的钩子设置是协程局部行为,Co::set() 只能在已进入协程上下文后生效。主进程(即 Co::run() 外)调用它,底层直接拒绝。
Co::run() 回调函数最开头,早于任何 go()
go() 回调内部、onRequest 回调里单独调用Co::set() 不是全局开关,它只影响当前协程及其后续派生的子协程;但因为 Co::run() 是协程调度入口,所以“在它里面最早设一次”就覆盖了整条协程树它们劫持的是完全不同的函数调用链:前者针对 PHP 流层(stream_socket_client()、fsockopen()、file_get_contents('http://')),后者专攻 libcurl 绑定的 curl_exec()。Guzzle 默认走流,所以开 SWOOLE_HOOK_TCP 就够;原生 cURL 必须显式加 SWOOLE_HOOK_CURL,否则整个 Worker 会被阻塞。
file_get_contents() → 归 SWOOLE_HOOK_TCP 管file_get_contents('/tmp/a.txt') → 必须开 SWOOLE_HOOK_FILE
SWOOLE_HOOK_TCP | SWOOLE_HOOK_FILE
SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL,避免漏掉 cURL别只看代码有没有报错,要测行为。最简单有效的方式是并发 sleep:启动两个 go(),各自调用 swoole_timer_sleep(1)(注意不是 sleep(1),后者会阻塞进程),观察总耗时。
PDO::query())strace -p $(pidof php) 观察是否还有 nanosleep 系统调用;有则说明仍有阻塞点Hook 本质是指针替换,只覆盖它明确注册的函数列表。像 PDO::query()、mysqli::query() 这类数据库操作,虽然底层也走 socket,但绕过了 PHP 流层,Swoole 默认不处理。它们不会自动协程化,必须依赖适配了协程的客户端(如 swoole_mysql 或 Hyperf 的协程 PDO)。
exec()、shell_exec()、proc_open() 等进程创建函数,Swoole 不 Hook,也不建议在协程中直接调用真正容易被忽略的点在于:Hook 不是魔法开关,它只作用于特定函数调用路径;一旦业务逻辑绕过这些路径(比如直连 socket、用扩展自定义 I/O),那协程调度就无从介入。判断一个函数是否被 Hook,最可靠的方式不是查文档,而是看它底层是否最终落入 Swoole 注册的函数指针表——这需要你对 PHP 扩展机制和 Swoole 的 ext-src/swoole_runtime.cc 实现有基本认知。