Skip to content

fix(manager): use ConcurrentHashMap for gateway asset id map - #3454

Open
cgrs5722 wants to merge 3 commits into
openremote:mainfrom
cgrs5722:fix/gateway-asset-id-map-concurrency
Open

cgrs5722 wants to merge 3 commits into
openremote:mainfrom
cgrs5722:fix/gateway-asset-id-map-concurrency

Conversation

@cgrs5722

Copy link
Copy Markdown

Description

GatewayService.assetIdGatewayIdMap is a plain HashMap that is written under synchronized in processGatewayChildAssetChange, but read without any synchronization from:

  • onAttributeEventIntercepted (get, attribute event interceptor thread)
  • deleteGateway (entrySet().stream() while writes can happen concurrently)
  • getLocallyRegisteredGatewayId (get, called from the Camel route predicate)

This is a data race: unsynchronized reads against synchronized writes on a HashMap can throw ConcurrentModificationException while streaming the entry set, or observe inconsistent state.

The fix switches the map to ConcurrentHashMap, consistent with the adjacent gatewayConnectorMap and tunnelInfos fields.

Fixes #3449.

Verification

  • ./gradlew :manager:compileJava passes (JDK 21).
  • A standalone probe replicating the exact access pattern (one thread doing synchronized put/remove as in processGatewayChildAssetChange, another thread doing get + entrySet().stream().filter(...) as in deleteGateway) fails with ConcurrentModificationException on HashMap within ~1000 reads, while ConcurrentHashMap completes hundreds of thousands of operations without error.
  • The integration tests in the test project (e.g. GatewayTest) require a running PostgreSQL + Keycloak stack via docker compose, which I don't have available locally.

🤖 Generated with Claude Code

assetIdGatewayIdMap is written under synchronization in
processGatewayChildAssetChange but read unsynchronized in
onAttributeEventIntercepted, deleteGateway and
getLocallyRegisteredGatewayId, which constitutes a data race
(e.g. ConcurrentModificationException while streaming the entry
set). Use a ConcurrentHashMap like the adjacent gatewayConnectorMap
and tunnelInfos fields.

Fixes openremote#3449

Co-Authored-By: Claude Code <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Oct 20, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@MartinaeyNL MartinaeyNL added Bug Error or issue in the system java Pull requests that update java code Gateways Topic Gateways: all features related to gateway features labels Oct 20, 2026
@richturner

Copy link
Copy Markdown
Member

Hi @cgrs5722 thanks for picking this up, by switching to ConcurrentHashMap any existing uses of synchronized on the map can also be removed as described in the linked issue.

@richturner

Copy link
Copy Markdown
Member

@cgrs5722 would be great to get this finished off, otherwise we'll have to close this off.

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Error or issue in the system Gateways Topic Gateways: all features related to gateway features java Pull requests that update java code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GatewayService.assetIdGatewayIdMap is a non-thread-safe HashMap read without synchronization

5 participants