Skip to content

Commit af74d9f

Browse files
Break the initialization cycle in NIO/cgroups code in a simpler and more thread-safe way.
1 parent 318f108 commit af74d9f

File tree

2 files changed

+43
-122
lines changed

2 files changed

+43
-122
lines changed

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

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
*/
2525
package com.oracle.svm.core.heap;
2626

27-
import java.util.concurrent.CountDownLatch;
28-
import java.util.concurrent.TimeUnit;
2927
import java.util.concurrent.locks.ReentrantLock;
3028

3129
import org.graalvm.nativeimage.ImageSingletons;
@@ -35,7 +33,6 @@
3533
import com.oracle.svm.core.Containers;
3634
import com.oracle.svm.core.SubstrateOptions;
3735
import com.oracle.svm.core.Uninterruptible;
38-
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicInteger;
3936
import com.oracle.svm.core.stack.StackOverflowCheck;
4037
import com.oracle.svm.core.thread.PlatformThreads;
4138
import com.oracle.svm.core.thread.VMOperation;
@@ -59,23 +56,17 @@ default boolean hasSize() {
5956
UnsignedWord size();
6057
}
6158

62-
private static final CountDownLatch CACHED_SIZE_AVAIL_LATCH = new CountDownLatch(1);
63-
private static final AtomicInteger INITIALIZING = new AtomicInteger(0);
6459
private static final ReentrantLock LOCK = new ReentrantLock();
6560
private static final UnsignedWord UNSET_SENTINEL = UnsignedUtils.MAX_VALUE;
6661
private static UnsignedWord cachedSize = UNSET_SENTINEL;
6762

6863
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
6964
public static boolean isInitialized() {
70-
return INITIALIZING.get() > 1;
65+
return cachedSize != UNSET_SENTINEL;
7166
}
7267

73-
/**
74-
*
75-
* @return {@code true} when PhycialMemory.size() is still initializing
76-
*/
77-
private static boolean isInitializing() {
78-
return INITIALIZING.get() == 1;
68+
public static boolean isInitializationInProgress() {
69+
return LOCK.isHeldByCurrentThread();
7970
}
8071

8172
/**
@@ -94,42 +85,23 @@ public static UnsignedWord size() {
9485
throw VMError.shouldNotReachHere("Accessing the physical memory size may require allocation and synchronization");
9586
}
9687

97-
LOCK.lock();
98-
try {
99-
if (!isInitialized()) {
100-
if (isInitializing()) {
101-
/*
102-
* Recursive initializations need to wait for the one initializing thread to
103-
* finish so as to get correct reads of the cachedSize value.
104-
*/
105-
try {
106-
boolean expired = !CACHED_SIZE_AVAIL_LATCH.await(1L, TimeUnit.SECONDS);
107-
if (expired) {
108-
throw new InternalError("Expired latch!");
109-
}
110-
VMError.guarantee(cachedSize != UNSET_SENTINEL, "Expected cached size to be set");
111-
return cachedSize;
112-
} catch (InterruptedException e) {
113-
throw VMError.shouldNotReachHere("Interrupt on countdown latch!");
88+
if (!isInitialized()) {
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();
114100
}
101+
} finally {
102+
LOCK.unlock();
115103
}
116-
INITIALIZING.incrementAndGet();
117-
long memoryLimit = SubstrateOptions.MaxRAM.getValue();
118-
if (memoryLimit > 0) {
119-
cachedSize = WordFactory.unsigned(memoryLimit);
120-
} else {
121-
memoryLimit = Containers.memoryLimitInBytes();
122-
cachedSize = memoryLimit > 0
123-
? WordFactory.unsigned(memoryLimit)
124-
: ImageSingletons.lookup(PhysicalMemorySupport.class).size();
125-
}
126-
// Now that we have set the cachedSize let other threads know it's
127-
// available to use.
128-
INITIALIZING.incrementAndGet();
129-
CACHED_SIZE_AVAIL_LATCH.countDown();
130104
}
131-
} finally {
132-
LOCK.unlock();
133105
}
134106

135107
return cachedSize;

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

Lines changed: 26 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
package com.oracle.svm.core.jdk;
2626

2727
import java.util.Map;
28-
import java.util.concurrent.atomic.AtomicInteger;
2928

3029
import com.oracle.svm.core.NeverInline;
3130
import com.oracle.svm.core.SubstrateOptions;
@@ -38,7 +37,6 @@
3837
import com.oracle.svm.core.annotate.TargetClass;
3938
import com.oracle.svm.core.heap.PhysicalMemory;
4039
import com.oracle.svm.core.snippets.KnownIntrinsics;
41-
import com.oracle.svm.core.util.VMError;
4240

4341
import jdk.internal.misc.Unsafe;
4442

@@ -72,44 +70,26 @@ public static ClassLoader latestUserDefinedLoader0() {
7270
private static long directMemory;
7371
@Alias @InjectAccessors(PageAlignDirectMemoryAccessors.class) //
7472
private static boolean pageAlignDirectMemory;
75-
76-
@Alias //
77-
public static native void initLevel(int newVal);
78-
79-
@Alias //
80-
public static native int initLevel();
8173
}
8274

8375
final class DirectMemoryAccessors {
76+
private static final long DIRECT_MEMORY_DURING_INITIALIZATION = 25 * 1024 * 1024;
8477

8578
/*
86-
* Full initialization is two-staged. First, we init directMemory to a static value (25MB) so
87-
* that initialization of PhysicalMemory has a chance to finish. At that point isInintialized
88-
* will be false, since we need to (potentially) set the value to the actual configured heap
89-
* size. That can only be done once PhysicalMemory init completed. We'd introduce a cycle
90-
* otherwise.
79+
* Not volatile to avoid a memory barrier when reading the values. Instead, an explicit barrier
80+
* is inserted when writing the values.
9181
*/
92-
private static boolean isInitialized;
93-
private static final int INITIALIZING = 1;
94-
private static final int INITIALIZED = 2;
95-
private static final AtomicInteger INIT_COUNT = new AtomicInteger();
96-
private static final long STATIC_DIRECT_MEMORY_AMOUNT = 25 * 1024 * 1024;
82+
private static boolean initialized;
9783
private static long directMemory;
9884

9985
static long getDirectMemory() {
100-
if (!isInitialized) {
101-
initialize();
86+
if (!initialized) {
87+
return tryInitialize();
10288
}
10389
return directMemory;
10490
}
10591

106-
private static void initialize() {
107-
if (INIT_COUNT.get() == INITIALIZED) {
108-
/*
109-
* Safeguard for recursive init
110-
*/
111-
return;
112-
}
92+
private static long tryInitialize() {
11393
/*
11494
* The JDK method VM.saveAndRemoveProperties looks at the system property
11595
* "sun.nio.MaxDirectMemorySize". However, that property is always set by the Java HotSpot
@@ -120,72 +100,41 @@ private static void initialize() {
120100
if (newDirectMemory == 0) {
121101
/*
122102
* No value explicitly specified. The default in the JDK in this case is the maximum
123-
* heap size. However, we cannot rely on Runtime.maxMemory() until PhysicalMemory has
124-
* fully initialized. Runtime.maxMemory() has a dependency on PhysicalMemory.size()
125-
* which in turn depends on container support which might use NIO. To avoid this cycle,
126-
* we first initialize the 'directMemory' field to an arbitrary value (25MB), and only
127-
* use the Runtime.maxMemory() API once PhysicalMemory has fully initialized.
103+
* heap size.
128104
*/
129-
if (!PhysicalMemory.isInitialized()) {
105+
if (PhysicalMemory.isInitializationInProgress()) {
130106
/*
131-
* While initializing physical memory we might end up back here with an INIT_COUNT
132-
* of 1, since we read the directMemory field during container support code
133-
* execution which runs when PhysicalMemory is still initializing.
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.
134111
*/
135-
VMError.guarantee(INIT_COUNT.get() <= INITIALIZING, "Initial run needs to have init count 0 or 1");
136-
newDirectMemory = STATIC_DIRECT_MEMORY_AMOUNT; // Static value during initialization
137-
INIT_COUNT.setRelease(INITIALIZING);
138-
} else {
139-
VMError.guarantee(INIT_COUNT.get() <= INITIALIZING, "Runtime.maxMemory() invariant");
140-
/*
141-
* Once we know PhysicalMemory has been properly initialized we can use
142-
* Runtime.maxMemory(). Note that we might end up in this branch for code explicitly
143-
* using the JDK cgroups code. At that point PhysicalMemory has likely been
144-
* initialized.
145-
*/
146-
INIT_COUNT.setRelease(INITIALIZED);
147-
newDirectMemory = Runtime.getRuntime().maxMemory();
148-
}
149-
} else {
150-
/*
151-
* For explicitly set direct memory we are done
152-
*/
153-
Unsafe.getUnsafe().storeFence();
154-
directMemory = newDirectMemory;
155-
isInitialized = true;
156-
if (Target_jdk_internal_misc_VM.initLevel() < 1) {
157-
// only the first accessor needs to set this
158-
Target_jdk_internal_misc_VM.initLevel(1);
112+
return DIRECT_MEMORY_DURING_INITIALIZATION;
159113
}
160-
return;
114+
newDirectMemory = Runtime.getRuntime().maxMemory();
161115
}
162-
VMError.guarantee(newDirectMemory > 0, "New direct memory should be initialized");
163116

164-
Unsafe.getUnsafe().storeFence();
117+
/*
118+
* The initialization is not synchronized, so multiple threads can race. Usually this will
119+
* lead to the same value, unless the runtime options are modified concurrently - which is
120+
* possible but not a case we care about.
121+
*/
165122
directMemory = newDirectMemory;
166-
if (PhysicalMemory.isInitialized() && INITIALIZED == INIT_COUNT.get()) {
167-
/*
168-
* Complete initialization hand-shake once PhysicalMemory is properly initialized. Also
169-
* set the VM init level to 1 so as to provoke the NIO code to re-set the internal
170-
* MAX_MEMORY field.
171-
*/
172-
isInitialized = true;
173-
if (Target_jdk_internal_misc_VM.initLevel() < 1) {
174-
// only the first accessor needs to set this
175-
Target_jdk_internal_misc_VM.initLevel(1);
176-
}
177-
}
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;
178129
}
179130
}
180131

181132
final class PageAlignDirectMemoryAccessors {
182-
183133
/*
184134
* Not volatile to avoid a memory barrier when reading the values. Instead, an explicit barrier
185135
* is inserted when writing the values.
186136
*/
187137
private static boolean initialized;
188-
189138
private static boolean pageAlignDirectMemory;
190139

191140
static boolean getPageAlignDirectMemory() {

0 commit comments

Comments
 (0)