-
Bug
-
Resolution: Not an Issue
-
P3
-
8u371, 11, 17, 18, 19, 20
-
aarch64
-
os_x
ADDITIONAL SYSTEM INFORMATION :
JDK 19 for aarch64 CPU on MacOS on MacBook Pro (16-inch, 2021).
Note that I cannot reproduce the problem on my x86_86 CPU MacBook Pro 2017.
A DESCRIPTION OF THE PROBLEM :
The following code produces the following output, which shouldn't be possible. An array that cannot contain null values (and is never modified) suddenly contains null values, and then doesn't contain any null values when printing the array a few lines later:
----------------------------------------------
Object o1 = seq1[i1];
Object o2 = seq2[i2];
try {
Objects.requireNonNull(o1, "ARRAY ELEMENTS CANNOT BE NULL");
Objects.requireNonNull(o2, "ARRAY ELEMENTS CANNOT BE NULL");
} catch (NullPointerException e) {
System.out.println("o1 was " + o1);
System.out.println("o2 was " + o2);
System.out.println("seq1 was " + asList(seq1));
System.out.println("seq2 was " + asList(seq2));
throw e;
}
----------------------------------------------
o1 was java.text.RuleBasedCollationKey@3e88889
o2 was null
seq1 was [java.text.RuleBasedCollationKey@3e88889, java.text.RuleBasedCollationKey@3f6a00a, java.text.RuleBasedCollationKey@404b78b, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@420e68d]
seq2 was [java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@42efe0e, java.text.RuleBasedCollationKey@43d158f]
----------------------------------------------
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Please run the executable test case below. You may need to try it a few times. On my MacBook Pro (16-inch, 2021) I can reproduce the problem reliably almost every time (but in rare cases it runs through without error).
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test should give the same result for every find(query, index) method call, and not randomly error out sometimes, with an NPE where there cannot be an NPE.
ACTUAL -
...
95-04: [[12345]]
o1 was java.text.RuleBasedCollationKey@3e88889
o2 was null
seq1 was [java.text.RuleBasedCollationKey@3e88889, java.text.RuleBasedCollationKey@3f6a00a, java.text.RuleBasedCollationKey@404b78b, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@420e68d]
seq2 was [java.text.RuleBasedCollationKey@404b78b, java.text.RuleBasedCollationKey@3f6a00a, java.text.RuleBasedCollationKey@4594491, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@3da7108]
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException: ARRAY ELEMENTS CANNOT BE NULL
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at test.ArrayInitializerRaceConditionTest.main(ArrayInitializerRaceConditionTest.java:44)
Caused by: java.lang.NullPointerException: ARRAY ELEMENTS CANNOT BE NULL
at java.base/java.util.Objects.requireNonNull(Objects.java:259)
at test.ArrayInitializerRaceConditionTest.equals(ArrayInitializerRaceConditionTest.java:123)
at test.ArrayInitializerRaceConditionTest.firstCommonSequence(ArrayInitializerRaceConditionTest.java:103)
at test.ArrayInitializerRaceConditionTest.matchFirstCommonSequence(ArrayInitializerRaceConditionTest.java:85)
at test.ArrayInitializerRaceConditionTest.find(ArrayInitializerRaceConditionTest.java:67)
at test.ArrayInitializerRaceConditionTest.lambda$3(ArrayInitializerRaceConditionTest.java:36)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1589)
---------- BEGIN SOURCE ----------
package test;
import static java.util.Arrays.*;
import static java.util.stream.Collectors.*;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
public class ArrayInitializerRaceConditionTest {
public static void main(String[] args) throws Exception {
for (int k = 0; k <= 100; k++) {
for (int n = 4; n <= 16; n++) {
Map<String, CollationKey> cache = new ConcurrentHashMap<String, CollationKey>();
List<IndexEntry> index = IntStream.range(0, 50000).mapToObj(i -> new IndexEntry(i, o -> tokenize(o, cache))).collect(toList());
CollationKey[] query = tokenize(12345, cache);
ExecutorService threadPool = Executors.newFixedThreadPool(n);
List<Future<List<IndexEntry>>> tasks = IntStream.range(0, n).mapToObj(t -> {
return threadPool.submit(() -> {
return find(query, index);
});
}).collect(toList());
threadPool.shutdown();
Set<String> output = new LinkedHashSet<String>();
for (Future<List<IndexEntry>> task : tasks) {
output.add(task.get().toString());
}
System.out.format("%02d-%02d: %s%n", k, n, output);
}
}
}
private static final Collator COLLATOR = Collator.getInstance(Locale.ENGLISH);
private static final Pattern SPACE = Pattern.compile("");
private static CollationKey[] tokenize(Object sequence, Map<String, CollationKey> cache) {
return SPACE.splitAsStream(sequence.toString()).map(s -> getCollationKey(s, cache)).toArray(CollationKey[]::new);
}
private static CollationKey getCollationKey(String word, Map<String, CollationKey> cache) {
return cache.computeIfAbsent(word, COLLATOR::getCollationKey);
}
private static List<IndexEntry> find(CollationKey[] query, List<IndexEntry> index) {
List<IndexEntry> matches = new ArrayList<IndexEntry>();
for (IndexEntry i : index) {
CollationKey[] key = i.getKey();
CollationKey[] commonName = matchFirstCommonSequence(new CollationKey[][] { query, key });
if (commonName != null && commonName.length >= query.length) {
matches.add(i);
}
}
return matches;
}
private static <E> E[] matchFirstCommonSequence(E[][] names) {
E[] common = null;
for (E[] words : names) {
if (common == null) {
// initialize common with current word array
common = words;
} else {
// find common sequence
common = firstCommonSequence(common, words);
// no common sequence
if (common == null) {
return null;
}
}
}
return common;
}
private static <E> E[] firstCommonSequence(E[] seq1, E[] seq2) {
E[] matchSeq = null;
for (int i = 0; i < seq1.length; i++) {
for (int j = 0; j < seq2.length; j++) {
// common sequence length
int len = 0;
// iterate over common sequence
while (equals(seq1, i + len, seq2, j + len)) {
len++;
}
// check if a common sequence was found
if (len > 0) {
return copyOfRange(seq1, i, i + len);
}
}
}
return matchSeq;
}
private static boolean equals(Object[] seq1, int i1, Object[] seq2, int i2) {
if (i1 < seq1.length && i2 < seq2.length) {
Object o1 = seq1[i1];
Object o2 = seq2[i2];
try {
Objects.requireNonNull(o1, "ARRAY ELEMENTS CANNOT BE NULL");
Objects.requireNonNull(o2, "ARRAY ELEMENTS CANNOT BE NULL");
} catch (NullPointerException e) {
System.out.println("o1 was " + o1);
System.out.println("o2 was " + o2);
System.out.println("seq1 was " + asList(seq1));
System.out.println("seq2 was " + asList(seq2));
throw e;
}
return o1.equals(o2);
}
return false;
}
private static class IndexEntry {
private Object object;
private Function<Object, CollationKey[]> prepare;
private CollationKey[] key;
public IndexEntry(Object object, Function<Object, CollationKey[]> prepare) {
this.object = object;
this.prepare = prepare;
}
public CollationKey[] getKey() {
if (key == null && object != null) {
key = prepare.apply(object);
// CONFIRM THAT ARRAY ELEMENTS ARE NOT NULL (this is always the case, so an exception is never thrown here)
for (CollationKey k : key) {
if (k == null) {
throw new IllegalStateException("CollationKey is NULL");
}
}
}
return key;
}
@Override
public String toString() {
return object.toString();
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Adding a .clone() after .toArray(CollationKey[]::new) somehow makes the test case above never fail for unknown reasons:
private static CollationKey[] tokenize(Object sequence, Map<String, CollationKey> cache) {
return SPACE.splitAsStream(sequence.toString()).map(s -> getCollationKey(s, cache)).toArray(CollationKey[]::new).clone();
}
FREQUENCY : always
JDK 19 for aarch64 CPU on MacOS on MacBook Pro (16-inch, 2021).
Note that I cannot reproduce the problem on my x86_86 CPU MacBook Pro 2017.
A DESCRIPTION OF THE PROBLEM :
The following code produces the following output, which shouldn't be possible. An array that cannot contain null values (and is never modified) suddenly contains null values, and then doesn't contain any null values when printing the array a few lines later:
----------------------------------------------
Object o1 = seq1[i1];
Object o2 = seq2[i2];
try {
Objects.requireNonNull(o1, "ARRAY ELEMENTS CANNOT BE NULL");
Objects.requireNonNull(o2, "ARRAY ELEMENTS CANNOT BE NULL");
} catch (NullPointerException e) {
System.out.println("o1 was " + o1);
System.out.println("o2 was " + o2);
System.out.println("seq1 was " + asList(seq1));
System.out.println("seq2 was " + asList(seq2));
throw e;
}
----------------------------------------------
o1 was java.text.RuleBasedCollationKey@3e88889
o2 was null
seq1 was [java.text.RuleBasedCollationKey@3e88889, java.text.RuleBasedCollationKey@3f6a00a, java.text.RuleBasedCollationKey@404b78b, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@420e68d]
seq2 was [java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@42efe0e, java.text.RuleBasedCollationKey@43d158f]
----------------------------------------------
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Please run the executable test case below. You may need to try it a few times. On my MacBook Pro (16-inch, 2021) I can reproduce the problem reliably almost every time (but in rare cases it runs through without error).
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test should give the same result for every find(query, index) method call, and not randomly error out sometimes, with an NPE where there cannot be an NPE.
ACTUAL -
...
95-04: [[12345]]
o1 was java.text.RuleBasedCollationKey@3e88889
o2 was null
seq1 was [java.text.RuleBasedCollationKey@3e88889, java.text.RuleBasedCollationKey@3f6a00a, java.text.RuleBasedCollationKey@404b78b, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@420e68d]
seq2 was [java.text.RuleBasedCollationKey@404b78b, java.text.RuleBasedCollationKey@3f6a00a, java.text.RuleBasedCollationKey@4594491, java.text.RuleBasedCollationKey@412cf0c, java.text.RuleBasedCollationKey@3da7108]
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException: ARRAY ELEMENTS CANNOT BE NULL
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at test.ArrayInitializerRaceConditionTest.main(ArrayInitializerRaceConditionTest.java:44)
Caused by: java.lang.NullPointerException: ARRAY ELEMENTS CANNOT BE NULL
at java.base/java.util.Objects.requireNonNull(Objects.java:259)
at test.ArrayInitializerRaceConditionTest.equals(ArrayInitializerRaceConditionTest.java:123)
at test.ArrayInitializerRaceConditionTest.firstCommonSequence(ArrayInitializerRaceConditionTest.java:103)
at test.ArrayInitializerRaceConditionTest.matchFirstCommonSequence(ArrayInitializerRaceConditionTest.java:85)
at test.ArrayInitializerRaceConditionTest.find(ArrayInitializerRaceConditionTest.java:67)
at test.ArrayInitializerRaceConditionTest.lambda$3(ArrayInitializerRaceConditionTest.java:36)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1589)
---------- BEGIN SOURCE ----------
package test;
import static java.util.Arrays.*;
import static java.util.stream.Collectors.*;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
public class ArrayInitializerRaceConditionTest {
public static void main(String[] args) throws Exception {
for (int k = 0; k <= 100; k++) {
for (int n = 4; n <= 16; n++) {
Map<String, CollationKey> cache = new ConcurrentHashMap<String, CollationKey>();
List<IndexEntry> index = IntStream.range(0, 50000).mapToObj(i -> new IndexEntry(i, o -> tokenize(o, cache))).collect(toList());
CollationKey[] query = tokenize(12345, cache);
ExecutorService threadPool = Executors.newFixedThreadPool(n);
List<Future<List<IndexEntry>>> tasks = IntStream.range(0, n).mapToObj(t -> {
return threadPool.submit(() -> {
return find(query, index);
});
}).collect(toList());
threadPool.shutdown();
Set<String> output = new LinkedHashSet<String>();
for (Future<List<IndexEntry>> task : tasks) {
output.add(task.get().toString());
}
System.out.format("%02d-%02d: %s%n", k, n, output);
}
}
}
private static final Collator COLLATOR = Collator.getInstance(Locale.ENGLISH);
private static final Pattern SPACE = Pattern.compile("");
private static CollationKey[] tokenize(Object sequence, Map<String, CollationKey> cache) {
return SPACE.splitAsStream(sequence.toString()).map(s -> getCollationKey(s, cache)).toArray(CollationKey[]::new);
}
private static CollationKey getCollationKey(String word, Map<String, CollationKey> cache) {
return cache.computeIfAbsent(word, COLLATOR::getCollationKey);
}
private static List<IndexEntry> find(CollationKey[] query, List<IndexEntry> index) {
List<IndexEntry> matches = new ArrayList<IndexEntry>();
for (IndexEntry i : index) {
CollationKey[] key = i.getKey();
CollationKey[] commonName = matchFirstCommonSequence(new CollationKey[][] { query, key });
if (commonName != null && commonName.length >= query.length) {
matches.add(i);
}
}
return matches;
}
private static <E> E[] matchFirstCommonSequence(E[][] names) {
E[] common = null;
for (E[] words : names) {
if (common == null) {
// initialize common with current word array
common = words;
} else {
// find common sequence
common = firstCommonSequence(common, words);
// no common sequence
if (common == null) {
return null;
}
}
}
return common;
}
private static <E> E[] firstCommonSequence(E[] seq1, E[] seq2) {
E[] matchSeq = null;
for (int i = 0; i < seq1.length; i++) {
for (int j = 0; j < seq2.length; j++) {
// common sequence length
int len = 0;
// iterate over common sequence
while (equals(seq1, i + len, seq2, j + len)) {
len++;
}
// check if a common sequence was found
if (len > 0) {
return copyOfRange(seq1, i, i + len);
}
}
}
return matchSeq;
}
private static boolean equals(Object[] seq1, int i1, Object[] seq2, int i2) {
if (i1 < seq1.length && i2 < seq2.length) {
Object o1 = seq1[i1];
Object o2 = seq2[i2];
try {
Objects.requireNonNull(o1, "ARRAY ELEMENTS CANNOT BE NULL");
Objects.requireNonNull(o2, "ARRAY ELEMENTS CANNOT BE NULL");
} catch (NullPointerException e) {
System.out.println("o1 was " + o1);
System.out.println("o2 was " + o2);
System.out.println("seq1 was " + asList(seq1));
System.out.println("seq2 was " + asList(seq2));
throw e;
}
return o1.equals(o2);
}
return false;
}
private static class IndexEntry {
private Object object;
private Function<Object, CollationKey[]> prepare;
private CollationKey[] key;
public IndexEntry(Object object, Function<Object, CollationKey[]> prepare) {
this.object = object;
this.prepare = prepare;
}
public CollationKey[] getKey() {
if (key == null && object != null) {
key = prepare.apply(object);
// CONFIRM THAT ARRAY ELEMENTS ARE NOT NULL (this is always the case, so an exception is never thrown here)
for (CollationKey k : key) {
if (k == null) {
throw new IllegalStateException("CollationKey is NULL");
}
}
}
return key;
}
@Override
public String toString() {
return object.toString();
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Adding a .clone() after .toArray(CollationKey[]::new) somehow makes the test case above never fail for unknown reasons:
private static CollationKey[] tokenize(Object sequence, Map<String, CollationKey> cache) {
return SPACE.splitAsStream(sequence.toString()).map(s -> getCollationKey(s, cache)).toArray(CollationKey[]::new).clone();
}
FREQUENCY : always