Skip to content

Commit 8781e80

Browse files
[GR-30807] Preparations to use a dedicated reference handler thread by default.
PullRequest: graal/10087
2 parents fea842c + b516b2d commit 8781e80

File tree

30 files changed

+416
-227
lines changed

30 files changed

+416
-227
lines changed

sdk/src/org.graalvm.nativeimage/snapshot.sigtest

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,21 +164,23 @@ CLSS public final static org.graalvm.nativeimage.Isolates$CreateIsolateParameter
164164
outer org.graalvm.nativeimage.Isolates
165165
innr public final static Builder
166166
meth public java.lang.String getAuxiliaryImagePath()
167+
meth public java.util.List<java.lang.String> getArguments()
167168
meth public org.graalvm.word.UnsignedWord getAuxiliaryImageReservedSpaceSize()
168169
meth public org.graalvm.word.UnsignedWord getReservedAddressSpaceSize()
169170
meth public static org.graalvm.nativeimage.Isolates$CreateIsolateParameters getDefault()
170171
supr java.lang.Object
171-
hfds DEFAULT,auxiliaryImagePath,auxiliaryImageReservedSpaceSize,reservedAddressSpaceSize
172+
hfds DEFAULT,arguments,auxiliaryImagePath,auxiliaryImageReservedSpaceSize,reservedAddressSpaceSize
172173

173174
CLSS public final static org.graalvm.nativeimage.Isolates$CreateIsolateParameters$Builder
174175
outer org.graalvm.nativeimage.Isolates$CreateIsolateParameters
175176
cons public init()
176177
meth public org.graalvm.nativeimage.Isolates$CreateIsolateParameters build()
178+
meth public org.graalvm.nativeimage.Isolates$CreateIsolateParameters$Builder appendArgument(java.lang.String)
177179
meth public org.graalvm.nativeimage.Isolates$CreateIsolateParameters$Builder auxiliaryImagePath(java.lang.String)
178180
meth public org.graalvm.nativeimage.Isolates$CreateIsolateParameters$Builder auxiliaryImageReservedSpaceSize(org.graalvm.word.UnsignedWord)
179181
meth public org.graalvm.nativeimage.Isolates$CreateIsolateParameters$Builder reservedAddressSpaceSize(org.graalvm.word.UnsignedWord)
180182
supr java.lang.Object
181-
hfds auxiliaryImagePath,auxiliaryImageReservedSpaceSize,reservedAddressSpaceSize
183+
hfds arguments,auxiliaryImagePath,auxiliaryImageReservedSpaceSize,reservedAddressSpaceSize
182184

183185
CLSS public final static org.graalvm.nativeimage.Isolates$IsolateException
184186
outer org.graalvm.nativeimage.Isolates
@@ -475,7 +477,7 @@ innr public final static FatalExceptionHandler
475477
innr public final static NotIncludedAutomatically
476478
intf java.lang.annotation.Annotation
477479
meth public abstract !hasdefault java.lang.Class<? extends java.util.function.BooleanSupplier> include()
478-
meth public abstract !hasdefault java.lang.Class<? extends org.graalvm.nativeimage.c.function.CEntryPoint$ExceptionHandler> exceptionHandler()
480+
meth public abstract !hasdefault java.lang.Class<?> exceptionHandler()
479481
meth public abstract !hasdefault java.lang.String name()
480482
meth public abstract !hasdefault java.lang.String[] documentation()
481483
meth public abstract !hasdefault org.graalvm.nativeimage.c.function.CEntryPoint$Builtin builtin()

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/Isolates.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -40,6 +40,9 @@
4040
*/
4141
package org.graalvm.nativeimage;
4242

43+
import java.util.ArrayList;
44+
import java.util.Collections;
45+
import java.util.List;
4346
import java.util.Objects;
4447

