Skip to content

Commit b5796de

Browse files
[GR-36408] [GR-36500] Add a mode where the reference handling can be triggered manually.
PullRequest: graal/10929
2 parents bf2286d + 9459b86 commit b5796de

File tree

20 files changed

+211
-156
lines changed

20 files changed

+211
-156
lines changed

compiler/src/org.graalvm.compiler.truffle.compiler.hotspot.libgraal/src/org/graalvm/compiler/truffle/compiler/hotspot/libgraal/TruffleToLibGraalEntryPoints.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,17 @@ public static void closeCompilation(JNIEnv env, JClass hsClazz, @CEntryPoint.Iso
274274
} finally {
275275
compilable.release(env);
276276
HSObject.cleanHandles(env);
277+
doReferenceHandling();
277278
}
278279
} catch (Throwable t) {
279280
JNIExceptionWrapper.throwInHotSpot(env, t);
280281
}
281282
}
282283

284+
private static void doReferenceHandling() {
285+
// Substituted in LibGraalFeature.
286+
}
287+
283288
@TruffleToLibGraal(Shutdown)
284289
@SuppressWarnings({"unused", "try"})
285290
@CEntryPoint(name = "Java_org_graalvm_compiler_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_shutdown")

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,8 +980,10 @@ def _native_image_launcher_extra_jvm_args():
980980
'-H:+PreserveFramePointer',
981981
'-H:-DeleteLocalSymbols',
982982

983-
# No VM-internal threads may be spawned for libgraal.
983+
984+
# No VM-internal threads may be spawned for libgraal and the reference handling is executed manually.
984985
'-H:-AllowVMInternalThreads',
986+
'-R:-AutomaticReferenceHandling',
985987
],
986988
),
987989
],

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

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import com.oracle.svm.core.thread.JavaVMOperation;
8585
import com.oracle.svm.core.thread.NativeVMOperation;
8686
import com.oracle.svm.core.thread.NativeVMOperationData;
87+
import com.oracle.svm.core.thread.PlatformThreads;
8788
import com.oracle.svm.core.thread.VMOperation;
8889
import com.oracle.svm.core.thread.VMThreads;
8990
import com.oracle.svm.core.util.TimeUtils;
@@ -151,7 +152,7 @@ private void collect(GCCause cause, boolean forceFullGC) {
151152
if (outOfMemory) {
152153
throw OUT_OF_MEMORY_ERROR;
153154
}
154-
doReferenceHandling();
155+
doReferenceHandlingInRegularThread();
155156
}
156157
}
157158

@@ -1055,46 +1056,23 @@ private void finishCollection() {
10551056
collectionInProgress = false;
10561057
}
10571058

1059+
// This method will be removed as soon as possible, see GR-36676.
1060+
static void doReferenceHandlingInRegularThread() {
1061+
if (ReferenceHandler.useRegularJavaThread() && !VMOperation.isInProgress() && PlatformThreads.isCurrentAssigned()) {
1062+
doReferenceHandling();
1063+
}
1064+
}
1065+
10581066
/**
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>
1067+
* Inside a VMOperation, we are not allowed to do certain things, e.g., perform synchronization
1068+
* (because it can deadlock when a lock is held outside the VMOperation). Similar restrictions
1069+
* apply if we are too early in the attach sequence of a thread.
10901070
*/
10911071
static void doReferenceHandling() {
1092-
if (ReferenceHandler.useDedicatedThread()) {
1093-
return;
1094-
}
1095-
1072+
assert !VMOperation.isInProgress() : "could result in deadlocks";
1073+
assert PlatformThreads.isCurrentAssigned() : "thread is not fully initialized yet";
10961074
/* 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()) {
1075+
if (HeapImpl.getHeapImpl().hasReferencePendingListUnsafe()) {
10981076
long startTime = System.nanoTime();
10991077
ReferenceHandler.processPendingReferencesInRegularThread();
11001078

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import com.oracle.svm.core.heap.ObjectHeader;
6565
import com.oracle.svm.core.heap.ObjectVisitor;
6666
import com.oracle.svm.core.heap.PhysicalMemory;
67+
import com.oracle.svm.core.heap.ReferenceHandler;
6768
import com.oracle.svm.core.heap.ReferenceHandlerThread;
6869
import com.oracle.svm.core.heap.ReferenceInternals;
6970
import com.oracle.svm.core.heap.RuntimeCodeInfoGCSupport;
@@ -470,6 +471,13 @@ public BarrierSet createBarrierSet(MetaAccessProvider metaAccess) {
470471
return RememberedSet.get().createBarrierSet(metaAccess);
471472
}
472473

474+
@Override
475+
public void doReferenceHandling() {
476+
if (ReferenceHandler.isExecutedManually()) {
477+
GCImpl.doReferenceHandling();
478+
}
479+
}
480+
473481
@SuppressFBWarnings(value = "VO_VOLATILE_INCREMENT", justification = "Only the GC increments the volatile field 'refListOfferCounter'.")
474482
void addToReferencePendingList(Reference<?> list) {
475483
assert VMOperation.isGCInProgress();

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,35 @@ private static Object slowPathNewInstance(Word objectHeader) {
175175
}
176176

177177
/**
178-
* NOTE: All code that is transitively reachable from this method may get executed as a side
179-
* effect of an allocation slow path. To prevent hard to debug transient issues, we execute as
180-
* little code as possible in this method (see {@link GCImpl#doReferenceHandling()} for more
181-
* details). Multiple threads may execute this method concurrently.
178+
* NOTE: Multiple threads may execute this method concurrently. All code that is transitively
179+
* reachable from this method may get executed as a side effect of an allocation slow path. To
180+
* prevent hard to debug transient issues, we execute as little code as possible in this method.
181+
*
182+
* If the executed code is too complex, then it can happen that we unexpectedly change some
183+
* shared global state as a side effect of an allocation. This may result in issues that look
184+
* similar to races but that can even happen in single-threaded environments, e.g.:
185+
*
186+
* <pre>
187+
* {@code
188+
* private static Object singleton;
189+
*
190+
* private static synchronized Object createSingleton() {
191+
* if (singleton == null) {
192+
* Object o = new Object();
193+
* // If the allocation above enters the allocation slow path code, and executes a complex
194+
* // slow path hook, then it is possible that createSingleton() gets recursively execute
195+
* // by the current thread. So, the assertion below may fail because the singleton got
196+
* // already initialized by the same thread in the meanwhile.
197+
* assert singleton == null;
198+
* singleton = o;
199+
* }
200+
* return result;
201+
* }
202+
* }
203+
* </pre>
182204
*/
183205
private static void runSlowPathHooks() {
184-
GCImpl.doReferenceHandling();
206+
GCImpl.doReferenceHandlingInRegularThread();
185207
GCImpl.getPolicy().updateSizeParameters();
186208
}
187209

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

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
*/
5757
public class IsolateArgumentParser {
5858
private static final RuntimeOptionKey<?>[] OPTIONS = {SubstrateGCOptions.MinHeapSize, SubstrateGCOptions.MaxHeapSize, SubstrateGCOptions.MaxNewSize,
59-
SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread};
59+
SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread, SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling};
6060
private static final int OPTION_COUNT = OPTIONS.length;
6161
private static final CGlobalData<CCharPointer> OPTION_NAMES = CGlobalDataFactory.createBytes(IsolateArgumentParser::createOptionNames);
6262
private static final CGlobalData<CIntPointer> OPTION_NAME_POSITIONS = CGlobalDataFactory.createBytes(IsolateArgumentParser::createOptionNamePosition);
@@ -134,8 +134,10 @@ private static byte[] createHostedValues() {
134134
private static long toLong(Object value) {
135135
if (value instanceof Boolean) {
136136
return ((Boolean) value) ? 1 : 0;
137+
} else if (value instanceof Integer) {
138+
return (Integer) value;
137139
} else if (value instanceof Long) {
138-
return ((Long) value);
140+
return (Long) value;
139141
} else {
140142
throw VMError.shouldNotReachHere("Unexpected option value: " + value);
141143
}
@@ -163,13 +165,13 @@ public static void parse(CEntryPointCreateIsolateParameters parameters, CLongPoi
163165
if (arg.isNonNull()) {
164166
CCharPointer tail = matchPrefix(arg);
165167
if (tail.isNonNull()) {
166-
tail = matchXOption(tail);
167-
if (tail.isNonNull()) {
168-
parseXOption(parsedArgs, numericValue, tail);
168+
CCharPointer xOptionTail = matchXOption(tail);
169+
if (xOptionTail.isNonNull()) {
170+
parseXOption(parsedArgs, numericValue, xOptionTail);
169171
} else {
170-
tail = matchXXOption(arg);
171-
if (tail.isNonNull()) {
172-
parseXXOption(parsedArgs, numericValue, tail);
172+
CCharPointer xxOptionTail = matchXXOption(tail);
173+
if (xxOptionTail.isNonNull()) {
174+
parseXXOption(parsedArgs, numericValue, xxOptionTail);
173175
}
174176
}
175177
}
@@ -190,16 +192,24 @@ public void verifyOptionValues() {
190192
}
191193
}
192194

195+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
193196
public static boolean getBooleanOptionValue(int index) {
194197
return PARSED_OPTION_VALUES[index] == 1;
195198
}
196199

200+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
201+
public static int getIntOptionValue(int index) {
202+
return (int) PARSED_OPTION_VALUES[index];
203+
}
204+
197205
private static Object getOptionValue(int index) {
198206
Class<?> optionValueType = OPTIONS[index].getDescriptor().getOptionValueType();
199207
long value = PARSED_OPTION_VALUES[index];
200208
if (optionValueType == Boolean.class) {
201209
assert value == 0 || value == 1;
202210
return value == 1;
211+
} else if (optionValueType == Integer.class) {
212+
return (int) value;
203213
} else if (optionValueType == Long.class) {
204214
return value;
205215
} else {
@@ -277,7 +287,7 @@ private static void parseXXOption(CLongPointer parsedArgs, CLongPointer numericV
277287
for (int i = 0; i < OPTION_COUNT; i++) {
278288
int pos = OPTION_NAME_POSITIONS.get().read(i);
279289
CCharPointer optionName = OPTION_NAMES.get().addressOf(pos);
280-
if (OPTION_TYPES.get().read(i) == OptionValueType.Long && parseNumericXXOption(tail, optionName, numericValue)) {
290+
if (OptionValueType.isNumeric(OPTION_TYPES.get().read(i)) && parseNumericXXOption(tail, optionName, numericValue)) {
281291
parsedArgs.write(i, numericValue.read());
282292
break;
283293
}
@@ -411,17 +421,25 @@ public static int getStructSize() {
411421

412422
private static class OptionValueType {
413423
public static byte Boolean = 1;
414-
public static byte Long = 2;
424+
public static byte Integer = 2;
425+
public static byte Long = 3;
415426

416427
public static byte fromClass(Class<?> c) {
417428
if (c == Boolean.class) {
418429
return Boolean;
430+
} else if (c == Integer.class) {
431+
return Integer;
419432
} else if (c == Long.class) {
420433
return Long;
421434
} else {
422435
throw VMError.shouldNotReachHere("Option value has unexpected type: " + c);
423436
}
424437
}
438+
439+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
440+
public static boolean isNumeric(byte optionValueType) {
441+
return optionValueType == Integer || optionValueType == Long;
442+
}
425443
}
426444
}
427445

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,11 @@ public Boolean getValue(OptionValues values) {
573573

574574
/** Use {@link ReferenceHandler#useDedicatedThread()} instead. */
575575
@Option(help = "Populate reference queues in a separate thread rather than after a garbage collection.", type = OptionType.Expert) //
576-
public static final RuntimeOptionKey<Boolean> UseReferenceHandlerThread = new RuntimeOptionKey<>(false);
576+
public static final RuntimeOptionKey<Boolean> UseReferenceHandlerThread = new ImmutableRuntimeOptionKey<>(true);
577+
578+
/** Use {@link ReferenceHandler#isExecutedManually()} instead. */
579+
@Option(help = "Determines if the reference handling is executed automatically or manually.", type = OptionType.Expert) //
580+
public static final RuntimeOptionKey<Boolean> AutomaticReferenceHandling = new ImmutableRuntimeOptionKey<>(true);
577581
}
578582

579583
@Option(help = "Overwrites the available number of processors provided by the OS. Any value <= 0 means using the processor count from the OS.")//

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/annotate/RestrictHeapAccess.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,5 @@ public boolean isMoreRestrictiveThan(Access other) {
4747

4848
Access access();
4949

50-
// Unnecessary, will be removed in GR-34779.
51-
boolean overridesCallers() default false;
52-
5350
String reason();
5451
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package com.oracle.svm.core.heap;
2626

2727
import java.lang.ref.Reference;
28+
import java.lang.ref.ReferenceQueue;
2829
import java.util.ArrayList;
2930
import java.util.List;
3031

@@ -196,9 +197,25 @@ public List<Class<?>> getLoadedClasses() {
196197
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
197198
public abstract boolean isInPrimaryImageHeap(Pointer objectPtr);
198199

200+
/**
201+
* If the automatic reference handling is disabled (see
202+
* {@link com.oracle.svm.core.SubstrateOptions.ConcealedOptions#AutomaticReferenceHandling}),
203+
* then this method can be called to do the reference handling manually. On execution, the
204+
* current thread will enqueue pending {@link Reference}s into their corresponding
205+
* {@link ReferenceQueue}s and it will execute pending cleaners.
206+
*
207+
* This method must not be called from within a VM operation as this could result in deadlocks.
208+
* Furthermore, it is up to the caller to ensure that this method is only called in places where
209+
* neither the reference handling nor the cleaner execution can cause any unexpected side
210+
* effects on the application behavior.
211+
*
212+
* If the automatic reference handling is enabled, then this method is a no-op.
213+
*/
214+
public abstract void doReferenceHandling();
215+
199216
/**
200217
* Determines if the heap currently has {@link Reference} objects that are pending to be
201-
* {@linkplain java.lang.ref.ReferenceQueue enqueued}.
218+
* {@linkplain ReferenceQueue enqueued}.
202219
*/
203220
public abstract boolean hasReferencePendingList();
204221

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,36 @@
2626

2727
import java.lang.ref.Reference;
2828

29+
import com.oracle.svm.core.IsolateArgumentParser;
30+
import com.oracle.svm.core.SubstrateOptions;
2931
import com.oracle.svm.core.SubstrateUtil;
3032
import com.oracle.svm.core.stack.StackOverflowCheck;
31-
import com.oracle.svm.core.thread.PlatformThreads;
32-
import com.oracle.svm.core.thread.VMOperation;
3333
import com.oracle.svm.core.util.VMError;
3434

3535
public final class ReferenceHandler {
3636
public static boolean useDedicatedThread() {
37-
return ReferenceHandlerThread.isSupported() && ReferenceHandlerThread.isEnabled();
37+
if (ReferenceHandlerThread.isSupported()) {
38+
int useReferenceHandlerThread = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread);
39+
int automaticReferenceHandling = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling);
40+
return IsolateArgumentParser.getBooleanOptionValue(useReferenceHandlerThread) && IsolateArgumentParser.getBooleanOptionValue(automaticReferenceHandling);
41+
}
42+
return false;
43+
}
44+
45+
public static boolean useRegularJavaThread() {
46+
int useReferenceHandlerThread = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread);
47+
int automaticReferenceHandling = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling);
48+
return (!ReferenceHandlerThread.isSupported() || !IsolateArgumentParser.getBooleanOptionValue(useReferenceHandlerThread)) &&
49+
IsolateArgumentParser.getBooleanOptionValue(automaticReferenceHandling);
50+
}
51+
52+
public static boolean isExecutedManually() {
53+
int automaticReferenceHandling = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling);
54+
return !IsolateArgumentParser.getBooleanOptionValue(automaticReferenceHandling);
3855
}
3956

4057
public static void processPendingReferencesInRegularThread() {
41-
assert !useDedicatedThread() && isReferenceHandlingAllowed();
58+
assert !useDedicatedThread();
4259

4360
/*
4461
* We might be running in a user thread that is close to a stack overflow, so enable the
@@ -74,15 +91,6 @@ static void processCleaners() {
7491
}
7592
}
7693

77-
public static boolean isReferenceHandlingAllowed() {
78-
/*
79-
* Inside a VMOperation, we are not allowed to do certain things, e.g., perform
80-
* synchronization (because it can deadlock when a lock is held outside the VMOperation).
81-
* Similar restrictions apply if we are too early in the attach sequence of a thread.
82-
*/
83-
return !VMOperation.isInProgress() && PlatformThreads.isCurrentAssigned();
84-
}
85-
8694
private ReferenceHandler() {
8795
}
8896
}

0 commit comments

Comments
 (0)