Skip to content

[GR-63494] [GR-63728] Simplify the safepoint code and only enable recurring callback support on demand. #10995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 8, 2025
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
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -55,13 +55,22 @@ private Threading() {
}

/**
* This method is intended for expert users.
* <p>
* Registers a {@link RecurringCallback callback handler} that is called by the current thread
* approximately at the provided interval. Only one callback can be active per thread. Each
* thread can have its own callback with a different interval (or none at all). No guarantees
* are made about the actual interval. For example, when the thread is waiting for a lock or
* executing native code, no callback can be done. Exceptions that are thrown during the
* execution of the callback are caught and ignored, unless they are thrown via a call to
* {@link RecurringCallbackAccess#throwException(Throwable)}.
* approximately at the provided interval. This functionality is only supported if the native
* binary is built with {@code -H:+SupportRecurringCallback}. Note that only carefully crafted,
* uninterruptible code can execute safely in a recurring callback. Executing any other code
* easily results in deadlocks, crashes, and difficult-to-debug anomalies.
* <p>
* Only one callback can be active per thread. Each thread can have its own callback with a
* different interval (or none at all). No guarantees are made about the actual interval. For
* example, when the thread is waiting for a lock or executing native code, no callback can be
* done.
* <p>
* Exceptions that are thrown during the execution of the callback and that are not caught in
* the callback are ignored. {@link RecurringCallbackAccess#throwException} can be used to
* explicitly throw an exception that is not ignored.
* <p>
* Specifying {@code null} for {@code callback} clears the current thread's callback (in which
* case, the values of {@code interval} and {@code unit} are ignored).
Expand Down
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This changelog summarizes major changes to GraalVM Native Image.
* (GR-58659) (GR-58660) Support for FFM API ("Panama") has been added for darwin-aarch64 and linux-aarch64.
* (GR-49525) Introduced `--future-defaults=[all|<options>|none]` that enables options that are planned to become defaults in future releases. The enabled options are:
1. `run-time-initialized-jdk` shifts away from build-time initialization of the JDK, instead initializing most of it at run time. This transition is gradual, with individual components of the JDK becoming run-time initialized in each release. This process should complete with JDK 29 when this option should not be needed anymore. Unless you store classes from the JDK in the image heap, this option should not affect you. In case this option breaks your build, follow the suggestions in the error messages.
* (GR-63494) Recurring callback support is no longer enabled by default. If this feature is needed, please specify `-H:+SupportRecurringCallback` at image build-time.

## GraalVM for JDK 24 (Internal Version 24.2.0)
* (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@

import com.oracle.svm.core.ReservedRegisters;
import com.oracle.svm.core.nodes.SafepointCheckNode;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.Safepoint;
import com.oracle.svm.core.thread.SafepointCheckCounter;
import com.oracle.svm.core.thread.SafepointSlowpath;
import com.oracle.svm.core.thread.ThreadingSupportImpl;

import jdk.graal.compiler.asm.aarch64.AArch64Address;
import jdk.graal.compiler.asm.aarch64.AArch64Assembler;
Expand Down Expand Up @@ -60,7 +60,7 @@ public void emitCode(CompilationResultBuilder crb, AArch64MacroAssembler masm) {
try (ScratchRegister scratchRegister = masm.getScratchRegister()) {
Register scratch = scratchRegister.getRegister();
masm.ldr(safepointSize, scratch, safepointAddress);
if (ThreadingSupportImpl.isRecurringCallbackSupported()) {
if (RecurringCallbackSupport.isEnabled()) {
/* Before subtraction, the counter is compared against 1. */
masm.subs(safepointSize, scratch, scratch, 1);
masm.str(safepointSize, scratch, safepointAddress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@

import com.oracle.svm.core.ReservedRegisters;
import com.oracle.svm.core.nodes.SafepointCheckNode;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.SafepointCheckCounter;
import com.oracle.svm.core.thread.ThreadingSupportImpl;

import jdk.graal.compiler.asm.amd64.AMD64Address;
import jdk.graal.compiler.asm.amd64.AMD64Assembler;
Expand All @@ -53,7 +53,7 @@ public AMD64SafepointCheckOp() {
public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) {
int counterOffset = SafepointCheckCounter.getThreadLocalOffset();
AMD64Address counter = new AMD64Address(ReservedRegisters.singleton().getThreadRegister(), counterOffset);
if (ThreadingSupportImpl.isRecurringCallbackSupported()) {
if (RecurringCallbackSupport.isEnabled()) {
masm.subl(counter, 1);
} else {
masm.cmpl(counter, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@
import com.oracle.svm.core.meta.SharedField;
import com.oracle.svm.core.meta.SubstrateObjectConstant;
import com.oracle.svm.core.nodes.SafepointCheckNode;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.SafepointCheckCounter;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.shadowed.org.bytedeco.llvm.LLVM.LLVMBasicBlockRef;
Expand Down Expand Up @@ -360,7 +360,7 @@ private LLVMValueRef emitCondition(LogicNode condition) {
threadData = builder.buildIntToPtr(threadData, builder.rawPointerType());
LLVMValueRef safepointCounterAddr = builder.buildGEP(threadData, builder.constantInt(SafepointCheckCounter.getThreadLocalOffset()));
LLVMValueRef safepointCount = builder.buildLoad(safepointCounterAddr, builder.intType());
if (ThreadingSupportImpl.isRecurringCallbackSupported()) {
if (RecurringCallbackSupport.isEnabled()) {
safepointCount = builder.buildSub(safepointCount, builder.constantInt(1));
builder.buildStore(safepointCount, builder.buildBitcast(safepointCounterAddr, builder.pointerType(builder.intType())));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
public final class BuildPhaseProvider {

private boolean featureRegistrationFinished;
private boolean setupFinished;
private boolean analysisFinished;
private boolean hostedUniverseBuilt;
private boolean readyForCompilation;
Expand All @@ -60,6 +61,14 @@ public static boolean isFeatureRegistrationFinished() {
return ImageSingletons.contains(BuildPhaseProvider.class) && singleton().featureRegistrationFinished;
}

public static void markSetupFinished() {
singleton().setupFinished = true;
}

public static boolean isSetupFinished() {
return ImageSingletons.contains(BuildPhaseProvider.class) && singleton().setupFinished;
}

public static void markAnalysisFinished() {
singleton().analysisFinished = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.thread.JavaThreads;
import com.oracle.svm.core.thread.PlatformThreads;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.thread.VMThreads.OSThreadHandle;
import com.oracle.svm.core.util.UserError;
Expand Down Expand Up @@ -268,7 +268,7 @@ private static int runCore0() {

@Uninterruptible(reason = "The caller initialized the thread state, so the callees do not need to be uninterruptible.", calleeMustBe = false)
private static void runShutdown() {
ThreadingSupportImpl.pauseRecurringCallback("Recurring callbacks can't be executed during shutdown.");
RecurringCallbackSupport.suspendCallbackTimer("Recurring callbacks can't be executed during shutdown.");
runShutdown0();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
import com.oracle.svm.core.stack.StackOverflowCheck;
import com.oracle.svm.core.thread.PlatformThreads;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.ThreadListenerSupport;
import com.oracle.svm.core.thread.ThreadStatusTransition;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.VMOperationControl;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.thread.VMThreads.SafepointBehavior;
Expand Down Expand Up @@ -666,7 +666,7 @@ private static int tearDownIsolate() {
}

/* After threadExit(), only uninterruptible code may be executed. */
ThreadingSupportImpl.pauseRecurringCallback("Execution of arbitrary code is prohibited during the last teardown steps.");
RecurringCallbackSupport.suspendCallbackTimer("Execution of arbitrary code is prohibited during the last teardown steps.");

/* Shut down VM thread. */
if (VMOperationControl.useDedicatedVMOperationThread()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
import com.oracle.svm.core.stack.JavaStackWalker;
import com.oracle.svm.core.stack.StackFrameVisitor;
import com.oracle.svm.core.thread.PlatformThreads;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMThreads.SafepointBehavior;
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
Expand Down Expand Up @@ -118,7 +118,7 @@ public static void deoptTest() {
try {
if (Heap.getHeap().isAllocationDisallowed() ||
!CEntryPointSnippets.isIsolateInitialized() ||
ThreadingSupportImpl.isRecurringCallbackPaused() ||
(RecurringCallbackSupport.isEnabled() && RecurringCallbackSupport.isCallbackTimerSuspended()) ||
VMOperation.isInProgress() ||
SafepointBehavior.ignoresSafepoints() ||
!PlatformThreads.isCurrentAssigned()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import java.util.Map;
import java.util.function.Predicate;

import jdk.graal.compiler.word.Word;
import org.graalvm.nativeimage.ImageInfo;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
Expand All @@ -56,7 +55,7 @@
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
import com.oracle.svm.core.stack.StackOverflowCheck;
import com.oracle.svm.core.thread.PlatformThreads;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.threadlocal.FastThreadLocal;
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
Expand Down Expand Up @@ -91,6 +90,7 @@
import jdk.graal.compiler.replacements.SnippetTemplate.Arguments;
import jdk.graal.compiler.replacements.SnippetTemplate.SnippetInfo;
import jdk.graal.compiler.replacements.Snippets;
import jdk.graal.compiler.word.Word;
import jdk.vm.ci.meta.ResolvedJavaMethod;

public final class StackOverflowCheckImpl implements StackOverflowCheck {
Expand Down Expand Up @@ -201,7 +201,7 @@ private static void onYellowZoneMadeAvailable(int oldState, int newState) {
* a recurring callback in the yellow zone is dangerous because a stack overflow in the
* recurring callback would then lead to a fatal error.
*/
ThreadingSupportImpl.pauseRecurringCallback("Recurring callbacks are considered user code and must not run in yellow zone");
RecurringCallbackSupport.suspendCallbackTimer("Recurring callbacks are considered user code and must not run in yellow zone");

stackBoundaryTL.set(stackBoundaryTL.get().subtract(Options.StackYellowZoneSize.getValue()));
}
Expand Down Expand Up @@ -241,7 +241,7 @@ private static void onYellowZoneProtected(int oldState, int newState) {
VMError.guarantee(newState < oldState && newState >= STATE_YELLOW_ENABLED, "StackOverflowCheckImpl.onYellowZoneProtected: Illegal state");

if (newState == STATE_YELLOW_ENABLED) {
ThreadingSupportImpl.resumeRecurringCallbackAtNextSafepoint();
RecurringCallbackSupport.resumeCallbackTimerAtNextSafepointCheck();

stackBoundaryTL.set(stackBoundaryTL.get().add(Options.StackYellowZoneSize.getValue()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.util.VMError;

Expand Down Expand Up @@ -76,7 +76,7 @@ public static boolean isReferenceHandlerThread(Thread other) {

@Override
public void run() {
ThreadingSupportImpl.pauseRecurringCallback("An exception in a recurring callback must not interrupt pending reference processing because it could result in a memory leak.");
RecurringCallbackSupport.suspendCallbackTimer("An exception in a recurring callback must not interrupt pending reference processing because it could result in a memory leak.");

this.isolateThread = CurrentIsolate.getCurrentThread();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
import com.oracle.svm.core.stack.JavaStackWalker;
import com.oracle.svm.core.stack.StackFrameVisitor;
import com.oracle.svm.core.thread.PlatformThreads;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.threadlocal.VMThreadLocalSupport;
Expand Down Expand Up @@ -421,7 +421,7 @@ public HeapDumpWriter(HeapDumpMetadata metadata) {

public boolean dumpHeap(RawFileDescriptor fd) {
assert VMOperation.isInProgressAtSafepoint();
assert ThreadingSupportImpl.isRecurringCallbackPaused();
assert RecurringCallbackSupport.isCallbackUnsupportedOrTimerSuspended();

noAllocationVerifier.open();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@

import java.nio.charset.StandardCharsets;

import com.oracle.svm.core.jfr.oldobject.JfrOldObjectRepository;
import jdk.graal.compiler.word.Word;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.word.UnsignedWord;

import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.heap.VMOperationInfos;
import com.oracle.svm.core.jfr.oldobject.JfrOldObjectRepository;
import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
import com.oracle.svm.core.jfr.sampler.JfrRecurringCallbackExecutionSampler;
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
Expand All @@ -48,13 +47,14 @@
import com.oracle.svm.core.os.RawFileOperationSupport.RawFileDescriptor;
import com.oracle.svm.core.sampler.SamplerBuffersAccess;
import com.oracle.svm.core.thread.JavaVMOperation;
import com.oracle.svm.core.thread.ThreadingSupportImpl;
import com.oracle.svm.core.thread.RecurringCallbackSupport;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMOperationControl;
import com.oracle.svm.core.thread.VMThreads;

import jdk.graal.compiler.api.replacements.Fold;
import jdk.graal.compiler.core.common.NumUtil;
import jdk.graal.compiler.word.Word;

/**
* This class is used when writing the in-memory JFR data to a file. For all operations, except
Expand Down Expand Up @@ -673,7 +673,7 @@ private void changeEpoch() {
@Uninterruptible(reason = "Prevent JFR recording.")
private static void processSamplerBuffers() {
assert VMOperation.isInProgressAtSafepoint();
assert ThreadingSupportImpl.isRecurringCallbackPaused();
assert RecurringCallbackSupport.isCallbackUnsupportedOrTimerSuspended();

JfrExecutionSampler.singleton().disallowThreadsInSamplerCode();
try {
Expand Down
Loading
Loading