Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,17 @@ public static void closeCompilation(JNIEnv env, JClass hsClazz, @CEntryPoint.Iso
} finally {
compilable.release(env);
HSObject.cleanHandles(env);
doReferenceHandling();
}
} catch (Throwable t) {
JNIExceptionWrapper.throwInHotSpot(env, t);
}
}

private static void doReferenceHandling() {
// Substituted in LibGraalFeature.
}

@TruffleToLibGraal(Shutdown)
@SuppressWarnings({"unused", "try"})
@CEntryPoint(name = "Java_org_graalvm_compiler_truffle_runtime_hotspot_libgraal_TruffleToLibGraalCalls_shutdown")
Expand Down
4 changes: 3 additions & 1 deletion substratevm/mx.substratevm/mx_substratevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,8 +990,10 @@ def _native_image_launcher_extra_jvm_args():
'-H:+PreserveFramePointer',
'-H:-DeleteLocalSymbols',

# No VM-internal threads may be spawned for libgraal.

# No VM-internal threads may be spawned for libgraal and the reference handling is executed manually.
'-H:-AllowVMInternalThreads',
'-R:-AutomaticReferenceHandling',
],
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import com.oracle.svm.core.thread.JavaVMOperation;
import com.oracle.svm.core.thread.NativeVMOperation;
import com.oracle.svm.core.thread.NativeVMOperationData;
import com.oracle.svm.core.thread.PlatformThreads;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.util.TimeUtils;
Expand Down Expand Up @@ -151,7 +152,7 @@ private void collect(GCCause cause, boolean forceFullGC) {
if (outOfMemory) {
throw OUT_OF_MEMORY_ERROR;
}
doReferenceHandling();
doReferenceHandlingInRegularThread();
}
}

Expand Down Expand Up @@ -1055,46 +1056,23 @@ private void finishCollection() {
collectionInProgress = false;
}

// This method will be removed as soon as possible, see GR-36676.
static void doReferenceHandlingInRegularThread() {
if (ReferenceHandler.useRegularJavaThread() && !VMOperation.isInProgress() && PlatformThreads.isCurrentAssigned()) {
doReferenceHandling();
}
}

/**
* NOTE: All code that is transitively reachable from this method may get executed as a side
* effect of a GC or as a side effect of an allocation slow path. To prevent hard to debug
* transient issues, we execute as little code as possible in this method. Multiple threads may
* execute this method concurrently.
*
* Without a dedicated reference handler thread, arbitrary complex code can get executed as a
* side effect of this method. So, allocations of Java objects or an explicitly triggered GC can
* result in a recursive invocation of methods.
*
* This can have the effect that global state changes unexpectedly and may result in issues that
* look similar to races but that can even happen in single-threaded environments, e.g.:
*
* <pre>
* {@code
* private static Object singleton;
*
* private static synchronized Object createSingleton() {
* if (singleton == null) {
* Object o = new Object();
* // If the allocation above enters the allocation slow path code, then it is possible
* // that doReferenceHandling() gets executed by the current thread. If the method
* // createSingleton() is called as a side effect of doReferenceHandling(), then the
* // assertion below may fail because the singleton got already initialized by the same
* // thread in the meanwhile.
* assert singleton == null;
* singleton = o;
* }
* return result;
* }
* }
* </pre>
* Inside a VMOperation, we are not allowed to do certain things, e.g., perform synchronization
* (because it can deadlock when a lock is held outside the VMOperation). Similar restrictions
* apply if we are too early in the attach sequence of a thread.
*/
static void doReferenceHandling() {
if (ReferenceHandler.useDedicatedThread()) {
return;
}

assert !VMOperation.isInProgress() : "could result in deadlocks";
assert PlatformThreads.isCurrentAssigned() : "thread is not fully initialized yet";
/* Most of the time, we won't have a pending reference list. So, we do that check first. */
if (HeapImpl.getHeapImpl().hasReferencePendingListUnsafe() && ReferenceHandler.isReferenceHandlingAllowed()) {
if (HeapImpl.getHeapImpl().hasReferencePendingListUnsafe()) {
long startTime = System.nanoTime();
ReferenceHandler.processPendingReferencesInRegularThread();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import com.oracle.svm.core.heap.ObjectHeader;
import com.oracle.svm.core.heap.ObjectVisitor;
import com.oracle.svm.core.heap.PhysicalMemory;
import com.oracle.svm.core.heap.ReferenceHandler;
import com.oracle.svm.core.heap.ReferenceHandlerThread;
import com.oracle.svm.core.heap.ReferenceInternals;
import com.oracle.svm.core.heap.RuntimeCodeInfoGCSupport;
Expand Down Expand Up @@ -470,6 +471,13 @@ public BarrierSet createBarrierSet(MetaAccessProvider metaAccess) {
return RememberedSet.get().createBarrierSet(metaAccess);
}

@Override
public void doReferenceHandling() {
if (ReferenceHandler.isExecutedManually()) {
GCImpl.doReferenceHandling();
}
}

@SuppressFBWarnings(value = "VO_VOLATILE_INCREMENT", justification = "Only the GC increments the volatile field 'refListOfferCounter'.")
void addToReferencePendingList(Reference<?> list) {
assert VMOperation.isGCInProgress();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,35 @@ private static Object slowPathNewInstance(Word objectHeader) {
}

/**
* NOTE: All code that is transitively reachable from this method may get executed as a side
* effect of an allocation slow path. To prevent hard to debug transient issues, we execute as
* little code as possible in this method (see {@link GCImpl#doReferenceHandling()} for more
* details). Multiple threads may execute this method concurrently.
* NOTE: Multiple threads may execute this method concurrently. All code that is transitively
* reachable from this method may get executed as a side effect of an allocation slow path. To
* prevent hard to debug transient issues, we execute as little code as possible in this method.
*
* If the executed code is too complex, then it can happen that we unexpectedly change some
* shared global state as a side effect of an allocation. This may result in issues that look
* similar to races but that can even happen in single-threaded environments, e.g.:
*
* <pre>
* {@code
* private static Object singleton;
*
* private static synchronized Object createSingleton() {
* if (singleton == null) {
* Object o = new Object();
* // If the allocation above enters the allocation slow path code, and executes a complex
* // slow path hook, then it is possible that createSingleton() gets recursively execute
* // by the current thread. So, the assertion below may fail because the singleton got
* // already initialized by the same thread in the meanwhile.
* assert singleton == null;
* singleton = o;
* }
* return result;
* }
* }
* </pre>
*/
private static void runSlowPathHooks() {
GCImpl.doReferenceHandling();
GCImpl.doReferenceHandlingInRegularThread();
GCImpl.getPolicy().updateSizeParameters();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
*/
public class IsolateArgumentParser {
private static final RuntimeOptionKey<?>[] OPTIONS = {SubstrateGCOptions.MinHeapSize, SubstrateGCOptions.MaxHeapSize, SubstrateGCOptions.MaxNewSize,
SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread};
SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread, SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling};
private static final int OPTION_COUNT = OPTIONS.length;
private static final CGlobalData<CCharPointer> OPTION_NAMES = CGlobalDataFactory.createBytes(IsolateArgumentParser::createOptionNames);
private static final CGlobalData<CIntPointer> OPTION_NAME_POSITIONS = CGlobalDataFactory.createBytes(IsolateArgumentParser::createOptionNamePosition);
Expand Down Expand Up @@ -134,8 +134,10 @@ private static byte[] createHostedValues() {
private static long toLong(Object value) {
if (value instanceof Boolean) {
return ((Boolean) value) ? 1 : 0;
} else if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Long) {
return ((Long) value);
return (Long) value;
} else {
throw VMError.shouldNotReachHere("Unexpected option value: " + value);
}
Expand Down Expand Up @@ -163,13 +165,13 @@ public static void parse(CEntryPointCreateIsolateParameters parameters, CLongPoi
if (arg.isNonNull()) {
CCharPointer tail = matchPrefix(arg);
if (tail.isNonNull()) {
tail = matchXOption(tail);
if (tail.isNonNull()) {
parseXOption(parsedArgs, numericValue, tail);
CCharPointer xOptionTail = matchXOption(tail);
if (xOptionTail.isNonNull()) {
parseXOption(parsedArgs, numericValue, xOptionTail);
} else {
tail = matchXXOption(arg);
if (tail.isNonNull()) {
parseXXOption(parsedArgs, numericValue, tail);
CCharPointer xxOptionTail = matchXXOption(tail);
if (xxOptionTail.isNonNull()) {
parseXXOption(parsedArgs, numericValue, xxOptionTail);
}
}
}
Expand All @@ -190,16 +192,24 @@ public void verifyOptionValues() {
}
}

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

@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static int getIntOptionValue(int index) {
return (int) PARSED_OPTION_VALUES[index];
}

private static Object getOptionValue(int index) {
Class<?> optionValueType = OPTIONS[index].getDescriptor().getOptionValueType();
long value = PARSED_OPTION_VALUES[index];
if (optionValueType == Boolean.class) {
assert value == 0 || value == 1;
return value == 1;
} else if (optionValueType == Integer.class) {
return (int) value;
} else if (optionValueType == Long.class) {
return value;
} else {
Expand Down Expand Up @@ -277,7 +287,7 @@ private static void parseXXOption(CLongPointer parsedArgs, CLongPointer numericV
for (int i = 0; i < OPTION_COUNT; i++) {
int pos = OPTION_NAME_POSITIONS.get().read(i);
CCharPointer optionName = OPTION_NAMES.get().addressOf(pos);
if (OPTION_TYPES.get().read(i) == OptionValueType.Long && parseNumericXXOption(tail, optionName, numericValue)) {
if (OptionValueType.isNumeric(OPTION_TYPES.get().read(i)) && parseNumericXXOption(tail, optionName, numericValue)) {
parsedArgs.write(i, numericValue.read());
break;
}
Expand Down Expand Up @@ -411,17 +421,25 @@ public static int getStructSize() {

private static class OptionValueType {
public static byte Boolean = 1;
public static byte Long = 2;
public static byte Integer = 2;
public static byte Long = 3;

public static byte fromClass(Class<?> c) {
if (c == Boolean.class) {
return Boolean;
} else if (c == Integer.class) {
return Integer;
} else if (c == Long.class) {
return Long;
} else {
throw VMError.shouldNotReachHere("Option value has unexpected type: " + c);
}
}

@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static boolean isNumeric(byte optionValueType) {
return optionValueType == Integer || optionValueType == Long;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,11 @@ public Boolean getValue(OptionValues values) {

/** Use {@link ReferenceHandler#useDedicatedThread()} instead. */
@Option(help = "Populate reference queues in a separate thread rather than after a garbage collection.", type = OptionType.Expert) //
public static final RuntimeOptionKey<Boolean> UseReferenceHandlerThread = new RuntimeOptionKey<>(false);
public static final RuntimeOptionKey<Boolean> UseReferenceHandlerThread = new ImmutableRuntimeOptionKey<>(true);

/** Use {@link ReferenceHandler#isExecutedManually()} instead. */
@Option(help = "Determines if the reference handling is executed automatically or manually.", type = OptionType.Expert) //
public static final RuntimeOptionKey<Boolean> AutomaticReferenceHandling = new ImmutableRuntimeOptionKey<>(true);
}

@Option(help = "Overwrites the available number of processors provided by the OS. Any value <= 0 means using the processor count from the OS.")//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,5 @@ public boolean isMoreRestrictiveThan(Access other) {

Access access();

// Unnecessary, will be removed in GR-34779.
boolean overridesCallers() default false;

String reason();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package com.oracle.svm.core.heap;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;

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

/**
* If the automatic reference handling is disabled (see
* {@link com.oracle.svm.core.SubstrateOptions.ConcealedOptions#AutomaticReferenceHandling}),
* then this method can be called to do the reference handling manually. On execution, the
* current thread will enqueue pending {@link Reference}s into their corresponding
* {@link ReferenceQueue}s and it will execute pending cleaners.
*
* This method must not be called from within a VM operation as this could result in deadlocks.
* Furthermore, it is up to the caller to ensure that this method is only called in places where
* neither the reference handling nor the cleaner execution can cause any unexpected side
* effects on the application behavior.
*
* If the automatic reference handling is enabled, then this method is a no-op.
*/
public abstract void doReferenceHandling();

/**
* Determines if the heap currently has {@link Reference} objects that are pending to be
* {@linkplain java.lang.ref.ReferenceQueue enqueued}.
* {@linkplain ReferenceQueue enqueued}.
*/
public abstract boolean hasReferencePendingList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,36 @@

import java.lang.ref.Reference;

import com.oracle.svm.core.IsolateArgumentParser;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.stack.StackOverflowCheck;
import com.oracle.svm.core.thread.PlatformThreads;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.util.VMError;

public final class ReferenceHandler {
public static boolean useDedicatedThread() {
return ReferenceHandlerThread.isSupported() && ReferenceHandlerThread.isEnabled();
if (ReferenceHandlerThread.isSupported()) {
int useReferenceHandlerThread = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread);
int automaticReferenceHandling = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling);
return IsolateArgumentParser.getBooleanOptionValue(useReferenceHandlerThread) && IsolateArgumentParser.getBooleanOptionValue(automaticReferenceHandling);
}
return false;
}

public static boolean useRegularJavaThread() {
int useReferenceHandlerThread = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.UseReferenceHandlerThread);
int automaticReferenceHandling = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling);
return (!ReferenceHandlerThread.isSupported() || !IsolateArgumentParser.getBooleanOptionValue(useReferenceHandlerThread)) &&
IsolateArgumentParser.getBooleanOptionValue(automaticReferenceHandling);
}

public static boolean isExecutedManually() {
int automaticReferenceHandling = IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling);
return !IsolateArgumentParser.getBooleanOptionValue(automaticReferenceHandling);
}

public static void processPendingReferencesInRegularThread() {
assert !useDedicatedThread() && isReferenceHandlingAllowed();
assert !useDedicatedThread();

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

public static boolean isReferenceHandlingAllowed() {
/*
* Inside a VMOperation, we are not allowed to do certain things, e.g., perform
* synchronization (because it can deadlock when a lock is held outside the VMOperation).
* Similar restrictions apply if we are too early in the attach sequence of a thread.
*/
return !VMOperation.isInProgress() && PlatformThreads.isCurrentAssigned();
}

private ReferenceHandler() {
}
}
Loading