InMemoryRegistryStore.putIfAbsent loses registrations when concurrent initialization fails

Author: walitemuriCreated Sep 14, 2026Updated Sep 14, 2026

InMemoryRegistryStore.putIfAbsent(key, value) can return null without storing value when it waits for a concurrent computeIfAbsent whose mapping function fails.

Reproduced on upstream master at a8a33164256ddb6e4bbc168f5c47ac34a348ee7f, using Java 21, with both platform and virtual threads.

Reproduction

  1. Thread A enters computeIfAbsent("key", mappingFunction) and blocks inside the mapping function. The registry now holds its incomplete future.
  2. Thread B calls putIfAbsent("key", "candidate") and waits in future.join().
  3. Allow A's mapping function to throw.
  4. B catches the failed future's exception and returns null, but never retries insertion. After both calls finish, find("key") is empty.

With two waiting putIfAbsent calls, both can return null and both candidates are lost. The same problem occurs if initialization throws CancellationException.

Expected behavior

After initialization fails, a waiting registration should atomically insert its candidate or return the value inserted by another contender. A failed initialization should not turn a successful-looking registration into a no-op.

Proposed fix and tests

Retry putIfAbsent after conditionally removing the failed future with entryMap.remove(key, future). Conditional removal protects a newer entry from another contender or from the failed initializer's cleanup. Keep mapping functions and future waits outside map locks.

The accompanying JUnit regression test holds initialization pending until each registration thread is waiting, then releases it. It checks the stored value as well as the return values, using one and two contenders on platform and virtual threads. Successful initialization is also covered to preserve existing behavior. The existing putIfAbsentShouldReturnNullWhenConcurrentComputeIfAbsentFails test only checks the return value and does not force the registration to wait before releasing initialization.

Prepared with AI assistance; validation details and the regression test will be included in the pull request.

Source: resilience4j/resilience4j