AskaEth
5847705e35
fix(gateway): 修 Snapshot 交出活 map 导致进程被 fatal 打死(并发 map 迭代+写入)
现场:cy.log:560,2026-10-03 21:00:28.403,栈顶
encoding/json.mapEncoder.encode ← writeJSON(rest.go:383) ← adminObservabilityMetrics(observability.go:89),
不可 recover,网关 recover 中间件接不住 ⇒ 单二进制进程整体退出(端口 10942 零监听、
所有后台服务含 backthink 心跳一起没;比第一次心跳 tick 早 32.6 秒)。
根因:MetricsCollector.Snapshot 在锁内只新造了 StatusCounts,却把 collector 自己的
活 map m.perMethod / m.perPath 直接交出去;锁外(handler 的 writeJSON)才开始遍历,
而每个请求的 logging 中间件(middleware.go:79)在别的 goroutine 里 Record 写这两个 map
⇒ 写有锁、迭代没锁。面板并行轮询十几个端点,/admin/observability/metrics 被打了 29 次
且常见同毫秒两个请求,必踩。
修法:Snapshot 改为交出副本(新增 copyCounts / copyStatusCounts,都在锁内遍历),
并把「必须在锁外可安全编码」的原因写进文档注释。对外读侧语义不变(仍是全量累计)。
测试(新增 observability_metrics_test.go,242 行):① 快照与 collector 双向隔离(确定性)
② 并发「写 + 编码快照」(-race 下复现生产竞态)③ 真 HTTP + 中间件 + 并行轮询
/configmetric 端点(= 21:00:28 现场)。
验证:go build/vet 绿;-race ./internal/services/gateway/... ok;全量 go test ./... 58 包 ok / 0 FAIL;
linux/amd64 + darwin/arm64 交叉编译绿。
变异验证:把 Snapshot 改回交活 map → 确定性测试 FAIL("snapshot shares the collector's perPath map")
+ -race 报出与生产同一处的 DATA RACE(Record 写在 observability_metrics.go:64)。
2026-10-03 21:38:59 +08:00
..
2026-10-03 19:27:25 +08:00
2026-09-06 04:26:02 +08:00
2026-09-08 23:20:12 +08:00
2026-10-03 16:08:51 +08:00
2026-10-03 21:01:33 +08:00
2026-10-03 16:55:22 +08:00
2026-10-03 20:12:29 +08:00
2026-09-08 21:36:03 +08:00
2026-09-06 15:53:48 +08:00
2026-10-03 15:22:03 +08:00
2026-10-03 19:54:22 +08:00
2026-09-06 04:17:20 +08:00
2026-10-03 21:33:40 +08:00
2026-10-03 21:38:59 +08:00
2026-10-03 16:08:52 +08:00
2026-09-06 08:10:35 +08:00
2026-09-05 23:09:26 +08:00