根本原因是事件循环未启动或文件描述符已关闭;需调用swoole_event_wait()显式等待,禁用协程环境使用,并避免在回调中直接fclose。

为什么 swoole_event_add 的回调不触发
根本原因通常是事件循环没启动,或者文件描述符已关闭。Swoole 的隐藏事件回调(比如 swoole_event_add 注册的)依赖底层 reactor 线程持续运行,一旦主流程退出、或调用 swoole_event_exit、或 fd 被提前 close,回调就永远等不到。
常见错误现象:swoole_event_add 返回 true,但注册的回调函数从不执行;调试时发现 strace 显示无 epoll_wait 调用。
- 确保在
swoole_event_add后调用swoole_event_wait()(非协程模式下必须显式等待) - 避免在回调里直接
fclose或stream_close,改用swoole_event_del($fd)+fclose - PHP CLI 模式下,不能依赖脚本自然结束来释放资源——必须手动
swoole_event_del再fclose - 协程环境下禁用
swoole_event_add,它和go+co::sleep不兼容,会卡死
swoole_timer_tick 和 swoole_timer_after 怎么选
两者底层都走同一个 timerfd,但语义和生命周期完全不同。选错会导致定时器堆积、内存泄漏,甚至进程假死。
使用场景:swoole_timer_tick 适合轮询检查(如心跳探测、缓存刷新),swoole_timer_after 适合单次延后动作(如连接超时、异步任务兜底)。
-
swoole_timer_tick(1000, function() { ... })每秒执行,返回 timer_id,必须用swoole_timer_clear($id)主动清理 -
swoole_timer_after(5000, function() { ... })只执行一次,无需手动清理,但闭包内若引用了大对象(如 $server、$request),可能延迟 GC - 高频 tick(如 tick +
usleep协程组合 - 在
onReceive回调里反复创建tick而不销毁,是内存泄漏最常见来源
协程里怎么安全复用传统回调逻辑
很多老项目用 stream_select + 回调模型,迁移到 Swoole 协程时,直接套用会导致“回调进了协程但没挂起”,结果就是 CPU 100%、协程调度失灵。
关键不是“怎么写回调”,而是“怎么让回调不破坏协程上下文”。Swoole 本身不提供“协程版事件回调”API,得绕道。
- 禁用
swoole_event_add/swoole_timer_*等传统 API,改用co::read、co::write、co::sleep - 如果必须保留回调(如集成第三方 SDK),用
Swoole\Coroutine::create(function () use ($callback) { $callback(); })包一层,但注意:这会新建协程,不共享父协程的局部变量 - 对 socket 类 fd,优先用
Swoole\Coroutine\Socket,它内部自动处理事件注册与协程挂起,比手写swoole_event_add安全十倍 - 别在回调里调
go—— 协程嵌套协程容易丢上下文,尤其涉及defer或异常捕获时
回调里 $server->connection_info 返回 false 的真实原因
这不是 bug,是设计约束:只有在 onReceive、onClose、onConnect 这类“连接上下文明确”的回调里,$server->connection_info 才可靠。其他地方(比如 swoole_timer_tick 或自定义 swoole_event_add 回调)查不到连接信息,因为根本不在连接上下文中。
常见错误现象:在定时器回调里硬查 $server->connection_info($fd),返回 false,接着 unset 导致空指针警告。
- 想查连接状态,优先用
$server->exist($fd)判断连接是否还活着 - 需要绑定连接数据,改用
$server->set(['task_worker_num' => 1])+$server->task()把 fd 传进 task 进程处理 - 真要跨回调访问连接数据,把关键字段(如 uid、ip)存在
swoole_table或redis,别强依赖connection_info - 注意
$fd在onClose后立刻失效,connection_info查不到,但$server->exist($fd)还可能返回 true,有短暂窗口期
真正难的不是写回调,是判断这个回调此刻有没有完整的上下文、有没有被正确挂起、有没有人悄悄 close 了 fd。这些细节不会报错,只会让行为变得不可预测。










