RFR(trivial): 8222394: HashMap.compute() throws CME on an empty Map if clear() called concurrently

Patrick Zhang OS patrick at os.amperecomputing.com
Fri Apr 12 09:46:24 UTC 2019


Created a ticket to track it, welcome any comments. Thanks. 

JBS https://bugs.openjdk.java.net/browse/JDK-8222394
Webrev: http://cr.openjdk.java.net/~qpzhang/map.clear/webrev.01 

Regards
Patrick

-----Original Message-----
From: core-libs-dev <core-libs-dev-bounces at openjdk.java.net> On Behalf Of Patrick Zhang OS
Sent: Saturday, March 30, 2019 1:34 PM
To: core-libs-dev <core-libs-dev at openjdk.java.net>
Subject: ConcurrentModificationException thrown by HashMap.compute() operating on an empty Map, expected/unexpected?

Hi,
Here I have a case simplified from a practical issue that throws ConcurrentModificationException (CME) unexpectedly (I think). [0] creates a HashMap, keeps it empty, and calls m.computeIfAbsent() or m.compute(), in which a "sneaky" m.clear() occurs, some of the test cases throw CME although there were no "structural" changes in fact. (A structural modification is defined as "any operation that adds or deletes one or more mappings...").

This case cannot be reproduced with jdk8u, while jdk9 and beyond can, after the bug [1] got fixed for computeIfAbsent() concurrent co-modification issues. A couple of test cases [2] were introduced at that time, and the focus was to verify the behaviors at resizing, while empty maps were not tested.

A possible "fix" for this issue is to move the unconditional "modCount++" [3] into the if-clause, which indicates that a "structural" change would be happening indeed.

public void clear() {
    Node<K,V>[] tab;
-   modCount++;
    if ((tab = table) != null && size > 0) {
+        modCount++;
          size = 0;
          for (int i = 0; i < tab.length; ++i)
            tab[i] = null;
        }
}

Therefore, a dilemma here is "modCount++ before-if-clause but overkills some cases" vs. "modCount++ into-if-clause but weakens the CME checking potentially". I want to make balance regarding how to "throw CME on a best-effort basis" more appropriately. Any suggestion?

I understand that CME here in HashMap.java cannot guarantee much and may be only for debugging purpose, any concurrent modification needs to be typically accomplished by synchronizing on some object that naturally encapsulates the map. So the mentioned issue is a just a tricky case.

[0]http://cr.openjdk.java.net/~qpzhang/map.clear/webrev.01/test/jdk/java/util/concurrent/ConcurrentMap/ConcurrentModification.java.udiff.html
[1]https://bugs.openjdk.java.net/browse/JDK-8071667
[2]http://hg.openjdk.java.net/jdk/jdk/file/5a9d780eb9dd/test/jdk/java/util/Map/FunctionalCMEs.java
[3]http://hg.openjdk.java.net/jdk/jdk/file/1042cac8bc2a/src/java.base/share/classes/java/util/HashMap.java#l860

Regards
Patrick



More information about the core-libs-dev mailing list