4548
import org.graalvm.nativeimage.impl.IsolateSupport;
@@ -90,13 +93,15 @@ public static final class Builder {
9093
private UnsignedWord reservedAddressSpaceSize;
9194
private String auxiliaryImagePath;
9295
private UnsignedWord auxiliaryImageReservedSpaceSize;
96+
private final List<String> arguments;
9397

9498
/**
9599
* Creates a new builder with default values.
96100
*
97101
* @since 19.0
98102
*/
99103
public Builder() {
104+
arguments = new ArrayList<>();
100105
}
101106

102107
/**
@@ -131,14 +136,27 @@ public Builder auxiliaryImageReservedSpaceSize(UnsignedWord size) {
131136
return this;
132137
}
133138

139+
/**
140+
* Appends an isolate argument. The syntax for arguments is the same as the one that is
141+
* used on the command-line when starting Native Image (e.g., {@code
142+
* -XX:+UseReferenceHandlerThread}). If the same argument is added multiple times, the
143+
* last specified value will be used.
144+
*
145+
* @since 22.1
146+
*/
147+
public Builder appendArgument(String argument) {
148+
this.arguments.add(argument);
149+
return this;
150+
}
151+
134152
/**
135153
* Produces the final {@link CreateIsolateParameters} with the values set previously by
136154
* the builder methods.
137155
*
138156
* @since 19.0
139157
*/
140158
public CreateIsolateParameters build() {
141-
return new CreateIsolateParameters(reservedAddressSpaceSize, auxiliaryImagePath, auxiliaryImageReservedSpaceSize);
159+
return new CreateIsolateParameters(reservedAddressSpaceSize, auxiliaryImagePath, auxiliaryImageReservedSpaceSize, arguments);
142160
}
143161
}
144162

@@ -156,11 +174,13 @@ public static CreateIsolateParameters getDefault() {
156174
private final UnsignedWord reservedAddressSpaceSize;
157175
private final String auxiliaryImagePath;
158176
private final UnsignedWord auxiliaryImageReservedSpaceSize;
177+
private final List<String> arguments;
159178

160-
private CreateIsolateParameters(UnsignedWord reservedAddressSpaceSize, String auxiliaryImagePath, UnsignedWord auxiliaryImageReservedSpaceSize) {
179+
private CreateIsolateParameters(UnsignedWord reservedAddressSpaceSize, String auxiliaryImagePath, UnsignedWord auxiliaryImageReservedSpaceSize, List<String> arguments) {
161180
this.reservedAddressSpaceSize = reservedAddressSpaceSize;
162181
this.auxiliaryImagePath = auxiliaryImagePath;
163182
this.auxiliaryImageReservedSpaceSize = auxiliaryImageReservedSpaceSize;
183+
this.arguments = arguments;
164184
}
165185

166186
/**
@@ -191,6 +211,15 @@ public String getAuxiliaryImagePath() {
191211
public UnsignedWord getAuxiliaryImageReservedSpaceSize() {
192212
return auxiliaryImageReservedSpaceSize;
193213
}
214+
215+
/**
216+
* Returns the list of additional isolate arguments.
217+
*
218+
* @since 22.1
219+
*/
220+
public List<String> getArguments() {
221+
return Collections.unmodifiableList(arguments);
222+
}
194223
}
195224

196225
/**

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,9 @@ def _native_image_launcher_extra_jvm_args():
995995
# These 2 arguments provide walkable call stacks for a crash in libgraal
996996
'-H:+PreserveFramePointer',
997997
'-H:-DeleteLocalSymbols',
998+
999+
# No VM-internal threads may be spawned for libgraal.
1000+
'-H:-AllowVMInternalThreads',
9981001
],
9991002
),
10001003
],

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@
8181
import com.oracle.svm.core.stack.JavaStackWalk;
8282
import com.oracle.svm.core.stack.JavaStackWalker;
8383
import com.oracle.svm.core.stack.ThreadStackPrinter;
84-
import com.oracle.svm.core.thread.JavaThreads;
8584
import com.oracle.svm.core.thread.JavaVMOperation;
8685
import com.oracle.svm.core.thread.NativeVMOperation;
8786
import com.oracle.svm.core.thread.NativeVMOperationData;
@@ -148,12 +147,11 @@ public void maybeCauseUserRequestedCollection() {
148147

149148
private void collect(GCCause cause, boolean forceFullGC) {
150149
if (!hasNeverCollectPolicy()) {
151-
UnsignedWord requestingEpoch = possibleCollectionPrologue();
152150
boolean outOfMemory = collectWithoutAllocating(cause, forceFullGC);
153151
if (outOfMemory) {
154152
throw OUT_OF_MEMORY_ERROR;
155153
}
156-
possibleCollectionEpilogue(requestingEpoch);
154+
doReferenceHandling();
157155
}
158156
}
159157

@@ -1057,50 +1055,53 @@ private void finishCollection() {
10571055
collectionInProgress = false;
10581056
}
10591057

1060-
UnsignedWord possibleCollectionPrologue() {
1061-
return getCollectionEpoch();
1062-
}
1063-
10641058
/**
1065-
* Do whatever is necessary if a collection occurred since the call to
1066-
* {@link #possibleCollectionPrologue()}. Note that this method may get called by several
1067-
* threads for the same collection.
1059+
* NOTE: All code that is transitively reachable from this method may get executed as a side
1060+
* effect of a GC or as a side effect of an allocation slow path. To prevent hard to debug
1061+
* transient issues, we execute as little code as possible in this method. Multiple threads may
1062+
* execute this method concurrently.
1063+
*
1064+
* Without a dedicated reference handler thread, arbitrary complex code can get executed as a
1065+
* side effect of this method. So, allocations of Java objects or an explicitly triggered GC can
1066+
* result in a recursive invocation of methods.
1067+
*
1068+
* This can have the effect that global state changes unexpectedly and may result in issues that
1069+
* look similar to races but that can even happen in single-threaded environments, e.g.:
1070+
*
1071+
* <pre>
1072+
* {@code
1073+
* private static Object singleton;
1074+
*
1075+
* private static synchronized Object createSingleton() {
1076+
* if (singleton == null) {
1077+
* Object o = new Object();
1078+
* // If the allocation above enters the allocation slow path code, then it is possible
1079+
* // that doReferenceHandling() gets executed by the current thread. If the method
1080+
* // createSingleton() is called as a side effect of doReferenceHandling(), then the
1081+
* // assertion below may fail because the singleton got already initialized by the same
1082+
* // thread in the meanwhile.
1083+
* assert singleton == null;
1084+
* singleton = o;
1085+
* }
1086+
* return result;
1087+
* }
1088+
* }
1089+
* </pre>
10681090
*/
1069-
void possibleCollectionEpilogue(UnsignedWord requestingEpoch) {
1070-
if (requestingEpoch.aboveOrEqual(getCollectionEpoch())) {
1071-
/* No GC happened, so do not run any epilogue. */
1072-
return;
1073-
1074-
} else if (VMOperation.isInProgress()) {
1075-
/*
1076-
* We are inside a VMOperation where we are not allowed to do certain things, e.g.,
1077-
* perform a synchronization (because it can deadlock when a lock is held outside the
1078-
* VMOperation).
1079-
*
1080-
* Note that the GC operation we are running the epilogue for is no longer in progress,
1081-
* otherwise this check would always return.
1082-
*/
1083-
return;
1084-
1085-
} else if (!JavaThreads.currentJavaThreadInitialized()) {
1086-
/*
1087-
* Too early in the attach sequence of a thread to do anything useful, e.g., perform a
1088-
* synchronization. Probably the allocation slow path for the first allocation of that
1089-
* thread caused this epilogue.
1090-
*/
1091+
static void doReferenceHandling() {
1092+
if (ReferenceHandler.useDedicatedThread()) {
10911093
return;
10921094
}
10931095

1094-
Timer refsTimer = new Timer("Enqueuing pending references and invoking internal cleaners");
1095-
Timer timer = refsTimer.open();
1096-
try {
1097-
ReferenceHandler.maybeProcessCurrentlyPending();
1098-
} finally {
1099-
timer.close();
1100-
}
1101-
if (SubstrateGCOptions.VerboseGC.getValue() && HeapOptions.PrintGCTimes.getValue()) {
1102-
Timers.logOneTimer(Log.log(), "[GC epilogue reference processing: ", refsTimer);
1103-
Log.log().string("]");
1096+
/* Most of the time, we won't have a pending reference list. So, we do that check first. */
1097+
if (HeapImpl.getHeapImpl().hasReferencePendingListUnsafe() && ReferenceHandler.isReferenceHandlingAllowed()) {
1098+
long startTime = System.nanoTime();
1099+
ReferenceHandler.processPendingReferencesInRegularThread();
1100+
1101+
if (SubstrateGCOptions.VerboseGC.getValue() && HeapOptions.PrintGCTimes.getValue()) {
1102+
long executionTime = System.nanoTime() - startTime;
1103+
Log.log().string("[GC epilogue reference processing and cleaners: ").signed(executionTime).string("]").newline();
1104+
}
11041105
}
11051106
}
11061107

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import org.graalvm.compiler.nodes.gc.BarrierSet;
3535
import org.graalvm.compiler.word.Word;
3636
import org.graalvm.nativeimage.CurrentIsolate;
37-
import org.graalvm.nativeimage.ImageSingletons;
3837
import org.graalvm.nativeimage.IsolateThread;
3938
import org.graalvm.nativeimage.Platform;
4039
import org.graalvm.nativeimage.Platforms;
@@ -64,7 +63,7 @@
6463
import com.oracle.svm.core.heap.ObjectHeader;
6564
import com.oracle.svm.core.heap.ObjectVisitor;
6665
import com.oracle.svm.core.heap.PhysicalMemory;
67-
import com.oracle.svm.core.heap.ReferenceHandlerThreadSupport;
66+
import com.oracle.svm.core.heap.ReferenceHandlerThread;
6867
import com.oracle.svm.core.heap.ReferenceInternals;
6968
import com.oracle.svm.core.heap.RuntimeCodeInfoGCSupport;
7069
import com.oracle.svm.core.hub.DynamicHub;
@@ -474,7 +473,7 @@ public BarrierSet createBarrierSet(MetaAccessProvider metaAccess) {
474473

475474
@SuppressFBWarnings(value = "VO_VOLATILE_INCREMENT", justification = "Only the GC increments the volatile field 'refListOfferCounter'.")
476475
void addToReferencePendingList(Reference<?> list) {
477-
VMOperation.guaranteeGCInProgress("Must only be called during a GC.");
476+
assert VMOperation.isGCInProgress();
478477
if (list == null) {
479478
return;
480479
}
@@ -505,12 +504,17 @@ void addToReferencePendingList(Reference<?> list) {
505504
public boolean hasReferencePendingList() {
506505
REF_MUTEX.lockNoTransition();
507506
try {
508-
return refPendingList != null;
507+
return hasReferencePendingListUnsafe();
509508
} finally {
510509
REF_MUTEX.unlock();
511510
}
512511
}
513512

513+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
514+
boolean hasReferencePendingListUnsafe() {
515+
return refPendingList != null;
516+
}
517+
514518
@Override
515519
public void waitForReferencePendingList() throws InterruptedException {
516520
/*
@@ -533,7 +537,7 @@ public void waitForReferencePendingList() throws InterruptedException {
533537
* everything in the reverse order here: we read the wakeup count *before* the call to
534538
* Thread.interrupted().
535539
*/
536-
assert Thread.currentThread() == ImageSingletons.lookup(ReferenceHandlerThreadSupport.class).getThread();
540+
assert ReferenceHandlerThread.isReferenceHandlerThread();
537541
long initialOffers = refListOfferCounter;
538542
long initialWakeUps = refListWaiterWakeUpCounter;
539543
if (hasReferencePendingList()) {

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ThreadLocalAllocation.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -152,26 +152,25 @@ private static Object slowPathNewInstance(Word objectHeader, UnsignedWord size)
152152
StackOverflowCheck.singleton().makeYellowZoneAvailable();
153153
try {
154154
DynamicHub hub = ObjectHeaderImpl.getObjectHeaderImpl().dynamicHubFromObjectHeader(objectHeader);
155-
UnsignedWord gcEpoch = HeapImpl.getHeapImpl().getGCImpl().possibleCollectionPrologue();
156155

157156
// the instance either is a frame instance or the size can be read from the hub
158-
if (!hub.isStoredContinuationClass()) {
159-
assert size.equal(hub.getLayoutEncoding());
160-
}
157+
assert hub.isStoredContinuationClass() || size.equal(hub.getLayoutEncoding());
161158
Object result = slowPathNewInstanceWithoutAllocating(hub, size);
162-
/*
163-
* If a collection happened, do follow-up tasks now that allocation, etc., is allowed.
164-
*/
165-
HeapImpl.getHeapImpl().getGCImpl().possibleCollectionEpilogue(gcEpoch);
166159
runSlowPathHooks();
167160
return result;
168161
} finally {
169162
StackOverflowCheck.singleton().protectYellowZone();
170163
}
171164
}
172165

173-
/** Use the end of slow-path allocation as a place to run periodic hook code. */
166+
/**
167+
* NOTE: All code that is transitively reachable from this method may get executed as a side
168+
* effect of an allocation slow path. To prevent hard to debug transient issues, we execute as
169+
* little code as possible in this method (see {@link GCImpl#doReferenceHandling()} for more
170+
* details). Multiple threads may execute this method concurrently.
171+
*/
174172
private static void runSlowPathHooks() {
173+
GCImpl.doReferenceHandling();
175174
GCImpl.getPolicy().updateSizeParameters();
176175
}
177176

@@ -213,12 +212,7 @@ private static Object slowPathNewArray(Word objectHeader, int length, int fillSt
213212
throw new OutOfMemoryError("Array allocation too large.");
214213
}
215214

216-
UnsignedWord gcEpoch = HeapImpl.getHeapImpl().getGCImpl().possibleCollectionPrologue();
217215
Object result = slowPathNewArrayWithoutAllocating(hub, length, size, fillStartOffset);
218-
/*
219-
* If a collection happened, do follow-up tasks now that allocation, etc., is allowed.
220-
*/
221-
HeapImpl.getHeapImpl().getGCImpl().possibleCollectionEpilogue(gcEpoch);
222216
runSlowPathHooks();
223217
return result;
224218
} finally {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/Containers.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import static com.oracle.svm.core.Containers.Options.PreferContainerQuotaForCPUCount;
2828
import static com.oracle.svm.core.Containers.Options.UseContainerSupport;
29+
import static com.oracle.svm.core.option.RuntimeOptionKey.RuntimeOptionKeyFlag.RelevantForCompilationIsolates;
2930

3031
import org.graalvm.compiler.options.Option;
3132
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
@@ -54,7 +55,7 @@ public static class Options {
5455

5556
@Option(help = "Calculate the container CPU availability based on the value of quotas (if set), when true. " +
5657
"Otherwise, use the CPU shares value, provided it is less than quota.")//
57-
public static final RuntimeOptionKey<Boolean> PreferContainerQuotaForCPUCount = new RuntimeOptionKey<>(true);
58+
public static final RuntimeOptionKey<Boolean> PreferContainerQuotaForCPUCount = new RuntimeOptionKey<>(true, RelevantForCompilationIsolates);
5859
}
5960

6061
/** Sentinel used when the value is unknown. */

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
* isolate is fully started.
5656
*/
5757
public class IsolateArgumentParser {
58-
private static final RuntimeOptionKey<?>[] OPTIONS = {SubstrateGCOptions.MinHeapSize, SubstrateGCOptions.MaxHeapSize, SubstrateGCOptions.MaxNewSize};
58+
private static final RuntimeOptionKey<?>[] OPTIONS = {SubstrateGCOptions.MinHeapSize, SubstrateGCOptions.MaxHeapSize, SubstrateGCOptions.MaxNewSize,
59+
SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread};
5960
private static final int OPTION_COUNT = OPTIONS.length;
6061
private static final CGlobalData<CCharPointer> OPTION_NAMES = CGlobalDataFactory.createBytes(IsolateArgumentParser::createOptionNames);
6162
private static final CGlobalData<CIntPointer> OPTION_NAME_POSITIONS = CGlobalDataFactory.createBytes(IsolateArgumentParser::createOptionNamePosition);
@@ -185,11 +186,15 @@ public void persistOptions(CLongPointer parsedArgs) {
185186

186187
public void verifyOptionValues() {
187188
for (int i = 0; i < OPTION_COUNT; i++) {
188-
validate(OPTIONS[i], getParsedOptionValue(i));
189+
validate(OPTIONS[i], getOptionValue(i));
189190
}
190191
}
191192

192-
private static Object getParsedOptionValue(int index) {
193+
public static boolean getBooleanOptionValue(int index) {
194+
return PARSED_OPTION_VALUES[index] == 1;
195+
}
196+
197+
private static Object getOptionValue(int index) {
193198
Class<?> optionValueType = OPTIONS[index].getDescriptor().getOptionValueType();
194199
long value = PARSED_OPTION_VALUES[index];
195200
if (optionValueType == Boolean.class) {

0 commit comments

Comments
 (0)