单例在workerman中易污染上下文,因其进程常驻且单例生命周期贯穿始终;若存用户id、请求参数等动态数据,协程切换后会残留上一请求值。应改用coroutine::getcontext()存取协程级数据,禁用自动绑定请求上下文逻辑,连接态缓存优先使用weakmap。

单例在Workerman里为什么容易污染上下文?
因为Workerman进程常驻,单例一旦初始化就存活整个生命周期;而协程或连接切换时,如果单例里存了用户ID、请求参数、数据库事务状态等动态数据,下一个请求进来就会读到上一个请求残留的值——这不是线程安全问题,是作用域错配。
用Coroutine::getContext()替代全局单例存请求数据
PHP 8.0+ 的 Coroutine::getContext() 是当前协程专属的键值存储,比静态变量安全得多。它不跨协程共享,也不依赖类实例生命周期。
- 不要这么写:
self::$currentUser = $user;(所有后续请求都可能读到) - 应该这么写:
Coroutine::getContext()['user'] = $user;,后续同协程内用Coroutine::getContext()['user']取 - 注意:仅限协程环境有效;非协程回调(如传统
onMessage)仍需手动传参或用连接 ID 做上下文隔离
静态单例必须禁用“自动绑定请求上下文”逻辑
很多框架封装的单例(如日志器、配置管理器)会悄悄把 $_GET 或 Request 对象塞进自身属性。Workerman 5.0 已移除 CGI 兼容层,$_GET 在 onMessage 里根本不存在,这类逻辑一执行就报错或静默污染。
- 检查单例类是否调用了
Request::instance()、input()等依赖超全局变量的方法 - 禁用所有自动初始化请求上下文的钩子,比如 ThinkPHP 的
App::bind('request', ...) - 改用显式传参:
$logger->withContext(['uid' => $uid, 'conn_id' => $connection->id])
连接级上下文优先用 WeakMap,别用静态数组索引
如果你非得在单例里缓存每个连接的状态(比如 WebSocket 用户登录态),用 WeakMap 是最稳妥的选择——它不阻止 GC 回收连接对象,也不会因闭包捕获导致循环引用。
- 错误示范:
self::$contexts[$connection->id] = $data;(ID 可能重复、连接对象被强引用) - 正确做法:
$map = new WeakMap(); $map[$connection] = $data;,连接关闭后自动失效 - 注意:
WeakMap键必须是对象,不能是整数或字符串;且 PHP 版本 ≥ 8.0











