来自 #288 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。
💡 [建议 · 并发] bannet 构造仍与连接耦合全局读竞争窗口 bannet/connection.go:52
问题根因:NewConnection 在构造时仍读 config.G.WorkerPoolSize(useWorkerPool)与 config.G.MaxPackageSize、config.G.MaxMsgChanLen。虽然这比每帧读全局有巨大改善,但 PR 的论据——'全局可变状态被并发读取是数据竞争的来源'——对 NewConnection 自身并未完全消除:多个连接并发创建时,若有人(测试或上层)在构造期间修改 config.G,NewConnection 的读取仍与其形成数据竞争。这与 PR 声称要消除的 storage 层问题属同一类,只是把竞争点从热路径移到了冷路径(构造期)。
为什么低级解法不够:这确实是冷路径,风险比热路径读小得多,所以把它列为'建议'而非'重要'。但仅做'移位置'不解决根因——全局可变单例被并发读的本质竞争仍存在,只是窗口变窄了。通用评审者会认为'已经够好了'。
架构级方案:与 storage 层一致地把 Connection 的配置参数改为显式注入:NewConnection(conn, connID, handle, server, cfg ConnectionConfig),其中 ConnectionConfig 由 Server 在 Accept 时构造一次(DefaultConnectionConfig()),连接实例不再直接 touch config.G。这样存储层与网络层的模式完全对齐——全局配置只在进程入口/accept 环路的一个点上被读取,且读的那个点可以加一份快照锁规避竞争。
代价/收益:代价:NewConnection 签名变化,牵动 Server/Accept 环路及所有测试构造点。收益:与 storage 层统一了设计模式,彻底消除网络层残余的全局竞争读取;也是后续把 service 层参数化的同一条路径上的过渡步骤。
💡 [建议 · 并发] bannet 构造仍与连接耦合全局读竞争窗口
bannet/connection.go:52问题根因:
NewConnection在构造时仍读config.G.WorkerPoolSize(useWorkerPool)与config.G.MaxPackageSize、config.G.MaxMsgChanLen。虽然这比每帧读全局有巨大改善,但 PR 的论据——'全局可变状态被并发读取是数据竞争的来源'——对NewConnection自身并未完全消除:多个连接并发创建时,若有人(测试或上层)在构造期间修改config.G,NewConnection的读取仍与其形成数据竞争。这与 PR 声称要消除的 storage 层问题属同一类,只是把竞争点从热路径移到了冷路径(构造期)。为什么低级解法不够:这确实是冷路径,风险比热路径读小得多,所以把它列为'建议'而非'重要'。但仅做'移位置'不解决根因——全局可变单例被并发读的本质竞争仍存在,只是窗口变窄了。通用评审者会认为'已经够好了'。
架构级方案:与 storage 层一致地把
Connection的配置参数改为显式注入:NewConnection(conn, connID, handle, server, cfg ConnectionConfig),其中ConnectionConfig由Server在Accept时构造一次(DefaultConnectionConfig()),连接实例不再直接 touchconfig.G。这样存储层与网络层的模式完全对齐——全局配置只在进程入口/accept 环路的一个点上被读取,且读的那个点可以加一份快照锁规避竞争。代价/收益:代价:
NewConnection签名变化,牵动Server/Accept环路及所有测试构造点。收益:与 storage 层统一了设计模式,彻底消除网络层残余的全局竞争读取;也是后续把service层参数化的同一条路径上的过渡步骤。