Skip to content

fix: guard capabilities map with RWMutex#3092

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

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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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)
Copy link

Copilot AI Apr 23, 2026

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)
}
Copy link

Copilot AI Apr 23, 2026

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
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.

3 participants