-
Notifications
You must be signed in to change notification settings - Fork 56
Open
Description
在 third-party/mcube/ioc/config/log 中,Config.loggers 是一个共享的 map[string]*zerolog.Logger,会在运行时被多个并发请求访问。
当前实现存在并发安全问题:
Logger(name)在创建子 logger 时会向m.loggers写入,并且这条写路径有加锁IsMyLogger(logger)会遍历m.loggers判断当前 logger 是否属于当前配置,但这条读路径没有加锁
在并发请求场景下,如果一个 goroutine 正在 IsMyLogger() 中遍历 m.loggers,另一个 goroutine 同时在 Logger() 中写入 m.loggers,就会触发 Go runtime panic:
fatal error: concurrent map iteration and map write
该 panic 会直接导致整个服务进程退出。
## 原始代码
IsMyLogger 原始实现如下:
func (m *Config) IsMyLogger(logger *zerolog.Logger) bool {
if logger == nil || m.root == nil {
return false
}
// 快速检查:是否是 root logger
if logger == m.root {
return true
}
// 遍历所有存储的 logger 进行比较
for _, storedLogger := range m.loggers {
if storedLogger == logger {
return true
}
}
return false
}从实现上看,m.loggers 的写操作在 Logger() 中受互斥锁保护,但 IsMyLogger() 对同一个 map 的遍历没有任何同步保护,因此在并发访问时会出现“遍历 map”与“写map”同时发生的情况。
panic 触发方式说明
问题的本质是:
- goroutine A 在 IsMyLogger() 中执行:
for _, storedLogger := range m.loggers {
...
}
- goroutine B 同时在 Logger() 中执行:
m.loggers[name] = &l
Go 的原生 map 不支持“并发遍历 + 并发写入”。一旦这两个路径重叠,就会触发运行时崩溃:
fatal error: concurrent map iteration and map write
影响范围
这是一个进程级崩溃问题,不是普通功能异常。
一旦在高并发场景下命中,整个服务会直接退出,对线上稳定性影响较大。只要业务代码频繁调用 log.FromCtx(...),并且系统会动态创建子 logger,就存在触发风险。
复现思路
可以通过并发执行以下两类操作复现:
- 多个 goroutine 调用 Logger(name),不断创建或读取不同名称的子 logger
- 多个 goroutine 同时调用 IsMyLogger(logger),对 m.loggers 做遍历判断
当“遍历 map”和“写 map”在时间上重叠时,即可触发 panic。
期望行为
无论在单线程还是高并发请求场景下,日志组件都不应因内部共享 map 的并发访问而触发 runtime panic。
建议修复
建议统一对 m.loggers 的读写做并发保护,例如:
- 将当前互斥锁改为 sync.RWMutex
- 在 Logger() 中对 map 的读取和写入使用同一把锁保护
- 在 IsMyLogger() 中遍历 m.loggers 前增加读锁
或者从设计上避免在 IsMyLogger() 中依赖遍历共享 map 来判断 logger 归属。
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels