Skip to content

Commit df95ad5

Browse files
christianhaeublzakkak
authored andcommitted
Break the initialization cycle in NIO/cgroups code in a simpler and more thread-safe way.
(cherry picked from commit af74d9f)
1 parent 9bf95cf commit df95ad5

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;
@@ -34,7 +32,6 @@
3432

3533
import com.oracle.svm.core.Containers;
3634
import com.oracle.svm.core.SubstrateOptions;
37-
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicInteger;
3835
import com.oracle.svm.core.stack.StackOverflowCheck;
3936
import com.oracle.svm.core.thread.PlatformThreads;
4037
import com.oracle.svm.core.thread.VMOperation;
@@ -58,22 +55,16 @@ default boolean hasSize() {
5855
UnsignedWord size();
5956
}
6057

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

6762
public static boolean isInitialized() {
68-
return INITIALIZING.get() > 1;
63+
return cachedSize != UNSET_SENTINEL;
6964
}
7065

71-
/**
72-
*
73-
* @return {@code true} when PhycialMemory.size() is still initializing
74-
*/
75-
private static boolean isInitializing() {
76-
return INITIALIZING.get() == 1;
66+
public static boolean isInitializationInProgress() {
67+
return LOCK.isHeldByCurrentThread();
7768
}
7869

7970
/**
@@ -92,42 +83,23 @@ public static UnsignedWord size() {
9283
throw VMError.shouldNotReachHere("Accessing the physical memory size may require allocation and synchronization");
9384
}
9485

95-
LOCK.lock();
96-
try {
97-
if (!isInitialized()) {
98-
if (isInitializing()) {
99-
/*
100-
* Recursive initializations need to wait for the one initializing thread to
101-
* finish so as to get correct reads of the cachedSize value.
102-
*/
103-
try {
104-
boolean expired = !CACHED_SIZE_AVAIL_LATCH.await(1L, TimeUnit.SECONDS);
105-
if (expired) {
106-
throw new InternalError("Expired latch!");
107-
}
108-
VMError.guarantee(cachedSize != UNSET_SENTINEL, "Expected cached size to be set");
109-
return cachedSize;
110-
} catch (InterruptedException e) {
111-
throw VMError.shouldNotReachHere("Interrupt on countdown latch!");
86+
if (!isInitialized()) {
87+
long memoryLimit = SubstrateOptions.MaxRAM.getValue();
88+
if (memoryLimit > 0) {
89+
cachedSize = WordFactory.unsigned(memoryLimit);
90+
} else {
91+
LOCK.lock();
92+
try {
93+
if (!isInitialized()) {
94+
memoryLimit = Containers.memoryLimitInBytes();
95+
cachedSize = memoryLimit > 0
96+
? WordFactory.unsigned(memoryLimit)
97+
: ImageSingletons.lookup(PhysicalMemorySupport.class).size();
11298
}
99+
} finally {
100+
LOCK.unlock();
113101
}
114-
INITIALIZING.incrementAndGet();
115-
long memoryLimit = SubstrateOptions.MaxRAM.getValue();
116-
if (memoryLimit > 0) {
117-
cachedSize = WordFactory.unsigned(memoryLimit);
118-
} else {
119-
memoryLimit = Containers.memoryLimitInBytes();
120-
cachedSize = memoryLimit > 0
121-
? WordFactory.unsigned(memoryLimit)
122-
: ImageSingletons.lookup(PhysicalMemorySupport.class).size();
123-
}
124-
// Now that we have set the cachedSize let other threads know it's
125-
// available to use.
126-
INITIALIZING.incrementAndGet();
127-
CACHED_SIZE_AVAIL_LATCH.countDown();
128102
}
129-
} finally {
130-
LOCK.unlock();
131103
}
132104

133105
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)