Skip to content

Fix deadlock in NamespacedHierarchicalStore.computeIfAbsent()#5348

Merged
mpkorstanje merged 7 commits into
mainfrom
rien/fix-namespaced-hierarchical-store-deadlock
Feb 11, 2026
Merged

Fix deadlock in NamespacedHierarchicalStore.computeIfAbsent()#5348
mpkorstanje merged 7 commits into
mainfrom
rien/fix-namespaced-hierarchical-store-deadlock

Conversation

@mpkorstanje

@mpkorstanje mpkorstanje commented Feb 3, 2026

Copy link
Copy Markdown
Member

In #5231 evaluation of the default value creator was deferred until after an entry was created in the concurrent hash map backing the store. This enables recursive updates. However, by calling evaluateIfNotNull inside compute threads that lost the race would wait inside Map::compute for the value to be evaluated.

var storedValue = storedValues.compute(compositeKey, (__, oldStoredValue) -> {
// guard against race conditions
// computeIfAbsent replaces both null and absent values
if (StoredValue.evaluateIfNotNull(oldStoredValue) != null) {
return oldStoredValue;
}
rejectIfClosed();
return candidateStoredValue;
});

Waiting here is a problem when the backing concurrent hashmap is reaching capacity. The thread waiting inside compute holds a lock while at the same time the thread that won the race will try to acquire a lock to resize the map.

By making the threads that lost the race wait outside of Map::compute we can prevent this dead lock. This does require that each thread retries when another thread failed to insert a value. But eventually either one other thread either successfully inserts a value or the thread uses its own default value creator.

Fixes: #5346


I hereby agree to the terms of the JUnit Contributor License Agreement.


Definition of Done

@mpkorstanje mpkorstanje force-pushed the rien/fix-namespaced-hierarchical-store-deadlock branch 2 times, most recently from 8190262 to e09a285 Compare February 3, 2026 01:52
@testlens-app

testlens-app Bot commented Feb 3, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 874ec9d
▶️ Tests: 50640 executed
⚪️ Checks: 15/15 completed


Learn more about TestLens at testlens.app.

@mpkorstanje mpkorstanje force-pushed the rien/fix-namespaced-hierarchical-store-deadlock branch from e09a285 to fdc46c3 Compare February 3, 2026 13:02
@mpkorstanje mpkorstanje marked this pull request as ready for review February 4, 2026 15:15

@marcphilipp marcphilipp left a comment

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.

Thanks for taking care of this! The reasoning makes sense to me. I only added a few nitpicks regarding explanatory comment.

@mpkorstanje mpkorstanje merged commit f29de38 into main Feb 11, 2026
18 checks passed
@mpkorstanje mpkorstanje deleted the rien/fix-namespaced-hierarchical-store-deadlock branch February 11, 2026 21:10
marcphilipp pushed a commit that referenced this pull request Feb 15, 2026
In #5231 evaluation of the default value creator was deferred until
after an entry was created in the concurrent hash map backing the store.
This enables recursive updates.  However, by calling `evaluateIfNotNull`
inside `compute` threads that lost the race would wait inside
`Map::compute` for the value to be evaluated.

Waiting here is a problem when the backing concurrent hashmap is
reaching capacity. The thread waiting inside compute holds a lock while
at the same time the thread that won the race will try to acquire a lock
to resize the map.

By making the threads that lost the race wait outside of `Map::compute`
we can prevent this dead lock. This does require that each thread
retries when another thread failed to insert a value. But eventually
either one other thread either successfully inserts a value or the
thread uses its own default value creator.

Fixes: #5346

Co-authored-by: Marc Philipp <mail@marcphilipp.de>

(cherry picked from commit f29de38)
marcphilipp pushed a commit that referenced this pull request Feb 15, 2026
In #5231 evaluation of the default value creator was deferred until
after an entry was created in the concurrent hash map backing the store.
This enables recursive updates.  However, by calling `evaluateIfNotNull`
inside `compute` threads that lost the race would wait inside
`Map::compute` for the value to be evaluated.

Waiting here is a problem when the backing concurrent hashmap is
reaching capacity. The thread waiting inside compute holds a lock while
at the same time the thread that won the race will try to acquire a lock
to resize the map.

By making the threads that lost the race wait outside of `Map::compute`
we can prevent this dead lock. This does require that each thread
retries when another thread failed to insert a value. But eventually
either one other thread either successfully inserts a value or the
thread uses its own default value creator.

Fixes: #5346

Co-authored-by: Marc Philipp <mail@marcphilipp.de>

(cherry picked from commit f29de38)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deadlock in NamespacedHierarchicalStore.computeIfAbsent()

2 participants