Skip to content

Avoid unnecessary synchronization in QueryCache#queryContains - #2298

Open
1wairesd wants to merge 2 commits into
EngineHub:version/7.0.xfrom
1wairesd:optimize/query-cache-fast-path
Open

Avoid unnecessary synchronization in QueryCache#queryContains#2298
1wairesd wants to merge 2 commits into
EngineHub:version/7.0.xfrom
1wairesd:optimize/query-cache-fast-path

Conversation

@1wairesd

@1wairesd 1wairesd commented Aug 3, 2026

Copy link
Copy Markdown

Use a lock-free ConcurrentHashMap.get() before falling back to compute() to avoid bucket-level locking when the result is already cached. On the hot path (repeated queries to the same block position within a tick), this eliminates contention on the CHM segment lock entirely, reducing overhead to a single volatile read.

Use a lock-free ConcurrentHashMap.get() before falling back to
compute() to avoid bucket-level locking when the result is already
cached. On the hot path (repeated queries to the same block position
within a tick), this eliminates contention on the CHM segment lock
entirely, reducing overhead to a single volatile read.
@SirYwell

SirYwell commented Aug 3, 2026

Copy link
Copy Markdown

It looks like the lock ensures that access to the inner map is synchronized, which seems to be required.

Replace EnumMap with ConcurrentHashMap in QueryOption.createCache() so
that concurrent reads and computeIfAbsent calls on the inner map are
thread-safe. This allows QueryCache.queryContains() to use a lock-free
get() fast-path before falling back to compute(), avoiding unnecessary
bucket-level locking on repeated queries to the same location.

Use QueryOption.values().length as initial capacity since the map will
never hold more entries than there are QueryOption enum constants.
@1wairesd

1wairesd commented Aug 3, 2026

Copy link
Copy Markdown
Author

Fixed the thread-safety concern by replacing EnumMap with ConcurrentHashMap for the inner option map in all three QueryOption.createCache() implementations. The computeIfAbsent calls on the inner map are now safe, and the lock-free get() fast-path in QueryCache.queryContains() is correct.

Comment on lines +61 to +68
// Fast path: avoid locking if the result is already cached
Map<QueryOption, ApplicableRegionSet> existing = cache.get(key);
if (existing != null) {
ApplicableRegionSet result = existing.get(option);
if (result != null) {
return result;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of this, can we split the createCache method into two parts, one that returns the appropriate map, and one that fills in the information with computeIfAbsent or whatever is typically used in the != null branch? That way, instead of still having lock contention for every initial query, we get out of the computeIfAbsent quickly and do a follow-up one only on the smaller map if necessary.

It might not be perfectly that clean, but I think we should avoid having expensive logic inside a compute or this fix won't help that much.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants