Skip to content

Commit 7e7dae5

Browse files
[GR-49300] Break the initialization cycle in NIO/cgroups code.
PullRequest: graal/15726
2 parents b280147 + a606940 commit 7e7dae5

File tree

3 files changed

+85
-33
lines changed

3 files changed

+85
-33
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@
2424
*/
2525
package com.oracle.svm.core.heap;
2626

27+
import java.util.concurrent.locks.ReentrantLock;
28+
2729
import org.graalvm.nativeimage.ImageSingletons;
2830
import org.graalvm.word.UnsignedWord;
2931
import org.graalvm.word.WordFactory;
3032

3133
import com.oracle.svm.core.Containers;
3234
import com.oracle.svm.core.SubstrateOptions;
3335
import com.oracle.svm.core.Uninterruptible;
34-
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicInteger;
3536
import com.oracle.svm.core.stack.StackOverflowCheck;
3637
import com.oracle.svm.core.thread.PlatformThreads;
3738
import com.oracle.svm.core.thread.VMOperation;
@@ -55,7 +56,7 @@ default boolean hasSize() {
5556
UnsignedWord size();
5657
}
5758

58-
private static final AtomicInteger INITIALIZING = new AtomicInteger(0);
59+
private static final ReentrantLock LOCK = new ReentrantLock();
5960
private static final UnsignedWord UNSET_SENTINEL = UnsignedUtils.MAX_VALUE;
6061
private static UnsignedWord cachedSize = UNSET_SENTINEL;
6162

@@ -64,6 +65,10 @@ public static boolean isInitialized() {
6465
return cachedSize != UNSET_SENTINEL;
6566
}
6667

68+
public static boolean isInitializationInProgress() {
69+
return LOCK.isHeldByCurrentThread();
70+
}
71+
6772
/**
6873
* Returns the size of physical memory in bytes, querying it from the OS if it has not been
6974
* initialized yet.
@@ -81,23 +86,21 @@ public static UnsignedWord size() {
8186
}
8287

8388
if (!isInitialized()) {
84-
/*
85-
* Multiple threads can race to initialize the cache. This is OK because all of them
86-
* will (most-likely) compute the same value.
87-
*/
88-
INITIALIZING.incrementAndGet();
89-
try {
90-
long memoryLimit = SubstrateOptions.MaxRAM.getValue();
91-
if (memoryLimit > 0) {
92-
cachedSize = WordFactory.unsigned(memoryLimit);
93-
} else {
94-
memoryLimit = Containers.memoryLimitInBytes();
95-
cachedSize = memoryLimit > 0
96-
? WordFactory.unsigned(memoryLimit)
97-
: ImageSingletons.lookup(PhysicalMemorySupport.class).size();
89+
long memoryLimit = SubstrateOptions.MaxRAM.getValue();
90+
if (memoryLimit > 0) {
91+
cachedSize = WordFactory.unsigned(memoryLimit);
92+
} else {
93+
LOCK.lock();
94+
try {
95+
if (!isInitialized()) {
96+
memoryLimit = Containers.memoryLimitInBytes();
97+
cachedSize = memoryLimit > 0
98+
? WordFactory.unsigned(memoryLimit)
99+
: ImageSingletons.lookup(PhysicalMemorySupport.class).size();
100+
}
101+
} finally {
102+
LOCK.unlock();
98103
}
99-
} finally {
100-
INITIALIZING.decrementAndGet();
101104
}
102105
}
103106

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_nio_Bits.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727
import java.util.concurrent.atomic.AtomicLong;
2828

2929
import com.oracle.svm.core.annotate.Alias;
30+
import com.oracle.svm.core.annotate.InjectAccessors;
3031
import com.oracle.svm.core.annotate.RecomputeFieldValue;
3132
import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind;
3233
import com.oracle.svm.core.annotate.TargetClass;
34+
import com.oracle.svm.core.heap.PhysicalMemory;
3335

3436
@TargetClass(className = "java.nio.Bits")
3537
final class Target_java_nio_Bits {
@@ -40,9 +42,9 @@ final class Target_java_nio_Bits {
4042
private static int PAGE_SIZE = -1;
4143

4244
@Alias @RecomputeFieldValue(kind = Kind.FromAlias) //
43-
private static boolean MEMORY_LIMIT_SET = false;
44-
@Alias @RecomputeFieldValue(kind = Kind.FromAlias) //
45-
private static long MAX_MEMORY = -1;
45+
private static boolean MEMORY_LIMIT_SET = true;
46+
@Alias @InjectAccessors(MaxMemoryAccessor.class) //
47+
private static long MAX_MEMORY;
4648

4749
@Alias @RecomputeFieldValue(kind = Kind.FromAlias) //
4850
private static AtomicLong RESERVED_MEMORY = new AtomicLong();
@@ -53,3 +55,23 @@ final class Target_java_nio_Bits {
5355

5456
// Checkstyle: resume
5557
}
58+
59+
/**
60+
* {@code java.nio.Bits} caches the max. direct memory size in the field {@code MAX_MEMORY}. We
61+
* disable this cache and always call {@link DirectMemoryAccessors#getDirectMemory()} instead, which
62+
* uses our own cache. Otherwise, it could happen that {@code MAX_MEMORY} caches a temporary value
63+
* that is used during early VM startup, before {@link PhysicalMemory} is fully initialized.
64+
*/
65+
final class MaxMemoryAccessor {
66+
// Checkstyle: stop
67+
68+
static long getMAX_MEMORY() {
69+
return DirectMemoryAccessors.getDirectMemory();
70+
}
71+
72+
static void setMAX_MEMORY(long value) {
73+
/* Nothing to do. */
74+
}
75+
76+
// Checkstyle: resume
77+
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind;
3636
import com.oracle.svm.core.annotate.Substitute;
3737
import com.oracle.svm.core.annotate.TargetClass;
38+
import com.oracle.svm.core.heap.PhysicalMemory;
3839
import com.oracle.svm.core.snippets.KnownIntrinsics;
3940

4041
import jdk.internal.misc.Unsafe;
@@ -67,36 +68,28 @@ public static ClassLoader latestUserDefinedLoader0() {
6768

6869
@Alias @InjectAccessors(DirectMemoryAccessors.class) //
6970
private static long directMemory;
70-
@Alias @InjectAccessors(DirectMemoryAccessors.class) //
71+
@Alias @InjectAccessors(PageAlignDirectMemoryAccessors.class) //
7172
private static boolean pageAlignDirectMemory;
7273
}
7374

7475
final class DirectMemoryAccessors {
76+
private static final long DIRECT_MEMORY_DURING_INITIALIZATION = 25 * 1024 * 1024;
7577

7678
/*
7779
* Not volatile to avoid a memory barrier when reading the values. Instead, an explicit barrier
7880
* is inserted when writing the values.
7981
*/
8082
private static boolean initialized;
81-
8283
private static long directMemory;
83-
private static boolean pageAlignDirectMemory;
8484

8585
static long getDirectMemory() {
8686
if (!initialized) {
87-
initialize();
87+
return tryInitialize();
8888
}
8989
return directMemory;
9090
}
9191

92-
static boolean getPageAlignDirectMemory() {
93-
if (!initialized) {
94-
initialize();
95-
}
96-
return pageAlignDirectMemory;
97-
}
98-
99-
private static void initialize() {
92+
private static long tryInitialize() {
10093
/*
10194
* The JDK method VM.saveAndRemoveProperties looks at the system property
10295
* "sun.nio.MaxDirectMemorySize". However, that property is always set by the Java HotSpot
@@ -109,6 +102,15 @@ private static void initialize() {
109102
* No value explicitly specified. The default in the JDK in this case is the maximum
110103
* heap size.
111104
*/
105+
if (PhysicalMemory.isInitializationInProgress()) {
106+
/*
107+
* When initializing PhysicalMemory, we use NIO/cgroups code that calls
108+
* VM.getDirectMemory(). When this initialization is in progress, we need to prevent
109+
* that Runtime.maxMemory() is called below because it would trigger a recursive
110+
* initialization of PhysicalMemory. So, we return a temporary value.
111+
*/
112+
return DIRECT_MEMORY_DURING_INITIALIZATION;
113+
}
112114
newDirectMemory = Runtime.getRuntime().maxMemory();
113115
}
114116

@@ -118,6 +120,31 @@ private static void initialize() {
118120
* possible but not a case we care about.
119121
*/
120122
directMemory = newDirectMemory;
123+
124+
/* Ensure values are published to other threads before marking fields as initialized. */
125+
Unsafe.getUnsafe().storeFence();
126+
initialized = true;
127+
128+
return newDirectMemory;
129+
}
130+
}
131+
132+
final class PageAlignDirectMemoryAccessors {
133+
/*
134+
* Not volatile to avoid a memory barrier when reading the values. Instead, an explicit barrier
135+
* is inserted when writing the values.
136+
*/
137+
private static boolean initialized;
138+
private static boolean pageAlignDirectMemory;
139+
140+
static boolean getPageAlignDirectMemory() {
141+
if (!initialized) {
142+
initialize();
143+
}
144+
return pageAlignDirectMemory;
145+
}
146+
147+
private static void initialize() {
121148
pageAlignDirectMemory = Boolean.getBoolean("sun.nio.PageAlignDirectMemory");
122149

123150
/* Ensure values are published to other threads before marking fields as initialized. */

0 commit comments

Comments
 (0)