-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
P4
-
Affects Version/s: 8, 25
-
Component/s: core-libs
-
generic
-
generic
A DESCRIPTION OF THE PROBLEM :
The size of a ConcurrentHashMap can affect the behaviour of nested computeIfAbsent calls. If the initial size of the map is in the sequence 12, 24, 48, 96, 192, then a nested computeIfAbsent call results in an IllegalStateException thrown (or infinite loop in Java 11). If the initial size is anything else, then the code will succeed.
Admittedly, the use of nested computeIfAbsent calls is likely to be considered poor practice. However, this inconsistency can lead to bugs being missed.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the code excerpt below (see test case code section)
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The result should be consistent, either it should always throw an exception or always succeed (irrespective of the initial map size).
ACTUAL -
On Java 11: Code runs in an infinite loop
Tested on Java 21 and 25:
Exception in thread "main" java.lang.IllegalStateException: Recursive update
at java.base/java.util.concurrent.ConcurrentHashMap.transfer(ConcurrentHashMap.java:2568)
at java.base/java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2370)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1792)
at Scratch.lambda$main$2(scratch_4.java:15)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1724)
at Scratch.main(scratch_4.java:13)
---------- BEGIN SOURCE ----------
final int size = 12; // or 24, 48, 96, 192...
final Map<Integer, Integer> map = new ConcurrentHashMap<>();
IntStream.range(1, size).forEach(i -> map.put(i, i));
map.computeIfAbsent(size, k ->
{
map.computeIfAbsent(size + 1, k2 -> size + 1);
return size;
});
---------- END SOURCE ----------
The size of a ConcurrentHashMap can affect the behaviour of nested computeIfAbsent calls. If the initial size of the map is in the sequence 12, 24, 48, 96, 192, then a nested computeIfAbsent call results in an IllegalStateException thrown (or infinite loop in Java 11). If the initial size is anything else, then the code will succeed.
Admittedly, the use of nested computeIfAbsent calls is likely to be considered poor practice. However, this inconsistency can lead to bugs being missed.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the code excerpt below (see test case code section)
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The result should be consistent, either it should always throw an exception or always succeed (irrespective of the initial map size).
ACTUAL -
On Java 11: Code runs in an infinite loop
Tested on Java 21 and 25:
Exception in thread "main" java.lang.IllegalStateException: Recursive update
at java.base/java.util.concurrent.ConcurrentHashMap.transfer(ConcurrentHashMap.java:2568)
at java.base/java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2370)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1792)
at Scratch.lambda$main$2(scratch_4.java:15)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1724)
at Scratch.main(scratch_4.java:13)
---------- BEGIN SOURCE ----------
final int size = 12; // or 24, 48, 96, 192...
final Map<Integer, Integer> map = new ConcurrentHashMap<>();
IntStream.range(1, size).forEach(i -> map.put(i, i));
map.computeIfAbsent(size, k ->
{
map.computeIfAbsent(size + 1, k2 -> size + 1);
return size;
});
---------- END SOURCE ----------
- relates to
-
JDK-8062841 ConcurrentHashMap.computeIfAbsent stuck in an endless loop
-
- Closed
-