fix:cache manager race #3569 - #3600
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a concurrency bug in the service-discovery CacheManager by making cache snapshotting (GetAll) consistent under concurrent updates and by making the periodic dump goroutine reliably stoppable (including safe repeated StopDump calls), addressing the panic scenario described in #3569.
Changes:
- Serialize
CacheManagercache operations (Get/Set/Delete/GetAll) with a single mutex and implementGetAllas an atomic snapshot. - Rework dump lifecycle control with
stopOnce+doneto make stopping idempotent and avoid deadlock windows. - Add regression tests covering concurrent access with frequent dumps and high-concurrency
StopDumpcalls.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| registry/servicediscovery/store/cache_manager.go | Adds locking around cache operations, makes GetAll snapshot-safe, and makes dump goroutine stop idempotent via sync.Once + done. |
| registry/servicediscovery/store/cache_manager_test.go | Adds concurrent-access + concurrent-stop regression tests to validate the race/deadlock fixes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3600 +/- ##
==========================================
+ Coverage 54.57% 54.60% +0.02%
==========================================
Files 460 460
Lines 35443 35465 +22
==========================================
+ Hits 19344 19364 +20
- Misses 14543 14544 +1
- Partials 1556 1557 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|



说明
Fixes #3569
本 PR 保证服务发现
CacheManager在并发更新期间生成一致的缓存快照,并使定时落盘任务能够可靠、幂等地停止。问题分析
进一步检查发现,项目通过
*lru.Cache使用github.com/hashicorp/golang-lru v0.5.4。该类型本身是并发安全的,其Get、Add、Remove、Keys、Len和Purge均有内部锁保护。实际问题发生在复合操作层面:
GetAll先通过Keys()获取键快照。Get(key)返回(nil, false)。nil写入结果。dumpCache调用gob.Register(nil),最终触发 panic。因此,可复现的问题是缓存快照不具备原子性,并在定时落盘时引发崩溃,而不是底层 LRU 双向链表发生了无锁数据竞争。
原
StopDump还会在持有CacheManager.lock时向无缓冲通道发送停止信号。如果落盘 goroutine 正在等待同一把锁,停止流程存在死锁窗口。修改内容
CacheManager缓存操作。GetAll相对于Get、Set和Delete是原子快照。nil写入落盘数据。Set方法执行。sync.Once保证StopDump可以安全重复调用。done通道等待落盘 goroutine 完全退出。done。回归测试设计
TestCacheManagerConcurrentAccess启动 16 个 goroutine。每个 goroutine 执行 500 轮并发Set、Get、Delete和GetAll,同时启用 1ms 周期的定时落盘任务。每轮测试包含:
SetGetDeleteGetAll并发操作结束后,测试还会:
TestCacheManagerConcurrentStopDump分别覆盖开启和关闭落盘两种模式,每种模式由 64 个 goroutine 同时调用StopDump,并验证重复停止不会阻塞或死锁。自测报告
测试环境:Go 1.25.5、Windows/AMD64、
CGO_ENABLED=1、32 个逻辑处理器。go test -count=1 ./registry/servicediscovery/storego test -race -count=1 ./registry/servicediscovery/store-count=20-race -count=20go test -count=1 ./registry/servicediscovery/...go vet ./registry/servicediscovery/...20 轮 race 回归共执行 396,480 次缓存读写及快照操作,并执行 2,560 次并发
StopDump调用。覆盖率明细
Get、Set、Delete、GetAll、StopDump、destroy:100%runDumpTask:90.9%getAllLocked:88.9%loadCache:84.6%dumpCache:83.3%NewCacheManager:83.3%性能测试
32 线程环境下,每项执行 5 次 1 秒 benchmark,取中位数:
GetGetAll由于底层缓存本身也有锁,外层互斥锁会增加单次操作开销;但原子
GetAll的性能明显提升,内存分配由 10,520 B / 12 allocs 降至约 6,104 B / 5 allocs。已知但不属于本 PR 的问题
go test -race ./registry/servicediscovery/...会在既有的TestServiceDiscoveryRegistryUnRegister_Concurrent中报告竞态。该测试明确无锁修改sdReg.instances,在基线提交上同样失败,与本 PR 无关。store 范围的 race 测试全部通过。Set(key, nil)仍可能在gob.Register(nil)处 panic。生产元数据路径会拒绝 nil,建议单独处理。检查清单
develop