JDK-8013649 : HashMap spliterator tryAdvance() encounters remaining elements after forEachRemaining()
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:collections
  • Affected Version: 8
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2013-04-30
  • Updated: 2021-03-03
  • Resolved: 2013-06-05
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
8 b94Fixed
Related Reports
Relates :  
Description
The spliterator added to HashMap will often (but not always) encounter a remaining element during a  tryAdvance() call after forEachRemaining() has been called.  (My guess is that the unpredictability is due to HashMap's random hashing seed).

See the attached test case.  It's loosely based on the testMixedTryAdvanceForEach() test from SpliteratorTraversingAndSplittingTest.java.  Running it a few times should eventually produce output similar to:

...
forEachRemaining: String158
forEachRemaining: String650
forEachRemaining: String29
forEachRemaining: String309
forEachRemaining: String715
Exception in thread "main" java.lang.Error: tryAdvance should have no elements: String51
	at ObjSplitter.fail(ObjSplitter.java:35)
	at ObjSplitter.lambda$3(ObjSplitter.java:31)
	at ObjSplitter$$Lambda$4.accept(Unknown Source)
	at java.util.HashMap$KeySpliterator.tryAdvance(HashMap.java:1528)
	at ObjSplitter.main(ObjSplitter.java:31)

Comments
Fixed in lambda repo: http://hg.openjdk.java.net/lambda/lambda/jdk/rev/d3baf2241054
31-05-2013

The following code reliably fails (might be the basis of a test case): HashMap<Integer, Integer> map = new HashMap<>(1, 4.0f); for (int i = 0; i < 2; i++) { map.put(i, i); } Spliterator splitter = map.keySet().spliterator(); splitter.tryAdvance(b -> System.out.println("tryAdvance: " + b)); // finish it splitter.forEachRemaining(b -> System.out.println("forEachRemaining: " + b)); // Confirm there are no more elements splitter.forEachRemaining(b -> fail("forEachRemaining should have no elements: " + b)); splitter.tryAdvance(b -> fail("tryAdvance should have no elements: " + b)); Basically we need to ensure that after the first tryAdvance the current entry is set to a non-null value, i.e. that the first bucket in the entry table references a linked list of 2 or more elements
30-05-2013

Yes, it seems so, in certain cases tryAdvance will traverse over entries in the last bucket. Same for value/entry spliterators, and also for WeakHashMap. Looks like we need to beef up the data used for testing maps in SpliteratorTraversingAndSplittingTest. It would be good if we could use a data set to reliably distribute keys to test this case. Any advice on that?
30-04-2013

Could this happen because forEachRemaining() updates "index", but not "current"? If current is non-null in tryAdvance(), we will still enter: while (current != null || index < hi) { even if ! index < hi.
30-04-2013