Skip to content

fix: guard capabilities map with RWMutex#3092

Closed
rachit77 wants to merge 2 commits into
berachain:mainfrom
rachit77:fix/capabilities-map-race
Closed

fix: guard capabilities map with RWMutex#3092
rachit77 wants to merge 2 commits into
berachain:mainfrom
rachit77:fix/capabilities-map-race

Conversation

@rachit77

Copy link
Copy Markdown

Summary

  • EngineClient.capabilities map had no mutex protecting it, while ExchangeCapabilities (write) and HasCapability (read) can be called concurrently from different goroutines
  • The race window is most likely during execution client reconnects: the startup ticker calls ExchangeCapabilities repeatedly while ReportingService calls HasCapability every 5 minutes independently

Copilot AI review requested due to automatic review settings April 23, 2026 06:47
@rachit77 rachit77 requested a review from a team as a code owner April 23, 2026 06:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Guards EngineClient.capabilities against concurrent access by introducing an RWMutex, addressing a potential data race between capability exchange (writes) and capability checks (reads).

Changes:

  • Add capabilitiesMu sync.RWMutex to EngineClient and protect HasCapability reads with RLock.
  • Protect ExchangeCapabilities map writes with Lock.
  • Add a concurrency/race-focused unit test for concurrent read/write access to the capabilities map.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
execution/client/client.go Adds an RWMutex to EngineClient and locks HasCapability reads.
execution/client/engine.go Wraps capability map writes during ExchangeCapabilities with a mutex.
execution/client/client_test.go Introduces a test intended to exercise concurrent read/write behavior under the race detector.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread execution/client/engine.go Outdated
Comment on lines 170 to 173
// Capture and log the capabilities that the execution client has.
s.capabilitiesMu.Lock()
for _, capability := range result {
s.logger.Info("Exchanged capability", "capability", capability)

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The write lock is held while calling s.logger.Info(...) for each capability. Since logging can block, this unnecessarily prolongs the critical section and can starve concurrent HasCapability readers. Consider logging outside the lock and only holding the lock around the map writes (or locking per write) to minimize contention.

Suggested change
// Capture and log the capabilities that the execution client has.
s.capabilitiesMu.Lock()
for _, capability := range result {
s.logger.Info("Exchanged capability", "capability", capability)
// Log the capabilities that the execution client has.
for _, capability := range result {
s.logger.Info("Exchanged capability", "capability", capability)
}
// Capture the capabilities that the execution client has.
s.capabilitiesMu.Lock()
for _, capability := range result {

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed: e9b2710

Comment on lines +46 to +58
for range iterations {
c.capabilitiesMu.Lock()
c.capabilities[cap] = struct{}{}
c.capabilitiesMu.Unlock()
}
}()

// Goroutine 2: repeatedly read a capability (simulates HasCapability).
go func() {
defer wg.Done()
for range iterations {
c.HasCapability(cap)
}

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for range iterations does not compile because iterations is an untyped integer constant, not an iterable. Use a counted loop (e.g., incrementing index) or range over a slice of length iterations so the test builds and can be exercised under go test -race.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for range iterations is valid Go 1.22+ syntax for ranging over an integer constant, and this repo targets Go 1.26, so this compiles and runs correctly

@rachit77

Copy link
Copy Markdown
Author

can anyone from Berachain team please take a look to this PR
cc: @fridrik01 @abi87 @calbera

@calbera calbera added the os-review Open source contribution to review label Apr 24, 2026
@fridrik01

Copy link
Copy Markdown
Contributor

@rachit77 Thank you for the PR submission, but I'm closing this as the capabilities map is read-only after services start, so the lock isnt needed in practice.

@fridrik01 fridrik01 closed this Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

os-review Open source contribution to review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants