fix(registry): add lock protection for serviceListeners map access#3442
fix(registry): add lock protection for serviceListeners map access#3442Aias00 wants to merge 7 commits into
Conversation
serviceListeners map was read/written without holding the lock in SubscribeURL and UnSubscribe, while the same struct's serviceMappingListeners already had proper lock protection. This could cause data races when concurrent subscribe/unsubscribe operations access the map. - SubscribeURL: use s.lock.Lock() to protect the check-then-act read+write of serviceListeners (write lock needed to prevent TOCTOU race) - UnSubscribe: use s.lock.RLock() for the read access of serviceListeners Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR improves concurrency safety in the service discovery registry by adding mutex protection around accesses to serviceListeners.
Changes:
- Add
RLock/RUnlockwhen readingserviceListenersduringUnSubscribe - Add
Lock/Unlockwhen reading/updatingserviceListenersduringSubscribeURL
Comments suppressed due to low confidence (2)
registry/servicediscovery/service_discovery_registry.go:1
- The lock is manually unlocked at the end of the block; this is fragile if a future change introduces an early return/panic path in the locked region. Consider using
defer s.lock.Unlock()immediately afters.lock.Lock()to make the critical section robust against future control-flow changes.
/*
registry/servicediscovery/service_discovery_registry.go:1
- The mutex is held while doing potentially expensive work (listener construction, iterating services, and calling
Register). This can unnecessarily block other goroutines that needserviceListeners. Consider narrowing the critical section to only the map get/set (e.g., read under lock, create/register outside lock, then write back under lock with a double-check) to reduce contention.
/*
💡 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 @@
## develop #3442 +/- ##
===========================================
+ Coverage 46.76% 53.93% +7.17%
===========================================
Files 295 461 +166
Lines 17172 35487 +18315
===========================================
+ Hits 8031 19141 +11110
- Misses 8287 14838 +6551
- Partials 854 1508 +654 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| protocol = url.Protocol | ||
| } | ||
| protocolServiceKey := url.ServiceKey() + ":" + protocol | ||
| s.lock.Lock() |
There was a problem hiding this comment.
unlock都跑到line 385 了,这太远了,一旦出现 panic 就死锁了,要善于使用 defer + lambda 表达式 啊,童子。我教你个技巧:
func() {
m.lock()
defer m.unlock()
xxx
}()
…deadlock on panic Wrap lock/unlock in anonymous functions with defer to ensure the lock is always released even if a panic occurs between Lock() and Unlock(), as suggested in PR review. Co-Authored-By: Claude <noreply@anthropic.com>
Resolved conflict in registry/servicediscovery/service_discovery_registry.go The conflict combined: - develop's sortServices() for generating serviceNamesKey - PR's lock protection for accessing serviceListeners map
8713ece to
936016a
Compare
|



Problem
serviceListenersmap inserviceDiscoveryRegistrywas read/written without holding the lock, while the same struct'sserviceMappingListenersalready had proper lock protection vias.lock. This could cause data races when concurrent subscribe/unsubscribe operations access the map.Unprotected access locations
SubscribeURL(line 365): reads.serviceListeners[serviceNamesKey]— no lockSubscribeURL(line 381): writes.serviceListeners[serviceNamesKey] = listener— no lockUnSubscribe(line 203): reads.serviceListeners[serviceNamesKey]— no lockContrast with protected
serviceMappingListenersfindMappedServices:s.lock.Lock()/s.lock.Unlock()stopListen:s.lock.Lock()/s.lock.Unlock()Fix
SubscribeURL: Uses.lock.Lock()/s.lock.Unlock()to protect the check-then-act read+write ofserviceListeners. A write lock is required (not RLock) because the method follows a check-then-act pattern — it first reads to check if a listener exists, then conditionally creates and writes one. Using RLock would allow concurrent reads but not prevent the TOCTOU race.UnSubscribe: Uses.lock.RLock()/s.lock.RUnlock()for the read-only access ofserviceListeners, consistent with read-lock usage elsewhere in the codebase.Testing
go build ./registry/servicediscovery/...✅go vet ./registry/servicediscovery/...✅go test ./registry/servicediscovery/... -count=1✅