JDK-8166507 : ConcurrentSkipListSet.clear() can leave the Set in an invalid state
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util.concurrent
  • Affected Version: 8,9
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2016-09-13
  • Updated: 2017-11-29
  • Resolved: 2016-11-29
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u152Fixed 9 b148Fixed
Description
FULL PRODUCT VERSION :
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Darwin lynx.local 16.0.0 Darwin Kernel Version 16.0.0: Mon Aug 29 17:56:20 PDT 2016; root:xnu-3789.1.32~3/RELEASE_X86_64 x86_64


A DESCRIPTION OF THE PROBLEM :
Calling clear() on a ConcurrentSkipListSet while also calling add() can cause the set to become unusable.

It may appear isEmpty() with size()==0, yet still contain elements per contains() and add().

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test program noted below.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The program should not terminate.
ACTUAL -
$ java TestSet
**** An empty set contains 'c'! (Iteration 79)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentSkipListSet;

public class TestSet {
    public static void main(String[] args) {
        int i = 0;
        while (true) {
            i++;
            ConcurrentSkipListSet<String> set = new ConcurrentSkipListSet<>();
            set.add("a");
            CompletableFuture<Void> future = CompletableFuture.runAsync(
                    () -> set.clear());
            set.add("b");
            set.add("c");
            future.join();
            if (set.isEmpty() && set.contains("c")) {
                System.out.println(
                        "**** An empty set contains 'c'! (Iteration " + i + ")");
                break;
            }
        }
    }
}

---------- END SOURCE ----------


Comments
http://cr.openjdk.java.net/~martin/webrevs/openjdk8/ConcurrentSkipListMap-clear/
31-05-2017

Yes, this is a bug: to avoid inconsistent reporting, clear should ensure all items are marked as deleted in case they are still accessible. This is now committed in CVS; it should be applied to jdk8 as well. *** ConcurrentSkipListMap.java.~1.163.~ 2016-06-17 09:01:33.940164570 -0400 --- ConcurrentSkipListMap.java 2016-09-24 11:26:02.388779603 -0400 *************** *** 1620,1629 **** /** * Removes all of the mappings from this map. */ ! public void clear() { ! initialize(); } ! /** * If the specified key is not already associated with a value, * attempts to compute its value using the given mapping function --- 1620,1646 ---- /** * Removes all of the mappings from this map. */ ! public void clear() { ! for (;;) { ! Node<K,V> b, n; ! HeadIndex<K,V> h = head, d = (HeadIndex<K,V>)h.down; ! if (d != null) ! casHead(h, d); // remove levels ! else if ((b = h.node) != null && (n = b.next) != null) { ! Node<K,V> f = n.next; // remove values ! if (n == b.next) { ! Object v = n.value; ! if (v == null) ! n.helpDelete(b, f); ! else if (n.casValue(v, null) && n.appendMarker(f)) ! b.casNext(n, f); ! } ! } ! else ! break; ! } } ! /** * If the specified key is not already associated with a value, * attempts to compute its value using the given mapping function
24-09-2016

The attached test case when run gives the following output : **** An empty set contains 'c'! (Iteration 71) Following are the results: JDK 8u112ea - Fail JDK 9ea + 128 - Fail
22-09-2016