Context::set()未配对del会导致协程退出时数据残留泄漏,子协程无法访问父协程Context数据,单例服务存请求级属性引发串流,定时任务等长周期场景易累积内存泄漏。

Context::set() 后没配对 Context::del() 会泄漏
协程退出时,Context::set() 写入的数据不会自动清理。如果中间件或服务里反复 Context::set('user', $user) 却不 Context::del('user'),这些数据就一直留在协程上下文存储里——不是“没释放”,是根本没人删。
常见泄漏场景:
- 中间件中 set 了
'request_id'、'user_id',但没在响应后清理 - 异步任务里 set 了临时配置,任务结束就不管了
- 用
Context::set(getRootCid(), ...)时,误以为根协程 ID 能自动回收,实际仍需手动 del
修复建议:
- 所有
Context::set()都应在作用域结束前配对Context::del(),尤其在中间件process()方法末尾 - 避免在
try块里 set 却在catch外漏掉 del;推荐用finally包裹清理逻辑 - Hyperf 3.2+ 可启用
hyperf/memory-leak-detector组件,运行php bin/hyperf.php memory:leak-detect扫描长期驻留的上下文键
子协程直接调 Context::get() 拿不到父协程数据
这不是 bug,是 Swoole 协程模型的设计前提:Context::get() 查的是当前协程 ID 对应的存储空间,而子协程有自己独立的 CID,父协程写入的数据天然不可见。
典型现象:
- 主协程 set 了
'trace_id',子协程Context::get('trace_id')返回null - 日志链路断开、RPC 元信息丢失、权限校验失败
正确做法不是“让它自动继承”,而是显式透传:
- 用
go(function () use ($ctx) { Context::copy($ctx); ... }),其中$ctx = Context::all() - 或更轻量:只传关键字段,
$userId = Context::get('user_id'); go(function () use ($userId) { Context::set('user_id', $userId); ... }); - 禁止在
AsyncTask::execute()或裸Coroutine::create()中直接Context::get(),除非确认框架已封装透传(如 Job 组件)
单例服务里存 $this->user_id 导致跨请求串流
Hyperf 默认把服务注册为单例,整个 Worker 进程生命周期内只初始化一次实例。$this->user_id 是对象属性,被所有协程共享——A 请求写入,B 请求读取,数据必然错乱。
这种写法比 Context 泄漏更危险,因为它不依赖协程上下文,而是直接污染对象状态:
-
UserService中定义protected $currentUser;并在方法里赋值 - Controller 里调用
$this->userService->getCurrentUser(),返回的可能是上一个请求的用户
修复路径很明确:
- 删掉所有
$this->xxx存请求级数据的写法 - 改用
Context::set('user', $user)+Context::get('user'),并确保 key 有命名空间前缀(如'auth.user')防冲突 - 若必须用服务属性,改用
#[Inject]注入非单例服务,或每次 new 实例(代价高,不推荐)
定时任务和长周期协程是泄漏重灾区
定时任务(@Cron)跑在常驻 Worker 进程里,每次执行若积累变量、缓存大数组、未释放资源,内存只增不减。它不像 HTTP 请求有明确生命周期,容易被忽略。
典型泄漏点:
- 全局数组
$this->cache[] = $item;不清空 - 闭包
use ($container)持有容器强引用,阻止 GC 回收 - Redis pipeline/multi 执行异常后没
exec()或reset(),连接卡在上下文里
排查建议:
- 用
co::stats()查当前协程数是否持续增长;用memory_get_usage(true)在任务前后打点对比 - 检查
config/autoload/crontab.php中任务频率是否过高(如每秒执行),考虑合并或降频 - 所有长周期逻辑,必须在 finally 块中清理上下文、关闭连接、unset 大对象
真正难发现的不是“哪行代码泄漏”,而是“哪段逻辑本该清理却忘了”。Context 泄漏往往叠加在定时任务、RPC 调用、异步队列这些长生命周期场景里,单独看都正常,合起来就缓慢 OOM。盯住 set/del 配对、go 透传、单例属性这三点,比调优协程启动频次重要得多。











