Skip to content

Commit 6f9fd7b

Browse files
Fix class initialization deadlock in AbstractArrayItem and AbstractMapItem
Convert static field initializers that trigger cross-class initialization to use lazy initialization via Lazy<T>. This prevents class initialization deadlock when JUnit 5's SEPARATE_THREAD timeout mode causes concurrent class loading from multiple threads. The deadlock occurred because static ARGUMENTS field initialization called IIntegerItem.type()/IAnyAtomicItem.type(), which triggered initialization of other classes. When multiple threads competed for class locks during parallel test execution, they could deadlock. Fixes #515
1 parent 47c155a commit 6f9fd7b

File tree

2 files changed

+26
-12
lines changed

2 files changed

+26
-12
lines changed

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/impl/AbstractArrayItem.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.stream.Collectors;
2323

2424
import edu.umd.cs.findbugs.annotations.NonNull;
25+
import nl.talsmasoftware.lazy4j.Lazy;
2526

2627
/**
2728
* The base class for {@link IArrayItem} implementations, that provides an
@@ -35,12 +36,19 @@ public abstract class AbstractArrayItem<ITEM extends ICollectionValue>
3536
implements IArrayItem<ITEM>, IFeatureCollectionFunctionItem {
3637
@NonNull
3738
private static final IEnhancedQName QNAME = IEnhancedQName.of("array");
39+
/**
40+
* The function arguments, lazily initialized to prevent class initialization
41+
* deadlock when multiple threads trigger class loading simultaneously.
42+
*/
3843
@NonNull
39-
private static final List<IArgument> ARGUMENTS = ObjectUtils.notNull(List.of(
40-
IArgument.builder().name("position").type(IIntegerItem.type()).one().build()));
41-
44+
private static final Lazy<List<IArgument>> ARGUMENTS = ObjectUtils.notNull(Lazy.of(() -> ObjectUtils.notNull(List.of(
45+
IArgument.builder().name("position").type(IIntegerItem.type()).one().build()))));
46+
/**
47+
* An empty array item singleton, lazily initialized to prevent class
48+
* initialization deadlock.
49+
*/
4250
@NonNull
43-
private static final IArrayItem<?> EMPTY = new ArrayItemN<>();
51+
private static final Lazy<IArrayItem<?>> EMPTY = ObjectUtils.notNull(Lazy.of(ArrayItemN::new));
4452

4553
/**
4654
* Get an immutable array item that is empty.
@@ -52,7 +60,7 @@ public abstract class AbstractArrayItem<ITEM extends ICollectionValue>
5260
@SuppressWarnings("unchecked")
5361
@NonNull
5462
public static <T extends ICollectionValue> IArrayItem<T> empty() {
55-
return (IArrayItem<T>) EMPTY;
63+
return (IArrayItem<T>) EMPTY.get();
5664
}
5765

5866
@Override
@@ -62,7 +70,7 @@ public IEnhancedQName getQName() {
6270

6371
@Override
6472
public List<IArgument> getArguments() {
65-
return ARGUMENTS;
73+
return ARGUMENTS.get();
6674
}
6775

6876
@Override

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/function/impl/AbstractMapItem.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.stream.Collectors;
2727

2828
import edu.umd.cs.findbugs.annotations.NonNull;
29+
import nl.talsmasoftware.lazy4j.Lazy;
2930

3031
/**
3132
* The base class for {@link IMapItem} implementations, that provide an
@@ -43,13 +44,18 @@ public abstract class AbstractMapItem<VALUE extends ICollectionValue>
4344
@NonNull
4445
private static final IEnhancedQName QNAME = IEnhancedQName.of("map");
4546
/**
46-
* The function arguments.
47+
* The function arguments, lazily initialized to prevent class initialization
48+
* deadlock when multiple threads trigger class loading simultaneously.
4749
*/
4850
@NonNull
49-
private static final List<IArgument> ARGUMENTS = ObjectUtils.notNull(List.of(
50-
IArgument.builder().name("key").type(IAnyAtomicItem.type()).one().build()));
51+
private static final Lazy<List<IArgument>> ARGUMENTS = ObjectUtils.notNull(Lazy.of(() -> ObjectUtils.notNull(List.of(
52+
IArgument.builder().name("key").type(IAnyAtomicItem.type()).one().build()))));
53+
/**
54+
* An empty map item singleton, lazily initialized to prevent class
55+
* initialization deadlock.
56+
*/
5157
@NonNull
52-
private static final IMapItem<?> EMPTY = new MapItemN<>();
58+
private static final Lazy<IMapItem<?>> EMPTY = ObjectUtils.notNull(Lazy.of(MapItemN::new));
5359

5460
/**
5561
* Get an immutable map item that is empty.
@@ -62,7 +68,7 @@ public abstract class AbstractMapItem<VALUE extends ICollectionValue>
6268
@SuppressWarnings("unchecked")
6369
@NonNull
6470
public static <V extends ICollectionValue> IMapItem<V> empty() {
65-
return (IMapItem<V>) EMPTY;
71+
return (IMapItem<V>) EMPTY.get();
6672
}
6773

6874
@Override
@@ -72,7 +78,7 @@ public IEnhancedQName getQName() {
7278

7379
@Override
7480
public List<IArgument> getArguments() {
75-
return ARGUMENTS;
81+
return ARGUMENTS.get();
7682
}
7783

7884
@Override

0 commit comments

Comments
 (0)