Skip to content

ioc/config/log: IsMyLogger 并发遍历 loggers map 未加锁,导致 fatal error: concurrent map iteration and map write #21

@kylinlingh

Description

@kylinlingh

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,就存在触发风险。

复现思路

可以通过并发执行以下两类操作复现:

  1. 多个 goroutine 调用 Logger(name),不断创建或读取不同名称的子 logger
  2. 多个 goroutine 同时调用 IsMyLogger(logger),对 m.loggers 做遍历判断

当“遍历 map”和“写 map”在时间上重叠时,即可触发 panic。

期望行为

无论在单线程还是高并发请求场景下,日志组件都不应因内部共享 map 的并发访问而触发 runtime panic。

建议修复

建议统一对 m.loggers 的读写做并发保护,例如:

  1. 将当前互斥锁改为 sync.RWMutex
  2. 在 Logger() 中对 map 的读取和写入使用同一把锁保护
  3. 在 IsMyLogger() 中遍历 m.loggers 前增加读锁

或者从设计上避免在 IsMyLogger() 中依赖遍历共享 map 来判断 logger 归属。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions